gcpukernel.hpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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.
  4. //
  5. // Copyright (C) 2018-2022 Intel Corporation
  6. #ifndef OPENCV_GAPI_GCPUKERNEL_HPP
  7. #define OPENCV_GAPI_GCPUKERNEL_HPP
  8. #ifdef _MSC_VER
  9. #pragma warning(disable: 4702) // "Unreachable code"
  10. // on postprocess(...) call inside OCVCallHelper
  11. #endif
  12. #include <functional>
  13. #include <unordered_map>
  14. #include <utility>
  15. #include <vector>
  16. #include <opencv2/core/mat.hpp>
  17. #include <opencv2/gapi/gcommon.hpp>
  18. #include <opencv2/gapi/gkernel.hpp>
  19. #include <opencv2/gapi/garg.hpp>
  20. #include <opencv2/gapi/gmetaarg.hpp>
  21. #include <opencv2/gapi/util/compiler_hints.hpp> //suppress_unused_warning
  22. #include <opencv2/gapi/util/util.hpp>
  23. // FIXME: namespace scheme for backends?
  24. namespace cv {
  25. namespace gimpl
  26. {
  27. // Forward-declare an internal class
  28. class GCPUExecutable;
  29. } // namespace gimpl
  30. namespace gapi
  31. {
  32. /**
  33. * @brief This namespace contains G-API CPU backend functions,
  34. * structures, and symbols.
  35. */
  36. namespace cpu
  37. {
  38. /**
  39. * \addtogroup gapi_std_backends
  40. * @{
  41. *
  42. * @brief G-API backends available in this OpenCV version
  43. *
  44. * G-API backends play a corner stone role in G-API execution
  45. * stack. Every backend is hardware-oriented and thus can run its
  46. * kernels efficiently on the target platform.
  47. *
  48. * Backends are usually "black boxes" for G-API users -- on the API
  49. * side, all backends are represented as different objects of the
  50. * same class cv::gapi::GBackend.
  51. * User can manipulate with backends by specifying which kernels to use.
  52. *
  53. * @sa @ref gapi_hld
  54. */
  55. /**
  56. * @brief Get a reference to CPU (OpenCV) backend.
  57. *
  58. * This is the default backend in G-API at the moment, providing
  59. * broader functional coverage but losing some graph model
  60. * advantages. Provided mostly for reference and prototyping
  61. * purposes.
  62. *
  63. * @sa gapi_std_backends
  64. */
  65. GAPI_EXPORTS cv::gapi::GBackend backend();
  66. /** @} */
  67. class GOCVFunctor;
  68. //! @cond IGNORED
  69. template<typename K, typename Callable>
  70. GOCVFunctor ocv_kernel(const Callable& c);
  71. template<typename K, typename Callable>
  72. GOCVFunctor ocv_kernel(Callable& c);
  73. //! @endcond
  74. } // namespace cpu
  75. } // namespace gapi
  76. // Represents arguments which are passed to a wrapped CPU function
  77. // FIXME: put into detail?
  78. class GAPI_EXPORTS GCPUContext
  79. {
  80. public:
  81. // Generic accessor API
  82. template<typename T>
  83. const T& inArg(int input) { return m_args.at(input).get<T>(); }
  84. // Syntax sugar
  85. const cv::Mat& inMat(int input);
  86. cv::Mat& outMatR(int output); // FIXME: Avoid cv::Mat m = ctx.outMatR()
  87. const cv::Scalar& inVal(int input);
  88. cv::Scalar& outValR(int output); // FIXME: Avoid cv::Scalar s = ctx.outValR()
  89. cv::MediaFrame& outFrame(int output);
  90. template<typename T> std::vector<T>& outVecR(int output) // FIXME: the same issue
  91. {
  92. return outVecRef(output).wref<T>();
  93. }
  94. template<typename T> T& outOpaqueR(int output) // FIXME: the same issue
  95. {
  96. return outOpaqueRef(output).wref<T>();
  97. }
  98. GArg state()
  99. {
  100. return m_state;
  101. }
  102. protected:
  103. detail::VectorRef& outVecRef(int output);
  104. detail::OpaqueRef& outOpaqueRef(int output);
  105. std::vector<GArg> m_args;
  106. GArg m_state;
  107. //FIXME: avoid conversion of arguments from internal representation to OpenCV one on each call
  108. //to OCV kernel. (This can be achieved by a two single time conversions in GCPUExecutable::run,
  109. //once on enter for input and output arguments, and once before return for output arguments only
  110. std::unordered_map<std::size_t, GRunArgP> m_results;
  111. friend class gimpl::GCPUExecutable;
  112. };
  113. class GAPI_EXPORTS GCPUKernel
  114. {
  115. public:
  116. // This function is a kernel's execution entry point (does the processing work)
  117. using RunF = std::function<void(GCPUContext &)>;
  118. // This function is a stateful kernel's setup routine (configures state)
  119. using SetupF = std::function<void(const GMetaArgs &, const GArgs &,
  120. GArg &, const GCompileArgs &)>;
  121. GCPUKernel();
  122. GCPUKernel(const RunF& runF, const SetupF& setupF = nullptr);
  123. RunF m_runF = nullptr;
  124. SetupF m_setupF = nullptr;
  125. bool m_isStateful = false;
  126. };
  127. // FIXME: This is an ugly ad-hoc implementation. TODO: refactor
  128. namespace detail
  129. {
  130. template<class T> struct get_in;
  131. template<> struct get_in<cv::GMat>
  132. {
  133. static cv::Mat get(GCPUContext &ctx, int idx) { return ctx.inMat(idx); }
  134. };
  135. template<> struct get_in<cv::GMatP>
  136. {
  137. static cv::Mat get(GCPUContext &ctx, int idx) { return get_in<cv::GMat>::get(ctx, idx); }
  138. };
  139. template<> struct get_in<cv::GFrame>
  140. {
  141. static cv::MediaFrame get(GCPUContext &ctx, int idx) { return ctx.inArg<cv::MediaFrame>(idx); }
  142. };
  143. template<> struct get_in<cv::GScalar>
  144. {
  145. static cv::Scalar get(GCPUContext &ctx, int idx) { return ctx.inVal(idx); }
  146. };
  147. template<typename U> struct get_in<cv::GArray<U> >
  148. {
  149. static const std::vector<U>& get(GCPUContext &ctx, int idx) { return ctx.inArg<VectorRef>(idx).rref<U>(); }
  150. };
  151. template<typename U> struct get_in<cv::GOpaque<U> >
  152. {
  153. static const U& get(GCPUContext &ctx, int idx) { return ctx.inArg<OpaqueRef>(idx).rref<U>(); }
  154. };
  155. //FIXME(dm): GArray<Mat>/GArray<GMat> conversion should be done more gracefully in the system
  156. template<> struct get_in<cv::GArray<cv::GMat> >: public get_in<cv::GArray<cv::Mat> >
  157. {
  158. };
  159. //FIXME(dm): GArray<Scalar>/GArray<GScalar> conversion should be done more gracefully in the system
  160. template<> struct get_in<cv::GArray<cv::GScalar> >: public get_in<cv::GArray<cv::Scalar> >
  161. {
  162. };
  163. // FIXME(dm): GArray<vector<U>>/GArray<GArray<U>> conversion should be done more gracefully in the system
  164. template<typename U> struct get_in<cv::GArray<cv::GArray<U>> >: public get_in<cv::GArray<std::vector<U>> >
  165. {
  166. };
  167. //FIXME(dm): GOpaque<Mat>/GOpaque<GMat> conversion should be done more gracefully in the system
  168. template<> struct get_in<cv::GOpaque<cv::GMat> >: public get_in<cv::GOpaque<cv::Mat> >
  169. {
  170. };
  171. //FIXME(dm): GOpaque<Scalar>/GOpaque<GScalar> conversion should be done more gracefully in the system
  172. template<> struct get_in<cv::GOpaque<cv::GScalar> >: public get_in<cv::GOpaque<cv::Mat> >
  173. {
  174. };
  175. template<class T> struct get_in
  176. {
  177. static T get(GCPUContext &ctx, int idx) { return ctx.inArg<T>(idx); }
  178. };
  179. struct tracked_cv_mat{
  180. tracked_cv_mat(cv::Mat& m) : r{m}, original_data{m.data} {}
  181. cv::Mat r;
  182. uchar* original_data;
  183. operator cv::Mat& (){ return r;}
  184. void validate() const{
  185. if (r.data != original_data)
  186. {
  187. util::throw_error
  188. (std::logic_error
  189. ("OpenCV kernel output parameter was reallocated. \n"
  190. "Incorrect meta data was provided ?"));
  191. }
  192. }
  193. };
  194. template<typename... Outputs>
  195. void postprocess(Outputs&... outs)
  196. {
  197. struct
  198. {
  199. void operator()(tracked_cv_mat* bm) { bm->validate(); }
  200. void operator()(...) { }
  201. } validate;
  202. //dummy array to unfold parameter pack
  203. int dummy[] = { 0, (validate(&outs), 0)... };
  204. cv::util::suppress_unused_warning(dummy);
  205. }
  206. template<class T> struct get_out;
  207. template<> struct get_out<cv::GMat>
  208. {
  209. static tracked_cv_mat get(GCPUContext &ctx, int idx)
  210. {
  211. auto& r = ctx.outMatR(idx);
  212. return {r};
  213. }
  214. };
  215. template<> struct get_out<cv::GMatP>
  216. {
  217. static tracked_cv_mat get(GCPUContext &ctx, int idx)
  218. {
  219. return get_out<cv::GMat>::get(ctx, idx);
  220. }
  221. };
  222. template<> struct get_out<cv::GScalar>
  223. {
  224. static cv::Scalar& get(GCPUContext &ctx, int idx)
  225. {
  226. return ctx.outValR(idx);
  227. }
  228. };
  229. template<> struct get_out<cv::GFrame>
  230. {
  231. static cv::MediaFrame& get(GCPUContext &ctx, int idx)
  232. {
  233. return ctx.outFrame(idx);
  234. }
  235. };
  236. template<typename U> struct get_out<cv::GArray<U>>
  237. {
  238. static std::vector<U>& get(GCPUContext &ctx, int idx)
  239. {
  240. return ctx.outVecR<U>(idx);
  241. }
  242. };
  243. //FIXME(dm): GArray<Mat>/GArray<GMat> conversion should be done more gracefully in the system
  244. template<> struct get_out<cv::GArray<cv::GMat> >: public get_out<cv::GArray<cv::Mat> >
  245. {
  246. };
  247. // FIXME(dm): GArray<vector<U>>/GArray<GArray<U>> conversion should be done more gracefully in the system
  248. template<typename U> struct get_out<cv::GArray<cv::GArray<U>> >: public get_out<cv::GArray<std::vector<U>> >
  249. {
  250. };
  251. template<typename U> struct get_out<cv::GOpaque<U>>
  252. {
  253. static U& get(GCPUContext &ctx, int idx)
  254. {
  255. return ctx.outOpaqueR<U>(idx);
  256. }
  257. };
  258. template<typename, typename>
  259. struct OCVSetupHelper;
  260. template<typename Impl, typename... Ins>
  261. struct OCVSetupHelper<Impl, std::tuple<Ins...>>
  262. {
  263. // Using 'auto' return type and 'decltype' specifier in both 'setup_impl' versions
  264. // to check existence of required 'Impl::setup' functions.
  265. // While 'decltype' specifier accepts expression we pass expression with 'comma-operator'
  266. // where first operand of comma-operator is call attempt to desired 'Impl::setup' and
  267. // the second operand is 'void()' expression.
  268. //
  269. // SFINAE for 'Impl::setup' which accepts compile arguments.
  270. template<int... IIs>
  271. static auto setup_impl(const GMetaArgs &metaArgs, const GArgs &args,
  272. GArg &state, const GCompileArgs &compileArgs,
  273. detail::Seq<IIs...>) ->
  274. decltype(Impl::setup(detail::get_in_meta<Ins>(metaArgs, args, IIs)...,
  275. std::declval<typename std::add_lvalue_reference<
  276. std::shared_ptr<typename Impl::State>
  277. >::type
  278. >(),
  279. compileArgs)
  280. , void())
  281. {
  282. // TODO: unique_ptr <-> shared_ptr conversion ?
  283. // To check: Conversion is possible only if the state which should be passed to
  284. // 'setup' user callback isn't required to have previous value
  285. std::shared_ptr<typename Impl::State> stPtr;
  286. Impl::setup(detail::get_in_meta<Ins>(metaArgs, args, IIs)..., stPtr, compileArgs);
  287. state = GArg(stPtr);
  288. }
  289. // SFINAE for 'Impl::setup' which doesn't accept compile arguments.
  290. template<int... IIs>
  291. static auto setup_impl(const GMetaArgs &metaArgs, const GArgs &args,
  292. GArg &state, const GCompileArgs &/* compileArgs */,
  293. detail::Seq<IIs...>) ->
  294. decltype(Impl::setup(detail::get_in_meta<Ins>(metaArgs, args, IIs)...,
  295. std::declval<typename std::add_lvalue_reference<
  296. std::shared_ptr<typename Impl::State>
  297. >::type
  298. >()
  299. )
  300. , void())
  301. {
  302. // The same comment as in 'setup' above.
  303. std::shared_ptr<typename Impl::State> stPtr;
  304. Impl::setup(detail::get_in_meta<Ins>(metaArgs, args, IIs)..., stPtr);
  305. state = GArg(stPtr);
  306. }
  307. static void setup(const GMetaArgs &metaArgs, const GArgs &args,
  308. GArg& state, const GCompileArgs &compileArgs)
  309. {
  310. setup_impl(metaArgs, args, state, compileArgs,
  311. typename detail::MkSeq<sizeof...(Ins)>::type());
  312. }
  313. };
  314. // OCVCallHelper is a helper class to call stateless OCV kernels and OCV kernel functors.
  315. template<typename, typename, typename>
  316. struct OCVCallHelper;
  317. // FIXME: probably can be simplified with std::apply or analogue.
  318. template<typename Impl, typename... Ins, typename... Outs>
  319. struct OCVCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...>>
  320. {
  321. template<typename... Inputs>
  322. struct call_and_postprocess
  323. {
  324. template<typename... Outputs>
  325. static void call(Inputs&&... ins, Outputs&&... outs)
  326. {
  327. //not using a std::forward on outs is deliberate in order to
  328. //cause compilation error, by trying to bind rvalue references to lvalue references
  329. Impl::run(std::forward<Inputs>(ins)..., outs...);
  330. postprocess(outs...);
  331. }
  332. template<typename... Outputs>
  333. static void call(Impl& impl, Inputs&&... ins, Outputs&&... outs)
  334. {
  335. impl(std::forward<Inputs>(ins)..., outs...);
  336. }
  337. };
  338. template<int... IIs, int... OIs>
  339. static void call_impl(GCPUContext &ctx, detail::Seq<IIs...>, detail::Seq<OIs...>)
  340. {
  341. //Make sure that OpenCV kernels do not reallocate memory for output parameters
  342. //by comparing it's state (data ptr) before and after the call.
  343. //This is done by converting each output Mat into tracked_cv_mat object, and binding
  344. //them to parameters of ad-hoc function
  345. call_and_postprocess<decltype(get_in<Ins>::get(ctx, IIs))...>
  346. ::call(get_in<Ins>::get(ctx, IIs)..., get_out<Outs>::get(ctx, OIs)...);
  347. }
  348. template<int... IIs, int... OIs>
  349. static void call_impl(cv::GCPUContext &ctx, Impl& impl,
  350. detail::Seq<IIs...>, detail::Seq<OIs...>)
  351. {
  352. call_and_postprocess<decltype(get_in<Ins>::get(ctx, IIs))...>
  353. ::call(impl, get_in<Ins>::get(ctx, IIs)..., get_out<Outs>::get(ctx, OIs)...);
  354. }
  355. static void call(GCPUContext &ctx)
  356. {
  357. call_impl(ctx,
  358. typename detail::MkSeq<sizeof...(Ins)>::type(),
  359. typename detail::MkSeq<sizeof...(Outs)>::type());
  360. }
  361. // NB: Same as call but calling the object
  362. // This necessary for kernel implementations that have a state
  363. // and are represented as an object
  364. static void callFunctor(cv::GCPUContext &ctx, Impl& impl)
  365. {
  366. call_impl(ctx, impl,
  367. typename detail::MkSeq<sizeof...(Ins)>::type(),
  368. typename detail::MkSeq<sizeof...(Outs)>::type());
  369. }
  370. };
  371. // OCVStCallHelper is a helper class to call stateful OCV kernels.
  372. template<typename, typename, typename>
  373. struct OCVStCallHelper;
  374. template<typename Impl, typename... Ins, typename... Outs>
  375. struct OCVStCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...>> :
  376. OCVCallHelper<Impl, std::tuple<Ins...>, std::tuple<Outs...>>
  377. {
  378. template<typename... Inputs>
  379. struct call_and_postprocess
  380. {
  381. template<typename... Outputs>
  382. static void call(typename Impl::State& st, Inputs&&... ins, Outputs&&... outs)
  383. {
  384. Impl::run(std::forward<Inputs>(ins)..., outs..., st);
  385. postprocess(outs...);
  386. }
  387. };
  388. template<int... IIs, int... OIs>
  389. static void call_impl(GCPUContext &ctx, detail::Seq<IIs...>, detail::Seq<OIs...>)
  390. {
  391. auto& st = *ctx.state().get<std::shared_ptr<typename Impl::State>>();
  392. call_and_postprocess<decltype(get_in<Ins>::get(ctx, IIs))...>
  393. ::call(st, get_in<Ins>::get(ctx, IIs)..., get_out<Outs>::get(ctx, OIs)...);
  394. }
  395. static void call(GCPUContext &ctx)
  396. {
  397. call_impl(ctx,
  398. typename detail::MkSeq<sizeof...(Ins)>::type(),
  399. typename detail::MkSeq<sizeof...(Outs)>::type());
  400. }
  401. };
  402. } // namespace detail
  403. template<class Impl, class K>
  404. class GCPUKernelImpl: public cv::detail::KernelTag
  405. {
  406. using CallHelper = cv::detail::OCVCallHelper<Impl, typename K::InArgs, typename K::OutArgs>;
  407. public:
  408. using API = K;
  409. static cv::gapi::GBackend backend() { return cv::gapi::cpu::backend(); }
  410. static cv::GCPUKernel kernel() { return GCPUKernel(&CallHelper::call); }
  411. };
  412. template<class Impl, class K, class S>
  413. class GCPUStKernelImpl: public cv::detail::KernelTag
  414. {
  415. using StSetupHelper = detail::OCVSetupHelper<Impl, typename K::InArgs>;
  416. using StCallHelper = detail::OCVStCallHelper<Impl, typename K::InArgs, typename K::OutArgs>;
  417. public:
  418. using API = K;
  419. using State = S;
  420. static cv::gapi::GBackend backend() { return cv::gapi::cpu::backend(); }
  421. static cv::GCPUKernel kernel() { return GCPUKernel(&StCallHelper::call,
  422. &StSetupHelper::setup); }
  423. };
  424. #define GAPI_OCV_KERNEL(Name, API) struct Name: public cv::GCPUKernelImpl<Name, API>
  425. // TODO: Reuse Anatoliy's logic for support of types with commas in macro.
  426. // Retrieve the common part from Anatoliy's logic to the separate place.
  427. #define GAPI_OCV_KERNEL_ST(Name, API, State) \
  428. struct Name: public cv::GCPUStKernelImpl<Name, API, State> \
  429. /// @private
  430. class gapi::cpu::GOCVFunctor : public gapi::GFunctor
  431. {
  432. public:
  433. using Impl = std::function<void(GCPUContext &)>;
  434. using Meta = cv::GKernel::M;
  435. GOCVFunctor(const char* id, const Meta &meta, const Impl& impl)
  436. : gapi::GFunctor(id), impl_{GCPUKernel(impl), meta}
  437. {
  438. }
  439. GKernelImpl impl() const override { return impl_; }
  440. gapi::GBackend backend() const override { return gapi::cpu::backend(); }
  441. private:
  442. GKernelImpl impl_;
  443. };
  444. //! @cond IGNORED
  445. template<typename K, typename Callable>
  446. gapi::cpu::GOCVFunctor gapi::cpu::ocv_kernel(Callable& c)
  447. {
  448. using P = cv::detail::OCVCallHelper<Callable, typename K::InArgs, typename K::OutArgs>;
  449. return GOCVFunctor{ K::id()
  450. , &K::getOutMeta
  451. , std::bind(&P::callFunctor, std::placeholders::_1, std::ref(c))
  452. };
  453. }
  454. template<typename K, typename Callable>
  455. gapi::cpu::GOCVFunctor gapi::cpu::ocv_kernel(const Callable& c)
  456. {
  457. using P = cv::detail::OCVCallHelper<Callable, typename K::InArgs, typename K::OutArgs>;
  458. return GOCVFunctor{ K::id()
  459. , &K::getOutMeta
  460. , std::bind(&P::callFunctor, std::placeholders::_1, c)
  461. };
  462. }
  463. //! @endcond
  464. } // namespace cv
  465. #endif // OPENCV_GAPI_GCPUKERNEL_HPP