Program Listing for File openclStorage.hpp#

Return to documentation for file (librapid/include/librapid/opencl/openclStorage.hpp)

#ifndef LIBRAPID_ARRAY_OPENCL_STORAGE_HPP
#define LIBRAPID_ARRAY_OPENCL_STORAGE_HPP

/*
 * This file defines the OpenCLStorage class, which is used to store the data of an Array
 * on an OpenCL device. It implements the same functions as the Storage class, but with
 * OpenCL-specific implementations and potentially different function signatures (often
 * regarding the return type).
 */

#if defined(LIBRAPID_HAS_OPENCL)

#   define LIBRAPID_CHECK_OPENCL                                                                  \
        LIBRAPID_ASSERT(global::openCLConfigured,                                                  \
                        "OpenCL has not been configured. Please call configureOpenCL() before "    \
                        "creating any Arrays with the OpenCL backend.")

namespace librapid {
    namespace typetraits {
        template<typename Scalar_>
        struct TypeInfo<OpenCLStorage<Scalar_>> {
            static constexpr bool isLibRapidType = true;
            using Scalar                         = Scalar_;
            using Backend                        = backend::OpenCL;
        };

        template<typename T>
        struct IsOpenCLStorage : std::false_type {};

        template<typename Scalar_>
        struct IsOpenCLStorage<OpenCLStorage<Scalar_>> : std::true_type {};

        LIBRAPID_DEFINE_AS_TYPE(typename Scalar_, OpenCLStorage<Scalar_>);
    } // namespace typetraits

    namespace detail {
#   define OPENCL_REF_OPERATOR(OP)                                                                \
        template<typename LHS, typename RHS>                                                       \
        auto operator OP(const OpenCLRef<LHS> &lhs, const RHS &rhs) {                              \
            return lhs.get() OP rhs;                                                               \
        }                                                                                          \
                                                                                                   \
        template<typename LHS, typename RHS>                                                       \
        auto operator OP(const LHS &lhs, const OpenCLRef<RHS> &rhs) {                              \
            return lhs OP rhs.get();                                                               \
        }                                                                                          \
                                                                                                   \
        template<typename LHS, typename RHS>                                                       \
        auto operator OP(const OpenCLRef<LHS> &lhs, const OpenCLRef<RHS> &rhs) {                   \
            return lhs.get() OP rhs.get();                                                         \
        }                                                                                          \
                                                                                                   \
        template<typename LHS, typename RHS>                                                       \
        auto operator OP##=(OpenCLRef<LHS> &lhs, const RHS &rhs) {                                 \
            lhs = lhs.get() OP rhs;                                                                \
        }                                                                                          \
                                                                                                   \
        template<typename LHS, typename RHS>                                                       \
        auto operator OP##=(OpenCLRef<LHS> &lhs, const OpenCLRef<RHS> &rhs) {                      \
            lhs = lhs.get() OP rhs.get();                                                          \
        }

