// 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-2021 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 { struct GTypeInfo { GShape shape; cv::detail::OpaqueKind kind; detail::HostCtor ctor; }; using GShapes = std::vector; using GKinds = std::vector; using GCtors = std::vector; using GTypesInfo = 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; std::string name; // kernel ID, defined by its API (signature) std::string tag; // some (implementation-specific) tag M outMeta; // generic adaptor to API::outMeta(...) GShapes outShapes; // types (shapes) kernel's outputs GKinds inKinds; // kinds of kernel's inputs (fixme: below) GCtors outCtors; // captured constructors for template output types }; // TODO: It's questionable if inKinds should really be here. Instead, // this information could come from meta. // GKernelImpl describes particular kernel implementation to the system struct GAPI_EXPORTS GKernelImpl { util::any opaque; // backend-specific opaque info GKernel::M outMeta; // for deserialized graphs, the outMeta is taken here }; 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 // 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); } }; template struct Yield > { static inline cv::GOpaque yield(cv::GCall &call, int i) { return call.yieldOpaque(i); } }; template<> struct Yield { static inline cv::GFrame yield(cv::GCall &call, int i) { return call.yieldFrame(i); } }; //////////////////////////////////////////////////////////////////////////// // 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 = GFrameDesc; }; template<> struct MetaType { using type = GScalarDesc; }; template struct MetaType > { using type = GArrayDesc; }; template struct MetaType > { using type = GOpaqueDesc; }; template struct MetaType { using type = T; }; // opaque args passed as-is // FIXME: Move it to type traits? // 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; // TODO: Args&&... here? static std::tuple on(Args... args) { cv::GCall call(GKernel{ K::id() , K::tag() , &K::getOutMeta , {detail::GTypeTraits::shape...} , {detail::GTypeTraits::op_kind...} , {detail::GObtainCtor::get()...}}); call.pass(args...); // TODO: std::forward() here? 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} , {detail::GTypeTraits::op_kind...} , {detail::GObtainCtor::get()}}); call.pass(args...); return detail::Yield::yield(call, 0); } }; namespace detail { // This tiny class eliminates the semantic difference between // GKernelType and GKernelTypeM. template class KernelTypeMedium; template class KernelTypeMedium(Args...)>> : public cv::GKernelTypeM(Args...)>> {}; template class KernelTypeMedium> : public cv::GKernelType> {}; } // namespace detail } // 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 #define GET_G_TYPED_KERNEL(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, NAME, ...) NAME #define COMBINE_SIGNATURE(...) __VA_ARGS__ // Ensure correct __VA_ARGS__ expansion on Windows #define __WRAP_VAARGS(x) x /** * Helper for G_TYPED_KERNEL 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 or a tuple of multiple values. * @param Id string identifier for the operation. Must be unique. */ #define G_TYPED_KERNEL_HELPER(Class, API, Id) \ G_ID_HELPER_BODY(Class, Id) \ struct Class final: public cv::detail::KernelTypeMedium, \ public G_ID_HELPER_CLASS(Class) // {body} is to be defined by user #define G_TYPED_KERNEL_HELPER_2(Class, _1, _2, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2), Id) #define G_TYPED_KERNEL_HELPER_3(Class, _1, _2, _3, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2, _3), Id) #define G_TYPED_KERNEL_HELPER_4(Class, _1, _2, _3, _4, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2, _3, _4), Id) #define G_TYPED_KERNEL_HELPER_5(Class, _1, _2, _3, _4, _5, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2, _3, _4, _5), Id) #define G_TYPED_KERNEL_HELPER_6(Class, _1, _2, _3, _4, _5, _6, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2, _3, _4, _5, _6), Id) #define G_TYPED_KERNEL_HELPER_7(Class, _1, _2, _3, _4, _5, _6, _7, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2, _3, _4, _5, _6, _7), Id) #define G_TYPED_KERNEL_HELPER_8(Class, _1, _2, _3, _4, _5, _6, _7, _8, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2, _3, _4, _5, _6, _7, _8), Id) #define G_TYPED_KERNEL_HELPER_9(Class, _1, _2, _3, _4, _5, _6, _7, _8, _9, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2, _3, _4, _5, _6, _7, _8, _9), Id) #define G_TYPED_KERNEL_HELPER_10(Class, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, Id) \ G_TYPED_KERNEL_HELPER(Class, COMBINE_SIGNATURE(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10), Id) /** * Declares a new G-API Operation. See [Kernel API](@ref gapi_kernel_api) * for more details. * * @param Class type name for this operation. */ #define G_TYPED_KERNEL(Class, ...) __WRAP_VAARGS(GET_G_TYPED_KERNEL(__VA_ARGS__, \ G_TYPED_KERNEL_HELPER_10, \ G_TYPED_KERNEL_HELPER_9, \ G_TYPED_KERNEL_HELPER_8, \ G_TYPED_KERNEL_HELPER_7, \ G_TYPED_KERNEL_HELPER_6, \ G_TYPED_KERNEL_HELPER_5, \ G_TYPED_KERNEL_HELPER_4, \ G_TYPED_KERNEL_HELPER_3, \ G_TYPED_KERNEL_HELPER_2, \ G_TYPED_KERNEL_HELPER)(Class, __VA_ARGS__)) \ /** * Declares a new G-API Operation. See [Kernel API](@ref gapi_kernel_api) for more details. * * @deprecated This macro is deprecated in favor of `G_TYPED_KERNEL` that is used for declaring any * G-API Operation. * * @param Class type name for this operation. */ #define G_TYPED_KERNEL_M G_TYPED_KERNEL #define G_API_OP G_TYPED_KERNEL #define G_API_OP_M G_API_OP namespace cv { namespace gapi { // Prework: model "Device" API before it gets to G-API headers. // FIXME: Don't mix with internal Backends class! /// @private 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 { /// @private class GFunctor { public: virtual cv::GKernelImpl impl() const = 0; virtual cv::gapi::GBackend backend() const = 0; const char* id() const { return m_id; } virtual ~GFunctor() = default; protected: GFunctor(const char* id) : m_id(id) { }; private: const char* m_id; }; /** \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_W_SIMPLE GKernelPackage { /// @private using M = std::unordered_map>; /// @private M m_id_kernels; /// @private std::vector m_transformations; protected: /// @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(), &KImpl::API::getOutMeta}; 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: void include(const GFunctor& functor) { m_id_kernels[functor.id()] = std::make_pair(functor.backend(), functor.impl()); } /** * @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 Returns vector of kernel ids included in the package * * @return vector of kernel ids included in the package */ std::vector get_kernel_ids() 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()); } /// @private bool includesAPI(const std::string &id) const; // 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 Adds a new kernel based on it's backend and id into the kernel package * * @param backend backend associated with the kernel * @param kernel_id a name/id of the kernel */ void include(const cv::gapi::GBackend& backend, const std::string& kernel_id) { removeAPI(kernel_id); m_id_kernels[kernel_id] = std::make_pair(backend, GKernelImpl{{}, {}}); } /** * @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 samples/cpp/tutorial_code/gapi/doc_snippets/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(cv::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; }; template GKernelPackage kernels(FF&... functors) { GKernelPackage pkg; int unused[] = { 0, (pkg.include(functors), 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::gapi::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