Path: blob/master/modules/gapi/src/backends/fluid/gfluidbackend.cpp
16344 views
// This file is part of OpenCV project.1// It is subject to the license terms in the LICENSE file found in the top-level directory2// of this distribution and at http://opencv.org/license.html.3//4// Copyright (C) 2018 Intel Corporation567#include "precomp.hpp"89#include <functional>10#include <iostream>11#include <iomanip> // std::fixed, std::setprecision12#include <unordered_set>13#include <stack>1415#include <ade/util/algorithm.hpp>16#include <ade/util/chain_range.hpp>17#include <ade/util/range.hpp>18#include <ade/util/zip_range.hpp>1920#include <ade/typed_graph.hpp>21#include <ade/execution_engine/execution_engine.hpp>2223#include "opencv2/gapi/gcommon.hpp"24#include "logger.hpp"2526#include "opencv2/gapi/own/convert.hpp"27#include "opencv2/gapi/gmat.hpp" //for version of descr_of28// PRIVATE STUFF!29#include "compiler/gobjref.hpp"30#include "compiler/gmodel.hpp"3132#include "backends/fluid/gfluidbuffer_priv.hpp"33#include "backends/fluid/gfluidbackend.hpp"34#include "backends/fluid/gfluidimgproc.hpp"35#include "backends/fluid/gfluidcore.hpp"3637#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!3839// 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 of41// new types?).42// Alternatively, is there a way to compose types graphs?43//44// If not, we need to introduce that!45using GFluidModel = ade::TypedGraph46< cv::gimpl::FluidUnit47, cv::gimpl::FluidData48, cv::gimpl::Protocol49, cv::gimpl::FluidUseOwnBorderBuffer50>;5152// FIXME: Same issue with Typed and ConstTyped53using GConstFluidModel = ade::ConstTypedGraph54< cv::gimpl::FluidUnit55, cv::gimpl::FluidData56, cv::gimpl::Protocol57, cv::gimpl::FluidUseOwnBorderBuffer58>;5960// FluidBackend middle-layer implementation ////////////////////////////////////61namespace62{63class GFluidBackendImpl final: public cv::gapi::GBackend::Priv64{65virtual void unpackKernel(ade::Graph &graph,66const ade::NodeHandle &op_node,67const cv::GKernelImpl &impl) override68{69GFluidModel fm(graph);70auto fluid_impl = cv::util::any_cast<cv::GFluidKernel>(impl.opaque);71fm.metadata(op_node).set(cv::gimpl::FluidUnit{fluid_impl, {}, 0, 0, 0.0});72}7374virtual EPtr compile(const ade::Graph &graph,75const cv::GCompileArgs &args,76const std::vector<ade::NodeHandle> &nodes) const override77{78using namespace cv::gimpl;79GModel::ConstGraph g(graph);80auto isl_graph = g.metadata().get<IslandModel>().model;81GIslandModel::Graph gim(*isl_graph);8283const auto num_islands = std::count_if84(gim.nodes().begin(), gim.nodes().end(),85[&](const ade::NodeHandle &nh) {86return gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND;87});8889const auto out_rois = cv::gimpl::getCompileArg<cv::GFluidOutputRois>(args);90if (num_islands > 1 && out_rois.has_value())91cv::util::throw_error(std::logic_error("GFluidOutputRois feature supports only one-island graphs"));9293auto rois = out_rois.value_or(cv::GFluidOutputRois());94return EPtr{new cv::gimpl::GFluidExecutable(graph, nodes, std::move(rois.rois))};95}9697virtual void addBackendPasses(ade::ExecutionEngineSetupContext &ectx) override;9899};100}101102cv::gapi::GBackend cv::gapi::fluid::backend()103{104static cv::gapi::GBackend this_backend(std::make_shared<GFluidBackendImpl>());105return this_backend;106}107108// FluidAgent implementation ///////////////////////////////////////////////////109110namespace cv { namespace gimpl {111struct FluidFilterAgent : public FluidAgent112{113private:114virtual int firstWindow() const override;115virtual int nextWindow() const override;116virtual int linesRead() const override;117public:118using FluidAgent::FluidAgent;119virtual void setInHeight(int) override { /* nothing */ }120};121122struct FluidResizeAgent : public FluidAgent123{124private:125virtual int firstWindow() const override;126virtual int nextWindow() const override;127virtual int linesRead() const override;128public:129using FluidAgent::FluidAgent;130virtual void setInHeight(int) override { /* nothing */ }131};132133struct FluidUpscaleAgent : public FluidAgent134{135private:136virtual int firstWindow() const override;137virtual int nextWindow() const override;138virtual int linesRead() const override;139140int m_inH;141public:142using FluidAgent::FluidAgent;143virtual void setInHeight(int h) override { m_inH = h; }144};145}} // namespace cv::gimpl146147cv::gimpl::FluidAgent::FluidAgent(const ade::Graph &g, ade::NodeHandle nh)148: k(GConstFluidModel(g).metadata(nh).get<FluidUnit>().k) // init(0)149, op_handle(nh) // init(1)150, op_name(GModel::ConstGraph(g).metadata(nh).get<Op>().k.name) // init(2)151{152std::set<int> out_w;153std::set<int> out_h;154GModel::ConstGraph cm(g);155for (auto out_data : nh->outNodes())156{157const auto &d = cm.metadata(out_data).get<Data>();158cv::GMatDesc d_meta = cv::util::get<cv::GMatDesc>(d.meta);159out_w.insert(d_meta.size.width);160out_h.insert(d_meta.size.height);161}162163// Different output sizes are not supported164GAPI_Assert(out_w.size() == 1 && out_h.size() == 1);165}166167void cv::gimpl::FluidAgent::reset()168{169m_producedLines = 0;170171auto lines = firstWindow();172for (auto &v : in_views)173{174if (v)175{176v.priv().reset(lines);177}178}179}180181namespace {182static int calcGcd (int n1, int n2)183{184return (n2 == 0) ? n1 : calcGcd (n2, n1 % n2);185}186187// This is an empiric formula and this is not 100% guaranteed188// that it produces correct results in all possible cases189// FIXME:190// prove correctness or switch to some trusted method191//192// When performing resize input/output pixels form a cyclic193// pattern where inH/gcd input pixels are mapped to outH/gcd194// output pixels (pattern repeats gcd times).195//196// Output pixel can partually cover some of the input pixels.197// There are 3 possible cases:198//199// :___ ___: :___ _:_ ___: :___ __: ___ :__ ___:200// |___|___| |___|_:_|___| |___|__:|___|:__|___|201// : : : : : : : : :202//203// 1) No partial coverage, max window = scaleFactor;204// 2) Partial coverage occurs on the one side of the output pixel,205// max window = scaleFactor + 1;206// 3) Partial coverage occurs at both sides of the output pixel,207// max window = scaleFactor + 2;208//209// Type of the coverage is determined by remainder of210// inPeriodH/outPeriodH division, but it's an heuristic211// (howbeit didn't found the proof of the opposite so far).212213static int calcResizeWindow(int inH, int outH)214{215GAPI_Assert(inH >= outH);216auto gcd = calcGcd(inH, outH);217int inPeriodH = inH/gcd;218int outPeriodH = outH/gcd;219int scaleFactor = inPeriodH / outPeriodH;220221switch ((inPeriodH) % (outPeriodH))222{223case 0: return scaleFactor; break;224case 1: return scaleFactor + 1; break;225default: return scaleFactor + 2;226}227}228229static int maxLineConsumption(const cv::GFluidKernel& k, int inH, int outH, int lpi)230{231switch (k.m_kind)232{233case cv::GFluidKernel::Kind::Filter: return k.m_window + lpi - 1; break;234case cv::GFluidKernel::Kind::Resize:235{236if (inH >= outH)237{238// FIXME:239// This is a suboptimal value, can be reduced240return calcResizeWindow(inH, outH) * lpi;241}242else243{244// FIXME:245// This is a suboptimal value, can be reduced246return (inH == 1) ? 1 : 2 + lpi - 1;247}248} break;249default: GAPI_Assert(false); return 0;250}251}252253static int borderSize(const cv::GFluidKernel& k)254{255switch (k.m_kind)256{257case cv::GFluidKernel::Kind::Filter: return (k.m_window - 1) / 2; break;258// Resize never reads from border pixels259case cv::GFluidKernel::Kind::Resize: return 0; break;260default: GAPI_Assert(false); return 0;261}262}263264double inCoord(int outIdx, double ratio)265{266return outIdx * ratio;267}268269int windowStart(int outIdx, double ratio)270{271return static_cast<int>(inCoord(outIdx, ratio) + 1e-3);272}273274int windowEnd(int outIdx, double ratio)275{276return static_cast<int>(std::ceil(inCoord(outIdx + 1, ratio) - 1e-3));277}278279double inCoordUpscale(int outCoord, double ratio)280{281// Calculate the projection of output pixel's center282return (outCoord + 0.5) * ratio - 0.5;283}284285int upscaleWindowStart(int outCoord, double ratio)286{287int start = static_cast<int>(inCoordUpscale(outCoord, ratio));288GAPI_DbgAssert(start >= 0);289return start;290}291292int upscaleWindowEnd(int outCoord, double ratio, int inSz)293{294int end = static_cast<int>(std::ceil(inCoordUpscale(outCoord, ratio)) + 1);295if (end > inSz)296{297end = inSz;298}299return end;300}301} // anonymous namespace302303int cv::gimpl::FluidFilterAgent::firstWindow() const304{305return k.m_window + k.m_lpi - 1;306}307308int cv::gimpl::FluidFilterAgent::nextWindow() const309{310int lpi = std::min(k.m_lpi, m_outputLines - m_producedLines - k.m_lpi);311return k.m_window - 1 + lpi;312}313314int cv::gimpl::FluidFilterAgent::linesRead() const315{316return k.m_lpi;317}318319int cv::gimpl::FluidResizeAgent::firstWindow() const320{321auto outIdx = out_buffers[0]->priv().y();322auto lpi = std::min(m_outputLines - m_producedLines, k.m_lpi);323return windowEnd(outIdx + lpi - 1, m_ratio) - windowStart(outIdx, m_ratio);324}325326int cv::gimpl::FluidResizeAgent::nextWindow() const327{328auto outIdx = out_buffers[0]->priv().y();329auto lpi = std::min(m_outputLines - m_producedLines - k.m_lpi, k.m_lpi);330auto nextStartIdx = outIdx + 1 + k.m_lpi - 1;331auto nextEndIdx = nextStartIdx + lpi - 1;332return windowEnd(nextEndIdx, m_ratio) - windowStart(nextStartIdx, m_ratio);333}334335int cv::gimpl::FluidResizeAgent::linesRead() const336{337auto outIdx = out_buffers[0]->priv().y();338return windowStart(outIdx + 1 + k.m_lpi - 1, m_ratio) - windowStart(outIdx, m_ratio);339}340341int cv::gimpl::FluidUpscaleAgent::firstWindow() const342{343auto outIdx = out_buffers[0]->priv().y();344auto lpi = std::min(m_outputLines - m_producedLines, k.m_lpi);345return upscaleWindowEnd(outIdx + lpi - 1, m_ratio, m_inH) - upscaleWindowStart(outIdx, m_ratio);346}347348int cv::gimpl::FluidUpscaleAgent::nextWindow() const349{350auto outIdx = out_buffers[0]->priv().y();351auto lpi = std::min(m_outputLines - m_producedLines - k.m_lpi, k.m_lpi);352auto nextStartIdx = outIdx + 1 + k.m_lpi - 1;353auto nextEndIdx = nextStartIdx + lpi - 1;354return upscaleWindowEnd(nextEndIdx, m_ratio, m_inH) - upscaleWindowStart(nextStartIdx, m_ratio);355}356357int cv::gimpl::FluidUpscaleAgent::linesRead() const358{359auto outIdx = out_buffers[0]->priv().y();360return upscaleWindowStart(outIdx + 1 + k.m_lpi - 1, m_ratio) - upscaleWindowStart(outIdx, m_ratio);361}362363bool cv::gimpl::FluidAgent::canRead() const364{365// An agent can work if every input buffer have enough data to start366for (const auto& in_view : in_views)367{368if (in_view)369{370if (!in_view.ready())371return false;372}373}374return true;375}376377bool cv::gimpl::FluidAgent::canWrite() const378{379// An agent can work if there is space to write in its output380// allocated buffers381GAPI_DbgAssert(!out_buffers.empty());382auto out_begin = out_buffers.begin();383auto out_end = out_buffers.end();384if (k.m_scratch) out_end--;385for (auto it = out_begin; it != out_end; ++it)386{387if ((*it)->priv().full())388{389return false;390}391}392return true;393}394395bool cv::gimpl::FluidAgent::canWork() const396{397return canRead() && canWrite();398}399400void cv::gimpl::FluidAgent::doWork()401{402GAPI_DbgAssert(m_outputLines > m_producedLines);403for (auto& in_view : in_views)404{405if (in_view) in_view.priv().prepareToRead();406}407408k.m_f(in_args, out_buffers);409410for (auto& in_view : in_views)411{412if (in_view) in_view.priv().readDone(linesRead(), nextWindow());413}414415for (auto out_buf : out_buffers)416{417out_buf->priv().writeDone();418// FIXME WARNING: Scratch buffers rotated here too!419}420421m_producedLines += k.m_lpi;422}423424bool cv::gimpl::FluidAgent::done() const425{426// m_producedLines is a multiple of LPI, while original427// height may be not.428return m_producedLines >= m_outputLines;429}430431void cv::gimpl::FluidAgent::debug(std::ostream &os)432{433os << "Fluid Agent " << std::hex << this434<< " (" << op_name << ") --"435<< " canWork=" << std::boolalpha << canWork()436<< " canRead=" << std::boolalpha << canRead()437<< " canWrite=" << std::boolalpha << canWrite()438<< " done=" << done()439<< " lines=" << std::dec << m_producedLines << "/" << m_outputLines440<< " {{\n";441for (auto out_buf : out_buffers)442{443out_buf->debug(os);444}445std::cout << "}}" << std::endl;446}447448// GCPUExcecutable implementation //////////////////////////////////////////////449450void cv::gimpl::GFluidExecutable::initBufferRois(std::vector<int>& readStarts, std::vector<cv::gapi::own::Rect>& rois)451{452GConstFluidModel fg(m_g);453auto proto = m_gm.metadata().get<Protocol>();454std::stack<ade::NodeHandle> nodesToVisit;455456if (proto.outputs.size() != m_outputRois.size())457{458GAPI_Assert(m_outputRois.size() == 0);459return;460}461462// First, initialize rois for output nodes, add them to traversal stack463for (const auto& it : ade::util::indexed(proto.out_nhs))464{465const auto idx = ade::util::index(it);466const auto nh = ade::util::value(it);467468const auto &d = m_gm.metadata(nh).get<Data>();469470// This is not our output471if (m_id_map.count(d.rc) == 0)472{473continue;474}475476if (d.shape == GShape::GMAT)477{478auto desc = util::get<GMatDesc>(d.meta);479if (m_outputRois[idx] == cv::gapi::own::Rect{})480{481m_outputRois[idx] = cv::gapi::own::Rect{0, 0, desc.size.width, desc.size.height};482}483484// Only slices are supported at the moment485GAPI_Assert(m_outputRois[idx].x == 0);486GAPI_Assert(m_outputRois[idx].width == desc.size.width);487488auto id = m_id_map.at(d.rc);489readStarts[id] = 0;490rois[id] = m_outputRois[idx];491nodesToVisit.push(nh);492}493}494495// Perform a wide search from each of the output nodes496// And extend roi of buffers by border_size497// Each node can be visited multiple times498// (if node has been already visited, the check that inferred rois are the same is performed)499while (!nodesToVisit.empty())500{501const auto startNode = nodesToVisit.top();502nodesToVisit.pop();503504if (!startNode->inNodes().empty())505{506GAPI_Assert(startNode->inNodes().size() == 1);507const auto& oh = startNode->inNodes().front();508509const auto& data = m_gm.metadata(startNode).get<Data>();510// only GMats participate in the process so it's valid to obtain GMatDesc511const auto& meta = util::get<GMatDesc>(data.meta);512513for (const auto& inNode : oh->inNodes())514{515const auto& in_data = m_gm.metadata(inNode).get<Data>();516517if (in_data.shape == GShape::GMAT && fg.metadata(inNode).contains<FluidData>())518{519const auto& in_meta = util::get<GMatDesc>(in_data.meta);520const auto& fd = fg.metadata(inNode).get<FluidData>();521522auto adjFilterRoi = [](cv::gapi::own::Rect produced, int b, int max_height) {523// Extend with border roi which should be produced, crop to logical image size524cv::gapi::own::Rect roi = {produced.x, produced.y - b, produced.width, produced.height + 2*b};525cv::gapi::own::Rect fullImg{ 0, 0, produced.width, max_height };526return roi & fullImg;527};528529auto adjResizeRoi = [](cv::gapi::own::Rect produced, cv::gapi::own::Size inSz, cv::gapi::own::Size outSz) {530auto map = [](int outCoord, int producedSz, int inSize, int outSize) {531double ratio = (double)inSize / outSize;532int w0 = 0, w1 = 0;533if (ratio >= 1.0)534{535w0 = windowStart(outCoord, ratio);536w1 = windowEnd (outCoord + producedSz - 1, ratio);537}538else539{540w0 = upscaleWindowStart(outCoord, ratio);541w1 = upscaleWindowEnd(outCoord + producedSz - 1, ratio, inSize);542}543return std::make_pair(w0, w1);544};545546auto mapY = map(produced.y, produced.height, inSz.height, outSz.height);547auto y0 = mapY.first;548auto y1 = mapY.second;549550auto mapX = map(produced.x, produced.width, inSz.width, outSz.width);551auto x0 = mapX.first;552auto x1 = mapX.second;553554cv::gapi::own::Rect roi = {x0, y0, x1 - x0, y1 - y0};555return roi;556};557558cv::gapi::own::Rect produced = rois[m_id_map.at(data.rc)];559560cv::gapi::own::Rect resized;561switch (fg.metadata(oh).get<FluidUnit>().k.m_kind)562{563case GFluidKernel::Kind::Filter: resized = produced; break;564case GFluidKernel::Kind::Resize: resized = adjResizeRoi(produced, in_meta.size, meta.size); break;565default: GAPI_Assert(false);566}567568int readStart = resized.y;569cv::gapi::own::Rect roi = adjFilterRoi(resized, fd.border_size, in_meta.size.height);570571auto in_id = m_id_map.at(in_data.rc);572if (rois[in_id] == cv::gapi::own::Rect{})573{574readStarts[in_id] = readStart;575rois[in_id] = roi;576// Continue traverse on internal (w.r.t Island) data nodes only.577if (fd.internal) nodesToVisit.push(inNode);578}579else580{581GAPI_Assert(readStarts[in_id] == readStart);582GAPI_Assert(rois[in_id] == roi);583}584} // if (in_data.shape == GShape::GMAT)585} // for (const auto& inNode : oh->inNodes())586} // if (!startNode->inNodes().empty())587} // while (!nodesToVisit.empty())588}589590cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,591const std::vector<ade::NodeHandle> &nodes,592const std::vector<cv::gapi::own::Rect> &outputRois)593: m_g(g), m_gm(m_g), m_nodes(nodes), m_outputRois(outputRois)594{595GConstFluidModel fg(m_g);596597// Initialize vector of data buffers, build list of operations598// FIXME: There _must_ be a better way to [query] count number of DATA nodes599std::size_t mat_count = 0;600std::size_t last_agent = 0;601std::map<std::size_t, ade::NodeHandle> all_gmat_ids;602603auto grab_mat_nh = [&](ade::NodeHandle nh) {604auto rc = m_gm.metadata(nh).get<Data>().rc;605if (m_id_map.count(rc) == 0)606{607all_gmat_ids[mat_count] = nh;608m_id_map[rc] = mat_count++;609}610};611612for (const auto &nh : m_nodes)613{614switch (m_gm.metadata(nh).get<NodeType>().t)615{616case NodeType::DATA:617if (m_gm.metadata(nh).get<Data>().shape == GShape::GMAT)618grab_mat_nh(nh);619break;620621case NodeType::OP:622{623const auto& fu = fg.metadata(nh).get<FluidUnit>();624switch (fu.k.m_kind)625{626case GFluidKernel::Kind::Filter: m_agents.emplace_back(new FluidFilterAgent(m_g, nh)); break;627case GFluidKernel::Kind::Resize:628{629if (fu.ratio >= 1.0)630{631m_agents.emplace_back(new FluidResizeAgent(m_g, nh));632}633else634{635m_agents.emplace_back(new FluidUpscaleAgent(m_g, nh));636}637} break;638default: GAPI_Assert(false);639}640// NB.: in_buffer_ids size is equal to Arguments size, not Edges size!!!641m_agents.back()->in_buffer_ids.resize(m_gm.metadata(nh).get<Op>().args.size(), -1);642for (auto eh : nh->inEdges())643{644// FIXME Only GMats are currently supported (which can be represented645// as fluid buffers646if (m_gm.metadata(eh->srcNode()).get<Data>().shape == GShape::GMAT)647{648const auto in_port = m_gm.metadata(eh).get<Input>().port;649const int in_buf = m_gm.metadata(eh->srcNode()).get<Data>().rc;650651m_agents.back()->in_buffer_ids[in_port] = in_buf;652grab_mat_nh(eh->srcNode());653}654}655// FIXME: Assumption that all operation outputs MUST be connected656m_agents.back()->out_buffer_ids.resize(nh->outEdges().size(), -1);657for (auto eh : nh->outEdges())658{659const auto& data = m_gm.metadata(eh->dstNode()).get<Data>();660const auto out_port = m_gm.metadata(eh).get<Output>().port;661const int out_buf = data.rc;662663m_agents.back()->out_buffer_ids[out_port] = out_buf;664if (data.shape == GShape::GMAT) grab_mat_nh(eh->dstNode());665}666if (fu.k.m_scratch)667m_scratch_users.push_back(last_agent);668last_agent++;669break;670}671default: GAPI_Assert(false);672}673}674675// Check that IDs form a continiuos set (important for further indexing)676GAPI_Assert(m_id_map.size() > 0);677GAPI_Assert(m_id_map.size() == static_cast<size_t>(mat_count));678679// Actually initialize Fluid buffers680GAPI_LOG_INFO(NULL, "Initializing " << mat_count << " fluid buffer(s)" << std::endl);681m_num_int_buffers = mat_count;682const std::size_t num_scratch = m_scratch_users.size();683684std::vector<int> readStarts(mat_count);685std::vector<cv::gapi::own::Rect> rois(mat_count);686687initBufferRois(readStarts, rois);688689// NB: Allocate ALL buffer object at once, and avoid any further reallocations690// (since raw pointers-to-elements are taken)691m_buffers.resize(m_num_int_buffers + num_scratch);692for (const auto &it : all_gmat_ids)693{694auto id = it.first;695auto nh = it.second;696const auto & d = m_gm.metadata(nh).get<Data>();697const auto &fd = fg.metadata(nh).get<FluidData>();698const auto meta = cv::util::get<GMatDesc>(d.meta);699700m_buffers[id].priv().init(meta, fd.lpi_write, readStarts[id], rois[id]);701702// TODO:703// Introduce Storage::INTERNAL_GRAPH and Storage::INTERNAL_ISLAND?704if (fd.internal == true)705{706m_buffers[id].priv().allocate(fd.border, fd.border_size, fd.max_consumption, fd.skew);707std::stringstream stream;708m_buffers[id].debug(stream);709GAPI_LOG_INFO(NULL, stream.str());710}711}712713// After buffers are allocated, repack: ...714for (auto &agent : m_agents)715{716// a. Agent input parameters with View pointers (creating Views btw)717const auto &op = m_gm.metadata(agent->op_handle).get<Op>();718const auto &fu = fg.metadata(agent->op_handle).get<FluidUnit>();719agent->in_args.resize(op.args.size());720agent->in_views.resize(op.args.size());721for (auto it : ade::util::zip(ade::util::iota(op.args.size()),722ade::util::toRange(agent->in_buffer_ids)))723{724auto in_idx = std::get<0>(it);725auto buf_idx = std::get<1>(it);726727if (buf_idx >= 0)728{729// IF there is input buffer, register a view (every unique730// reader has its own), and store it in agent Args731gapi::fluid::Buffer &buffer = m_buffers.at(m_id_map.at(buf_idx));732733auto inEdge = GModel::getInEdgeByPort(m_g, agent->op_handle, in_idx);734auto ownStorage = fg.metadata(inEdge).get<FluidUseOwnBorderBuffer>().use;735736gapi::fluid::View view = buffer.mkView(fu.line_consumption, fu.border_size, fu.border, ownStorage);737// NB: It is safe to keep ptr as view lifetime is buffer lifetime738agent->in_views[in_idx] = view;739agent->in_args[in_idx] = GArg(view);740agent->m_ratio = fu.ratio;741}742else743{744// Copy(FIXME!) original args as is745agent->in_args[in_idx] = op.args[in_idx];746}747}748749// cache input height to avoid costly meta() call750// (actually cached and used only in upscale)751if (agent->in_views[0])752{753agent->setInHeight(agent->in_views[0].meta().size.height);754}755756// b. Agent output parameters with Buffer pointers.757agent->out_buffers.resize(agent->op_handle->outEdges().size(), nullptr);758for (auto it : ade::util::zip(ade::util::iota(agent->out_buffers.size()),759ade::util::toRange(agent->out_buffer_ids)))760{761auto out_idx = std::get<0>(it);762auto buf_idx = m_id_map.at(std::get<1>(it));763agent->out_buffers.at(out_idx) = &m_buffers.at(buf_idx);764agent->m_outputLines = m_buffers.at(buf_idx).priv().outputLines();765}766}767768// After parameters are there, initialize scratch buffers769if (num_scratch)770{771GAPI_LOG_INFO(NULL, "Initializing " << num_scratch << " scratch buffer(s)" << std::endl);772std::size_t last_scratch_id = 0;773774for (auto i : m_scratch_users)775{776auto &agent = m_agents.at(i);777GAPI_Assert(agent->k.m_scratch);778779// Collect input metas to trigger scratch buffer initialization780// Array is sparse (num of elements == num of GArgs, not edges)781GMetaArgs in_metas(agent->in_args.size());782for (auto eh : agent->op_handle->inEdges())783{784const auto& in_data = m_gm.metadata(eh->srcNode()).get<Data>();785in_metas[m_gm.metadata(eh).get<Input>().port] = in_data.meta;786}787788// Trigger Scratch buffer initialization method789const std::size_t new_scratch_idx = m_num_int_buffers + last_scratch_id;790791agent->k.m_is(in_metas, agent->in_args, m_buffers.at(new_scratch_idx));792std::stringstream stream;793m_buffers[new_scratch_idx].debug(stream);794GAPI_LOG_INFO(NULL, stream.str());795agent->out_buffers.emplace_back(&m_buffers[new_scratch_idx]);796last_scratch_id++;797}798}799800std::size_t total_size = 0;801for (const auto &i : ade::util::indexed(m_buffers))802{803// Check that all internal and scratch buffers are allocated804const auto idx = ade::util::index(i);805const auto b = ade::util::value(i);806if (idx >= m_num_int_buffers ||807fg.metadata(all_gmat_ids[idx]).get<FluidData>().internal == true)808{809GAPI_Assert(b.priv().size() > 0);810}811812// Buffers which will be bound to real images may have size of 0 at this moment813// (There can be non-zero sized const border buffer allocated in such buffers)814total_size += b.priv().size();815}816GAPI_LOG_INFO(NULL, "Internal buffers: " << std::fixed << std::setprecision(2) << static_cast<float>(total_size)/1024 << " KB\n");817}818819// FIXME: Document what it does820void cv::gimpl::GFluidExecutable::bindInArg(const cv::gimpl::RcDesc &rc, const GRunArg &arg)821{822switch (rc.shape)823{824case GShape::GMAT: m_buffers[m_id_map.at(rc.id)].priv().bindTo(util::get<cv::gapi::own::Mat>(arg), true); break;825case GShape::GSCALAR: m_res.slot<cv::gapi::own::Scalar>()[rc.id] = util::get<cv::gapi::own::Scalar>(arg); break;826default: util::throw_error(std::logic_error("Unsupported GShape type"));827}828}829830void cv::gimpl::GFluidExecutable::bindOutArg(const cv::gimpl::RcDesc &rc, const GRunArgP &arg)831{832// Only GMat is supported as return type833switch (rc.shape)834{835case GShape::GMAT:836{837cv::GMatDesc desc = m_buffers[m_id_map.at(rc.id)].meta();838auto &outMat = *util::get<cv::gapi::own::Mat*>(arg);839GAPI_Assert(outMat.data != nullptr);840GAPI_Assert(descr_of(outMat) == desc && "Output argument was not preallocated as it should be ?");841m_buffers[m_id_map.at(rc.id)].priv().bindTo(outMat, false);842break;843}844default: util::throw_error(std::logic_error("Unsupported return GShape type"));845}846}847848void cv::gimpl::GFluidExecutable::packArg(cv::GArg &in_arg, const cv::GArg &op_arg)849{850GAPI_Assert(op_arg.kind != cv::detail::ArgKind::GMAT851&& op_arg.kind != cv::detail::ArgKind::GSCALAR);852853if (op_arg.kind == cv::detail::ArgKind::GOBJREF)854{855const cv::gimpl::RcDesc &ref = op_arg.get<cv::gimpl::RcDesc>();856if (ref.shape == GShape::GSCALAR)857{858in_arg = GArg(m_res.slot<cv::gapi::own::Scalar>()[ref.id]);859}860}861}862863void cv::gimpl::GFluidExecutable::run(std::vector<InObj> &&input_objs,864std::vector<OutObj> &&output_objs)865{866// Bind input buffers from parameters867for (auto& it : input_objs) bindInArg(it.first, it.second);868for (auto& it : output_objs) bindOutArg(it.first, it.second);869870// Reset Buffers and Agents state before we go871for (auto &buffer : m_buffers)872buffer.priv().reset();873874for (auto &agent : m_agents)875{876agent->reset();877// Pass input cv::Scalar's to agent argument878const auto& op = m_gm.metadata(agent->op_handle).get<Op>();879for (const auto& it : ade::util::indexed(op.args))880{881const auto& arg = ade::util::value(it);882packArg(agent->in_args[ade::util::index(it)], arg);883}884}885886// Explicitly reset Scratch buffers, if any887for (auto scratch_i : m_scratch_users)888{889auto &agent = m_agents[scratch_i];890GAPI_DbgAssert(agent->k.m_scratch);891agent->k.m_rs(*agent->out_buffers.back());892}893894// Now start executing our stuff!895// Fluid execution is:896// - run through list of Agents from Left to Right897// - for every Agent:898// - if all input Buffers have enough data to fulfill899// Agent's window - trigger Agent900// - on trigger, Agent takes all input lines from input buffers901// and produces a single output line902// - once Agent finishes, input buffers get "readDone()",903// and output buffers get "writeDone()"904// - if there's not enough data, Agent is skipped905// Yes, THAT easy!906bool complete = true;907do {908complete = true;909bool work_done=false;910for (auto &agent : m_agents)911{912// agent->debug(std::cout);913if (!agent->done())914{915if (agent->canWork())916{917agent->doWork(); work_done=true;918}919if (!agent->done()) complete = false;920}921}922GAPI_Assert(work_done || complete);923} while (!complete); // FIXME: number of iterations can be calculated statically924}925926// FIXME: these passes operate on graph global level!!!927// Need to fix this for heterogeneous (island-based) processing928void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)929{930using namespace cv::gimpl;931932// FIXME: all passes were moved to "exec" stage since Fluid933// should check Islands configuration first (which is now quite934// limited), and only then continue with all other passes.935//936// The passes/stages API must be streamlined!937ectx.addPass("exec", "init_fluid_data", [](ade::passes::PassContext &ctx)938{939GModel::Graph g(ctx.graph);940if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!941return;942943auto isl_graph = g.metadata().get<IslandModel>().model;944GIslandModel::Graph gim(*isl_graph);945946GFluidModel fg(ctx.graph);947948const auto setFluidData = [&](ade::NodeHandle nh, bool internal) {949FluidData fd;950fd.internal = internal;951fg.metadata(nh).set(fd);952};953954for (const auto& nh : gim.nodes())955{956if (gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND)957{958const auto isl = gim.metadata(nh).get<FusedIsland>().object;959if (isl->backend() == cv::gapi::fluid::backend())960{961// add FluidData to all data nodes inside island962for (const auto node : isl->contents())963{964if (g.metadata(node).get<NodeType>().t == NodeType::DATA)965setFluidData(node, true);966}967968// add FluidData to slot if it's read/written by fluid969std::vector<ade::NodeHandle> io_handles;970for (const auto &in_op : isl->in_ops())971{972ade::util::copy(in_op->inNodes(), std::back_inserter(io_handles));973}974for (const auto &out_op : isl->out_ops())975{976ade::util::copy(out_op->outNodes(), std::back_inserter(io_handles));977}978for (const auto &io_node : io_handles)979{980if (!fg.metadata(io_node).contains<FluidData>())981setFluidData(io_node, false);982}983} // if (fluid backend)984} // if (ISLAND)985} // for (gim.nodes())986});987// FIXME:988// move to unpackKernel method989// when https://gitlab-icv.inn.intel.com/G-API/g-api/merge_requests/66 is merged990ectx.addPass("exec", "init_fluid_unit_borders", [](ade::passes::PassContext &ctx)991{992GModel::Graph g(ctx.graph);993if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!994return;995996GFluidModel fg(ctx.graph);997998auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();999for (auto node : sorted)1000{1001if (fg.metadata(node).contains<FluidUnit>())1002{1003// FIXME: check that op has only one data node on input1004auto &fu = fg.metadata(node).get<FluidUnit>();1005const auto &op = g.metadata(node).get<Op>();10061007// Trigger user-defined "getBorder" callback1008fu.border = fu.k.m_b(GModel::collectInputMeta(fg, node), op.args);1009}1010}1011});1012ectx.addPass("exec", "init_fluid_units", [](ade::passes::PassContext &ctx)1013{1014GModel::Graph g(ctx.graph);1015if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!1016return;10171018GFluidModel fg(ctx.graph);10191020auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();1021for (auto node : sorted)1022{1023if (fg.metadata(node).contains<FluidUnit>())1024{1025std::set<int> in_hs, out_ws, out_hs;10261027for (const auto& in : node->inNodes())1028{1029const auto& d = g.metadata(in).get<Data>();1030if (d.shape == cv::GShape::GMAT)1031{1032const auto& meta = cv::util::get<cv::GMatDesc>(d.meta);1033in_hs.insert(meta.size.height);1034}1035}10361037for (const auto& out : node->outNodes())1038{1039const auto& d = g.metadata(out).get<Data>();1040if (d.shape == cv::GShape::GMAT)1041{1042const auto& meta = cv::util::get<cv::GMatDesc>(d.meta);1043out_ws.insert(meta.size.width);1044out_hs.insert(meta.size.height);1045}1046}10471048GAPI_Assert(in_hs.size() == 1 && out_ws.size() == 1 && out_hs.size() == 1);10491050auto in_h = *in_hs .cbegin();1051auto out_h = *out_hs.cbegin();10521053auto &fu = fg.metadata(node).get<FluidUnit>();1054fu.ratio = (double)in_h / out_h;10551056int line_consumption = maxLineConsumption(fu.k, in_h, out_h, fu.k.m_lpi);1057int border_size = borderSize(fu.k);10581059fu.border_size = border_size;1060fu.line_consumption = line_consumption;10611062GModel::log(g, node, "Line consumption: " + std::to_string(fu.line_consumption));1063GModel::log(g, node, "Border size: " + std::to_string(fu.border_size));1064}1065}1066});1067ectx.addPass("exec", "init_line_consumption", [](ade::passes::PassContext &ctx)1068{1069GModel::Graph g(ctx.graph);1070if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!1071return;10721073GFluidModel fg(ctx.graph);1074for (const auto node : g.nodes())1075{1076if (fg.metadata(node).contains<FluidUnit>())1077{1078const auto &fu = fg.metadata(node).get<FluidUnit>();10791080for (auto in_data_node : node->inNodes())1081{1082auto &fd = fg.metadata(in_data_node).get<FluidData>();10831084// Update (not Set) fields here since a single data node may be1085// accessed by multiple consumers1086fd.max_consumption = std::max(fu.line_consumption, fd.max_consumption);1087fd.border_size = std::max(fu.border_size, fd.border_size);10881089GModel::log(g, in_data_node, "Line consumption: " + std::to_string(fd.max_consumption)1090+ " (upd by " + std::to_string(fu.line_consumption) + ")", node);1091GModel::log(g, in_data_node, "Border size: " + std::to_string(fd.border_size), node);1092}1093}1094}1095});1096ectx.addPass("exec", "calc_latency", [](ade::passes::PassContext &ctx)1097{1098GModel::Graph g(ctx.graph);1099if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!1100return;11011102GFluidModel fg(ctx.graph);11031104auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();1105for (auto node : sorted)1106{1107if (fg.metadata(node).contains<FluidUnit>())1108{1109const auto &fu = fg.metadata(node).get<FluidUnit>();11101111const int own_latency = fu.line_consumption - fu.border_size;1112GModel::log(g, node, "LPI: " + std::to_string(fu.k.m_lpi));11131114// Output latency is max(input_latency) + own_latency1115int in_latency = 0;1116for (auto in_data_node : node->inNodes())1117{1118// FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)1119in_latency = std::max(in_latency, fg.metadata(in_data_node).get<FluidData>().latency);1120}1121const int out_latency = in_latency + own_latency;11221123for (auto out_data_node : node->outNodes())1124{1125// FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)1126auto &fd = fg.metadata(out_data_node).get<FluidData>();1127fd.latency = out_latency;1128fd.lpi_write = fu.k.m_lpi;1129GModel::log(g, out_data_node, "Latency: " + std::to_string(out_latency));1130}1131}1132}1133});1134ectx.addPass("exec", "calc_skew", [](ade::passes::PassContext &ctx)1135{1136GModel::Graph g(ctx.graph);1137if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!1138return;11391140GFluidModel fg(ctx.graph);11411142auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();1143for (auto node : sorted)1144{1145if (fg.metadata(node).contains<FluidUnit>())1146{1147int max_latency = 0;1148for (auto in_data_node : node->inNodes())1149{1150// FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)1151max_latency = std::max(max_latency, fg.metadata(in_data_node).get<FluidData>().latency);1152}1153for (auto in_data_node : node->inNodes())1154{1155// FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)1156auto &fd = fg.metadata(in_data_node).get<FluidData>();11571158// Update (not Set) fields here since a single data node may be1159// accessed by multiple consumers1160fd.skew = std::max(fd.skew, max_latency - fd.latency);11611162GModel::log(g, in_data_node, "Skew: " + std::to_string(fd.skew), node);1163}1164}1165}1166});11671168ectx.addPass("exec", "init_buffer_borders", [](ade::passes::PassContext &ctx)1169{1170GModel::Graph g(ctx.graph);1171if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!1172return;11731174GFluidModel fg(ctx.graph);1175auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();1176for (auto node : sorted)1177{1178if (fg.metadata(node).contains<FluidData>())1179{1180auto &fd = fg.metadata(node).get<FluidData>();11811182// Assign border stuff to FluidData11831184// In/out data nodes are bound to user data directly,1185// so cannot be extended with a border1186if (fd.internal == true)1187{1188// For now border of the buffer's storage is the border1189// of the first reader whose border size is the same.1190// FIXME: find more clever strategy of border picking1191// (it can be a border which is common for majority of the1192// readers, also we can calculate the number of lines which1193// will be copied by views on each iteration and base our choice1194// on this criteria)1195auto readers = node->outNodes();1196const auto &candidate = ade::util::find_if(readers, [&](ade::NodeHandle nh) {1197return fg.metadata(nh).contains<FluidUnit>() &&1198fg.metadata(nh).get<FluidUnit>().border_size == fd.border_size;1199});12001201GAPI_Assert(candidate != readers.end());12021203const auto &fu = fg.metadata(*candidate).get<FluidUnit>();1204fd.border = fu.border;1205}12061207if (fd.border)1208{1209GModel::log(g, node, "Border type: " + std::to_string(fd.border->type), node);1210}1211}1212}1213});1214ectx.addPass("exec", "init_view_borders", [](ade::passes::PassContext &ctx)1215{1216GModel::Graph g(ctx.graph);1217if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!1218return;12191220GFluidModel fg(ctx.graph);1221for (auto node : g.nodes())1222{1223if (fg.metadata(node).contains<FluidData>())1224{1225auto &fd = fg.metadata(node).get<FluidData>();1226for (auto out_edge : node->outEdges())1227{1228const auto dstNode = out_edge->dstNode();1229if (fg.metadata(dstNode).contains<FluidUnit>())1230{1231const auto &fu = fg.metadata(dstNode).get<FluidUnit>();12321233// There is no need in own storage for view if it's border is1234// the same as the buffer's (view can have equal or smaller border1235// size in this case)1236if (fu.border_size == 0 ||1237(fu.border && fd.border && (*fu.border == *fd.border)))1238{1239GAPI_Assert(fu.border_size <= fd.border_size);1240fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{false});1241}1242else1243{1244fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{true});1245GModel::log(g, out_edge, "OwnBufferStorage: true");1246}1247}1248}1249}1250}1251});1252}125312541255