#   define OPENCL_REF_OPERATOR_NO_ASSIGN(OP)                                                      \
        template<typename LHS, typename RHS>                                                       \
        auto operator OP(const OpenCLRef<LHS> &lhs, const RHS &rhs) {                              \
            return lhs.get() OP rhs;                                                               \
        }                                                                                          \
                                                                                                   \
        template<typename LHS, typename RHS>                                                       \
        auto operator OP(const LHS &lhs, const OpenCLRef<RHS> &rhs) {                              \
            return lhs OP rhs.get();                                                               \
        }                                                                                          \
                                                                                                   \
        template<typename LHS, typename RHS>                                                       \
        auto operator OP(const OpenCLRef<LHS> &lhs, const OpenCLRef<RHS> &rhs) {                   \
            return lhs.get() OP rhs.get();                                                         \
        }

        template<typename T>
        class OpenCLRef {
        public:
            OpenCLRef(const cl::Buffer &buffer, size_t offset) :
                    m_buffer(buffer), m_offset(offset) {}

            LIBRAPID_ALWAYS_INLINE OpenCLRef &operator=(const T &val) {
                global::openCLQueue.enqueueWriteBuffer(
                  m_buffer, CL_TRUE, m_offset * sizeof(T), sizeof(T), &val);
                return *this;
            }

            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE T get() const {
                T tmp;
                global::openCLQueue.enqueueReadBuffer(
                  m_buffer, CL_TRUE, m_offset * sizeof(T), sizeof(T), &tmp);
                global::openCLQueue.finish();
                return tmp;
            }

            template<typename CAST>
            LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE operator CAST() const {
                return static_cast<CAST>(get());
            }

            LIBRAPID_NODISCARD std::string str(const std::string &format = "{}") const {
                return fmt::format(format, get());
            }

        private:
            cl::Buffer m_buffer;
            size_t m_offset;
        };

        OPENCL_REF_OPERATOR(+)
        OPENCL_REF_OPERATOR(-)
        OPENCL_REF_OPERATOR(*)
        OPENCL_REF_OPERATOR(/)
        OPENCL_REF_OPERATOR(%)
        OPENCL_REF_OPERATOR(^)
        OPENCL_REF_OPERATOR(&)
        OPENCL_REF_OPERATOR(|)
        OPENCL_REF_OPERATOR(<<)
        OPENCL_REF_OPERATOR(>>)
        OPENCL_REF_OPERATOR_NO_ASSIGN(==)
        OPENCL_REF_OPERATOR_NO_ASSIGN(!=)
        OPENCL_REF_OPERATOR_NO_ASSIGN(<)
        OPENCL_REF_OPERATOR_NO_ASSIGN(>)
        OPENCL_REF_OPERATOR_NO_ASSIGN(<=)
        OPENCL_REF_OPERATOR_NO_ASSIGN(>=)
    } // namespace detail

    template<typename Scalar_>
    class OpenCLStorage {
    public:
        using Scalar                        = Scalar_;
        using Pointer                       = Scalar *;
        using ConstPointer                  = const Scalar *;
        using Reference                     = Scalar &;
        using ConstReference                = const Scalar &;
        using SizeType                      = size_t;
        using DifferenceType                = ptrdiff_t;
        static constexpr cl_int bufferFlags = CL_MEM_READ_WRITE;

        OpenCLStorage() = default;

        LIBRAPID_ALWAYS_INLINE explicit OpenCLStorage(SizeType size);

        LIBRAPID_ALWAYS_INLINE OpenCLStorage(SizeType size, Scalar value);

        LIBRAPID_ALWAYS_INLINE OpenCLStorage(const cl::Buffer &buffer, SizeType size,
                                             bool ownsData);

        LIBRAPID_ALWAYS_INLINE OpenCLStorage(const OpenCLStorage &other);

        LIBRAPID_ALWAYS_INLINE OpenCLStorage(OpenCLStorage &&other) LIBRAPID_RELEASE_NOEXCEPT;

        LIBRAPID_ALWAYS_INLINE OpenCLStorage(std::initializer_list<Scalar> list);

        LIBRAPID_ALWAYS_INLINE explicit OpenCLStorage(const std::vector<Scalar> &vec);

        LIBRAPID_ALWAYS_INLINE OpenCLStorage &operator=(const OpenCLStorage &other);

        LIBRAPID_ALWAYS_INLINE OpenCLStorage &
        operator=(OpenCLStorage &&other) LIBRAPID_RELEASE_NOEXCEPT;

        void set(const OpenCLStorage &other);

        OpenCLStorage copy() const;

        template<typename ShapeType>
        static ShapeType defaultShape();

        template<typename V>
        static OpenCLStorage fromData(const std::initializer_list<V> &list);

        template<typename V>
        static OpenCLStorage fromData(const std::vector<V> &vec);

        ~OpenCLStorage();

        LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize);

        LIBRAPID_ALWAYS_INLINE void resize(SizeType newSize, SizeType value);

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE SizeType size() const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE detail::OpenCLRef<Scalar>
        operator[](SizeType index);

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE const detail::OpenCLRef<Scalar>
        operator[](SizeType index) const;

        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE const cl::Buffer &data() const;
        LIBRAPID_NODISCARD LIBRAPID_ALWAYS_INLINE cl::Buffer &data();

    private:
        LIBRAPID_ALWAYS_INLINE void resizeImpl(SizeType newSize);
        LIBRAPID_ALWAYS_INLINE void resizeImpl(SizeType newSize, int);

        SizeType m_size;
        cl::Buffer m_buffer;
        bool m_ownsData = true;
    };

    template<typename Scalar>
    OpenCLStorage<Scalar>::OpenCLStorage(SizeType size) :
            m_size(size), m_buffer(global::openCLContext, bufferFlags, size * sizeof(Scalar)),
            m_ownsData(true) {
        LIBRAPID_CHECK_OPENCL;
    }

    template<typename Scalar>
    OpenCLStorage<Scalar>::OpenCLStorage(SizeType size, Scalar value) :
            m_size(size), m_buffer(global::openCLContext, bufferFlags, size * sizeof(Scalar)) {
        LIBRAPID_CHECK_OPENCL;
        global::openCLQueue.enqueueFillBuffer(m_buffer, value, 0, size * sizeof(Scalar));
    }

    template<typename Scalar>
    OpenCLStorage<Scalar>::OpenCLStorage(const cl::Buffer &buffer, SizeType size, bool ownsData) :
            m_size(size), m_buffer(buffer), m_ownsData(ownsData) {
        LIBRAPID_CHECK_OPENCL;
    }

    template<typename Scalar>
    OpenCLStorage<Scalar>::OpenCLStorage(const OpenCLStorage &other) :
            m_size(other.m_size), m_buffer(other.m_buffer), m_ownsData(true) {
        LIBRAPID_CHECK_OPENCL;
    }

    template<typename Scalar>
    OpenCLStorage<Scalar>::OpenCLStorage(OpenCLStorage &&other) LIBRAPID_RELEASE_NOEXCEPT
            : m_size(std::move(other.m_size)),
              m_buffer(std::move(other.m_buffer)),
              m_ownsData(other.m_ownsData) {
        LIBRAPID_CHECK_OPENCL;
        other.m_size     = 0;
        other.m_ownsData = false;
    }

    template<typename Scalar>
    OpenCLStorage<Scalar>::OpenCLStorage(std::initializer_list<Scalar> list) :
            m_size(list.size()),
            m_buffer(global::openCLContext, bufferFlags, list.size() * sizeof(Scalar)),
            m_ownsData(true) {
        LIBRAPID_CHECK_OPENCL;
        global::openCLQueue.enqueueWriteBuffer(
          m_buffer, CL_TRUE, 0, m_size * sizeof(Scalar), list.begin());
    }

    template<typename Scalar>
    OpenCLStorage<Scalar>::OpenCLStorage(const std::vector<Scalar> &vec) :
            m_size(vec.size()),
            m_buffer(global::openCLContext, bufferFlags, m_size * sizeof(Scalar)),
            m_ownsData(true) {
        LIBRAPID_CHECK_OPENCL;
        global::openCLQueue.enqueueWriteBuffer(
          m_buffer, CL_TRUE, 0, m_size * sizeof(Scalar), vec.data());
    }

    template<typename Scalar>
    OpenCLStorage<Scalar> &OpenCLStorage<Scalar>::operator=(const OpenCLStorage &other) {
        LIBRAPID_CHECK_OPENCL;
        if (this != &other) {
            if (m_ownsData) {
                m_buffer = other.m_buffer;
                m_size   = other.m_size;
            } else {
                LIBRAPID_ASSERT(m_size == other.m_size,
                                "Cannot copy storage with {} elements to dependent storage with "
                                "{} elements",
                                other.m_size,
                                m_size);

                global::openCLQueue.enqueueCopyBuffer(
                  other.m_buffer, m_buffer, 0, 0, m_size * sizeof(Scalar));
            }
        }
        return *this;
    }

    template<typename Scalar>
    OpenCLStorage<Scalar> &
    OpenCLStorage<Scalar>::operator=(OpenCLStorage &&other) LIBRAPID_RELEASE_NOEXCEPT {
        LIBRAPID_CHECK_OPENCL;
        if (this != &other) {
            if (m_ownsData) {
                std::swap(m_buffer, other.m_buffer);
                std::swap(m_size, other.m_size);
                std::swap(m_ownsData, other.m_ownsData);
            } else {
                LIBRAPID_ASSERT(m_size == other.m_size,
                                "Cannot move into dependent OpenCLStorage "
                                "with different size");
                global::openCLQueue.enqueueCopyBuffer(
                  other.m_buffer, m_buffer, 0, 0, m_size * sizeof(Scalar));
            }
        }
        return *this;
    }

    template<typename Scalar>
    void OpenCLStorage<Scalar>::set(const OpenCLStorage &other) {
        LIBRAPID_CHECK_OPENCL;
        m_buffer   = other.m_buffer;
        m_size     = other.m_size;
        m_ownsData = other.m_ownsData;
    }

    template<typename Scalar>
    auto OpenCLStorage<Scalar>::copy() const -> OpenCLStorage {
        LIBRAPID_CHECK_OPENCL;
        OpenCLStorage<Scalar> result(m_size);
        global::openCLQueue.enqueueCopyBuffer(
          m_buffer, result.m_buffer, 0, 0, m_size * sizeof(Scalar));
        return result;
    }

    template<typename Scalar>
    template<typename ShapeType>
    ShapeType OpenCLStorage<Scalar>::defaultShape() {
        return ShapeType({0});
    }

    template<typename Scalar>
    template<typename V>
    OpenCLStorage<Scalar> OpenCLStorage<Scalar>::fromData(const std::initializer_list<V> &list) {
        return OpenCLStorage<Scalar>(list);
    }

    template<typename Scalar>
    template<typename V>
    OpenCLStorage<Scalar> OpenCLStorage<Scalar>::fromData(const std::vector<V> &vec) {
        return OpenCLStorage<Scalar>(vec);
    }

    template<typename Scalar>
    OpenCLStorage<Scalar>::~OpenCLStorage() {
        // cl::Buffer is reference counted, so we do not need to worry about whether the array
        // owns the buffer or not. If it does, the buffer will be deleted when the array is
        // destroyed. If it does not, the buffer will be deleted when the last array referencing
        // it is destroyed.
    }

    template<typename Scalar>
    void OpenCLStorage<Scalar>::resize(SizeType newSize) {
        resizeImpl(newSize);
    }

    template<typename Scalar>
    void OpenCLStorage<Scalar>::resize(SizeType newSize, SizeType value) {
        resizeImpl(newSize, 0);
    }

    template<typename Scalar>
    void OpenCLStorage<Scalar>::resizeImpl(SizeType newSize) {
        if (newSize == m_size) return;
        m_size = newSize;
        cl::Buffer newBuffer(global::openCLContext, bufferFlags, newSize * sizeof(Scalar));
        global::openCLQueue.enqueueCopyBuffer(m_buffer, newBuffer, 0, 0, m_size * sizeof(Scalar));
        m_buffer = std::move(newBuffer);
    }

    template<typename Scalar>
    void OpenCLStorage<Scalar>::resizeImpl(SizeType newSize, int) {
        if (newSize == m_size) return;
        m_size   = newSize;
        m_buffer = cl::Buffer(global::openCLContext, bufferFlags, newSize * sizeof(Scalar));
    }

    template<typename Scalar>
    auto OpenCLStorage<Scalar>::size() const -> SizeType {
        return m_size;
    }

    template<typename Scalar>
    auto OpenCLStorage<Scalar>::operator[](SizeType index) const
      -> const detail::OpenCLRef<Scalar> {
        LIBRAPID_ASSERT(index >= 0 && index < m_size,
                        "Index {} is out of range for OpenCLStorage with {} elements",
                        index,
                        m_size);
        return detail::OpenCLRef<Scalar>(m_buffer, index);
    }

    template<typename Scalar>
    auto OpenCLStorage<Scalar>::operator[](SizeType index) -> detail::OpenCLRef<Scalar> {
        LIBRAPID_ASSERT(index >= 0 && index < m_size,
                        "Index {} is out of range for OpenCLStorage with {} elements",
                        index,
                        m_size);
        return detail::OpenCLRef<Scalar>(m_buffer, index);
    }

    template<typename Scalar>
    auto OpenCLStorage<Scalar>::data() const -> const cl::Buffer & {
        return m_buffer;
    }

    template<typename Scalar>
    auto OpenCLStorage<Scalar>::data() -> cl::Buffer & {
        return m_buffer;
    }
} // namespace librapid

#endif // LIBRAPID_HAS_OPENCL

#endif // LIBRAPID_ARRAY_OPENCL_STORAGE_HPP