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-2020 Intel Corporation
6
7
8#include "precomp.hpp"
9
10#include <vector>
11#include <stack>
12#include <unordered_map>
13
14#include <ade/util/algorithm.hpp> // any_of
15#include <ade/util/zip_range.hpp> // zip_range, indexed
16
17#include <ade/graph.hpp>
18#include <ade/passes/check_cycles.hpp>
19
20#include "api/gcomputation_priv.hpp"
21#include "api/gnode_priv.hpp" // FIXME: why it is here?
22#include "api/gproto_priv.hpp" // FIXME: why it is here?
23#include "api/gcall_priv.hpp" // FIXME: why it is here?
24
25#include "api/gbackend_priv.hpp" // Backend basic API (newInstance, etc)
26
27#include "compiler/gmodel.hpp"
28#include "compiler/gmodelbuilder.hpp"
29#include "compiler/gcompiler.hpp"
30#include "compiler/gcompiled_priv.hpp"
31#include "compiler/gstreaming_priv.hpp"
32#include "compiler/passes/passes.hpp"
33#include "compiler/passes/pattern_matching.hpp"
34
35#include "executor/gexecutor.hpp"
36#include "executor/gthreadedexecutor.hpp"
37#include "executor/gstreamingexecutor.hpp"
38#include "backends/common/gbackend.hpp"
39#include "backends/common/gmetabackend.hpp"
40#include "backends/streaming/gstreamingbackend.hpp" // cv::gimpl::streaming::kernels()
41
42// <FIXME:>
43#if !defined(GAPI_STANDALONE)
44#include <opencv2/gapi/cpu/core.hpp> // Also directly refer to Core,
45#include <opencv2/gapi/cpu/imgproc.hpp> // ...Imgproc
46#include <opencv2/gapi/cpu/video.hpp> // ...and Video kernel implementations
47#include <opencv2/gapi/render/render.hpp> // render::ocv::backend()
48#endif // !defined(GAPI_STANDALONE)
49// </FIXME:>
50
51#include <opencv2/gapi/gcompoundkernel.hpp> // compound::backend()
52
53#include "logger.hpp"
54
55namespace
56{
57 cv::GKernelPackage getKernelPackage(cv::GCompileArgs &args)
58 {
59 auto withAuxKernels = [](const cv::GKernelPackage& pkg) {
60 cv::GKernelPackage aux_pkg;
61 for (const auto &b : pkg.backends()) {
62 aux_pkg = cv::gapi::combine(lhs: aux_pkg, rhs: b.priv().auxiliaryKernels());
63 }
64 // Always include built-in meta<> and copy implementation
65 return cv::gapi::combine(a: pkg,
66 b: aux_pkg,
67 rest: cv::gimpl::meta::kernels(),
68 rest: cv::gimpl::streaming::kernels());
69 };
70
71 auto has_use_only = cv::gapi::getCompileArg<cv::gapi::use_only>(args);
72 if (has_use_only)
73 return withAuxKernels(has_use_only.value().pkg);
74
75 static auto ocv_pkg =
76#if !defined(GAPI_STANDALONE)
77 cv::gapi::combine(a: cv::gapi::core::cpu::kernels(),
78 b: cv::gapi::imgproc::cpu::kernels(),
79 rest: cv::gapi::video::cpu::kernels(),
80 rest: cv::gapi::render::ocv::kernels(),
81 rest: cv::gapi::streaming::kernels());
82#else
83 cv::GKernelPackage();
84#endif // !defined(GAPI_STANDALONE)
85
86 auto user_pkg = cv::gapi::getCompileArg<cv::GKernelPackage>(args);
87 auto user_pkg_with_aux = withAuxKernels(user_pkg.value_or(default_value: cv::GKernelPackage{}));
88 return cv::gapi::combine(lhs: ocv_pkg, rhs: user_pkg_with_aux);
89 }
90
91 cv::gapi::GNetPackage getNetworkPackage(cv::GCompileArgs &args)
92 {
93 return cv::gapi::getCompileArg<cv::gapi::GNetPackage>(args)
94 .value_or(default_value: cv::gapi::GNetPackage{});
95 }
96
97 cv::util::optional<std::string> getGraphDumpDirectory(cv::GCompileArgs& args)
98 {
99 auto dump_info = cv::gapi::getCompileArg<cv::graph_dump_path>(args);
100 if (!dump_info.has_value())
101 {
102 const std::string path = cv::utils::getConfigurationParameterString(name: "GRAPH_DUMP_PATH");
103 return !path.empty()
104 ? cv::util::make_optional(value: path)
105 : cv::util::optional<std::string>();
106 }
107 else
108 {
109 return cv::util::make_optional(value&: dump_info.value().m_dump_path);
110 }
111 }
112
113 template<typename C>
114 cv::GKernelPackage auxKernelsFrom(const C& c) {
115 cv::GKernelPackage result;
116 for (const auto &b : c) {
117 result = cv::gapi::combine(result, b.priv().auxiliaryKernels());
118 }
119 return result;
120 }
121
122 using adeGraphs = std::vector<std::unique_ptr<ade::Graph>>;
123
124 // Creates ADE graphs (patterns and substitutes) from pkg's transformations
125 void makeTransformationGraphs(const cv::GKernelPackage& pkg,
126 adeGraphs& patterns,
127 adeGraphs& substitutes) {
128 const auto& transforms = pkg.get_transformations();
129 const auto size = transforms.size();
130 if (0u == size) return;
131
132 // pre-generate all required graphs
133 patterns.resize(new_size: size);
134 substitutes.resize(new_size: size);
135 for (auto it : ade::util::zip(ranges: ade::util::toRange(val: transforms),
136 ranges: ade::util::toRange(val&: patterns),
137 ranges: ade::util::toRange(val&: substitutes))) {
138 const auto& t = std::get<0>(t&: it);
139 auto& p = std::get<1>(t&: it);
140 auto& s = std::get<2>(t&: it);
141 p = cv::gimpl::GCompiler::makeGraph(t.pattern().priv());
142 s = cv::gimpl::GCompiler::makeGraph(t.substitute().priv());
143 }
144 }
145
146 void checkTransformations(const cv::GKernelPackage& pkg,
147 const adeGraphs& patterns,
148 const adeGraphs& substitutes) {
149 const auto& transforms = pkg.get_transformations();
150 const auto size = transforms.size();
151 if (0u == size) return;
152
153 GAPI_Assert(size == patterns.size());
154 GAPI_Assert(size == substitutes.size());
155
156 const auto empty = [] (const cv::gimpl::SubgraphMatch& m) {
157 return m.inputDataNodes.empty() && m.startOpNodes.empty()
158 && m.finishOpNodes.empty() && m.outputDataNodes.empty()
159 && m.inputTestDataNodes.empty() && m.outputTestDataNodes.empty();
160 };
161
162 // **FIXME**: verify other types of endless loops. now, only checking if pattern exists in
163 // substitute within __the same__ transformation
164 for (size_t i = 0; i < size; ++i) {
165 const auto& p = patterns[i];
166 const auto& s = substitutes[i];
167
168 auto matchInSubstitute = cv::gimpl::findMatches(patternGraph: *p, compGraph: *s);
169 if (!empty(matchInSubstitute)) {
170 std::stringstream ss;
171 ss << "Error: (in transformation with description: '"
172 << transforms[i].description
173 << "') pattern is detected inside substitute";
174 throw std::runtime_error(ss.str());
175 }
176 }
177 }
178} // anonymous namespace
179
180
181// GCompiler implementation ////////////////////////////////////////////////////
182
183cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c,
184 GMetaArgs &&metas,
185 GCompileArgs &&args)
186 : m_c(c), m_metas(std::move(metas)), m_args(std::move(args))
187{
188 using namespace std::placeholders;
189
190 auto kernels_to_use = getKernelPackage(args&: m_args);
191 auto networks_to_use = getNetworkPackage(args&: m_args);
192 std::unordered_set<cv::gapi::GBackend> all_backends;
193 const auto take = [&](std::vector<cv::gapi::GBackend> &&v) {
194 all_backends.insert(first: v.begin(), last: v.end());
195 };
196 take(kernels_to_use.backends());
197 take(networks_to_use.backends());
198
199 m_all_kernels = cv::gapi::combine(lhs: kernels_to_use,
200 rhs: auxKernelsFrom(c: all_backends));
201 // NB: The expectation in the line above is that
202 // NN backends (present here via network package) always add their
203 // inference kernels via auxiliary...()
204
205 // sanity check transformations
206 {
207 adeGraphs patterns, substitutes;
208 // FIXME: returning vectors of unique_ptrs from makeTransformationGraphs results in
209 // compile error (at least) on GCC 9 with usage of copy-ctor of std::unique_ptr, so
210 // using initialization by lvalue reference instead
211 makeTransformationGraphs(pkg: m_all_kernels, patterns, substitutes);
212 checkTransformations(pkg: m_all_kernels, patterns, substitutes);
213
214 // NB: saving generated patterns to m_all_patterns to be used later in passes
215 m_all_patterns = std::move(patterns);
216 }
217
218 auto dump_path = getGraphDumpDirectory(args&: m_args);
219
220 m_e.addPassStage(stageName: "init");
221 m_e.addPass(stageName: "init", passName: "check_cycles", pass: ade::passes::CheckCycles());
222 m_e.addPass(stageName: "init", passName: "apply_transformations",
223 pass: std::bind(f&: passes::applyTransformations, args: _1, args: std::cref(t: m_all_kernels),
224 args: std::cref(t: m_all_patterns))); // Note: and re-using patterns here
225 m_e.addPass(stageName: "init", passName: "expand_kernels",
226 pass: std::bind(f&: passes::expandKernels, args: _1,
227 args&: m_all_kernels)); // NB: package is copied
228 m_e.addPass(stageName: "init", passName: "topo_sort", pass: ade::passes::TopologicalSort());
229 m_e.addPass(stageName: "init", passName: "init_islands", pass&: passes::initIslands);
230 m_e.addPass(stageName: "init", passName: "check_islands", pass&: passes::checkIslands);
231 // TODO:
232 // - Check basic graph validity (i.e., all inputs are connected)
233 // - Complex dependencies (i.e. parent-child) unrolling
234 // - etc, etc, etc
235
236 // Remove GCompoundBackend to avoid calling setupBackend() with it in the list
237 m_all_kernels.remove(backend: cv::gapi::compound::backend());
238
239 m_e.addPassStage(stageName: "kernels");
240 m_e.addPass(stageName: "kernels", passName: "bind_net_params",
241 pass: std::bind(f&: passes::bindNetParams, args: _1,
242 args&: networks_to_use));
243 m_e.addPass(stageName: "kernels", passName: "resolve_kernels",
244 pass: std::bind(f&: passes::resolveKernels, args: _1,
245 args: std::ref(t&: m_all_kernels))); // NB: and not copied here
246 // (no compound backend present here)
247 m_e.addPass(stageName: "kernels", passName: "check_islands_content", pass&: passes::checkIslandsContent);
248
249 // Special stage for intrinsics handling
250 m_e.addPassStage(stageName: "intrin");
251 m_e.addPass(stageName: "intrin", passName: "desync", pass&: passes::intrinDesync);
252 m_e.addPass(stageName: "intrin", passName: "finalizeIntrin", pass&: passes::intrinFinalize);
253
254 //Input metas may be empty when a graph is compiled for streaming
255 m_e.addPassStage(stageName: "meta");
256 if (!m_metas.empty())
257 {
258 m_e.addPass(stageName: "meta", passName: "initialize", pass: std::bind(f&: passes::initMeta, args: _1, args: std::ref(t: m_metas)));
259 m_e.addPass(stageName: "meta", passName: "propagate", pass: std::bind(f&: passes::inferMeta, args: _1, args: false));
260 m_e.addPass(stageName: "meta", passName: "finalize", pass&: passes::storeResultingMeta);
261 // moved to another stage, FIXME: two dumps?
262 // m_e.addPass("meta", "dump_dot", passes::dumpDotStdout);
263 }
264 // Special stage for backend-specific transformations
265 // FIXME: document passes hierarchy and order for backend developers
266 m_e.addPassStage(stageName: "transform");
267
268 m_e.addPassStage(stageName: "exec");
269 m_e.addPass(stageName: "exec", passName: "fuse_islands", pass&: passes::fuseIslands);
270 m_e.addPass(stageName: "exec", passName: "sync_islands", pass&: passes::syncIslandTags);
271
272 // FIXME: Since a set of passes is shared between
273 // GCompiled/GStreamingCompiled, this pass is added here unconditionally
274 // (even if it is not actually required to produce a GCompiled).
275 // FIXME: add a better way to do that!
276 m_e.addPass(stageName: "exec", passName: "add_streaming", pass&: passes::addStreaming);
277
278 // Note: Must be called after addStreaming as addStreaming pass
279 // can possibly add new nodes to the IslandModel
280 m_e.addPass(stageName: "exec", passName: "sort_islands", pass&: passes::topoSortIslands);
281
282 if (dump_path.has_value())
283 {
284 m_e.addPass(stageName: "exec", passName: "dump_dot", pass: std::bind(f&: passes::dumpGraph, args: _1,
285 args&: dump_path.value()));
286 }
287
288 // FIXME: This should be called for "ActiveBackends" only (see metadata).
289 // However, ActiveBackends are known only after passes are actually executed.
290 // At these stage, they are not executed yet.
291 ade::ExecutionEngineSetupContext ectx(m_e);
292 auto backends = m_all_kernels.backends();
293 for (auto &b : backends)
294 {
295 b.priv().addBackendPasses(ectx);
296 if (!m_metas.empty())
297 {
298 b.priv().addMetaSensitiveBackendPasses(ectx);
299 }
300 }
301}
302
303void cv::gimpl::GCompiler::validateInputMeta()
304{
305 // FIXME: implement testing/accessor methods at the Priv's API level?
306 if (!util::holds_alternative<GComputation::Priv::Expr>(v: m_c.priv().m_shape))
307 {
308 GAPI_LOG_WARNING(NULL, "Metadata validation is not implemented yet for"
309 " deserialized graphs!");
310 return;
311 }
312 const auto &c_expr = util::get<cv::GComputation::Priv::Expr>(v: m_c.priv().m_shape);
313 if (m_metas.size() != c_expr.m_ins.size())
314 {
315 util::throw_error(e: std::logic_error
316 ("COMPILE: GComputation interface / metadata mismatch! "
317 "(expected " + std::to_string(val: c_expr.m_ins.size()) + ", "
318 "got " + std::to_string(val: m_metas.size()) + " meta arguments)"));
319 }
320
321 const auto meta_matches = [](const GMetaArg &meta, const GProtoArg &proto) {
322 switch (proto.index())
323 {
324 // FIXME: Auto-generate methods like this from traits:
325 case GProtoArg::index_of<cv::GMat>():
326 case GProtoArg::index_of<cv::GMatP>():
327 return util::holds_alternative<cv::GMatDesc>(v: meta);
328
329 case GProtoArg::index_of<cv::GFrame>():
330 return util::holds_alternative<cv::GFrameDesc>(v: meta);
331
332 case GProtoArg::index_of<cv::GScalar>():
333 return util::holds_alternative<cv::GScalarDesc>(v: meta);
334
335 case GProtoArg::index_of<cv::detail::GArrayU>():
336 return util::holds_alternative<cv::GArrayDesc>(v: meta);
337
338 case GProtoArg::index_of<cv::detail::GOpaqueU>():
339 return util::holds_alternative<cv::GOpaqueDesc>(v: meta);
340
341 default:
342 GAPI_Error("InternalError");
343 }
344 return false; // should never happen
345 };
346
347 GAPI_LOG_DEBUG(nullptr, "Total count: " << m_metas.size());
348 for (const auto meta_arg_idx : ade::util::indexed(conts: ade::util::zip(ranges: m_metas, ranges: c_expr.m_ins)))
349 {
350 const auto &meta = std::get<0>(t: ade::util::value(val: meta_arg_idx));
351 const auto &proto = std::get<1>(t: ade::util::value(val: meta_arg_idx));
352
353 const auto index = ade::util::index(val: meta_arg_idx);
354 GAPI_LOG_DEBUG(nullptr, "Process index: " << index);
355
356 // check types validity
357 if (!meta_matches(meta, proto))
358 {
359 util::throw_error(e: std::logic_error
360 ("GComputation object type / metadata descriptor mismatch "
361 "(argument " + std::to_string(val: index) + ")"));
362 // FIXME: report what we've got and what we've expected
363 }
364
365 // check value consistency
366 gimpl::proto::validate_input_meta_arg(meta); //may throw
367 }
368 // All checks are ok
369}
370
371void cv::gimpl::GCompiler::validateOutProtoArgs()
372{
373 // FIXME: implement testing/accessor methods at the Priv's API level?
374 if (!util::holds_alternative<GComputation::Priv::Expr>(v: m_c.priv().m_shape))
375 {
376 GAPI_LOG_WARNING(NULL, "Output parameter validation is not implemented yet for"
377 " deserialized graphs!");
378 return;
379 }
380 const auto &c_expr = util::get<cv::GComputation::Priv::Expr>(v: m_c.priv().m_shape);
381 for (const auto out_pos : ade::util::indexed(conts: c_expr.m_outs))
382 {
383 const auto &node = proto::origin_of(arg: ade::util::value(val: out_pos)).node;
384 if (node.shape() != cv::GNode::NodeShape::CALL)
385 {
386 auto pos = ade::util::index(val: out_pos);
387 util::throw_error(e: std::logic_error
388 ("Computation output " + std::to_string(val: pos) +
389 " is not a result of any operation"));
390 }
391 }
392}
393
394cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::generateGraph()
395{
396 if (!m_metas.empty())
397 {
398 // Metadata may be empty if we're compiling our graph for streaming
399 validateInputMeta();
400 }
401 validateOutProtoArgs();
402 auto g = makeGraph(m_c.priv());
403 if (!m_metas.empty())
404 {
405 GModel::Graph(*g).metadata().set(OriginalInputMeta{.inputMeta: m_metas});
406 }
407 // FIXME: remove m_args, remove GCompileArgs from backends' method signatures,
408 // rework backends to access GCompileArgs from graph metadata
409 GModel::Graph(*g).metadata().set(CompileArgs{.args: m_args});
410 return g;
411}
412
413void cv::gimpl::GCompiler::runPasses(ade::Graph &g)
414{
415 m_e.runPasses(graph&: g);
416 GAPI_LOG_INFO(NULL, "All compiler passes are successful");
417}
418
419void cv::gimpl::GCompiler::compileIslands(ade::Graph &g)
420{
421 compileIslands(g, args: m_args);
422}
423
424void cv::gimpl::GCompiler::compileIslands(ade::Graph &g, const cv::GCompileArgs &args)
425{
426 GModel::Graph gm(g);
427 std::shared_ptr<ade::Graph> gptr(gm.metadata().get<IslandModel>().model);
428 GIslandModel::Graph gim(*gptr);
429
430 GIslandModel::compileIslands(g&: gim, orig_g: g, args);
431}
432
433cv::GCompiled cv::gimpl::GCompiler::produceCompiled(GPtr &&pg)
434{
435 // This is the final compilation step. Here:
436 // - An instance of GExecutor is created. Depending on the platform,
437 // build configuration, etc, a GExecutor may be:
438 // - a naive single-thread graph interpreter;
439 // - a std::thread-based thing
440 // - a TBB-based thing, etc.
441 // - All this stuff is wrapped into a GCompiled object and returned
442 // to user.
443
444 // Note: this happens in the last pass ("compile_islands"):
445 // - Each GIsland of GIslandModel instantiates its own,
446 // backend-specific executable object
447 // - Every backend gets a subgraph to execute, and builds
448 // an execution plan for it (backend-specific execution)
449 // ...before call to produceCompiled();
450
451 GModel::ConstGraph cgr(*pg);
452 const auto &outMetas = GModel::ConstGraph(*pg).metadata()
453 .get<OutputMeta>().outMeta;
454 // FIXME: select which executor will be actually used,
455 // make GExecutor abstract.
456
457 auto use_threaded_exec = cv::gapi::getCompileArg<cv::use_threaded_executor>(args: m_args);
458 std::unique_ptr<GAbstractExecutor> pE;
459 if (use_threaded_exec) {
460 const auto num_threads = use_threaded_exec.value().num_threads;
461 GAPI_LOG_INFO(NULL, "Threaded executor with " << num_threads << " thread(s) will be used");
462 pE.reset(p: new GThreadedExecutor(num_threads, std::move(pg)));
463 } else {
464 pE.reset(p: new GExecutor(std::move(pg)));
465 }
466 GCompiled compiled;
467 compiled.priv().setup(metaArgs: m_metas, outMetas, pE: std::move(pE));
468
469 return compiled;
470}
471
472cv::GStreamingCompiled cv::gimpl::GCompiler::produceStreamingCompiled(GPtr &&pg)
473{
474 GStreamingCompiled compiled;
475 GMetaArgs outMetas;
476
477 // FIXME: the whole below construct is ugly, need to revise
478 // how G*Compiled learns about its meta.
479 if (!m_metas.empty())
480 {
481 outMetas = GModel::ConstGraph(*pg).metadata().get<OutputMeta>().outMeta;
482 }
483
484 GModel::ConstGraph cgr(*pg);
485
486 std::unique_ptr<GStreamingExecutor> pE(new GStreamingExecutor(std::move(pg),
487 m_args));
488 if (!m_metas.empty() && !outMetas.empty())
489 {
490 compiled.priv().setup(metaArgs: m_metas, outMetas, pE: std::move(pE));
491 }
492 else if (m_metas.empty() && outMetas.empty())
493 {
494 // Otherwise, set it up with executor object only
495 compiled.priv().setup(std::move(pE));
496 }
497 else GAPI_Error("Impossible happened -- please report a bug");
498 return compiled;
499}
500
501cv::GCompiled cv::gimpl::GCompiler::compile()
502{
503 std::unique_ptr<ade::Graph> pG = generateGraph();
504 runPasses(g&: *pG);
505 compileIslands(g&: *pG);
506 return produceCompiled(pg: std::move(pG));
507}
508
509cv::GStreamingCompiled cv::gimpl::GCompiler::compileStreaming()
510{
511 // FIXME: self-note to DM: now keep these compile()/compileStreaming() in sync!
512 std::unique_ptr<ade::Graph> pG = generateGraph();
513 GModel::Graph(*pG).metadata().set(Streaming{});
514 runPasses(g&: *pG);
515 if (!m_metas.empty())
516 {
517 // If the metadata has been passed, compile our islands!
518 compileIslands(g&: *pG);
519 }
520 return produceStreamingCompiled(pg: std::move(pG));
521}
522
523void cv::gimpl::GCompiler::runMetaPasses(ade::Graph &g, const cv::GMetaArgs &metas)
524{
525 auto pass_ctx = ade::passes::PassContext{.graph: g};
526 cv::gimpl::passes::initMeta(ctx&: pass_ctx, metas);
527 cv::gimpl::passes::inferMeta(ctx&: pass_ctx, meta_is_initialized: true);
528 cv::gimpl::passes::storeResultingMeta(ctx&: pass_ctx);
529
530 // Also run meta-sensitive backend-specific passes, if there's any.
531 // FIXME: This may be hazardous if our backend are not very robust
532 // in their passes -- how can we guarantee correct functioning in the
533 // future?
534 ade::ExecutionEngine engine;
535 engine.addPassStage(stageName: "exec"); // FIXME: Need a better decision on how we replicate
536 // our main compiler stages here.
537 ade::ExecutionEngineSetupContext ectx(engine);
538
539 // NB: &&b or &b doesn't work here since "backends" is a set. Nevermind
540 for (auto b : GModel::Graph(g).metadata().get<ActiveBackends>().backends)
541 {
542 b.priv().addMetaSensitiveBackendPasses(ectx);
543 }
544 engine.runPasses(graph&: g);
545}
546
547// Creates ADE graph from input/output proto args OR from its
548// deserialized form
549cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::makeGraph(const cv::GComputation::Priv &priv) {
550 std::unique_ptr<ade::Graph> pG(new ade::Graph);
551 ade::Graph& g = *pG;
552
553 if (cv::util::holds_alternative<cv::GComputation::Priv::Expr>(v: priv.m_shape)) {
554 auto c_expr = cv::util::get<cv::GComputation::Priv::Expr>(v: priv.m_shape);
555 cv::gimpl::GModel::Graph gm(g);
556 cv::gimpl::GModel::init(g&: gm);
557 cv::gimpl::GModelBuilder builder(g);
558 auto proto_slots = builder.put(ins: c_expr.m_ins, outs: c_expr.m_outs);
559
560 // Store Computation's protocol in metadata
561 cv::gimpl::Protocol p;
562 std::tie(args&: p.inputs, args&: p.outputs, args&: p.in_nhs, args&: p.out_nhs) = proto_slots;
563 gm.metadata().set(p);
564 } else if (cv::util::holds_alternative<cv::GComputation::Priv::Dump>(v: priv.m_shape)) {
565 auto c_dump = cv::util::get<cv::GComputation::Priv::Dump>(v: priv.m_shape);
566 cv::gapi::s11n::reconstruct(s: c_dump, g);
567 }
568 return pG;
569}
570

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of opencv/modules/gapi/src/compiler/gcompiler.cpp