1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html.
5 // Copyright (C) 2018-2019 Intel Corporation
8 #ifndef OPENCV_GAPI_GKERNEL_HPP
9 #define OPENCV_GAPI_GKERNEL_HPP
13 #include <string> // string
14 #include <type_traits> // false_type, true_type
15 #include <unordered_map> // map (for GKernelPackage)
16 #include <utility> // tuple
17 #include <vector> // lookup order
19 #include <opencv2/gapi/gcommon.hpp> // CompileArgTag
20 #include <opencv2/gapi/util/util.hpp> // Seq
21 #include <opencv2/gapi/gcall.hpp>
22 #include <opencv2/gapi/garg.hpp> // GArg
23 #include <opencv2/gapi/gmetaarg.hpp> // GMetaArg
24 #include <opencv2/gapi/gtype_traits.hpp> // GTypeTraits
25 #include <opencv2/gapi/util/compiler_hints.hpp> //suppress_unused_warning
30 using GShapes = std::vector<GShape>;
32 // GKernel describes kernel API to the system
33 // FIXME: add attributes of a kernel, (e.g. number and types
35 struct GAPI_EXPORTS GKernel
37 using M = std::function<GMetaArgs(const GMetaArgs &, const GArgs &)>;
39 const std::string name; // kernel ID, defined by its API (signature)
40 const M outMeta; // generic adaptor to API::outMeta(...)
41 const GShapes outShapes; // types (shapes) kernel's outputs
44 // GKernelImpl describes particular kernel implementation to the system
45 struct GAPI_EXPORTS GKernelImpl
47 util::any opaque; // backend-specific opaque info
50 template<typename, typename> class GKernelTypeM;
54 ////////////////////////////////////////////////////////////////////////////
55 // yield() is used in graph construction time as a generic method to obtain
56 // lazy "return value" of G-API operations
61 template<typename T> struct Yield;
62 template<> struct Yield<cv::GMat>
64 static inline cv::GMat yield(cv::GCall &call, int i) { return call.yield(i); }
66 template<> struct Yield<cv::GScalar>
68 static inline cv::GScalar yield(cv::GCall &call, int i) { return call.yieldScalar(i); }
70 template<typename U> struct Yield<cv::GArray<U> >
72 static inline cv::GArray<U> yield(cv::GCall &call, int i) { return call.yieldArray<U>(i); }
74 } // anonymous namespace
76 ////////////////////////////////////////////////////////////////////////////
77 // Helper classes which brings outputMeta() marshalling to kernel
80 // 1. MetaType establishes G#Type -> G#Meta mapping between G-API dynamic
81 // types and its metadata descriptor types.
82 // This mapping is used to transform types to call outMeta() callback.
83 template<typename T> struct MetaType;
84 template<> struct MetaType<cv::GMat> { using type = GMatDesc; };
85 template<> struct MetaType<cv::GScalar> { using type = GScalarDesc; };
86 template<typename U> struct MetaType<cv::GArray<U> > { using type = GArrayDesc; };
87 template<typename T> struct MetaType { using type = T; }; // opaque args passed as-is
89 // 2. Hacky test based on MetaType to check if we operate on G-* type or not
90 template<typename T> using is_nongapi_type = std::is_same<T, typename MetaType<T>::type>;
92 // 3. Two ways to transform input arguments to its meta - for G-* and non-G* types:
94 typename std::enable_if<!is_nongapi_type<T>::value, typename MetaType<T>::type>
95 ::type get_in_meta(const GMetaArgs &in_meta, const GArgs &, int idx)
97 return util::get<typename MetaType<T>::type>(in_meta.at(idx));
101 typename std::enable_if<is_nongapi_type<T>::value, T>
102 ::type get_in_meta(const GMetaArgs &, const GArgs &in_args, int idx)
104 return in_args.at(idx).template get<T>();
107 // 4. The MetaHelper itself: an entity which generates outMeta() call
108 // based on kernel signature, with arguments properly substituted.
109 // 4.1 - case for multiple return values
110 // FIXME: probably can be simplified with std::apply or analogue.
111 template<typename, typename, typename>
114 template<typename K, typename... Ins, typename... Outs>
115 struct MetaHelper<K, std::tuple<Ins...>, std::tuple<Outs...> >
117 template<int... IIs, int... OIs>
118 static GMetaArgs getOutMeta_impl(const GMetaArgs &in_meta,
119 const GArgs &in_args,
124 using R = std::tuple<typename MetaType<Outs>::type...>;
125 const R r = K::outMeta( get_in_meta<Ins>(in_meta, in_args, IIs)... );
126 return GMetaArgs{ GMetaArg(std::get<OIs>(r))... };
128 // FIXME: help users identify how outMeta must look like (via default impl w/static_assert?)
130 static GMetaArgs getOutMeta(const GMetaArgs &in_meta,
131 const GArgs &in_args)
133 return getOutMeta_impl(in_meta,
135 typename detail::MkSeq<sizeof...(Ins)>::type(),
136 typename detail::MkSeq<sizeof...(Outs)>::type());
140 // 4.1 - case for a single return value
141 // FIXME: How to avoid duplication here?
142 template<typename K, typename... Ins, typename Out>
143 struct MetaHelper<K, std::tuple<Ins...>, Out >
146 static GMetaArgs getOutMeta_impl(const GMetaArgs &in_meta,
147 const GArgs &in_args,
151 using R = typename MetaType<Out>::type;
152 const R r = K::outMeta( get_in_meta<Ins>(in_meta, in_args, IIs)... );
153 return GMetaArgs{ GMetaArg(r) };
155 // FIXME: help users identify how outMeta must look like (via default impl w/static_assert?)
157 static GMetaArgs getOutMeta(const GMetaArgs &in_meta,
158 const GArgs &in_args)
160 return getOutMeta_impl(in_meta,
162 typename detail::MkSeq<sizeof...(Ins)>::type());
166 } // namespace detail
168 // GKernelType and GKernelTypeM are base classes which implement typed ::on()
169 // method based on kernel signature. GKernelTypeM stands for multiple-return-value kernels
171 // G_TYPED_KERNEL and G_TYPED_KERNEK_M macros inherit user classes from GKernelType and
172 // GKernelTypeM respectively.
174 template<typename K, typename... R, typename... Args>
175 class GKernelTypeM<K, std::function<std::tuple<R...>(Args...)> >:
176 public detail::MetaHelper<K, std::tuple<Args...>, std::tuple<R...> >
179 static std::tuple<R...> yield(cv::GCall &call, detail::Seq<IIs...>)
181 return std::make_tuple(detail::Yield<R>::yield(call, IIs)...);
185 using InArgs = std::tuple<Args...>;
186 using OutArgs = std::tuple<R...>;
188 static std::tuple<R...> on(Args... args)
190 cv::GCall call(GKernel{K::id(), &K::getOutMeta, {detail::GTypeTraits<R>::shape...}});
192 return yield(call, typename detail::MkSeq<sizeof...(R)>::type());
196 template<typename, typename> class GKernelType;
198 template<typename K, typename R, typename... Args>
199 class GKernelType<K, std::function<R(Args...)> >:
200 public detail::MetaHelper<K, std::tuple<Args...>, R >
203 using InArgs = std::tuple<Args...>;
204 using OutArgs = std::tuple<R>;
206 static R on(Args... args)
208 cv::GCall call(GKernel{K::id(), &K::getOutMeta, {detail::GTypeTraits<R>::shape}});
210 return detail::Yield<R>::yield(call, 0);
217 // FIXME: I don't know a better way so far. Feel free to suggest one
218 // The problem is that every typed kernel should have ::id() but body
219 // of the class is defined by user (with outMeta, other stuff)
221 #define G_ID_HELPER_CLASS(Class) Class##IdHelper
223 #define G_ID_HELPER_BODY(Class, Id) \
226 struct G_ID_HELPER_CLASS(Class) \
228 static constexpr const char * id() {return Id;}; \
232 #define G_TYPED_KERNEL(Class, API, Id) \
233 G_ID_HELPER_BODY(Class, Id) \
234 struct Class final: public cv::GKernelType<Class, std::function API >, \
235 public detail::G_ID_HELPER_CLASS(Class)
236 // {body} is to be defined by user
238 #define G_TYPED_KERNEL_M(Class, API, Id) \
239 G_ID_HELPER_BODY(Class, Id) \
240 struct Class final: public cv::GKernelTypeM<Class, std::function API >, \
241 public detail::G_ID_HELPER_CLASS(Class) \
242 // {body} is to be defined by user
246 // Declare <unite> in cv:: namespace
247 enum class unite_policy
255 // Prework: model "Device" API before it gets to G-API headers.
256 // FIXME: Don't mix with internal Backends class!
257 class GAPI_EXPORTS GBackend
262 // TODO: make it template (call `new` within??)
264 explicit GBackend(std::shared_ptr<Priv> &&p);
267 const Priv& priv() const;
268 std::size_t hash() const;
270 bool operator== (const GBackend &rhs) const;
273 std::shared_ptr<Priv> m_priv;
276 inline bool operator != (const GBackend &lhs, const GBackend &rhs)
278 return !(lhs == rhs);
285 template<> struct hash<cv::gapi::GBackend>
287 std::size_t operator() (const cv::gapi::GBackend &b) const
297 /** \addtogroup gapi_compile_args
301 // Lookup order is in fact a vector of Backends to traverse during look-up
303 * @brief Priority list of backends to use during kernel
304 * resolution process.
306 * Priority is descending -- the first backend in the list has the
307 * top priority, and the last one has the lowest priority.
309 * If there's multiple implementations available for a kernel at
310 * the moment of graph compilation, a kernel (and thus a backend)
311 * will be selected according to this order (if the parameter is passed).
313 * Default order is not specified (and by default, only
314 * CPU(OpenCV) backend is involved in graph compilation).
316 using GLookupOrder = std::vector<GBackend>;
318 * @brief Create a backend lookup order -- priority list of
319 * backends to use during graph compilation process.
321 * @sa GLookupOrder, @ref gapi_std_backends
323 inline GLookupOrder lookup_order(std::initializer_list<GBackend> &&list)
325 return GLookupOrder(std::move(list));
328 // FIXME: Hide implementation
330 * @brief A container class for heterogeneous kernel
331 * implementation collections.
333 * GKernelPackage is a special container class which stores kernel
334 * _implementations_. Objects of this class are created and passed
335 * to cv::GComputation::compile() to specify which kernels to use
336 * in the compiled graph. GKernelPackage may contain kernels of
337 * different backends, e.g. be heterogeneous.
339 * The most easy way to create a kernel package is to use function
340 * cv::gapi::kernels(). This template functions takes kernel
341 * implementations in form of type list (variadic template) and
342 * generates a kernel package atop of that.
344 * Kernel packages can be also generated programatically, starting
345 * with an empty package (created with the default constructor)
346 * and then by populating it with kernels via call to
347 * GKernelPackage::include(). Note this method is also a template
348 * one since G-API kernel implementations are _types_, not objects.
350 * Finally, two kernel packages can be combined into a new one
351 * with function cv::gapi::combine(). There are different rules
352 * apply to this process, see also cv::gapi::unite_policy for
355 class GAPI_EXPORTS GKernelPackage
358 using S = std::unordered_map<std::string, GKernelImpl>;
361 using M = std::unordered_map<GBackend, S>;
368 // Check if package contains ANY implementation of a kernel API
369 // by API textual id.
370 bool includesAPI(const std::string &id) const;
373 // Remove ALL implementations of the given API (identified by ID)
374 void removeAPI(const std::string &id);
378 * @brief Returns total number of kernels in the package
379 * (accross all backends included)
381 * @return a number of kernels in the package
383 std::size_t size() const;
386 * @brief Test if a particular kernel _implementation_ KImpl is
387 * included in this kernel package.
391 * @return true if there is such kernel, false otherwise.
393 template<typename KImpl>
394 bool includes() const
396 const auto set_iter = m_backend_kernels.find(KImpl::backend());
397 return (set_iter != m_backend_kernels.end())
398 ? (set_iter->second.count(KImpl::API::id()) > 0)
403 * @brief Remove all kernels associated with the given backend
406 * Does nothing if there's no kernels of this backend in the package.
408 * @param backend backend which kernels to remove
410 void remove(const GBackend& backend);
413 * @brief Remove all kernels implementing the given API from
416 * Does nothing if there's no kernels implementing the given interface.
418 template<typename KAPI>
421 removeAPI(KAPI::id());
424 // FIXME: Rename to includes() and distinguish API/impl case by
427 * Check if package contains ANY implementation of a kernel API
430 template<typename KAPI>
431 bool includesAPI() const
433 return includesAPI(KAPI::id());
437 * @brief Find a kernel (by its API), given the look-up order.
439 * If order is empty, returns first suitable implementation.
440 * Throws if nothing found.
442 * @return Backend which hosts matching kernel implementation.
444 * @sa cv::gapi::lookup_order
446 template<typename KAPI>
447 GBackend lookup(const GLookupOrder &order = {}) const
449 return lookup(KAPI::id(), order).first;
453 std::pair<cv::gapi::GBackend, cv::GKernelImpl>
454 lookup(const std::string &id, const GLookupOrder &order = {}) const;
456 // FIXME: No overwrites allowed?
458 * @brief Put a new kernel implementation KImpl into package.
460 * @param up unite policy to use. If the package has already
461 * implementation for this kernel (probably from another
462 * backend), and cv::unite_policy::KEEP is passed, the
463 * existing implementation remains in package; on
464 * cv::unite_policy::REPLACE all other existing
465 * implementations are first dropped from the package.
467 template<typename KImpl>
468 void include(const cv::unite_policy up = cv::unite_policy::KEEP)
470 auto backend = KImpl::backend();
471 auto kernel_id = KImpl::API::id();
472 auto kernel_impl = GKernelImpl{KImpl::kernel()};
473 if (up == cv::unite_policy::REPLACE) removeAPI(kernel_id);
474 else GAPI_Assert(up == cv::unite_policy::KEEP);
476 // Regardless of the policy, store new impl in its storage slot.
477 m_backend_kernels[backend][kernel_id] = std::move(kernel_impl);
481 * @brief Lists all backends which are included into package
483 * @return vector of backends
485 std::vector<GBackend> backends() const;
487 // TODO: Doxygen bug -- it wants me to place this comment
490 * @brief Create a new package based on `lhs` and `rhs`,
491 * with unity policy defined by `policy`.
493 * @param lhs "Left-hand-side" package in the process
494 * @param rhs "Right-hand-side" package in the process
495 * @param policy Unite policy which is used in case of conflicts
496 * -- when the same kernel API is implemented in both packages by
497 * different backends; cv::unite_policy::KEEP keeps both
498 * implementation in the resulting package, while
499 * cv::unite_policy::REPLACE gives precedence two kernels from
502 * @return a new kernel package.
504 friend GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &lhs,
505 const GKernelPackage &rhs,
506 const cv::unite_policy policy);
510 * @brief Create a kernel package object containing kernels
511 * specified in variadic template argument.
513 * In G-API, kernel implementations are _types_. Every backend has
514 * its own kernel API (like GAPI_OCV_KERNEL() and
515 * GAPI_FLUID_KERNEL()) but all of that APIs define a new type for
516 * each kernel implementation.
518 * Use this function to pass kernel implementations (defined in
519 * either way) to the system. Example:
521 * @snippet modules/gapi/samples/api_ref_snippets.cpp kernels_snippet
523 * Note that kernels() itself is a function returning object, not
524 * a type, so having `()` at the end is important -- it must be a
527 template<typename... KK> GKernelPackage kernels()
531 // For those who wonder - below is a trick to call a number of
532 // methods based on parameter pack (zeroes just help hiding these
533 // calls into a sequence which helps to expand this parameter pack).
534 // Just note that `f(),a` always equals to `a` (with f() called!)
535 // and parentheses are used to hide function call in the expanded sequence.
536 // Leading 0 helps to handle case when KK is an empty list (kernels<>()).
538 int unused[] = { 0, (pkg.include<KK>(), 0)... };
539 cv::util::suppress_unused_warning(unused);
545 GAPI_EXPORTS GKernelPackage combine(const GKernelPackage &lhs,
546 const GKernelPackage &rhs,
547 const cv::unite_policy policy);
552 template<> struct CompileArgTag<cv::gapi::GKernelPackage>
554 static const char* tag() { return "gapi.kernel_package"; }
556 template<> struct CompileArgTag<cv::gapi::GLookupOrder>
558 static const char* tag() { return "gapi.lookup_order"; }
560 } // namespace detail
563 #endif // OPENCV_GAPI_GKERNEL_HPP