// This file is part of OpenCV project. // It is subject to the license terms in the LICENSE file found in the top-level directory // of this distribution and at http://opencv.org/license.html. // // Copyright (C) 2018-2019 Intel Corporation #ifndef OPENCV_GAPI_GKERNEL_HPP #define OPENCV_GAPI_GKERNEL_HPP #include #include #include // string #include // false_type, true_type #include // map (for GKernelPackage) #include // tuple #include // CompileArgTag #include // Seq #include #include // GArg #include // GMetaArg #include // GTypeTraits #include //suppress_unused_warning #include namespace cv { using GShapes = std::vector; // GKernel describes kernel API to the system // FIXME: add attributes of a kernel, (e.g. number and types // of inputs, etc) struct GAPI_EXPORTS GKernel { using M = std::function; const std::string name; // kernel ID, defined by its API (signature) const std::string tag; // some (implementation-specific) tag const M outMeta; // generic adaptor to API::outMeta(...) const GShapes outShapes; // types (shapes) kernel's outputs }; // GKernelImpl describes particular kernel implementation to the system struct GAPI_EXPORTS GKernelImpl { util::any opaque; // backend-specific opaque info }; template class GKernelTypeM; namespace detail { //////////////////////////////////////////////////////////////////////////// // yield() is used in graph construction time as a generic method to obtain // lazy "return value" of G-API operations // namespace { template struct Yield; template<> struct Yield { static inline cv::GMat yield(cv::GCall &call, int i) { return call.yield(i); } }; template<> struct Yield { static inline cv::GMatP yield(cv::GCall &call, int i) { return call.yieldP(i); } }; template<> struct Yield { static inline cv::GScalar yield(cv::GCall &call, int i) { return call.yieldScalar(i); } }; template struct Yield > { static inline cv::GArray yield(cv::GCall &call, int i) { return call.yieldArray(i); } }; } // anonymous namespace //////////////////////////////////////////////////////////////////////////// // Helper classes which brings outputMeta() marshalling to kernel // implementations // // 1. MetaType establishes G#Type -> G#Meta mapping between G-API dynamic // types and its metadata descriptor types. // This mapping is used to transform types to call outMeta() callback. template struct MetaType; template<> struct MetaType { using type = GMatDesc; }; template<> struct MetaType { using type = GMatDesc; }; template<> struct MetaType { using type = GScalarDesc; }; template struct MetaType > { using type = GArrayDesc; }; template struct MetaType { using type = T; }; // opaque args passed as-is // 2. Hacky test based on MetaType to check if we operate on G-* type or not template using is_nongapi_type = std::is_same::type>; // 3. Two ways to transform input arguments to its meta - for G-* and non-G* types: template typename std::enable_if::value, typename MetaType::type> ::type get_in_meta(const GMetaArgs &in_meta, const GArgs &, int idx) { return util::get::type>(in_meta.at(idx)); } template typename std::enable_if::value, T> ::type get_in_meta(const GMetaArgs &, const GArgs &in_args, int idx) { return in_args.at(idx).template get(); } // 4. The MetaHelper itself: an entity which generates outMeta() call // based on kernel signature, with arguments properly substituted. // 4.1 - case for multiple return values // FIXME: probably can be simplified with std::apply or analogue. template struct MetaHelper; template struct MetaHelper, std::tuple > { template static GMetaArgs getOutMeta_impl(const GMetaArgs &in_meta, const GArgs &in_args, detail::Seq, detail::Seq) { // FIXME: decay? using R = std::tuple::type...>; const R r = K::outMeta( get_in_meta(in_meta, in_args, IIs)... ); return GMetaArgs{ GMetaArg(std::get(r))... }; } // FIXME: help users identify how outMeta must look like (via default impl w/static_assert?) static GMetaArgs getOutMeta(const GMetaArgs &in_meta, const GArgs &in_args) { return getOutMeta_impl(in_meta, in_args, typename detail::MkSeq::type(), typename detail::MkSeq::type()); } }; // 4.1 - case for a single return value // FIXME: How to avoid duplication here? template struct MetaHelper, Out > { template static GMetaArgs getOutMeta_impl(const GMetaArgs &in_meta, const GArgs &in_args, detail::Seq) { // FIXME: decay? using R = typename MetaType::type; const R r = K::outMeta( get_in_meta(in_meta, in_args, IIs)... ); return GMetaArgs{ GMetaArg(r) }; } // FIXME: help users identify how outMeta must look like (via default impl w/static_assert?) static GMetaArgs getOutMeta(const GMetaArgs &in_meta, const GArgs &in_args) { return getOutMeta_impl(in_meta, in_args, typename detail::MkSeq::type()); } }; //////////////////////////////////////////////////////////////////////////// // Helper class to introduce tags to calls. By default there's no tag struct NoTag { static constexpr const char *tag() { return ""; } }; } // namespace detail // GKernelType and GKernelTypeM are base classes which implement typed ::on() // method based on kernel signature. GKernelTypeM stands for multiple-return-value kernels // // G_TYPED_KERNEL and G_TYPED_KERNEL_M macros inherit user classes from GKernelType and // GKernelTypeM respectively. template class GKernelTypeM(Args...)> > : public detail::MetaHelper, std::tuple> , public detail::NoTag { template static std::tuple yield(cv::GCall &call, detail::Seq) { return std::make_tuple(detail::Yield::yield(call, IIs)...); } public: using InArgs = std::tuple; using OutArgs = std::tuple; static std::tuple on(Args... args) { cv::GCall call(GKernel{K::id(), K::tag(), &K::getOutMeta, {detail::GTypeTraits::shape...}}); call.pass(args...); return yield(call, typename detail::MkSeq::type()); } }; template class GKernelType; template class GKernelType > : public detail::MetaHelper, R> , public detail::NoTag { public: using InArgs = std::tuple; using OutArgs = std::tuple; static R on(Args... args) { cv::GCall call(GKernel{K::id(), K::tag(), &K::getOutMeta, {detail::GTypeTraits::shape}}); call.pass(args...); return detail::Yield::yield(call, 0); } }; } // namespace cv // FIXME: I don't know a better way so far. Feel free to suggest one // The problem is that every typed kernel should have ::id() but body // of the class is defined by user (with outMeta, other stuff) //! @cond IGNORED #define G_ID_HELPER_CLASS(Class) Class##IdHelper #define G_ID_HELPER_BODY(Class, Id) \ struct G_ID_HELPER_CLASS(Class) \ { \ static constexpr const char * id() {return Id;} \ }; \ //! @endcond /** * Declares a new G-API Operation. See [Kernel API](@ref gapi_kernel_api) * for more details. * * @param Class type name for this operation. * @param API an `std::function<>`-like signature for the operation; * return type is a single value. * @param Id string identifier for the operation. Must be unique. */ #define G_TYPED_KERNEL(Class, API, Id) \ G_ID_HELPER_BODY(Class, Id) \ struct Class final: public cv::GKernelType, \ public G_ID_HELPER_CLASS(Class) // {body} is to be defined by user /** * Declares a new G-API Operation. See [Kernel API](@ref gapi_kernel_api) * for more details. * * @param Class type name for this operation. * @param API an `std::function<>`-like signature for the operation; * return type is a tuple of multiple values. * @param Id string identifier for the operation. Must be unique. */ #define G_TYPED_KERNEL_M(Class, API, Id) \ G_ID_HELPER_BODY(Class, Id) \ struct Class final: public cv::GKernelTypeM, \ public G_ID_HELPER_CLASS(Class) // {body} is to be defined by user #define G_API_OP G_TYPED_KERNEL #define G_API_OP_M G_TYPED_KERNEL_M namespace cv { namespace gapi { // Prework: model "Device" API before it gets to G-API headers. // FIXME: Don't mix with internal Backends class! class GAPI_EXPORTS GBackend { public: class Priv; // TODO: make it template (call `new` within??) GBackend(); explicit GBackend(std::shared_ptr &&p); Priv& priv(); const Priv& priv() const; std::size_t hash() const; bool operator== (const GBackend &rhs) const; private: std::shared_ptr m_priv; }; inline bool operator != (const GBackend &lhs, const GBackend &rhs) { return !(lhs == rhs); } } // namespace gapi } // namespace cv namespace std { template<> struct hash { std::size_t operator() (const cv::gapi::GBackend &b) const { return b.hash(); } }; } // namespace std namespace cv { namespace gapi { /** \addtogroup gapi_compile_args * @{ */ // FIXME: Hide implementation /** * @brief A container class for heterogeneous kernel * implementation collections and graph transformations. * * GKernelPackage is a special container class which stores kernel * _implementations_ and graph _transformations_. Objects of this class * are created and passed to cv::GComputation::compile() to specify * which kernels to use and which transformations to apply in the * compiled graph. GKernelPackage may contain kernels of * different backends, e.g. be heterogeneous. * * The most easy way to create a kernel package is to use function * cv::gapi::kernels(). This template functions takes kernel * implementations in form of type list (variadic template) and * generates a kernel package atop of that. * * Kernel packages can be also generated programmatically, starting * with an empty package (created with the default constructor) * and then by populating it with kernels via call to * GKernelPackage::include(). Note this method is also a template * one since G-API kernel and transformation implementations are _types_, * not objects. * * Finally, two kernel packages can be combined into a new one * with function cv::gapi::combine(). */ class GAPI_EXPORTS GKernelPackage { /// @private using M = std::unordered_map>; /// @private M m_id_kernels; /// @private std::vector m_transformations; protected: /// @private // Check if package contains ANY implementation of a kernel API // by API textual id. bool includesAPI(const std::string &id) const; /// @private // Remove ALL implementations of the given API (identified by ID) void removeAPI(const std::string &id); /// @private // Partial include() specialization for kernels template typename std::enable_if<(std::is_base_of::value), void>::type includeHelper() { auto backend = KImpl::backend(); auto kernel_id = KImpl::API::id(); auto kernel_impl = GKernelImpl{KImpl::kernel()}; removeAPI(kernel_id); m_id_kernels[kernel_id] = std::make_pair(backend, kernel_impl); } /// @private // Partial include() specialization for transformations template typename std::enable_if<(std::is_base_of::value), void>::type includeHelper() { m_transformations.emplace_back(TImpl::transformation()); } public: /** * @brief Returns total number of kernels * in the package (across all backends included) * * @return a number of kernels in the package */ std::size_t size() const; /** * @brief Returns vector of transformations included in the package * * @return vector of transformations included in the package */ const std::vector& get_transformations() const; /** * @brief Test if a particular kernel _implementation_ KImpl is * included in this kernel package. * * @sa includesAPI() * * @note cannot be applied to transformations * * @return true if there is such kernel, false otherwise. */ template bool includes() const { static_assert(std::is_base_of::value, "includes() can be applied to kernels only"); auto kernel_it = m_id_kernels.find(KImpl::API::id()); return kernel_it != m_id_kernels.end() && kernel_it->second.first == KImpl::backend(); } /** * @brief Remove all kernels associated with the given backend * from the package. * * Does nothing if there's no kernels of this backend in the package. * * @param backend backend which kernels to remove */ void remove(const GBackend& backend); /** * @brief Remove all kernels implementing the given API from * the package. * * Does nothing if there's no kernels implementing the given interface. */ template void remove() { removeAPI(KAPI::id()); } // FIXME: Rename to includes() and distinguish API/impl case by // statically? /** * Check if package contains ANY implementation of a kernel API * by API type. */ template bool includesAPI() const { return includesAPI(KAPI::id()); } // FIXME: The below comment is wrong, and who needs this function? /** * @brief Find a kernel (by its API) * * Returns implementation corresponding id. * Throws if nothing found. * * @return Backend which hosts matching kernel implementation. * */ template GBackend lookup() const { return lookup(KAPI::id()).first; } /// @private std::pair lookup(const std::string &id) const; // FIXME: No overwrites allowed? /** * @brief Put a new kernel implementation or a new transformation * KImpl into the package. */ template void include() { includeHelper(); } /** * @brief Lists all backends which are included into package * * @return vector of backends */ std::vector backends() const; // TODO: Doxygen bug -- it wants me to place this comment // here, not below. /** * @brief Create a new package based on `lhs` and `rhs`. * * @param lhs "Left-hand-side" package in the process * @param rhs "Right-hand-side" package in the process * @return a new kernel package. */ friend GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &lhs, const GKernelPackage &rhs); }; /** * @brief Create a kernel package object containing kernels * and transformations specified in variadic template argument. * * In G-API, kernel implementations and transformations are _types_. * Every backend has its own kernel API (like GAPI_OCV_KERNEL() and * GAPI_FLUID_KERNEL()) but all of that APIs define a new type for * each kernel implementation. * * Use this function to pass kernel implementations (defined in * either way) and transformations to the system. Example: * * @snippet modules/gapi/samples/api_ref_snippets.cpp kernels_snippet * * Note that kernels() itself is a function returning object, not * a type, so having `()` at the end is important -- it must be a * function call. */ template GKernelPackage kernels() { // FIXME: currently there is no check that transformations' signatures are unique // and won't be any intersection in graph compilation stage static_assert(detail::all_unique::value, "Kernels API must be unique"); GKernelPackage pkg; // For those who wonder - below is a trick to call a number of // methods based on parameter pack (zeroes just help hiding these // calls into a sequence which helps to expand this parameter pack). // Just note that `f(),a` always equals to `a` (with f() called!) // and parentheses are used to hide function call in the expanded sequence. // Leading 0 helps to handle case when KK is an empty list (kernels<>()). int unused[] = { 0, (pkg.include(), 0)... }; cv::util::suppress_unused_warning(unused); return pkg; }; /** @} */ // FYI - this function is already commented above GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &lhs, const GKernelPackage &rhs); /** * @brief Combines multiple G-API kernel packages into one * * @overload * * This function successively combines the passed kernel packages using a right fold. * Calling `combine(a, b, c)` is equal to `combine(a, combine(b, c))`. * * @return The resulting kernel package */ template GKernelPackage combine(const GKernelPackage &a, const GKernelPackage &b, Ps&&... rest) { return combine(a, combine(b, rest...)); } /** \addtogroup gapi_compile_args * @{ */ /** * @brief cv::use_only() is a special combinator which hints G-API to use only * kernels specified in cv::GComputation::compile() (and not to extend kernels available by * default with that package). */ struct GAPI_EXPORTS use_only { GKernelPackage pkg; }; /** @} */ } // namespace gapi namespace detail { template<> struct CompileArgTag { static const char* tag() { return "gapi.kernel_package"; } }; template<> struct CompileArgTag { static const char* tag() { return "gapi.use_only"; } }; } // namespace detail } // namespace cv #endif // OPENCV_GAPI_GKERNEL_HPP