// 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 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 // lookup order #include // CompileArgTag #include // Seq #include #include // GArg #include // GMetaArg #include // GTypeTraits #include //suppress_unused_warning 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 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::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 = 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()); } }; } // 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_KERNEK_M macros inherit user classes from GKernelType and // GKernelTypeM respectively. template class GKernelTypeM(Args...)> >: public detail::MetaHelper, std::tuple > { 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::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: using InArgs = std::tuple; using OutArgs = std::tuple; static R on(Args... args) { cv::GCall call(GKernel{K::id(), &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) #define G_ID_HELPER_CLASS(Class) Class##IdHelper #define G_ID_HELPER_BODY(Class, Id) \ namespace detail \ { \ struct G_ID_HELPER_CLASS(Class) \ { \ static constexpr const char * id() {return Id;}; \ }; \ } #define G_TYPED_KERNEL(Class, API, Id) \ G_ID_HELPER_BODY(Class, Id) \ struct Class final: public cv::GKernelType, \ public detail::G_ID_HELPER_CLASS(Class) // {body} is to be defined by user #define G_TYPED_KERNEL_M(Class, API, Id) \ G_ID_HELPER_BODY(Class, Id) \ struct Class final: public cv::GKernelTypeM, \ public detail::G_ID_HELPER_CLASS(Class) \ // {body} is to be defined by user namespace cv { // Declare in cv:: namespace enum class unite_policy { REPLACE, KEEP }; 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 * @{ */ // Lookup order is in fact a vector of Backends to traverse during look-up /** * @brief Priority list of backends to use during kernel * resolution process. * * Priority is descending -- the first backend in the list has the * top priority, and the last one has the lowest priority. * * If there's multiple implementations available for a kernel at * the moment of graph compilation, a kernel (and thus a backend) * will be selected according to this order (if the parameter is passed). * * Default order is not specified (and by default, only * CPU(OpenCV) backend is involved in graph compilation). */ using GLookupOrder = std::vector; /** * @brief Create a backend lookup order -- priority list of * backends to use during graph compilation process. * * @sa GLookupOrder, @ref gapi_std_backends */ inline GLookupOrder lookup_order(std::initializer_list &&list) { return GLookupOrder(std::move(list)); } // FIXME: Hide implementation /** * @brief A container class for heterogeneous kernel * implementation collections. * * GKernelPackage is a special container class which stores kernel * _implementations_. Objects of this class are created and passed * to cv::GComputation::compile() to specify which kernels to use * 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 programatically, 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 implementations are _types_, not objects. * * Finally, two kernel packages can be combined into a new one * with function cv::gapi::combine(). There are different rules * apply to this process, see also cv::gapi::unite_policy for * details. */ class GAPI_EXPORTS GKernelPackage { /// @private using S = std::unordered_map; /// @private using M = std::unordered_map; /// @private M m_backend_kernels; 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); 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 Test if a particular kernel _implementation_ KImpl is * included in this kernel package. * * @sa includesAPI() * * @return true if there is such kernel, false otherwise. */ template bool includes() const { const auto set_iter = m_backend_kernels.find(KImpl::backend()); return (set_iter != m_backend_kernels.end()) ? (set_iter->second.count(KImpl::API::id()) > 0) : false; } /** * @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()); } /** * @brief Find a kernel (by its API), given the look-up order. * * If order is empty, returns first suitable implementation. * Throws if nothing found. * * @return Backend which hosts matching kernel implementation. * * @sa cv::gapi::lookup_order */ template GBackend lookup(const GLookupOrder &order = {}) const { return lookup(KAPI::id(), order).first; } /// @private std::pair lookup(const std::string &id, const GLookupOrder &order = {}) const; // FIXME: No overwrites allowed? /** * @brief Put a new kernel implementation KImpl into package. * * @param up unite policy to use. If the package has already * implementation for this kernel (probably from another * backend), and cv::unite_policy::KEEP is passed, the * existing implementation remains in package; on * cv::unite_policy::REPLACE all other existing * implementations are first dropped from the package. */ template void include(const cv::unite_policy up = cv::unite_policy::KEEP) { auto backend = KImpl::backend(); auto kernel_id = KImpl::API::id(); auto kernel_impl = GKernelImpl{KImpl::kernel()}; if (up == cv::unite_policy::REPLACE) removeAPI(kernel_id); else GAPI_Assert(up == cv::unite_policy::KEEP); // Regardless of the policy, store new impl in its storage slot. m_backend_kernels[backend][kernel_id] = std::move(kernel_impl); } /** * @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`, * with unity policy defined by `policy`. * * @param lhs "Left-hand-side" package in the process * @param rhs "Right-hand-side" package in the process * @param policy Unite policy which is used in case of conflicts * -- when the same kernel API is implemented in both packages by * different backends; cv::unite_policy::KEEP keeps both * implementation in the resulting package, while * cv::unite_policy::REPLACE gives precedence two kernels from * "Right-hand-side". * * @return a new kernel package. */ friend GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &lhs, const GKernelPackage &rhs, const cv::unite_policy policy); }; /** * @brief Create a kernel package object containing kernels * specified in variadic template argument. * * In G-API, kernel implementations 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) 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() { 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; }; /** @} */ GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &lhs, const GKernelPackage &rhs, const cv::unite_policy policy); } // namespace gapi namespace detail { template<> struct CompileArgTag { static const char* tag() { return "gapi.kernel_package"; } }; template<> struct CompileArgTag { static const char* tag() { return "gapi.lookup_order"; } }; } // namespace detail } // namespace cv #endif // OPENCV_GAPI_GKERNEL_HPP