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-2020 Intel Corporation
12 #include <iomanip> // std::fixed, std::setprecision
14 #include <unordered_set>
17 #include <ade/util/algorithm.hpp>
18 #include <ade/util/chain_range.hpp>
19 #include <ade/util/iota_range.hpp>
20 #include <ade/util/range.hpp>
21 #include <ade/util/zip_range.hpp>
23 #include <ade/typed_graph.hpp>
24 #include <ade/execution_engine/execution_engine.hpp>
26 #include <opencv2/gapi/gcommon.hpp>
29 #include <opencv2/gapi/gmat.hpp> //for version of descr_of
31 #include "compiler/gobjref.hpp"
32 #include "compiler/gmodel.hpp"
34 #include "backends/fluid/gfluidbuffer_priv.hpp"
35 #include "backends/fluid/gfluidbackend.hpp"
37 #include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
39 // FIXME: Is there a way to take a typed graph (our GModel),
40 // and create a new typed graph _ATOP_ of that (by extending with a couple of
42 // Alternatively, is there a way to compose types graphs?
44 // If not, we need to introduce that!
45 using GFluidModel = ade::TypedGraph
46 < cv::gimpl::FluidUnit
47 , cv::gimpl::FluidData
49 , cv::gimpl::FluidUseOwnBorderBuffer
52 // FIXME: Same issue with Typed and ConstTyped
53 using GConstFluidModel = ade::ConstTypedGraph
54 < cv::gimpl::FluidUnit
55 , cv::gimpl::FluidData
57 , cv::gimpl::FluidUseOwnBorderBuffer
60 // FluidBackend middle-layer implementation ////////////////////////////////////
63 class GFluidBackendImpl final: public cv::gapi::GBackend::Priv
65 virtual void unpackKernel(ade::Graph &graph,
66 const ade::NodeHandle &op_node,
67 const cv::GKernelImpl &impl) override
69 GFluidModel fm(graph);
70 auto fluid_impl = cv::util::any_cast<cv::GFluidKernel>(impl.opaque);
71 fm.metadata(op_node).set(cv::gimpl::FluidUnit{fluid_impl, {}, 0, -1, {}, 0.0});
74 virtual EPtr compile(const ade::Graph &graph,
75 const cv::GCompileArgs &args,
76 const std::vector<ade::NodeHandle> &nodes) const override
78 using namespace cv::gimpl;
79 GModel::ConstGraph g(graph);
80 auto isl_graph = g.metadata().get<IslandModel>().model;
81 GIslandModel::Graph gim(*isl_graph);
83 const auto num_islands = std::count_if
84 (gim.nodes().begin(), gim.nodes().end(),
85 [&](const ade::NodeHandle &nh) {
86 return gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND;
89 const auto out_rois = cv::gapi::getCompileArg<cv::GFluidOutputRois>(args);
90 if (num_islands > 1 && out_rois.has_value())
91 cv::util::throw_error(std::logic_error("GFluidOutputRois feature supports only one-island graphs"));
93 auto rois = out_rois.value_or(cv::GFluidOutputRois());
95 auto graph_data = fluidExtractInputDataFromGraph(graph, nodes);
96 const auto parallel_out_rois = cv::gapi::getCompileArg<cv::GFluidParallelOutputRois>(args);
97 const auto gpfor = cv::gapi::getCompileArg<cv::GFluidParallelFor>(args);
99 #if !defined(GAPI_STANDALONE)
100 auto default_pfor = [](std::size_t count, std::function<void(std::size_t)> f){
101 struct Body : cv::ParallelLoopBody {
103 Body( decltype(f) && _f) : func(_f){}
104 virtual void operator() (const cv::Range& r) const CV_OVERRIDE
106 for (std::size_t i : ade::util::iota(r.start, r.end))
112 cv::parallel_for_(cv::Range{0,static_cast<int>(count)}, Body{std::move(f)});
115 auto default_pfor = [](std::size_t count, std::function<void(std::size_t)> f){
116 for (auto i : ade::util::iota(count)){
122 auto pfor = gpfor.has_value() ? gpfor.value().parallel_for : default_pfor;
124 return parallel_out_rois.has_value() ?
125 EPtr{new cv::gimpl::GParallelFluidExecutable (graph, graph_data, std::move(parallel_out_rois.value().parallel_rois), pfor)}
126 : EPtr{new cv::gimpl::GFluidExecutable (graph, graph_data, std::move(rois.rois))}
130 virtual void addMetaSensitiveBackendPasses(ade::ExecutionEngineSetupContext &ectx) override;
135 cv::gapi::GBackend cv::gapi::fluid::backend()
137 static cv::gapi::GBackend this_backend(std::make_shared<GFluidBackendImpl>());
141 // FluidAgent implementation ///////////////////////////////////////////////////
143 namespace cv { namespace gimpl {
146 FluidMapper(double ratio, int lpi) : m_ratio(ratio), m_lpi(lpi) {}
147 virtual ~FluidMapper() = default;
148 virtual int firstWindow(int outCoord, int lpi) const = 0;
149 virtual std::pair<int,int> linesReadAndNextWindow(int outCoord, int lpi) const = 0;
152 double m_ratio = 0.0;
156 struct FluidDownscaleMapper : public FluidMapper
158 virtual int firstWindow(int outCoord, int lpi) const override;
159 virtual std::pair<int,int> linesReadAndNextWindow(int outCoord, int lpi) const override;
160 using FluidMapper::FluidMapper;
163 struct FluidUpscaleMapper : public FluidMapper
165 virtual int firstWindow(int outCoord, int lpi) const override;
166 virtual std::pair<int,int> linesReadAndNextWindow(int outCoord, int lpi) const override;
167 FluidUpscaleMapper(double ratio, int lpi, int inHeight) : FluidMapper(ratio, lpi), m_inHeight(inHeight) {}
172 struct FluidFilterAgent : public FluidAgent
175 virtual int firstWindow(std::size_t inPort) const override;
176 virtual std::pair<int,int> linesReadAndnextWindow(std::size_t inPort) const override;
177 virtual void setRatio(double) override { /* nothing */ }
179 using FluidAgent::FluidAgent;
182 FluidFilterAgent(const ade::Graph &g, ade::NodeHandle nh)
184 , m_window(GConstFluidModel(g).metadata(nh).get<FluidUnit>().window)
188 struct FluidResizeAgent : public FluidAgent
191 virtual int firstWindow(std::size_t inPort) const override;
192 virtual std::pair<int,int> linesReadAndnextWindow(std::size_t inPort) const override;
193 virtual void setRatio(double ratio) override;
195 std::unique_ptr<FluidMapper> m_mapper;
197 using FluidAgent::FluidAgent;
200 struct Fluid420toRGBAgent : public FluidAgent
203 virtual int firstWindow(std::size_t inPort) const override;
204 virtual std::pair<int,int> linesReadAndnextWindow(std::size_t inPort) const override;
205 virtual void setRatio(double) override { /* nothing */ }
207 using FluidAgent::FluidAgent;
209 }} // namespace cv::gimpl
211 cv::gimpl::FluidAgent::FluidAgent(const ade::Graph &g, ade::NodeHandle nh)
212 : k(GConstFluidModel(g).metadata(nh).get<FluidUnit>().k) // init(0)
213 , op_handle(nh) // init(1)
214 , op_name(GModel::ConstGraph(g).metadata(nh).get<Op>().k.name) // init(2)
218 GModel::ConstGraph cm(g);
219 for (auto out_data : nh->outNodes())
221 const auto &d = cm.metadata(out_data).get<Data>();
222 cv::GMatDesc d_meta = cv::util::get<cv::GMatDesc>(d.meta);
223 out_w.insert(d_meta.size.width);
224 out_h.insert(d_meta.size.height);
227 // Different output sizes are not supported
228 GAPI_Assert(out_w.size() == 1 && out_h.size() == 1);
231 void cv::gimpl::FluidAgent::reset()
235 for (const auto& it : ade::util::indexed(in_views))
237 auto& v = ade::util::value(it);
240 auto idx = ade::util::index(it);
241 auto lines = firstWindow(idx);
242 v.priv().reset(lines);
248 static int calcGcd (int n1, int n2)
250 return (n2 == 0) ? n1 : calcGcd (n2, n1 % n2);
253 // This is an empiric formula and this is not 100% guaranteed
254 // that it produces correct results in all possible cases
256 // prove correctness or switch to some trusted method
258 // When performing resize input/output pixels form a cyclic
259 // pattern where inH/gcd input pixels are mapped to outH/gcd
260 // output pixels (pattern repeats gcd times).
262 // Output pixel can partually cover some of the input pixels.
263 // There are 3 possible cases:
265 // :___ ___: :___ _:_ ___: :___ __: ___ :__ ___:
266 // |___|___| |___|_:_|___| |___|__:|___|:__|___|
269 // 1) No partial coverage, max window = scaleFactor;
270 // 2) Partial coverage occurs on the one side of the output pixel,
271 // max window = scaleFactor + 1;
272 // 3) Partial coverage occurs at both sides of the output pixel,
273 // max window = scaleFactor + 2;
275 // Type of the coverage is determined by remainder of
276 // inPeriodH/outPeriodH division, but it's an heuristic
277 // (howbeit didn't found the proof of the opposite so far).
279 static int calcResizeWindow(int inH, int outH)
281 GAPI_Assert(inH >= outH);
282 auto gcd = calcGcd(inH, outH);
283 int inPeriodH = inH/gcd;
284 int outPeriodH = outH/gcd;
285 int scaleFactor = inPeriodH / outPeriodH;
287 switch ((inPeriodH) % (outPeriodH))
289 case 0: return scaleFactor; break;
290 case 1: return scaleFactor + 1; break;
291 default: return scaleFactor + 2;
295 static int maxLineConsumption(const cv::GFluidKernel::Kind kind, int window, int inH, int outH, int lpi, std::size_t inPort)
299 case cv::GFluidKernel::Kind::Filter: return window + lpi - 1; break;
300 case cv::GFluidKernel::Kind::Resize:
305 // This is a suboptimal value, can be reduced
306 return calcResizeWindow(inH, outH) * lpi;
311 // This is a suboptimal value, can be reduced
312 return (inH == 1) ? 1 : 2 + lpi - 1;
315 case cv::GFluidKernel::Kind::YUV420toRGB: return inPort == 0 ? 2 : 1; break;
316 default: GAPI_Assert(false); return 0;
320 static int borderSize(const cv::GFluidKernel::Kind kind, int window)
324 case cv::GFluidKernel::Kind::Filter: return (window - 1) / 2; break;
325 // Resize never reads from border pixels
326 case cv::GFluidKernel::Kind::Resize: return 0; break;
327 case cv::GFluidKernel::Kind::YUV420toRGB: return 0; break;
328 default: GAPI_Assert(false); return 0;
332 inline double inCoord(int outIdx, double ratio)
334 return outIdx * ratio;
337 inline int windowStart(int outIdx, double ratio)
339 return static_cast<int>(inCoord(outIdx, ratio) + 1e-3);
342 inline int windowEnd(int outIdx, double ratio)
344 return static_cast<int>(std::ceil(inCoord(outIdx + 1, ratio) - 1e-3));
347 inline double inCoordUpscale(int outCoord, double ratio)
349 // Calculate the projection of output pixel's center
350 return (outCoord + 0.5) * ratio - 0.5;
353 inline int upscaleWindowStart(int outCoord, double ratio)
355 int start = static_cast<int>(inCoordUpscale(outCoord, ratio));
356 GAPI_DbgAssert(start >= 0);
360 inline int upscaleWindowEnd(int outCoord, double ratio, int inSz)
362 int end = static_cast<int>(std::ceil(inCoordUpscale(outCoord, ratio)) + 1);
369 } // anonymous namespace
371 int cv::gimpl::FluidDownscaleMapper::firstWindow(int outCoord, int lpi) const
373 return windowEnd(outCoord + lpi - 1, m_ratio) - windowStart(outCoord, m_ratio);
376 std::pair<int,int> cv::gimpl::FluidDownscaleMapper::linesReadAndNextWindow(int outCoord, int lpi) const
378 auto nextStartIdx = outCoord + 1 + m_lpi - 1;
379 auto nextEndIdx = nextStartIdx + lpi - 1;
381 auto currStart = windowStart(outCoord, m_ratio);
382 auto nextStart = windowStart(nextStartIdx, m_ratio);
383 auto nextEnd = windowEnd(nextEndIdx, m_ratio);
385 auto lines_read = nextStart - currStart;
386 auto next_window = nextEnd - nextStart;
388 return std::make_pair(lines_read, next_window);
391 int cv::gimpl::FluidUpscaleMapper::firstWindow(int outCoord, int lpi) const
393 return upscaleWindowEnd(outCoord + lpi - 1, m_ratio, m_inHeight) - upscaleWindowStart(outCoord, m_ratio);
396 std::pair<int,int> cv::gimpl::FluidUpscaleMapper::linesReadAndNextWindow(int outCoord, int lpi) const
398 auto nextStartIdx = outCoord + 1 + m_lpi - 1;
399 auto nextEndIdx = nextStartIdx + lpi - 1;
401 auto currStart = upscaleWindowStart(outCoord, m_ratio);
402 auto nextStart = upscaleWindowStart(nextStartIdx, m_ratio);
403 auto nextEnd = upscaleWindowEnd(nextEndIdx, m_ratio, m_inHeight);
405 auto lines_read = nextStart - currStart;
406 auto next_window = nextEnd - nextStart;
408 return std::make_pair(lines_read, next_window);
411 int cv::gimpl::FluidFilterAgent::firstWindow(std::size_t) const
413 int lpi = std::min(k.m_lpi, m_outputLines - m_producedLines);
414 return m_window + lpi - 1;
417 std::pair<int,int> cv::gimpl::FluidFilterAgent::linesReadAndnextWindow(std::size_t) const
419 int lpi = std::min(k.m_lpi, m_outputLines - m_producedLines - k.m_lpi);
420 return std::make_pair(k.m_lpi, m_window - 1 + lpi);
423 int cv::gimpl::FluidResizeAgent::firstWindow(std::size_t) const
425 auto outIdx = out_buffers[0]->priv().y();
426 auto lpi = std::min(m_outputLines - m_producedLines, k.m_lpi);
427 return m_mapper->firstWindow(outIdx, lpi);
430 std::pair<int,int> cv::gimpl::FluidResizeAgent::linesReadAndnextWindow(std::size_t) const
432 auto outIdx = out_buffers[0]->priv().y();
433 auto lpi = std::min(m_outputLines - m_producedLines - k.m_lpi, k.m_lpi);
434 return m_mapper->linesReadAndNextWindow(outIdx, lpi);
437 int cv::gimpl::Fluid420toRGBAgent::firstWindow(std::size_t inPort) const
439 // 2 lines for Y, 1 for UV
440 return inPort == 0 ? 2 : 1;
443 std::pair<int,int> cv::gimpl::Fluid420toRGBAgent::linesReadAndnextWindow(std::size_t inPort) const
445 // 2 lines for Y, 1 for UV
446 return inPort == 0 ? std::make_pair(2, 2) : std::make_pair(1, 1);
449 void cv::gimpl::FluidResizeAgent::setRatio(double ratio)
453 m_mapper.reset(new FluidDownscaleMapper(ratio, k.m_lpi));
457 m_mapper.reset(new FluidUpscaleMapper(ratio, k.m_lpi, in_views[0].meta().size.height));
461 bool cv::gimpl::FluidAgent::canRead() const
463 // An agent can work if every input buffer have enough data to start
464 for (const auto& in_view : in_views)
468 if (!in_view.ready())
475 bool cv::gimpl::FluidAgent::canWrite() const
477 // An agent can work if there is space to write in its output
479 GAPI_DbgAssert(!out_buffers.empty());
480 auto out_begin = out_buffers.begin();
481 auto out_end = out_buffers.end();
482 if (k.m_scratch) out_end--;
483 for (auto it = out_begin; it != out_end; ++it)
485 if ((*it)->priv().full())
493 bool cv::gimpl::FluidAgent::canWork() const
495 return canRead() && canWrite();
498 void cv::gimpl::FluidAgent::doWork()
500 GAPI_DbgAssert(m_outputLines > m_producedLines);
501 for (auto& in_view : in_views)
503 if (in_view) in_view.priv().prepareToRead();
506 k.m_f(in_args, out_buffers);
508 for (const auto& it : ade::util::indexed(in_views))
510 auto& in_view = ade::util::value(it);
514 auto idx = ade::util::index(it);
515 auto pair = linesReadAndnextWindow(idx);
516 in_view.priv().readDone(pair.first, pair.second);
520 for (auto out_buf : out_buffers)
522 out_buf->priv().writeDone();
523 // FIXME WARNING: Scratch buffers rotated here too!
526 m_producedLines += k.m_lpi;
529 bool cv::gimpl::FluidAgent::done() const
531 // m_producedLines is a multiple of LPI, while original
532 // height may be not.
533 return m_producedLines >= m_outputLines;
536 void cv::gimpl::FluidAgent::debug(std::ostream &os)
538 os << "Fluid Agent " << std::hex << this
539 << " (" << op_name << ") --"
540 << " canWork=" << std::boolalpha << canWork()
541 << " canRead=" << std::boolalpha << canRead()
542 << " canWrite=" << std::boolalpha << canWrite()
543 << " done=" << done()
544 << " lines=" << std::dec << m_producedLines << "/" << m_outputLines
546 for (auto out_buf : out_buffers)
550 std::cout << "}}" << std::endl;
553 // GCPUExcecutable implementation //////////////////////////////////////////////
555 void cv::gimpl::GFluidExecutable::initBufferRois(std::vector<int>& readStarts,
556 std::vector<cv::Rect>& rois,
557 const std::vector<cv::Rect>& out_rois)
559 GConstFluidModel fg(m_g);
560 auto proto = m_gm.metadata().get<Protocol>();
561 std::stack<ade::NodeHandle> nodesToVisit;
564 // There is possible case when user pass the vector full of default Rect{}-s,
565 // Can be diagnosed and handled appropriately
566 if (proto.outputs.size() != out_rois.size())
568 GAPI_Assert(out_rois.size() == 0);
569 // No inference required, buffers will obtain roi from meta
573 // First, initialize rois for output nodes, add them to traversal stack
574 for (const auto& it : ade::util::indexed(proto.out_nhs))
576 const auto idx = ade::util::index(it);
577 const auto nh = ade::util::value(it);
579 const auto &d = m_gm.metadata(nh).get<Data>();
581 // This is not our output
582 if (m_id_map.count(d.rc) == 0)
587 if (d.shape == GShape::GMAT)
589 auto desc = util::get<GMatDesc>(d.meta);
590 auto id = m_id_map.at(d.rc);
593 const auto& out_roi = out_rois[idx];
594 if (out_roi == cv::Rect{})
596 rois[id] = cv::Rect{ 0, 0, desc.size.width, desc.size.height };
600 GAPI_Assert(out_roi.height > 0);
601 GAPI_Assert(out_roi.y + out_roi.height <= desc.size.height);
603 // Only slices are supported at the moment
604 GAPI_Assert(out_roi.x == 0);
605 GAPI_Assert(out_roi.width == desc.size.width);
609 nodesToVisit.push(nh);
613 // Perform a wide search from each of the output nodes
614 // And extend roi of buffers by border_size
615 // Each node can be visited multiple times
616 // (if node has been already visited, the check that inferred rois are the same is performed)
617 while (!nodesToVisit.empty())
619 const auto startNode = nodesToVisit.top();
622 if (!startNode->inNodes().empty())
624 GAPI_Assert(startNode->inNodes().size() == 1);
625 const auto& oh = startNode->inNodes().front();
627 const auto& data = m_gm.metadata(startNode).get<Data>();
628 // only GMats participate in the process so it's valid to obtain GMatDesc
629 const auto& meta = util::get<GMatDesc>(data.meta);
631 for (const auto& in_edge : oh->inEdges())
633 const auto& in_node = in_edge->srcNode();
634 const auto& in_data = m_gm.metadata(in_node).get<Data>();
636 if (in_data.shape == GShape::GMAT && fg.metadata(in_node).contains<FluidData>())
638 const auto& in_meta = util::get<GMatDesc>(in_data.meta);
639 const auto& fd = fg.metadata(in_node).get<FluidData>();
641 auto adjFilterRoi = [](cv::Rect produced, int b, int max_height) {
642 // Extend with border roi which should be produced, crop to logical image size
643 cv::Rect roi = {produced.x, produced.y - b, produced.width, produced.height + 2*b};
644 cv::Rect fullImg{ 0, 0, produced.width, max_height };
645 return roi & fullImg;
648 auto adjResizeRoi = [](cv::Rect produced, cv::Size inSz, cv::Size outSz) {
649 auto map = [](int outCoord, int producedSz, int inSize, int outSize) {
650 double ratio = (double)inSize / outSize;
654 w0 = windowStart(outCoord, ratio);
655 w1 = windowEnd (outCoord + producedSz - 1, ratio);
659 w0 = upscaleWindowStart(outCoord, ratio);
660 w1 = upscaleWindowEnd(outCoord + producedSz - 1, ratio, inSize);
662 return std::make_pair(w0, w1);
665 auto mapY = map(produced.y, produced.height, inSz.height, outSz.height);
666 auto y0 = mapY.first;
667 auto y1 = mapY.second;
669 auto mapX = map(produced.x, produced.width, inSz.width, outSz.width);
670 auto x0 = mapX.first;
671 auto x1 = mapX.second;
673 cv::Rect roi = {x0, y0, x1 - x0, y1 - y0};
677 auto adj420Roi = [&](cv::Rect produced, std::size_t port) {
678 GAPI_Assert(produced.x % 2 == 0);
679 GAPI_Assert(produced.y % 2 == 0);
680 GAPI_Assert(produced.width % 2 == 0);
681 GAPI_Assert(produced.height % 2 == 0);
685 case 0: roi = produced; break;
687 case 2: roi = cv::Rect{ produced.x/2, produced.y/2, produced.width/2, produced.height/2 }; break;
688 default: GAPI_Assert(false);
693 cv::Rect produced = rois[m_id_map.at(data.rc)];
695 // Apply resize-specific roi transformations
697 switch (fg.metadata(oh).get<FluidUnit>().k.m_kind)
699 case GFluidKernel::Kind::Filter: resized = produced; break;
700 case GFluidKernel::Kind::Resize: resized = adjResizeRoi(produced, in_meta.size, meta.size); break;
701 case GFluidKernel::Kind::YUV420toRGB: resized = adj420Roi(produced, m_gm.metadata(in_edge).get<Input>().port); break;
702 default: GAPI_Assert(false);
705 // All below transformations affect roi of the writer, preserve read start position here
706 int readStart = resized.y;
708 // Extend required input roi (both y and height) to be even if it's produced by CS420toRGB
709 if (!in_node->inNodes().empty()) {
710 auto in_data_producer = in_node->inNodes().front();
711 if (fg.metadata(in_data_producer).get<FluidUnit>().k.m_kind == GFluidKernel::Kind::YUV420toRGB) {
712 if (resized.y % 2 != 0) {
717 if (resized.height % 2 != 0) {
723 // Apply filter-specific roi transformations, clip to image size
724 // Note: done even for non-filter kernels as applies border-related transformations
725 // (required in the case when there are multiple readers with different border requirements)
726 auto roi = adjFilterRoi(resized, fd.border_size, in_meta.size.height);
728 auto in_id = m_id_map.at(in_data.rc);
729 if (rois[in_id] == cv::Rect{})
731 readStarts[in_id] = readStart;
733 // Continue traverse on internal (w.r.t Island) data nodes only.
734 if (fd.internal) nodesToVisit.push(in_node);
738 GAPI_Assert(readStarts[in_id] == readStart);
739 GAPI_Assert(rois[in_id] == roi);
741 } // if (in_data.shape == GShape::GMAT)
742 } // for (const auto& in_edge : oh->inEdges())
743 } // if (!startNode->inNodes().empty())
744 } // while (!nodesToVisit.empty())
747 cv::gimpl::FluidGraphInputData cv::gimpl::fluidExtractInputDataFromGraph(const ade::Graph &g, const std::vector<ade::NodeHandle> &nodes)
749 decltype(FluidGraphInputData::m_agents_data) agents_data;
750 decltype(FluidGraphInputData::m_scratch_users) scratch_users;
751 decltype(FluidGraphInputData::m_id_map) id_map;
752 decltype(FluidGraphInputData::m_all_gmat_ids) all_gmat_ids;
753 std::size_t mat_count = 0;
755 GConstFluidModel fg(g);
756 GModel::ConstGraph m_gm(g);
758 // Initialize vector of data buffers, build list of operations
759 // FIXME: There _must_ be a better way to [query] count number of DATA nodes
761 auto grab_mat_nh = [&](ade::NodeHandle nh) {
762 auto rc = m_gm.metadata(nh).get<Data>().rc;
763 if (id_map.count(rc) == 0)
765 all_gmat_ids[mat_count] = nh;
766 id_map[rc] = mat_count++;
770 std::size_t last_agent = 0;
772 for (const auto &nh : nodes)
774 switch (m_gm.metadata(nh).get<NodeType>().t)
777 if (m_gm.metadata(nh).get<Data>().shape == GShape::GMAT)
783 const auto& fu = fg.metadata(nh).get<FluidUnit>();
785 agents_data.push_back({fu.k.m_kind, nh, {}, {}});
786 // NB.: in_buffer_ids size is equal to Arguments size, not Edges size!!!
787 agents_data.back().in_buffer_ids.resize(m_gm.metadata(nh).get<Op>().args.size(), -1);
788 for (auto eh : nh->inEdges())
790 // FIXME Only GMats are currently supported (which can be represented
792 if (m_gm.metadata(eh->srcNode()).get<Data>().shape == GShape::GMAT)
794 const auto in_port = m_gm.metadata(eh).get<Input>().port;
795 const int in_buf = m_gm.metadata(eh->srcNode()).get<Data>().rc;
797 agents_data.back().in_buffer_ids[in_port] = in_buf;
798 grab_mat_nh(eh->srcNode());
801 // FIXME: Assumption that all operation outputs MUST be connected
802 agents_data.back().out_buffer_ids.resize(nh->outEdges().size(), -1);
803 for (auto eh : nh->outEdges())
805 const auto& data = m_gm.metadata(eh->dstNode()).get<Data>();
806 const auto out_port = m_gm.metadata(eh).get<Output>().port;
807 const int out_buf = data.rc;
809 agents_data.back().out_buffer_ids[out_port] = out_buf;
810 if (data.shape == GShape::GMAT) grab_mat_nh(eh->dstNode());
813 scratch_users.push_back(last_agent);
817 default: GAPI_Assert(false);
821 // Check that IDs form a continiuos set (important for further indexing)
822 GAPI_Assert(id_map.size() > 0);
823 GAPI_Assert(id_map.size() == static_cast<size_t>(mat_count));
825 return FluidGraphInputData {std::move(agents_data), std::move(scratch_users), std::move(id_map), std::move(all_gmat_ids), mat_count};
828 cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
829 const cv::gimpl::FluidGraphInputData &traverse_res,
830 const std::vector<cv::Rect> &outputRois)
832 m_num_int_buffers (traverse_res.m_mat_count),
833 m_scratch_users (traverse_res.m_scratch_users),
834 m_id_map (traverse_res.m_id_map),
835 m_all_gmat_ids (traverse_res.m_all_gmat_ids),
836 m_buffers(m_num_int_buffers + m_scratch_users.size())
838 GConstFluidModel fg(m_g);
840 auto create_fluid_agent = [&g](agent_data_t const& agent_data) -> std::unique_ptr<FluidAgent> {
841 std::unique_ptr<FluidAgent> agent_ptr;
842 switch (agent_data.kind)
844 case GFluidKernel::Kind::Filter: agent_ptr.reset(new FluidFilterAgent(g, agent_data.nh)); break;
845 case GFluidKernel::Kind::Resize: agent_ptr.reset(new FluidResizeAgent(g, agent_data.nh)); break;
846 case GFluidKernel::Kind::YUV420toRGB: agent_ptr.reset(new Fluid420toRGBAgent(g, agent_data.nh)); break;
847 default: GAPI_Assert(false);
849 std::tie(agent_ptr->in_buffer_ids, agent_ptr->out_buffer_ids) = std::tie(agent_data.in_buffer_ids, agent_data.out_buffer_ids);
853 for (auto const& agent_data : traverse_res.m_agents_data){
854 m_agents.push_back(create_fluid_agent(agent_data));
857 // Actually initialize Fluid buffers
858 GAPI_LOG_INFO(NULL, "Initializing " << m_num_int_buffers << " fluid buffer(s)" << std::endl);
860 // After buffers are allocated, repack: ...
861 for (auto &agent : m_agents)
863 // a. Agent input parameters with View pointers (creating Views btw)
864 const auto &op = m_gm.metadata(agent->op_handle).get<Op>();
865 const auto &fu = fg.metadata(agent->op_handle).get<FluidUnit>();
866 agent->in_args.resize(op.args.size());
867 agent->in_views.resize(op.args.size());
868 for (auto it : ade::util::indexed(ade::util::toRange(agent->in_buffer_ids)))
870 auto in_idx = ade::util::index(it);
871 auto buf_idx = ade::util::value(it);
875 // IF there is input buffer, register a view (every unique
876 // reader has its own), and store it in agent Args
877 gapi::fluid::Buffer &buffer = m_buffers.at(m_id_map.at(buf_idx));
879 auto inEdge = GModel::getInEdgeByPort(m_g, agent->op_handle, in_idx);
880 auto ownStorage = fg.metadata(inEdge).get<FluidUseOwnBorderBuffer>().use;
882 // NB: It is safe to keep ptr as view lifetime is buffer lifetime
883 agent->in_views[in_idx] = buffer.mkView(fu.border_size, ownStorage);
884 agent->in_args[in_idx] = GArg(&agent->in_views[in_idx]);
885 buffer.addView(&agent->in_views[in_idx]);
889 // Copy(FIXME!) original args as is
890 agent->in_args[in_idx] = op.args[in_idx];
894 // b. Agent output parameters with Buffer pointers.
895 agent->out_buffers.resize(agent->op_handle->outEdges().size(), nullptr);
896 for (auto it : ade::util::indexed(ade::util::toRange(agent->out_buffer_ids)))
898 auto out_idx = ade::util::index(it);
899 auto buf_idx = m_id_map.at(ade::util::value(it));
900 agent->out_buffers.at(out_idx) = &m_buffers.at(buf_idx);
904 // After parameters are there, initialize scratch buffers
905 const std::size_t num_scratch = m_scratch_users.size();
908 GAPI_LOG_INFO(NULL, "Initializing " << num_scratch << " scratch buffer(s)" << std::endl);
909 std::size_t last_scratch_id = 0;
911 for (auto i : m_scratch_users)
913 auto &agent = m_agents.at(i);
914 GAPI_Assert(agent->k.m_scratch);
915 const std::size_t new_scratch_idx = m_num_int_buffers + last_scratch_id;
916 agent->out_buffers.emplace_back(&m_buffers[new_scratch_idx]);
921 makeReshape(outputRois);
923 GAPI_LOG_INFO(NULL, "Internal buffers: " << std::fixed << std::setprecision(2) << static_cast<float>(total_buffers_size())/1024 << " KB\n");
926 std::size_t cv::gimpl::GFluidExecutable::total_buffers_size() const
928 GConstFluidModel fg(m_g);
929 std::size_t total_size = 0;
930 for (const auto &i : ade::util::indexed(m_buffers))
932 // Check that all internal and scratch buffers are allocated
933 const auto idx = ade::util::index(i);
934 const auto& b = ade::util::value(i);
935 if (idx >= m_num_int_buffers ||
936 fg.metadata(m_all_gmat_ids.at(idx)).get<FluidData>().internal == true)
938 GAPI_Assert(b.priv().size() > 0);
941 // Buffers which will be bound to real images may have size of 0 at this moment
942 // (There can be non-zero sized const border buffer allocated in such buffers)
943 total_size += b.priv().size();
950 void resetFluidData(ade::Graph& graph)
952 using namespace cv::gimpl;
953 GModel::Graph g(graph);
954 GFluidModel fg(graph);
955 for (const auto node : g.nodes())
957 if (g.metadata(node).get<NodeType>().t == NodeType::DATA)
959 auto& fd = fg.metadata(node).get<FluidData>();
962 fd.max_consumption = 0;
965 GModel::log_clear(g, node);
969 void initFluidUnits(ade::Graph& graph)
971 using namespace cv::gimpl;
972 GModel::Graph g(graph);
973 GFluidModel fg(graph);
975 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
976 for (auto node : sorted)
978 if (fg.metadata(node).contains<FluidUnit>())
980 std::set<int> in_hs, out_ws, out_hs;
982 for (const auto& in : node->inNodes())
984 const auto& d = g.metadata(in).get<Data>();
985 if (d.shape == cv::GShape::GMAT)
987 const auto& meta = cv::util::get<cv::GMatDesc>(d.meta);
988 in_hs.insert(meta.size.height);
992 for (const auto& out : node->outNodes())
994 const auto& d = g.metadata(out).get<Data>();
995 if (d.shape == cv::GShape::GMAT)
997 const auto& meta = cv::util::get<cv::GMatDesc>(d.meta);
998 out_ws.insert(meta.size.width);
999 out_hs.insert(meta.size.height);
1003 auto &fu = fg.metadata(node).get<FluidUnit>();
1005 GAPI_Assert((out_ws.size() == 1 && out_hs.size() == 1) &&
1006 ((in_hs.size() == 1) ||
1007 ((in_hs.size() == 2) && fu.k.m_kind == cv::GFluidKernel::Kind::YUV420toRGB)));
1009 const auto &op = g.metadata(node).get<Op>();
1010 fu.line_consumption.resize(op.args.size(), 0);
1012 auto in_h = *in_hs .cbegin();
1013 auto out_h = *out_hs.cbegin();
1015 fu.ratio = (double)in_h / out_h;
1017 // Set line consumption for each image (GMat) input
1018 for (const auto& in_edge : node->inEdges())
1020 const auto& d = g.metadata(in_edge->srcNode()).get<Data>();
1021 if (d.shape == cv::GShape::GMAT)
1023 auto port = g.metadata(in_edge).get<Input>().port;
1024 fu.line_consumption[port] = maxLineConsumption(fu.k.m_kind, fu.window, in_h, out_h, fu.k.m_lpi, port);
1026 GModel::log(g, node, "Line consumption (port " + std::to_string(port) + "): "
1027 + std::to_string(fu.line_consumption[port]));
1031 fu.border_size = borderSize(fu.k.m_kind, fu.window);
1032 GModel::log(g, node, "Border size: " + std::to_string(fu.border_size));
1038 // Split into initLineConsumption and initBorderSizes,
1039 // call only consumption related stuff during reshape
1040 void initLineConsumption(ade::Graph& graph)
1042 using namespace cv::gimpl;
1043 GModel::Graph g(graph);
1044 GFluidModel fg(graph);
1046 for (const auto &node : g.nodes())
1048 if (fg.metadata(node).contains<FluidUnit>())
1050 const auto &fu = fg.metadata(node).get<FluidUnit>();
1052 for (const auto &in_edge : node->inEdges())
1054 const auto &in_data_node = in_edge->srcNode();
1055 auto port = g.metadata(in_edge).get<Input>().port;
1057 auto &fd = fg.metadata(in_data_node).get<FluidData>();
1059 // Update (not Set) fields here since a single data node may be
1060 // accessed by multiple consumers
1061 fd.max_consumption = std::max(fu.line_consumption[port], fd.max_consumption);
1062 fd.border_size = std::max(fu.border_size, fd.border_size);
1064 GModel::log(g, in_data_node, "Line consumption: " + std::to_string(fd.max_consumption)
1065 + " (upd by " + std::to_string(fu.line_consumption[port]) + ")", node);
1066 GModel::log(g, in_data_node, "Border size: " + std::to_string(fd.border_size), node);
1072 void calcLatency(ade::Graph& graph)
1074 using namespace cv::gimpl;
1075 GModel::Graph g(graph);
1076 GFluidModel fg(graph);
1078 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1079 for (const auto &node : sorted)
1081 if (fg.metadata(node).contains<FluidUnit>())
1083 const auto &fu = fg.metadata(node).get<FluidUnit>();
1085 GModel::log(g, node, "LPI: " + std::to_string(fu.k.m_lpi));
1087 // Output latency is max(input_latency) + own_latency
1088 int out_latency = 0;
1089 for (const auto &in_edge: node->inEdges())
1091 // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
1092 const auto port = g.metadata(in_edge).get<Input>().port;
1093 const auto own_latency = fu.line_consumption[port] - fu.border_size;
1094 const auto in_latency = fg.metadata(in_edge->srcNode()).get<FluidData>().latency;
1095 out_latency = std::max(out_latency, in_latency + own_latency);
1098 for (const auto &out_data_node : node->outNodes())
1100 // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
1101 auto &fd = fg.metadata(out_data_node).get<FluidData>();
1102 // If fluid node is external, it will be bound to a real image without
1103 // fluid buffer allocation, so set its latency to 0 not to confuse later latency propagation.
1104 // Latency is used in fluid buffer allocation process and is not used by the scheduler
1105 // so latency doesn't affect the execution and setting it to 0 is legal
1106 fd.latency = fd.internal ? out_latency : 0;
1107 fd.lpi_write = fu.k.m_lpi;
1108 GModel::log(g, out_data_node, "Latency: " + std::to_string(fd.latency));
1114 void calcSkew(ade::Graph& graph)
1116 using namespace cv::gimpl;
1117 GModel::Graph g(graph);
1118 GFluidModel fg(graph);
1120 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1121 for (const auto &node : sorted)
1123 if (fg.metadata(node).contains<FluidUnit>())
1125 int max_latency = 0;
1126 for (const auto &in_data_node : node->inNodes())
1128 // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
1129 max_latency = std::max(max_latency, fg.metadata(in_data_node).get<FluidData>().latency);
1131 for (const auto &in_data_node : node->inNodes())
1133 // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
1134 auto &fd = fg.metadata(in_data_node).get<FluidData>();
1136 // Update (not Set) fields here since a single data node may be
1137 // accessed by multiple consumers
1138 fd.skew = std::max(fd.skew, max_latency - fd.latency);
1140 GModel::log(g, in_data_node, "Skew: " + std::to_string(fd.skew), node);
1147 void cv::gimpl::GFluidExecutable::makeReshape(const std::vector<cv::Rect> &out_rois)
1149 GConstFluidModel fg(m_g);
1151 // Calculate rois for each fluid buffer
1152 std::vector<int> readStarts(m_num_int_buffers);
1153 std::vector<cv::Rect> rois(m_num_int_buffers);
1154 initBufferRois(readStarts, rois, out_rois);
1156 // NB: Allocate ALL buffer object at once, and avoid any further reallocations
1157 // (since raw pointers-to-elements are taken)
1158 for (const auto &it : m_all_gmat_ids)
1161 auto nh = it.second;
1162 const auto & d = m_gm.metadata(nh).get<Data>();
1163 const auto &fd = fg.metadata(nh).get<FluidData>();
1164 const auto meta = cv::util::get<GMatDesc>(d.meta);
1166 m_buffers[id].priv().init(meta, fd.lpi_write, readStarts[id], rois[id]);
1169 // Introduce Storage::INTERNAL_GRAPH and Storage::INTERNAL_ISLAND?
1170 if (fd.internal == true)
1172 // FIXME: do max_consumption calculation properly (e.g. in initLineConsumption)
1173 int max_consumption = 0;
1174 if (nh->outNodes().empty()) {
1175 // nh is always a DATA node, so it is safe to get inNodes().front() since there's
1176 // always a single writer (OP node)
1177 max_consumption = fg.metadata(nh->inNodes().front()).get<FluidUnit>().k.m_lpi;
1179 max_consumption = fd.max_consumption;
1181 m_buffers[id].priv().allocate(fd.border, fd.border_size, max_consumption, fd.skew);
1182 std::stringstream stream;
1183 m_buffers[id].debug(stream);
1184 GAPI_LOG_INFO(NULL, stream.str());
1188 // Allocate views, initialize agents
1189 for (auto &agent : m_agents)
1191 const auto &fu = fg.metadata(agent->op_handle).get<FluidUnit>();
1192 for (auto it : ade::util::indexed(ade::util::toRange(agent->in_buffer_ids)))
1194 auto in_idx = ade::util::index(it);
1195 auto buf_idx = ade::util::value(it);
1199 agent->in_views[in_idx].priv().allocate(fu.line_consumption[in_idx], fu.border);
1203 agent->setRatio(fu.ratio);
1204 agent->m_outputLines = agent->out_buffers.front()->priv().outputLines();
1207 // Initialize scratch buffers
1208 if (m_scratch_users.size())
1210 for (auto i : m_scratch_users)
1212 auto &agent = m_agents.at(i);
1213 GAPI_Assert(agent->k.m_scratch);
1215 // Trigger Scratch buffer initialization method
1216 agent->k.m_is(GModel::collectInputMeta(m_gm, agent->op_handle), agent->in_args, *agent->out_buffers.back());
1217 std::stringstream stream;
1218 agent->out_buffers.back()->debug(stream);
1219 GAPI_LOG_INFO(NULL, stream.str());
1223 // FIXME: calculate the size (lpi * ..)
1225 m_script.reserve(10000);
1228 void cv::gimpl::GFluidExecutable::reshape(ade::Graph &g, const GCompileArgs &args)
1230 // FIXME: Probably this needs to be integrated into common pass re-run routine
1231 // Backends may want to mark with passes to re-run on reshape and framework could
1232 // do it system-wide (without need in every backend handling reshape() directly).
1233 // This design needs to be analyzed for implementation.
1236 initLineConsumption(g);
1239 const auto out_rois = cv::gapi::getCompileArg<cv::GFluidOutputRois>(args).value_or(cv::GFluidOutputRois());
1240 makeReshape(out_rois.rois);
1243 // FIXME: Document what it does
1244 void cv::gimpl::GFluidExecutable::bindInArg(const cv::gimpl::RcDesc &rc, const GRunArg &arg)
1246 magazine::bindInArg(m_res, rc, arg);
1247 if (rc.shape == GShape::GMAT) {
1248 auto& mat = m_res.slot<cv::Mat>()[rc.id];
1249 // fluid::Buffer::bindTo() is not connected to magazine::bindIn/OutArg and unbind() calls,
1250 // it's simply called each run() without any requirement to call some fluid-specific
1251 // unbind() at the end of run()
1252 m_buffers[m_id_map.at(rc.id)].priv().bindTo(mat, true);
1256 void cv::gimpl::GFluidExecutable::bindOutArg(const cv::gimpl::RcDesc &rc, const GRunArgP &arg)
1258 // Only GMat is supported as return type
1259 if (rc.shape != GShape::GMAT) {
1260 util::throw_error(std::logic_error("Unsupported return GShape type"));
1262 magazine::bindOutArg(m_res, rc, arg);
1263 auto& mat = m_res.slot<cv::Mat>()[rc.id];
1264 m_buffers[m_id_map.at(rc.id)].priv().bindTo(mat, false);
1267 void cv::gimpl::GFluidExecutable::packArg(cv::GArg &in_arg, const cv::GArg &op_arg)
1269 GAPI_Assert(op_arg.kind != cv::detail::ArgKind::GMAT
1270 && op_arg.kind != cv::detail::ArgKind::GSCALAR
1271 && op_arg.kind != cv::detail::ArgKind::GARRAY
1272 && op_arg.kind != cv::detail::ArgKind::GOPAQUE);
1274 if (op_arg.kind == cv::detail::ArgKind::GOBJREF)
1276 const cv::gimpl::RcDesc &ref = op_arg.get<cv::gimpl::RcDesc>();
1277 if (ref.shape == GShape::GSCALAR)
1279 in_arg = GArg(m_res.slot<cv::Scalar>()[ref.id]);
1281 else if (ref.shape == GShape::GARRAY)
1283 in_arg = GArg(m_res.slot<cv::detail::VectorRef>()[ref.id]);
1285 else if (ref.shape == GShape::GOPAQUE)
1287 in_arg = GArg(m_res.slot<cv::detail::OpaqueRef>()[ref.id]);
1292 void cv::gimpl::GFluidExecutable::run(std::vector<InObj> &&input_objs,
1293 std::vector<OutObj> &&output_objs)
1295 run(input_objs, output_objs);
1297 void cv::gimpl::GFluidExecutable::run(std::vector<InObj> &input_objs,
1298 std::vector<OutObj> &output_objs)
1300 // Bind input buffers from parameters
1301 for (auto& it : input_objs) bindInArg(it.first, it.second);
1302 for (auto& it : output_objs) bindOutArg(it.first, it.second);
1304 // Reset Buffers and Agents state before we go
1305 for (auto &buffer : m_buffers)
1306 buffer.priv().reset();
1308 for (auto &agent : m_agents)
1311 // Pass input cv::Scalar's to agent argument
1312 const auto& op = m_gm.metadata(agent->op_handle).get<Op>();
1313 for (const auto& it : ade::util::indexed(op.args))
1315 const auto& arg = ade::util::value(it);
1316 packArg(agent->in_args[ade::util::index(it)], arg);
1320 // Explicitly reset Scratch buffers, if any
1321 for (auto scratch_i : m_scratch_users)
1323 auto &agent = m_agents[scratch_i];
1324 GAPI_DbgAssert(agent->k.m_scratch);
1325 agent->k.m_rs(*agent->out_buffers.back());
1328 // Now start executing our stuff!
1329 // Fluid execution is:
1330 // - run through list of Agents from Left to Right
1331 // - for every Agent:
1332 // - if all input Buffers have enough data to fulfill
1333 // Agent's window - trigger Agent
1334 // - on trigger, Agent takes all input lines from input buffers
1335 // and produces a single output line
1336 // - once Agent finishes, input buffers get "readDone()",
1337 // and output buffers get "writeDone()"
1338 // - if there's not enough data, Agent is skipped
1341 if (m_script.empty())
1343 bool complete = true;
1346 bool work_done=false;
1347 for (auto &agent : m_agents)
1349 // agent->debug(std::cout);
1352 if (agent->canWork())
1354 agent->doWork(); work_done=true;
1355 m_script.push_back(agent.get());
1357 if (!agent->done()) complete = false;
1360 GAPI_Assert(work_done || complete);
1361 } while (!complete); // FIXME: number of iterations can be calculated statically
1365 for (auto &agent : m_script)
1371 // In/Out args clean-up is mandatory now with RMat
1372 for (auto &it : input_objs) magazine::unbind(m_res, it.first);
1373 for (auto &it : output_objs) magazine::unbind(m_res, it.first);
1376 cv::gimpl::GParallelFluidExecutable::GParallelFluidExecutable(const ade::Graph &g,
1377 const FluidGraphInputData &graph_data,
1378 const std::vector<GFluidOutputRois> ¶llelOutputRois,
1379 const decltype(parallel_for) &pfor)
1380 : parallel_for(pfor)
1382 for (auto&& rois : parallelOutputRois){
1383 tiles.emplace_back(new GFluidExecutable(g, graph_data, rois.rois));
1388 void cv::gimpl::GParallelFluidExecutable::reshape(ade::Graph&, const GCompileArgs& )
1391 GAPI_Assert(false && "Not Implemented;");
1394 void cv::gimpl::GParallelFluidExecutable::run(std::vector<InObj> &&input_objs,
1395 std::vector<OutObj> &&output_objs)
1397 parallel_for(tiles.size(), [&, this](std::size_t index){
1398 GAPI_Assert((bool)tiles[index]);
1399 tiles[index]->run(input_objs, output_objs);
1404 // FIXME: these passes operate on graph global level!!!
1405 // Need to fix this for heterogeneous (island-based) processing
1406 void GFluidBackendImpl::addMetaSensitiveBackendPasses(ade::ExecutionEngineSetupContext &ectx)
1408 using namespace cv::gimpl;
1410 // FIXME: all passes were moved to "exec" stage since Fluid
1411 // should check Islands configuration first (which is now quite
1412 // limited), and only then continue with all other passes.
1414 // The passes/stages API must be streamlined!
1415 ectx.addPass("exec", "init_fluid_data", [](ade::passes::PassContext &ctx)
1417 GModel::Graph g(ctx.graph);
1418 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1421 auto isl_graph = g.metadata().get<IslandModel>().model;
1422 GIslandModel::Graph gim(*isl_graph);
1424 GFluidModel fg(ctx.graph);
1426 const auto setFluidData = [&](ade::NodeHandle nh, bool internal) {
1428 fd.internal = internal;
1429 fg.metadata(nh).set(fd);
1432 for (const auto& nh : gim.nodes())
1434 switch (gim.metadata(nh).get<NodeKind>().k)
1436 case NodeKind::ISLAND:
1438 const auto isl = gim.metadata(nh).get<FusedIsland>().object;
1439 if (isl->backend() == cv::gapi::fluid::backend())
1441 // Add FluidData to all data nodes inside island,
1442 // set internal = true if node is not a slot in terms of higher-level GIslandModel
1443 for (const auto node : isl->contents())
1445 if (g.metadata(node).get<NodeType>().t == NodeType::DATA &&
1446 !fg.metadata(node).contains<FluidData>())
1447 setFluidData(node, true);
1449 } // if (fluid backend)
1450 } break; // case::ISLAND
1451 case NodeKind::SLOT:
1453 // add FluidData to slot if it's read/written by fluid
1454 // regardless if it is one fluid island (both writing to and reading from this object)
1455 // or two distinct islands (both fluid)
1456 auto isFluidIsland = [&](const ade::NodeHandle& node) {
1457 // With Streaming, Emitter islands may have no FusedIsland thing in meta.
1458 // FIXME: Probably this is a concept misalignment
1459 if (!gim.metadata(node).contains<FusedIsland>()) {
1460 const auto kind = gim.metadata(node).get<NodeKind>().k;
1461 GAPI_Assert(kind == NodeKind::EMIT || kind == NodeKind::SINK);
1464 const auto isl = gim.metadata(node).get<FusedIsland>().object;
1465 return isl->backend() == cv::gapi::fluid::backend();
1468 if (ade::util::any_of(ade::util::chain(nh->inNodes(), nh->outNodes()), isFluidIsland))
1470 auto data_node = gim.metadata(nh).get<DataSlot>().original_data_node;
1471 setFluidData(data_node, false);
1473 } break; // case::SLOT
1474 case NodeKind::EMIT:
1475 case NodeKind::SINK:
1476 break; // do nothing for Streaming nodes
1477 default: GAPI_Assert(false);
1479 } // for (gim.nodes())
1482 // move to unpackKernel method
1483 // when https://gitlab-icv.inn.intel.com/G-API/g-api/merge_requests/66 is merged
1484 ectx.addPass("exec", "init_fluid_unit_windows_and_borders", [](ade::passes::PassContext &ctx)
1486 GModel::Graph g(ctx.graph);
1487 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1490 GFluidModel fg(ctx.graph);
1492 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1493 for (auto node : sorted)
1495 if (fg.metadata(node).contains<FluidUnit>())
1497 // FIXME: check that op has only one data node on input
1498 auto &fu = fg.metadata(node).get<FluidUnit>();
1499 const auto &op = g.metadata(node).get<Op>();
1500 auto inputMeta = GModel::collectInputMeta(fg, node);
1502 // Trigger user-defined "getWindow" callback
1503 fu.window = fu.k.m_gw(inputMeta, op.args);
1505 // Trigger user-defined "getBorder" callback
1506 fu.border = fu.k.m_b(inputMeta, op.args);
1510 ectx.addPass("exec", "init_fluid_units", [](ade::passes::PassContext &ctx)
1512 GModel::Graph g(ctx.graph);
1513 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1516 initFluidUnits(ctx.graph);
1518 ectx.addPass("exec", "init_line_consumption", [](ade::passes::PassContext &ctx)
1520 GModel::Graph g(ctx.graph);
1521 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1524 initLineConsumption(ctx.graph);
1526 ectx.addPass("exec", "calc_latency", [](ade::passes::PassContext &ctx)
1528 GModel::Graph g(ctx.graph);
1529 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1532 calcLatency(ctx.graph);
1534 ectx.addPass("exec", "calc_skew", [](ade::passes::PassContext &ctx)
1536 GModel::Graph g(ctx.graph);
1537 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1540 calcSkew(ctx.graph);
1543 ectx.addPass("exec", "init_buffer_borders", [](ade::passes::PassContext &ctx)
1545 GModel::Graph g(ctx.graph);
1546 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1549 GFluidModel fg(ctx.graph);
1550 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1551 for (auto node : sorted)
1553 if (fg.metadata(node).contains<FluidData>())
1555 auto &fd = fg.metadata(node).get<FluidData>();
1557 // Assign border stuff to FluidData
1559 // In/out data nodes are bound to user data directly,
1560 // so cannot be extended with a border
1561 if (fd.internal == true)
1563 // For now border of the buffer's storage is the border
1564 // of the first reader whose border size is the same.
1565 // FIXME: find more clever strategy of border picking
1566 // (it can be a border which is common for majority of the
1567 // readers, also we can calculate the number of lines which
1568 // will be copied by views on each iteration and base our choice
1569 // on this criteria)
1570 auto readers = node->outNodes();
1572 // There can be a situation when __internal__ nodes produced as part of some
1573 // operation are unused later in the graph:
1576 // |------> internal_1 // unused node
1577 // |------> internal_2 -> OP2
1580 // To allow graphs like the one above, skip nodes with empty outNodes()
1581 if (readers.empty()) {
1585 const auto &candidate = ade::util::find_if(readers, [&](ade::NodeHandle nh) {
1586 return fg.metadata(nh).contains<FluidUnit>() &&
1587 fg.metadata(nh).get<FluidUnit>().border_size == fd.border_size;
1590 GAPI_Assert(candidate != readers.end());
1592 const auto &fu = fg.metadata(*candidate).get<FluidUnit>();
1593 fd.border = fu.border;
1598 GModel::log(g, node, "Border type: " + std::to_string(fd.border->type), node);
1603 ectx.addPass("exec", "init_view_borders", [](ade::passes::PassContext &ctx)
1605 GModel::Graph g(ctx.graph);
1606 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1609 GFluidModel fg(ctx.graph);
1610 for (auto node : g.nodes())
1612 if (fg.metadata(node).contains<FluidData>())
1614 auto &fd = fg.metadata(node).get<FluidData>();
1615 for (auto out_edge : node->outEdges())
1617 const auto dstNode = out_edge->dstNode();
1618 if (fg.metadata(dstNode).contains<FluidUnit>())
1620 const auto &fu = fg.metadata(dstNode).get<FluidUnit>();
1622 // There is no need in own storage for view if it's border is
1623 // the same as the buffer's (view can have equal or smaller border
1624 // size in this case)
1625 if (fu.border_size == 0 ||
1626 (fu.border && fd.border && (*fu.border == *fd.border)))
1628 GAPI_Assert(fu.border_size <= fd.border_size);
1629 fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{false});
1633 fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{true});
1634 GModel::log(g, out_edge, "OwnBufferStorage: true");