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
12 #include <iomanip> // std::fixed, std::setprecision
13 #include <unordered_set>
16 #include <ade/util/algorithm.hpp>
17 #include <ade/util/chain_range.hpp>
18 #include <ade/util/range.hpp>
19 #include <ade/util/zip_range.hpp>
21 #include <ade/typed_graph.hpp>
22 #include <ade/execution_engine/execution_engine.hpp>
24 #include "opencv2/gapi/gcommon.hpp"
27 #include "opencv2/gapi/own/convert.hpp"
28 #include "opencv2/gapi/gmat.hpp" //for version of descr_of
30 #include "compiler/gobjref.hpp"
31 #include "compiler/gmodel.hpp"
33 #include "backends/fluid/gfluidbuffer_priv.hpp"
34 #include "backends/fluid/gfluidbackend.hpp"
36 #include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
38 // FIXME: Is there a way to take a typed graph (our GModel),
39 // and create a new typed graph _ATOP_ of that (by extending with a couple of
41 // Alternatively, is there a way to compose types graphs?
43 // If not, we need to introduce that!
44 using GFluidModel = ade::TypedGraph
45 < cv::gimpl::FluidUnit
46 , cv::gimpl::FluidData
48 , cv::gimpl::FluidUseOwnBorderBuffer
51 // FIXME: Same issue with Typed and ConstTyped
52 using GConstFluidModel = ade::ConstTypedGraph
53 < cv::gimpl::FluidUnit
54 , cv::gimpl::FluidData
56 , cv::gimpl::FluidUseOwnBorderBuffer
59 // FluidBackend middle-layer implementation ////////////////////////////////////
62 class GFluidBackendImpl final: public cv::gapi::GBackend::Priv
64 virtual void unpackKernel(ade::Graph &graph,
65 const ade::NodeHandle &op_node,
66 const cv::GKernelImpl &impl) override
68 GFluidModel fm(graph);
69 auto fluid_impl = cv::util::any_cast<cv::GFluidKernel>(impl.opaque);
70 fm.metadata(op_node).set(cv::gimpl::FluidUnit{fluid_impl, {}, 0, 0, 0.0});
73 virtual EPtr compile(const ade::Graph &graph,
74 const cv::GCompileArgs &args,
75 const std::vector<ade::NodeHandle> &nodes) const override
77 using namespace cv::gimpl;
78 GModel::ConstGraph g(graph);
79 auto isl_graph = g.metadata().get<IslandModel>().model;
80 GIslandModel::Graph gim(*isl_graph);
82 const auto num_islands = std::count_if
83 (gim.nodes().begin(), gim.nodes().end(),
84 [&](const ade::NodeHandle &nh) {
85 return gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND;
88 const auto out_rois = cv::gimpl::getCompileArg<cv::GFluidOutputRois>(args);
89 if (num_islands > 1 && out_rois.has_value())
90 cv::util::throw_error(std::logic_error("GFluidOutputRois feature supports only one-island graphs"));
92 auto rois = out_rois.value_or(cv::GFluidOutputRois());
93 return EPtr{new cv::gimpl::GFluidExecutable(graph, nodes, std::move(rois.rois))};
96 virtual void addBackendPasses(ade::ExecutionEngineSetupContext &ectx) override;
101 cv::gapi::GBackend cv::gapi::fluid::backend()
103 static cv::gapi::GBackend this_backend(std::make_shared<GFluidBackendImpl>());
107 // FluidAgent implementation ///////////////////////////////////////////////////
109 namespace cv { namespace gimpl {
112 FluidMapper(double ratio, int lpi) : m_ratio(ratio), m_lpi(lpi) {}
113 virtual ~FluidMapper() = default;
114 virtual int firstWindow(int outCoord, int lpi) const = 0;
115 virtual std::pair<int,int> linesReadAndNextWindow(int outCoord, int lpi) const = 0;
118 double m_ratio = 0.0;
122 struct FluidDownscaleMapper : public FluidMapper
124 virtual int firstWindow(int outCoord, int lpi) const override;
125 virtual std::pair<int,int> linesReadAndNextWindow(int outCoord, int lpi) const override;
126 using FluidMapper::FluidMapper;
129 struct FluidUpscaleMapper : public FluidMapper
131 virtual int firstWindow(int outCoord, int lpi) const override;
132 virtual std::pair<int,int> linesReadAndNextWindow(int outCoord, int lpi) const override;
133 FluidUpscaleMapper(double ratio, int lpi, int inHeight) : FluidMapper(ratio, lpi), m_inHeight(inHeight) {}
138 struct FluidFilterAgent : public FluidAgent
141 virtual int firstWindow() const override;
142 virtual std::pair<int,int> linesReadAndnextWindow() const override;
143 virtual void setRatio(double) override { /* nothing */ }
145 using FluidAgent::FluidAgent;
148 struct FluidResizeAgent : public FluidAgent
151 virtual int firstWindow() const override;
152 virtual std::pair<int,int> linesReadAndnextWindow() const override;
153 virtual void setRatio(double ratio) override;
155 std::unique_ptr<FluidMapper> m_mapper;
157 using FluidAgent::FluidAgent;
159 }} // namespace cv::gimpl
161 cv::gimpl::FluidAgent::FluidAgent(const ade::Graph &g, ade::NodeHandle nh)
162 : k(GConstFluidModel(g).metadata(nh).get<FluidUnit>().k) // init(0)
163 , op_handle(nh) // init(1)
164 , op_name(GModel::ConstGraph(g).metadata(nh).get<Op>().k.name) // init(2)
168 GModel::ConstGraph cm(g);
169 for (auto out_data : nh->outNodes())
171 const auto &d = cm.metadata(out_data).get<Data>();
172 cv::GMatDesc d_meta = cv::util::get<cv::GMatDesc>(d.meta);
173 out_w.insert(d_meta.size.width);
174 out_h.insert(d_meta.size.height);
177 // Different output sizes are not supported
178 GAPI_Assert(out_w.size() == 1 && out_h.size() == 1);
181 void cv::gimpl::FluidAgent::reset()
185 auto lines = firstWindow();
186 for (auto &v : in_views)
190 v.priv().reset(lines);
196 static int calcGcd (int n1, int n2)
198 return (n2 == 0) ? n1 : calcGcd (n2, n1 % n2);
201 // This is an empiric formula and this is not 100% guaranteed
202 // that it produces correct results in all possible cases
204 // prove correctness or switch to some trusted method
206 // When performing resize input/output pixels form a cyclic
207 // pattern where inH/gcd input pixels are mapped to outH/gcd
208 // output pixels (pattern repeats gcd times).
210 // Output pixel can partually cover some of the input pixels.
211 // There are 3 possible cases:
213 // :___ ___: :___ _:_ ___: :___ __: ___ :__ ___:
214 // |___|___| |___|_:_|___| |___|__:|___|:__|___|
217 // 1) No partial coverage, max window = scaleFactor;
218 // 2) Partial coverage occurs on the one side of the output pixel,
219 // max window = scaleFactor + 1;
220 // 3) Partial coverage occurs at both sides of the output pixel,
221 // max window = scaleFactor + 2;
223 // Type of the coverage is determined by remainder of
224 // inPeriodH/outPeriodH division, but it's an heuristic
225 // (howbeit didn't found the proof of the opposite so far).
227 static int calcResizeWindow(int inH, int outH)
229 GAPI_Assert(inH >= outH);
230 auto gcd = calcGcd(inH, outH);
231 int inPeriodH = inH/gcd;
232 int outPeriodH = outH/gcd;
233 int scaleFactor = inPeriodH / outPeriodH;
235 switch ((inPeriodH) % (outPeriodH))
237 case 0: return scaleFactor; break;
238 case 1: return scaleFactor + 1; break;
239 default: return scaleFactor + 2;
243 static int maxLineConsumption(const cv::GFluidKernel& k, int inH, int outH, int lpi)
247 case cv::GFluidKernel::Kind::Filter: return k.m_window + lpi - 1; break;
248 case cv::GFluidKernel::Kind::Resize:
253 // This is a suboptimal value, can be reduced
254 return calcResizeWindow(inH, outH) * lpi;
259 // This is a suboptimal value, can be reduced
260 return (inH == 1) ? 1 : 2 + lpi - 1;
263 default: GAPI_Assert(false); return 0;
267 static int borderSize(const cv::GFluidKernel& k)
271 case cv::GFluidKernel::Kind::Filter: return (k.m_window - 1) / 2; break;
272 // Resize never reads from border pixels
273 case cv::GFluidKernel::Kind::Resize: return 0; break;
274 default: GAPI_Assert(false); return 0;
278 inline double inCoord(int outIdx, double ratio)
280 return outIdx * ratio;
283 inline int windowStart(int outIdx, double ratio)
285 return static_cast<int>(inCoord(outIdx, ratio) + 1e-3);
288 inline int windowEnd(int outIdx, double ratio)
290 return static_cast<int>(std::ceil(inCoord(outIdx + 1, ratio) - 1e-3));
293 inline double inCoordUpscale(int outCoord, double ratio)
295 // Calculate the projection of output pixel's center
296 return (outCoord + 0.5) * ratio - 0.5;
299 inline int upscaleWindowStart(int outCoord, double ratio)
301 int start = static_cast<int>(inCoordUpscale(outCoord, ratio));
302 GAPI_DbgAssert(start >= 0);
306 inline int upscaleWindowEnd(int outCoord, double ratio, int inSz)
308 int end = static_cast<int>(std::ceil(inCoordUpscale(outCoord, ratio)) + 1);
315 } // anonymous namespace
317 int cv::gimpl::FluidDownscaleMapper::firstWindow(int outCoord, int lpi) const
319 return windowEnd(outCoord + lpi - 1, m_ratio) - windowStart(outCoord, m_ratio);
322 std::pair<int,int> cv::gimpl::FluidDownscaleMapper::linesReadAndNextWindow(int outCoord, int lpi) const
324 auto nextStartIdx = outCoord + 1 + m_lpi - 1;
325 auto nextEndIdx = nextStartIdx + lpi - 1;
327 auto currStart = windowStart(outCoord, m_ratio);
328 auto nextStart = windowStart(nextStartIdx, m_ratio);
329 auto nextEnd = windowEnd(nextEndIdx, m_ratio);
331 auto lines_read = nextStart - currStart;
332 auto next_window = nextEnd - nextStart;
334 return std::make_pair(lines_read, next_window);
337 int cv::gimpl::FluidUpscaleMapper::firstWindow(int outCoord, int lpi) const
339 return upscaleWindowEnd(outCoord + lpi - 1, m_ratio, m_inHeight) - upscaleWindowStart(outCoord, m_ratio);
342 std::pair<int,int> cv::gimpl::FluidUpscaleMapper::linesReadAndNextWindow(int outCoord, int lpi) const
344 auto nextStartIdx = outCoord + 1 + m_lpi - 1;
345 auto nextEndIdx = nextStartIdx + lpi - 1;
347 auto currStart = upscaleWindowStart(outCoord, m_ratio);
348 auto nextStart = upscaleWindowStart(nextStartIdx, m_ratio);
349 auto nextEnd = upscaleWindowEnd(nextEndIdx, m_ratio, m_inHeight);
351 auto lines_read = nextStart - currStart;
352 auto next_window = nextEnd - nextStart;
354 return std::make_pair(lines_read, next_window);
357 int cv::gimpl::FluidFilterAgent::firstWindow() const
359 return k.m_window + k.m_lpi - 1;
362 std::pair<int,int> cv::gimpl::FluidFilterAgent::linesReadAndnextWindow() const
364 int lpi = std::min(k.m_lpi, m_outputLines - m_producedLines - k.m_lpi);
365 return std::make_pair(k.m_lpi, k.m_window - 1 + lpi);
368 int cv::gimpl::FluidResizeAgent::firstWindow() const
370 auto outIdx = out_buffers[0]->priv().y();
371 auto lpi = std::min(m_outputLines - m_producedLines, k.m_lpi);
372 return m_mapper->firstWindow(outIdx, lpi);
375 std::pair<int,int> cv::gimpl::FluidResizeAgent::linesReadAndnextWindow() const
377 auto outIdx = out_buffers[0]->priv().y();
378 auto lpi = std::min(m_outputLines - m_producedLines - k.m_lpi, k.m_lpi);
379 return m_mapper->linesReadAndNextWindow(outIdx, lpi);
382 void cv::gimpl::FluidResizeAgent::setRatio(double ratio)
386 m_mapper.reset(new FluidDownscaleMapper(ratio, k.m_lpi));
390 m_mapper.reset(new FluidUpscaleMapper(ratio, k.m_lpi, in_views[0].meta().size.height));
394 bool cv::gimpl::FluidAgent::canRead() const
396 // An agent can work if every input buffer have enough data to start
397 for (const auto& in_view : in_views)
401 if (!in_view.ready())
408 bool cv::gimpl::FluidAgent::canWrite() const
410 // An agent can work if there is space to write in its output
412 GAPI_DbgAssert(!out_buffers.empty());
413 auto out_begin = out_buffers.begin();
414 auto out_end = out_buffers.end();
415 if (k.m_scratch) out_end--;
416 for (auto it = out_begin; it != out_end; ++it)
418 if ((*it)->priv().full())
426 bool cv::gimpl::FluidAgent::canWork() const
428 return canRead() && canWrite();
431 void cv::gimpl::FluidAgent::doWork()
433 GAPI_DbgAssert(m_outputLines > m_producedLines);
434 for (auto& in_view : in_views)
436 if (in_view) in_view.priv().prepareToRead();
439 k.m_f(in_args, out_buffers);
441 for (auto& in_view : in_views)
445 auto pair = linesReadAndnextWindow();
446 in_view.priv().readDone(pair.first, pair.second);
450 for (auto out_buf : out_buffers)
452 out_buf->priv().writeDone();
453 // FIXME WARNING: Scratch buffers rotated here too!
456 m_producedLines += k.m_lpi;
459 bool cv::gimpl::FluidAgent::done() const
461 // m_producedLines is a multiple of LPI, while original
462 // height may be not.
463 return m_producedLines >= m_outputLines;
466 void cv::gimpl::FluidAgent::debug(std::ostream &os)
468 os << "Fluid Agent " << std::hex << this
469 << " (" << op_name << ") --"
470 << " canWork=" << std::boolalpha << canWork()
471 << " canRead=" << std::boolalpha << canRead()
472 << " canWrite=" << std::boolalpha << canWrite()
473 << " done=" << done()
474 << " lines=" << std::dec << m_producedLines << "/" << m_outputLines
476 for (auto out_buf : out_buffers)
480 std::cout << "}}" << std::endl;
483 // GCPUExcecutable implementation //////////////////////////////////////////////
485 void cv::gimpl::GFluidExecutable::initBufferRois(std::vector<int>& readStarts,
486 std::vector<cv::gapi::own::Rect>& rois,
487 const std::vector<cv::gapi::own::Rect>& out_rois)
489 GConstFluidModel fg(m_g);
490 auto proto = m_gm.metadata().get<Protocol>();
491 std::stack<ade::NodeHandle> nodesToVisit;
494 // There is possible case when user pass the vector full of default Rect{}-s,
495 // Can be diagnosed and handled appropriately
496 if (proto.outputs.size() != out_rois.size())
498 GAPI_Assert(out_rois.size() == 0);
499 // No inference required, buffers will obtain roi from meta
503 // First, initialize rois for output nodes, add them to traversal stack
504 for (const auto& it : ade::util::indexed(proto.out_nhs))
506 const auto idx = ade::util::index(it);
507 const auto nh = ade::util::value(it);
509 const auto &d = m_gm.metadata(nh).get<Data>();
511 // This is not our output
512 if (m_id_map.count(d.rc) == 0)
517 if (d.shape == GShape::GMAT)
519 auto desc = util::get<GMatDesc>(d.meta);
520 auto id = m_id_map.at(d.rc);
523 if (out_rois[idx] == gapi::own::Rect{})
525 rois[id] = gapi::own::Rect{ 0, 0, desc.size.width, desc.size.height };
529 // Only slices are supported at the moment
530 GAPI_Assert(out_rois[idx].x == 0);
531 GAPI_Assert(out_rois[idx].width == desc.size.width);
532 rois[id] = out_rois[idx];
535 nodesToVisit.push(nh);
539 // Perform a wide search from each of the output nodes
540 // And extend roi of buffers by border_size
541 // Each node can be visited multiple times
542 // (if node has been already visited, the check that inferred rois are the same is performed)
543 while (!nodesToVisit.empty())
545 const auto startNode = nodesToVisit.top();
548 if (!startNode->inNodes().empty())
550 GAPI_Assert(startNode->inNodes().size() == 1);
551 const auto& oh = startNode->inNodes().front();
553 const auto& data = m_gm.metadata(startNode).get<Data>();
554 // only GMats participate in the process so it's valid to obtain GMatDesc
555 const auto& meta = util::get<GMatDesc>(data.meta);
557 for (const auto& inNode : oh->inNodes())
559 const auto& in_data = m_gm.metadata(inNode).get<Data>();
561 if (in_data.shape == GShape::GMAT && fg.metadata(inNode).contains<FluidData>())
563 const auto& in_meta = util::get<GMatDesc>(in_data.meta);
564 const auto& fd = fg.metadata(inNode).get<FluidData>();
566 auto adjFilterRoi = [](cv::gapi::own::Rect produced, int b, int max_height) {
567 // Extend with border roi which should be produced, crop to logical image size
568 cv::gapi::own::Rect roi = {produced.x, produced.y - b, produced.width, produced.height + 2*b};
569 cv::gapi::own::Rect fullImg{ 0, 0, produced.width, max_height };
570 return roi & fullImg;
573 auto adjResizeRoi = [](cv::gapi::own::Rect produced, cv::gapi::own::Size inSz, cv::gapi::own::Size outSz) {
574 auto map = [](int outCoord, int producedSz, int inSize, int outSize) {
575 double ratio = (double)inSize / outSize;
579 w0 = windowStart(outCoord, ratio);
580 w1 = windowEnd (outCoord + producedSz - 1, ratio);
584 w0 = upscaleWindowStart(outCoord, ratio);
585 w1 = upscaleWindowEnd(outCoord + producedSz - 1, ratio, inSize);
587 return std::make_pair(w0, w1);
590 auto mapY = map(produced.y, produced.height, inSz.height, outSz.height);
591 auto y0 = mapY.first;
592 auto y1 = mapY.second;
594 auto mapX = map(produced.x, produced.width, inSz.width, outSz.width);
595 auto x0 = mapX.first;
596 auto x1 = mapX.second;
598 cv::gapi::own::Rect roi = {x0, y0, x1 - x0, y1 - y0};
602 cv::gapi::own::Rect produced = rois[m_id_map.at(data.rc)];
604 cv::gapi::own::Rect resized;
605 switch (fg.metadata(oh).get<FluidUnit>().k.m_kind)
607 case GFluidKernel::Kind::Filter: resized = produced; break;
608 case GFluidKernel::Kind::Resize: resized = adjResizeRoi(produced, in_meta.size, meta.size); break;
609 default: GAPI_Assert(false);
612 int readStart = resized.y;
613 cv::gapi::own::Rect roi = adjFilterRoi(resized, fd.border_size, in_meta.size.height);
615 auto in_id = m_id_map.at(in_data.rc);
616 if (rois[in_id] == cv::gapi::own::Rect{})
618 readStarts[in_id] = readStart;
620 // Continue traverse on internal (w.r.t Island) data nodes only.
621 if (fd.internal) nodesToVisit.push(inNode);
625 GAPI_Assert(readStarts[in_id] == readStart);
626 GAPI_Assert(rois[in_id] == roi);
628 } // if (in_data.shape == GShape::GMAT)
629 } // for (const auto& inNode : oh->inNodes())
630 } // if (!startNode->inNodes().empty())
631 } // while (!nodesToVisit.empty())
634 cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
635 const std::vector<ade::NodeHandle> &nodes,
636 const std::vector<cv::gapi::own::Rect> &outputRois)
639 GConstFluidModel fg(m_g);
641 // Initialize vector of data buffers, build list of operations
642 // FIXME: There _must_ be a better way to [query] count number of DATA nodes
643 std::size_t mat_count = 0;
644 std::size_t last_agent = 0;
646 auto grab_mat_nh = [&](ade::NodeHandle nh) {
647 auto rc = m_gm.metadata(nh).get<Data>().rc;
648 if (m_id_map.count(rc) == 0)
650 m_all_gmat_ids[mat_count] = nh;
651 m_id_map[rc] = mat_count++;
655 for (const auto &nh : nodes)
657 switch (m_gm.metadata(nh).get<NodeType>().t)
660 if (m_gm.metadata(nh).get<Data>().shape == GShape::GMAT)
666 const auto& fu = fg.metadata(nh).get<FluidUnit>();
669 case GFluidKernel::Kind::Filter: m_agents.emplace_back(new FluidFilterAgent(m_g, nh)); break;
670 case GFluidKernel::Kind::Resize: m_agents.emplace_back(new FluidResizeAgent(m_g, nh)); break;
671 default: GAPI_Assert(false);
673 // NB.: in_buffer_ids size is equal to Arguments size, not Edges size!!!
674 m_agents.back()->in_buffer_ids.resize(m_gm.metadata(nh).get<Op>().args.size(), -1);
675 for (auto eh : nh->inEdges())
677 // FIXME Only GMats are currently supported (which can be represented
679 if (m_gm.metadata(eh->srcNode()).get<Data>().shape == GShape::GMAT)
681 const auto in_port = m_gm.metadata(eh).get<Input>().port;
682 const int in_buf = m_gm.metadata(eh->srcNode()).get<Data>().rc;
684 m_agents.back()->in_buffer_ids[in_port] = in_buf;
685 grab_mat_nh(eh->srcNode());
688 // FIXME: Assumption that all operation outputs MUST be connected
689 m_agents.back()->out_buffer_ids.resize(nh->outEdges().size(), -1);
690 for (auto eh : nh->outEdges())
692 const auto& data = m_gm.metadata(eh->dstNode()).get<Data>();
693 const auto out_port = m_gm.metadata(eh).get<Output>().port;
694 const int out_buf = data.rc;
696 m_agents.back()->out_buffer_ids[out_port] = out_buf;
697 if (data.shape == GShape::GMAT) grab_mat_nh(eh->dstNode());
700 m_scratch_users.push_back(last_agent);
704 default: GAPI_Assert(false);
708 // Check that IDs form a continiuos set (important for further indexing)
709 GAPI_Assert(m_id_map.size() > 0);
710 GAPI_Assert(m_id_map.size() == static_cast<size_t>(mat_count));
712 // Actually initialize Fluid buffers
713 GAPI_LOG_INFO(NULL, "Initializing " << mat_count << " fluid buffer(s)" << std::endl);
714 m_num_int_buffers = mat_count;
715 const std::size_t num_scratch = m_scratch_users.size();
716 m_buffers.resize(m_num_int_buffers + num_scratch);
718 // After buffers are allocated, repack: ...
719 for (auto &agent : m_agents)
721 // a. Agent input parameters with View pointers (creating Views btw)
722 const auto &op = m_gm.metadata(agent->op_handle).get<Op>();
723 const auto &fu = fg.metadata(agent->op_handle).get<FluidUnit>();
724 agent->in_args.resize(op.args.size());
725 agent->in_views.resize(op.args.size());
726 for (auto it : ade::util::indexed(ade::util::toRange(agent->in_buffer_ids)))
728 auto in_idx = ade::util::index(it);
729 auto buf_idx = ade::util::value(it);
733 // IF there is input buffer, register a view (every unique
734 // reader has its own), and store it in agent Args
735 gapi::fluid::Buffer &buffer = m_buffers.at(m_id_map.at(buf_idx));
737 auto inEdge = GModel::getInEdgeByPort(m_g, agent->op_handle, in_idx);
738 auto ownStorage = fg.metadata(inEdge).get<FluidUseOwnBorderBuffer>().use;
740 gapi::fluid::View view = buffer.mkView(fu.border_size, ownStorage);
741 // NB: It is safe to keep ptr as view lifetime is buffer lifetime
742 agent->in_views[in_idx] = view;
743 agent->in_args[in_idx] = GArg(view);
747 // Copy(FIXME!) original args as is
748 agent->in_args[in_idx] = op.args[in_idx];
752 // b. Agent output parameters with Buffer pointers.
753 agent->out_buffers.resize(agent->op_handle->outEdges().size(), nullptr);
754 for (auto it : ade::util::indexed(ade::util::toRange(agent->out_buffer_ids)))
756 auto out_idx = ade::util::index(it);
757 auto buf_idx = m_id_map.at(ade::util::value(it));
758 agent->out_buffers.at(out_idx) = &m_buffers.at(buf_idx);
762 // After parameters are there, initialize scratch buffers
765 GAPI_LOG_INFO(NULL, "Initializing " << num_scratch << " scratch buffer(s)" << std::endl);
766 std::size_t last_scratch_id = 0;
768 for (auto i : m_scratch_users)
770 auto &agent = m_agents.at(i);
771 GAPI_Assert(agent->k.m_scratch);
772 const std::size_t new_scratch_idx = m_num_int_buffers + last_scratch_id;
773 agent->out_buffers.emplace_back(&m_buffers[new_scratch_idx]);
778 makeReshape(outputRois);
780 std::size_t total_size = 0;
781 for (const auto &i : ade::util::indexed(m_buffers))
783 // Check that all internal and scratch buffers are allocated
784 const auto idx = ade::util::index(i);
785 const auto b = ade::util::value(i);
786 if (idx >= m_num_int_buffers ||
787 fg.metadata(m_all_gmat_ids[idx]).get<FluidData>().internal == true)
789 GAPI_Assert(b.priv().size() > 0);
792 // Buffers which will be bound to real images may have size of 0 at this moment
793 // (There can be non-zero sized const border buffer allocated in such buffers)
794 total_size += b.priv().size();
796 GAPI_LOG_INFO(NULL, "Internal buffers: " << std::fixed << std::setprecision(2) << static_cast<float>(total_size)/1024 << " KB\n");
801 void resetFluidData(ade::Graph& graph)
803 using namespace cv::gimpl;
804 GModel::Graph g(graph);
805 GFluidModel fg(graph);
806 for (const auto node : g.nodes())
808 if (g.metadata(node).get<NodeType>().t == NodeType::DATA)
810 auto& fd = fg.metadata(node).get<FluidData>();
813 fd.max_consumption = 0;
818 void initFluidUnits(ade::Graph& graph)
820 using namespace cv::gimpl;
821 GModel::Graph g(graph);
822 GFluidModel fg(graph);
824 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
825 for (auto node : sorted)
827 if (fg.metadata(node).contains<FluidUnit>())
829 std::set<int> in_hs, out_ws, out_hs;
831 for (const auto& in : node->inNodes())
833 const auto& d = g.metadata(in).get<Data>();
834 if (d.shape == cv::GShape::GMAT)
836 const auto& meta = cv::util::get<cv::GMatDesc>(d.meta);
837 in_hs.insert(meta.size.height);
841 for (const auto& out : node->outNodes())
843 const auto& d = g.metadata(out).get<Data>();
844 if (d.shape == cv::GShape::GMAT)
846 const auto& meta = cv::util::get<cv::GMatDesc>(d.meta);
847 out_ws.insert(meta.size.width);
848 out_hs.insert(meta.size.height);
852 GAPI_Assert(in_hs.size() == 1 && out_ws.size() == 1 && out_hs.size() == 1);
854 auto in_h = *in_hs .cbegin();
855 auto out_h = *out_hs.cbegin();
857 auto &fu = fg.metadata(node).get<FluidUnit>();
858 fu.ratio = (double)in_h / out_h;
860 int line_consumption = maxLineConsumption(fu.k, in_h, out_h, fu.k.m_lpi);
861 int border_size = borderSize(fu.k);
863 fu.border_size = border_size;
864 fu.line_consumption = line_consumption;
866 GModel::log(g, node, "Line consumption: " + std::to_string(fu.line_consumption));
867 GModel::log(g, node, "Border size: " + std::to_string(fu.border_size));
873 // Split into initLineConsumption and initBorderSizes,
874 // call only consumption related stuff during reshape
875 void initLineConsumption(ade::Graph& graph)
877 using namespace cv::gimpl;
878 GModel::Graph g(graph);
879 GFluidModel fg(graph);
881 for (const auto &node : g.nodes())
883 if (fg.metadata(node).contains<FluidUnit>())
885 const auto &fu = fg.metadata(node).get<FluidUnit>();
887 for (const auto &in_data_node : node->inNodes())
889 auto &fd = fg.metadata(in_data_node).get<FluidData>();
891 // Update (not Set) fields here since a single data node may be
892 // accessed by multiple consumers
893 fd.max_consumption = std::max(fu.line_consumption, fd.max_consumption);
894 fd.border_size = std::max(fu.border_size, fd.border_size);
896 GModel::log(g, in_data_node, "Line consumption: " + std::to_string(fd.max_consumption)
897 + " (upd by " + std::to_string(fu.line_consumption) + ")", node);
898 GModel::log(g, in_data_node, "Border size: " + std::to_string(fd.border_size), node);
904 void calcLatency(ade::Graph& graph)
906 using namespace cv::gimpl;
907 GModel::Graph g(graph);
908 GFluidModel fg(graph);
910 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
911 for (const auto &node : sorted)
913 if (fg.metadata(node).contains<FluidUnit>())
915 const auto &fu = fg.metadata(node).get<FluidUnit>();
917 const int own_latency = fu.line_consumption - fu.border_size;
918 GModel::log(g, node, "LPI: " + std::to_string(fu.k.m_lpi));
920 // Output latency is max(input_latency) + own_latency
922 for (const auto &in_data_node : node->inNodes())
924 // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
925 in_latency = std::max(in_latency, fg.metadata(in_data_node).get<FluidData>().latency);
927 const int out_latency = in_latency + own_latency;
929 for (const auto &out_data_node : node->outNodes())
931 // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
932 auto &fd = fg.metadata(out_data_node).get<FluidData>();
933 // If fluid node is external, it will be bound to a real image without
934 // fluid buffer allocation, so set its latency to 0 not to confuse later latency propagation.
935 // Latency is used in fluid buffer allocation process and is not used by the scheduler
936 // so latency doesn't affect the execution and setting it to 0 is legal
937 fd.latency = fd.internal ? out_latency : 0;
938 fd.lpi_write = fu.k.m_lpi;
939 GModel::log(g, out_data_node, "Latency: " + std::to_string(fd.latency));
945 void calcSkew(ade::Graph& graph)
947 using namespace cv::gimpl;
948 GModel::Graph g(graph);
949 GFluidModel fg(graph);
951 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
952 for (const auto &node : sorted)
954 if (fg.metadata(node).contains<FluidUnit>())
957 for (const auto &in_data_node : node->inNodes())
959 // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
960 max_latency = std::max(max_latency, fg.metadata(in_data_node).get<FluidData>().latency);
962 for (const auto &in_data_node : node->inNodes())
964 // FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
965 auto &fd = fg.metadata(in_data_node).get<FluidData>();
967 // Update (not Set) fields here since a single data node may be
968 // accessed by multiple consumers
969 fd.skew = std::max(fd.skew, max_latency - fd.latency);
971 GModel::log(g, in_data_node, "Skew: " + std::to_string(fd.skew), node);
978 void cv::gimpl::GFluidExecutable::makeReshape(const std::vector<gapi::own::Rect> &out_rois)
980 GConstFluidModel fg(m_g);
982 // Calculate rois for each fluid buffer
983 std::vector<int> readStarts(m_num_int_buffers);
984 std::vector<cv::gapi::own::Rect> rois(m_num_int_buffers);
985 initBufferRois(readStarts, rois, out_rois);
987 // NB: Allocate ALL buffer object at once, and avoid any further reallocations
988 // (since raw pointers-to-elements are taken)
989 for (const auto &it : m_all_gmat_ids)
993 const auto & d = m_gm.metadata(nh).get<Data>();
994 const auto &fd = fg.metadata(nh).get<FluidData>();
995 const auto meta = cv::util::get<GMatDesc>(d.meta);
997 m_buffers[id].priv().init(meta, fd.lpi_write, readStarts[id], rois[id]);
1000 // Introduce Storage::INTERNAL_GRAPH and Storage::INTERNAL_ISLAND?
1001 if (fd.internal == true)
1003 m_buffers[id].priv().allocate(fd.border, fd.border_size, fd.max_consumption, fd.skew);
1004 std::stringstream stream;
1005 m_buffers[id].debug(stream);
1006 GAPI_LOG_INFO(NULL, stream.str());
1010 // Allocate views, initialize agents
1011 for (auto &agent : m_agents)
1013 const auto &fu = fg.metadata(agent->op_handle).get<FluidUnit>();
1014 for (auto it : ade::util::indexed(ade::util::toRange(agent->in_buffer_ids)))
1016 auto in_idx = ade::util::index(it);
1017 auto buf_idx = ade::util::value(it);
1021 agent->in_views[in_idx].priv().allocate(fu.line_consumption, fu.border);
1025 agent->setRatio(fu.ratio);
1026 agent->m_outputLines = agent->out_buffers.front()->priv().outputLines();
1029 // Initialize scratch buffers
1030 if (m_scratch_users.size())
1032 for (auto i : m_scratch_users)
1034 auto &agent = m_agents.at(i);
1035 GAPI_Assert(agent->k.m_scratch);
1037 // Trigger Scratch buffer initialization method
1038 agent->k.m_is(GModel::collectInputMeta(m_gm, agent->op_handle), agent->in_args, *agent->out_buffers.back());
1039 std::stringstream stream;
1040 agent->out_buffers.back()->debug(stream);
1041 GAPI_LOG_INFO(NULL, stream.str());
1045 // FIXME: calculate the size (lpi * ..)
1047 m_script.reserve(10000);
1050 void cv::gimpl::GFluidExecutable::reshape(ade::Graph &g, const GCompileArgs &args)
1052 // FIXME: Probably this needs to be integrated into common pass re-run routine
1053 // Backends may want to mark with passes to re-run on reshape and framework could
1054 // do it system-wide (without need in every backend handling reshape() directly).
1055 // This design needs to be analyzed for implementation.
1058 initLineConsumption(g);
1061 const auto out_rois = cv::gimpl::getCompileArg<cv::GFluidOutputRois>(args).value_or(cv::GFluidOutputRois());
1062 makeReshape(out_rois.rois);
1065 // FIXME: Document what it does
1066 void cv::gimpl::GFluidExecutable::bindInArg(const cv::gimpl::RcDesc &rc, const GRunArg &arg)
1070 case GShape::GMAT: m_buffers[m_id_map.at(rc.id)].priv().bindTo(util::get<cv::gapi::own::Mat>(arg), true); break;
1071 case GShape::GSCALAR: m_res.slot<cv::gapi::own::Scalar>()[rc.id] = util::get<cv::gapi::own::Scalar>(arg); break;
1072 default: util::throw_error(std::logic_error("Unsupported GShape type"));
1076 void cv::gimpl::GFluidExecutable::bindOutArg(const cv::gimpl::RcDesc &rc, const GRunArgP &arg)
1078 // Only GMat is supported as return type
1083 cv::GMatDesc desc = m_buffers[m_id_map.at(rc.id)].meta();
1084 auto &outMat = *util::get<cv::gapi::own::Mat*>(arg);
1085 GAPI_Assert(outMat.data != nullptr);
1086 GAPI_Assert(descr_of(outMat) == desc && "Output argument was not preallocated as it should be ?");
1087 m_buffers[m_id_map.at(rc.id)].priv().bindTo(outMat, false);
1090 default: util::throw_error(std::logic_error("Unsupported return GShape type"));
1094 void cv::gimpl::GFluidExecutable::packArg(cv::GArg &in_arg, const cv::GArg &op_arg)
1096 GAPI_Assert(op_arg.kind != cv::detail::ArgKind::GMAT
1097 && op_arg.kind != cv::detail::ArgKind::GSCALAR);
1099 if (op_arg.kind == cv::detail::ArgKind::GOBJREF)
1101 const cv::gimpl::RcDesc &ref = op_arg.get<cv::gimpl::RcDesc>();
1102 if (ref.shape == GShape::GSCALAR)
1104 in_arg = GArg(m_res.slot<cv::gapi::own::Scalar>()[ref.id]);
1109 void cv::gimpl::GFluidExecutable::run(std::vector<InObj> &&input_objs,
1110 std::vector<OutObj> &&output_objs)
1112 // Bind input buffers from parameters
1113 for (auto& it : input_objs) bindInArg(it.first, it.second);
1114 for (auto& it : output_objs) bindOutArg(it.first, it.second);
1116 // Reset Buffers and Agents state before we go
1117 for (auto &buffer : m_buffers)
1118 buffer.priv().reset();
1120 for (auto &agent : m_agents)
1123 // Pass input cv::Scalar's to agent argument
1124 const auto& op = m_gm.metadata(agent->op_handle).get<Op>();
1125 for (const auto& it : ade::util::indexed(op.args))
1127 const auto& arg = ade::util::value(it);
1128 packArg(agent->in_args[ade::util::index(it)], arg);
1132 // Explicitly reset Scratch buffers, if any
1133 for (auto scratch_i : m_scratch_users)
1135 auto &agent = m_agents[scratch_i];
1136 GAPI_DbgAssert(agent->k.m_scratch);
1137 agent->k.m_rs(*agent->out_buffers.back());
1140 // Now start executing our stuff!
1141 // Fluid execution is:
1142 // - run through list of Agents from Left to Right
1143 // - for every Agent:
1144 // - if all input Buffers have enough data to fulfill
1145 // Agent's window - trigger Agent
1146 // - on trigger, Agent takes all input lines from input buffers
1147 // and produces a single output line
1148 // - once Agent finishes, input buffers get "readDone()",
1149 // and output buffers get "writeDone()"
1150 // - if there's not enough data, Agent is skipped
1153 if (m_script.empty())
1155 bool complete = true;
1158 bool work_done=false;
1159 for (auto &agent : m_agents)
1161 // agent->debug(std::cout);
1164 if (agent->canWork())
1166 agent->doWork(); work_done=true;
1167 m_script.push_back(agent.get());
1169 if (!agent->done()) complete = false;
1172 GAPI_Assert(work_done || complete);
1173 } while (!complete); // FIXME: number of iterations can be calculated statically
1177 for (auto &agent : m_script)
1184 // FIXME: these passes operate on graph global level!!!
1185 // Need to fix this for heterogeneous (island-based) processing
1186 void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
1188 using namespace cv::gimpl;
1190 // FIXME: all passes were moved to "exec" stage since Fluid
1191 // should check Islands configuration first (which is now quite
1192 // limited), and only then continue with all other passes.
1194 // The passes/stages API must be streamlined!
1195 ectx.addPass("exec", "init_fluid_data", [](ade::passes::PassContext &ctx)
1197 GModel::Graph g(ctx.graph);
1198 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1201 auto isl_graph = g.metadata().get<IslandModel>().model;
1202 GIslandModel::Graph gim(*isl_graph);
1204 GFluidModel fg(ctx.graph);
1206 const auto setFluidData = [&](ade::NodeHandle nh, bool internal) {
1208 fd.internal = internal;
1209 fg.metadata(nh).set(fd);
1212 for (const auto& nh : gim.nodes())
1214 switch (gim.metadata(nh).get<NodeKind>().k)
1216 case NodeKind::ISLAND:
1218 const auto isl = gim.metadata(nh).get<FusedIsland>().object;
1219 if (isl->backend() == cv::gapi::fluid::backend())
1221 // Add FluidData to all data nodes inside island,
1222 // set internal = true if node is not a slot in terms of higher-level GIslandModel
1223 for (const auto node : isl->contents())
1225 if (g.metadata(node).get<NodeType>().t == NodeType::DATA &&
1226 !fg.metadata(node).contains<FluidData>())
1227 setFluidData(node, true);
1229 } // if (fluid backend)
1230 } break; // case::ISLAND
1231 case NodeKind::SLOT:
1233 // add FluidData to slot if it's read/written by fluid
1234 // regardless if it is one fluid island (both writing to and reading from this object)
1235 // or two distinct islands (both fluid)
1236 auto isFluidIsland = [&](const ade::NodeHandle& node) {
1237 const auto isl = gim.metadata(node).get<FusedIsland>().object;
1238 return isl->backend() == cv::gapi::fluid::backend();
1241 if (ade::util::any_of(ade::util::chain(nh->inNodes(), nh->outNodes()), isFluidIsland))
1243 auto data_node = gim.metadata(nh).get<DataSlot>().original_data_node;
1244 setFluidData(data_node, false);
1246 } break; // case::SLOT
1247 default: GAPI_Assert(false);
1249 } // for (gim.nodes())
1252 // move to unpackKernel method
1253 // when https://gitlab-icv.inn.intel.com/G-API/g-api/merge_requests/66 is merged
1254 ectx.addPass("exec", "init_fluid_unit_borders", [](ade::passes::PassContext &ctx)
1256 GModel::Graph g(ctx.graph);
1257 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1260 GFluidModel fg(ctx.graph);
1262 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1263 for (auto node : sorted)
1265 if (fg.metadata(node).contains<FluidUnit>())
1267 // FIXME: check that op has only one data node on input
1268 auto &fu = fg.metadata(node).get<FluidUnit>();
1269 const auto &op = g.metadata(node).get<Op>();
1271 // Trigger user-defined "getBorder" callback
1272 fu.border = fu.k.m_b(GModel::collectInputMeta(fg, node), op.args);
1276 ectx.addPass("exec", "init_fluid_units", [](ade::passes::PassContext &ctx)
1278 GModel::Graph g(ctx.graph);
1279 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1282 initFluidUnits(ctx.graph);
1284 ectx.addPass("exec", "init_line_consumption", [](ade::passes::PassContext &ctx)
1286 GModel::Graph g(ctx.graph);
1287 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1290 initLineConsumption(ctx.graph);
1292 ectx.addPass("exec", "calc_latency", [](ade::passes::PassContext &ctx)
1294 GModel::Graph g(ctx.graph);
1295 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1298 calcLatency(ctx.graph);
1300 ectx.addPass("exec", "calc_skew", [](ade::passes::PassContext &ctx)
1302 GModel::Graph g(ctx.graph);
1303 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1306 calcSkew(ctx.graph);
1309 ectx.addPass("exec", "init_buffer_borders", [](ade::passes::PassContext &ctx)
1311 GModel::Graph g(ctx.graph);
1312 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1315 GFluidModel fg(ctx.graph);
1316 auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1317 for (auto node : sorted)
1319 if (fg.metadata(node).contains<FluidData>())
1321 auto &fd = fg.metadata(node).get<FluidData>();
1323 // Assign border stuff to FluidData
1325 // In/out data nodes are bound to user data directly,
1326 // so cannot be extended with a border
1327 if (fd.internal == true)
1329 // For now border of the buffer's storage is the border
1330 // of the first reader whose border size is the same.
1331 // FIXME: find more clever strategy of border picking
1332 // (it can be a border which is common for majority of the
1333 // readers, also we can calculate the number of lines which
1334 // will be copied by views on each iteration and base our choice
1335 // on this criteria)
1336 auto readers = node->outNodes();
1337 const auto &candidate = ade::util::find_if(readers, [&](ade::NodeHandle nh) {
1338 return fg.metadata(nh).contains<FluidUnit>() &&
1339 fg.metadata(nh).get<FluidUnit>().border_size == fd.border_size;
1342 GAPI_Assert(candidate != readers.end());
1344 const auto &fu = fg.metadata(*candidate).get<FluidUnit>();
1345 fd.border = fu.border;
1350 GModel::log(g, node, "Border type: " + std::to_string(fd.border->type), node);
1355 ectx.addPass("exec", "init_view_borders", [](ade::passes::PassContext &ctx)
1357 GModel::Graph g(ctx.graph);
1358 if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1361 GFluidModel fg(ctx.graph);
1362 for (auto node : g.nodes())
1364 if (fg.metadata(node).contains<FluidData>())
1366 auto &fd = fg.metadata(node).get<FluidData>();
1367 for (auto out_edge : node->outEdges())
1369 const auto dstNode = out_edge->dstNode();
1370 if (fg.metadata(dstNode).contains<FluidUnit>())
1372 const auto &fu = fg.metadata(dstNode).get<FluidUnit>();
1374 // There is no need in own storage for view if it's border is
1375 // the same as the buffer's (view can have equal or smaller border
1376 // size in this case)
1377 if (fu.border_size == 0 ||
1378 (fu.border && fd.border && (*fu.border == *fd.border)))
1380 GAPI_Assert(fu.border_size <= fd.border_size);
1381 fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{false});
1385 fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{true});
1386 GModel::log(g, out_edge, "OwnBufferStorage: true");