1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-29 23:33:05 +04:00

Merge pull request #12608 from dmatveev:gapi

* G-API Initial code upload

* Update G-API code base to Sep-24-2018

* The majority of OpenCV buildbot problems was addressed

* Update G-API code base to 24-Sep-18 EOD

* G-API code base update 25-Sep-2018

* Linux warnings should be resolved
* Documentation build should become green
* Number of Windows warnings should be reduced

* Update G-API code base to 25-Sep-18 EOD

* ARMv7 build issue should be resolved
* ADE is bumped to latest version and should fix Clang builds for macOS/iOS
* Remaining Windows warnings should be resolved
* New Linux32 / ARMv7 warnings should be resolved

* G-API code base update 25-Sep-2018-EOD2

* Final Windows warnings should be resolved now

* G-API code base update 26-Sep-2018

* Fixed issues with precompiled headers in module and its tests
This commit is contained in:
Dmitry Matveev
2018-09-26 21:50:39 +03:00
committed by Alexander Alekhin
parent 852f061b26
commit 29e88e50ff
166 changed files with 35254 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
This directory contains implementation of G-API frontend (public API classes).
+43
View File
@@ -0,0 +1,43 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <ade/util/assert.hpp>
#include "api/gapi_priv.hpp"
#include "api/gnode_priv.hpp"
cv::GOrigin::GOrigin(GShape s,
const cv::GNode& n,
std::size_t p,
const cv::gimpl::HostCtor c)
: shape(s), node(n), port(p), ctor(c)
{
}
cv::GOrigin::GOrigin(GShape s, cv::gimpl::ConstVal v)
: shape(s), node(cv::GNode::Const()), value(v), port(INVALID_PORT)
{
}
bool cv::detail::GOriginCmp::operator() (const cv::GOrigin &lhs,
const cv::GOrigin &rhs) const
{
const GNode::Priv* lhs_p = &lhs.node.priv();
const GNode::Priv* rhs_p = &rhs.node.priv();
if (lhs_p == rhs_p)
{
if (lhs.port == rhs.port)
{
// A data Origin is uniquely identified by {node/port} pair.
// The situation when there're two Origins with same {node/port}s
// but with different shapes (data formats) is illegal!
GAPI_Assert(lhs.shape == rhs.shape);
}
return lhs.port < rhs.port;
}
else return lhs_p < rhs_p;
}
+77
View File
@@ -0,0 +1,77 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_PRIV_HPP
#define OPENCV_GAPI_PRIV_HPP
#include <set> // set
#include <map> // map
#include <limits>
#include "opencv2/gapi/util/variant.hpp" // variant
#include "opencv2/gapi/garray.hpp" // ConstructVec
#include "opencv2/gapi/gscalar.hpp"
#include "opencv2/gapi/gcommon.hpp"
#include "opencv2/gapi/opencv_includes.hpp"
#include "api/gnode.hpp"
namespace cv
{
namespace gimpl
{
// Union type for various user-defined type constructors (GArray<T>, etc)
// FIXME: Replace construct-only API with a more generic one
// (probably with bits of introspection)
// Not required for non-user-defined types (GMat, GScalar, etc)
using HostCtor = util::variant
< util::monostate
, detail::ConstructVec
>;
using ConstVal = util::variant
< util::monostate
, cv::gapi::own::Scalar
>;
}
// TODO namespace gimpl?
struct GOrigin
{
static constexpr const std::size_t INVALID_PORT = std::numeric_limits<std::size_t>::max();
GOrigin(GShape s,
const GNode& n,
std::size_t p = INVALID_PORT,
const gimpl::HostCtor h = {});
GOrigin(GShape s, gimpl::ConstVal value);
const GShape shape; // Shape of a produced object
const GNode node; // a GNode which produces an object
const gimpl::ConstVal value; // Node can have initial constant value, now only scalar is supported
const std::size_t port; // GNode's output number; FIXME: "= max_size" in C++14
gimpl::HostCtor ctor; // FIXME: replace with an interface?
};
namespace detail
{
struct GOriginCmp
{
bool operator() (const GOrigin &lhs, const GOrigin &rhs) const;
};
} // namespace cv::details
// TODO introduce a hash on GOrigin and define this via unordered_ ?
using GOriginSet = std::set<GOrigin, detail::GOriginCmp>;
template<typename T> using GOriginMap = std::map<GOrigin, T, detail::GOriginCmp>;
} // namespace cv
#endif // OPENCV_GAPI_PRIV_HPP
+44
View File
@@ -0,0 +1,44 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include "opencv2/gapi/garray.hpp"
#include "api/gapi_priv.hpp" // GOrigin
// cv::detail::GArrayU public implementation ///////////////////////////////////
cv::detail::GArrayU::GArrayU()
: m_priv(new GOrigin(GShape::GARRAY, cv::GNode::Param()))
{
}
cv::detail::GArrayU::GArrayU(const GNode &n, std::size_t out)
: m_priv(new GOrigin(GShape::GARRAY, n, out))
{
}
cv::GOrigin& cv::detail::GArrayU::priv()
{
return *m_priv;
}
const cv::GOrigin& cv::detail::GArrayU::priv() const
{
return *m_priv;
}
void cv::detail::GArrayU::setConstructFcn(ConstructVec &&cv)
{
m_priv->ctor = std::move(cv);
}
namespace cv {
std::ostream& operator<<(std::ostream& os, const cv::GArrayDesc &)
{
// FIXME: add type information here
os << "(array)";
return os;
}
}
+269
View File
@@ -0,0 +1,269 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <memory> // unique_ptr
#include "opencv2/gapi/gkernel.hpp"
#include "opencv2/gapi/own/convert.hpp"
#include "api/gbackend_priv.hpp"
#include "backends/common/gbackend.hpp"
#include "compiler/gobjref.hpp"
#include "compiler/gislandmodel.hpp"
// GBackend private implementation /////////////////////////////////////////////
void cv::gapi::GBackend::Priv::unpackKernel(ade::Graph & /*graph */ ,
const ade::NodeHandle & /*op_node*/ ,
const GKernelImpl & /*impl */ )
{
// Default implementation is still there as Priv
// is instantiated by some tests.
// Priv is even instantiated as a mock object in a number of tests
// as a backend and this method is called for mock objects (doing nothing).
// FIXME: add a warning message here
// FIXME: Do something with this! Ideally this function should be "=0";
}
std::unique_ptr<cv::gimpl::GIslandExecutable>
cv::gapi::GBackend::Priv::compile(const ade::Graph&,
const GCompileArgs&,
const std::vector<ade::NodeHandle> &) const
{
// ...and this method is here for the same reason!
GAPI_Assert(false);
return {};
}
void cv::gapi::GBackend::Priv::addBackendPasses(ade::ExecutionEngineSetupContext &)
{
// Do nothing by default, plugins may override this to
// add custom (backend-specific) graph transformations
}
// GBackend public implementation //////////////////////////////////////////////
cv::gapi::GBackend::GBackend()
{
}
cv::gapi::GBackend::GBackend(std::shared_ptr<cv::gapi::GBackend::Priv> &&p)
: m_priv(std::move(p))
{
}
cv::gapi::GBackend::Priv& cv::gapi::GBackend::priv()
{
return *m_priv;
}
const cv::gapi::GBackend::Priv& cv::gapi::GBackend::priv() const
{
return *m_priv;
}
std::size_t cv::gapi::GBackend::hash() const
{
return std::hash<const cv::gapi::GBackend::Priv*>{}(m_priv.get());
}
bool cv::gapi::GBackend::operator== (const cv::gapi::GBackend &rhs) const
{
return m_priv == rhs.m_priv;
}
// Abstract Host-side data manipulation ////////////////////////////////////////
// Reused between CPU backend and more generic GExecutor
namespace cv {
namespace gimpl {
namespace magazine {
// FIXME implement the below functions with visit()?
void bindInArg(Mag& mag, const RcDesc &rc, const GRunArg &arg)
{
switch (rc.shape)
{
case GShape::GMAT:
{
auto& mag_mat = mag.template slot<cv::gapi::own::Mat>()[rc.id];
switch (arg.index())
{
case GRunArg::index_of<cv::gapi::own::Mat>() : mag_mat = util::get<cv::gapi::own::Mat>(arg); break;
case GRunArg::index_of<cv::Mat>() : mag_mat = to_own(util::get<cv::Mat>(arg)); break;
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
break;
}
case GShape::GSCALAR:
{
auto& mag_scalar = mag.template slot<cv::gapi::own::Scalar>()[rc.id];
switch (arg.index())
{
case GRunArg::index_of<cv::gapi::own::Scalar>() : mag_scalar = util::get<cv::gapi::own::Scalar>(arg); break;
case GRunArg::index_of<cv::Scalar>() : mag_scalar = to_own(util::get<cv::Scalar>(arg)); break;
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
break;
}
case GShape::GARRAY:
mag.template slot<cv::detail::VectorRef>()[rc.id] = util::get<cv::detail::VectorRef>(arg);
break;
default:
util::throw_error(std::logic_error("Unsupported GShape type"));
}
}
void bindOutArg(Mag& mag, const RcDesc &rc, const GRunArgP &arg)
{
switch (rc.shape)
{
case GShape::GMAT:
{
auto& mag_mat = mag.template slot<cv::gapi::own::Mat>()[rc.id];
switch (arg.index())
{
case GRunArgP::index_of<cv::gapi::own::Mat*>() : mag_mat = * util::get<cv::gapi::own::Mat*>(arg); break;
case GRunArgP::index_of<cv::Mat*>() : mag_mat = to_own(* util::get<cv::Mat*>(arg)); break;
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
break;
}
case GShape::GSCALAR:
{
auto& mag_scalar = mag.template slot<cv::gapi::own::Scalar>()[rc.id];
switch (arg.index())
{
case GRunArgP::index_of<cv::gapi::own::Scalar*>() : mag_scalar = *util::get<cv::gapi::own::Scalar*>(arg); break;
case GRunArgP::index_of<cv::Scalar*>() : mag_scalar = to_own(*util::get<cv::Scalar*>(arg)); break;
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
break;
}
case GShape::GARRAY:
mag.template slot<cv::detail::VectorRef>()[rc.id] = util::get<cv::detail::VectorRef>(arg);
break;
default:
util::throw_error(std::logic_error("Unsupported GShape type"));
break;
}
}
void resetInternalData(Mag& mag, const Data &d)
{
if (d.storage != Data::Storage::INTERNAL)
return;
switch (d.shape)
{
case GShape::GARRAY:
util::get<cv::detail::ConstructVec>(d.ctor)
(mag.template slot<cv::detail::VectorRef>()[d.rc]);
break;
case GShape::GSCALAR:
mag.template slot<cv::gapi::own::Scalar>()[d.rc] = cv::gapi::own::Scalar();
break;
case GShape::GMAT:
// Do nothign here - FIXME unify with initInternalData?
break;
default:
util::throw_error(std::logic_error("Unsupported GShape type"));
break;
}
}
cv::GRunArg getArg(const Mag& mag, const RcDesc &ref)
{
// Wrap associated CPU object (either host or an internal one)
switch (ref.shape)
{
case GShape::GMAT: return GRunArg(mag.template slot<cv::gapi::own::Mat>().at(ref.id));
case GShape::GSCALAR: return GRunArg(mag.template slot<cv::gapi::own::Scalar>().at(ref.id));
// Note: .at() is intentional for GArray as object MUST be already there
// (and constructed by either bindIn/Out or resetInternal)
case GShape::GARRAY: return GRunArg(mag.template slot<cv::detail::VectorRef>().at(ref.id));
default:
util::throw_error(std::logic_error("Unsupported GShape type"));
break;
}
}
cv::GRunArgP getObjPtr(Mag& mag, const RcDesc &rc)
{
switch (rc.shape)
{
case GShape::GMAT: return GRunArgP(&mag.template slot<cv::gapi::own::Mat>() [rc.id]);
case GShape::GSCALAR: return GRunArgP(&mag.template slot<cv::gapi::own::Scalar>()[rc.id]);
// Note: .at() is intentional for GArray as object MUST be already there
// (and constructer by either bindIn/Out or resetInternal)
case GShape::GARRAY:
// FIXME(DM): For some absolutely unknown to me reason, move
// semantics is involved here without const_cast to const (and
// value from map is moved into return value GRunArgP, leaving
// map with broken value I've spent few late Friday hours
// debugging this!!!1
return GRunArgP(const_cast<const Mag&>(mag)
.template slot<cv::detail::VectorRef>().at(rc.id));
default:
util::throw_error(std::logic_error("Unsupported GShape type"));
break;
}
}
void writeBack(const Mag& mag, const RcDesc &rc, GRunArgP &g_arg)
{
switch (rc.shape)
{
case GShape::GARRAY:
// Do nothing - should we really do anything here?
break;
case GShape::GMAT:
{
//simply check that memory was not reallocated, i.e.
//both instances of Mat pointing to the same memory
uchar* out_arg_data = nullptr;
switch (g_arg.index())
{
case GRunArgP::index_of<cv::gapi::own::Mat*>() : out_arg_data = util::get<cv::gapi::own::Mat*>(g_arg)->data; break;
case GRunArgP::index_of<cv::Mat*>() : out_arg_data = util::get<cv::Mat*>(g_arg)->data; break;
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
auto& in_mag = mag.template slot<cv::gapi::own::Mat>().at(rc.id);
GAPI_Assert((out_arg_data == in_mag.data) && " data for output parameters was reallocated ?");
break;
}
case GShape::GSCALAR:
{
switch (g_arg.index())
{
case GRunArgP::index_of<cv::gapi::own::Scalar*>() : *util::get<cv::gapi::own::Scalar*>(g_arg) = mag.template slot<cv::gapi::own::Scalar>().at(rc.id); break;
case GRunArgP::index_of<cv::Scalar*>() : *util::get<cv::Scalar*>(g_arg) = cv::gapi::own::to_ocv(mag.template slot<cv::gapi::own::Scalar>().at(rc.id)); break;
default: util::throw_error(std::logic_error("content type of the runtime argument does not match to resource description ?"));
}
break;
}
default:
util::throw_error(std::logic_error("Unsupported GShape type"));
break;
}
}
} // namespace magazine
} // namespace gimpl
} // namespace cv
+53
View File
@@ -0,0 +1,53 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef GAPI_API_GBACKEND_PRIV_HPP
#define GAPI_API_GBACKEND_PRIV_HPP
#include <memory>
#include <unordered_set>
#include <ade/graph.hpp>
#include <ade/passes/pass_base.hpp> // passes::PassContext
#include <ade/execution_engine/execution_engine.hpp> // ..SetupContext
#include "opencv2/gapi/gcommon.hpp"
#include "opencv2/gapi/gkernel.hpp"
namespace cv
{
namespace gimpl
{
class GBackend;
class GIslandExecutable;
} // namespace gimpl
} // namespace cv
// GAPI_EXPORTS is here to make tests build on Windows
class GAPI_EXPORTS cv::gapi::GBackend::Priv
{
public:
using EPtr = std::unique_ptr<cv::gimpl::GIslandExecutable>;
virtual void unpackKernel(ade::Graph &graph,
const ade::NodeHandle &op_node,
const GKernelImpl &impl);
// FIXME: since backends are not passed to ADE anymore,
// there's no need in having both cv::gimpl::GBackend
// and cv::gapi::GBackend - these two things can be unified
// NOTE - nodes are guaranteed to be topologically sorted.
virtual EPtr compile(const ade::Graph &graph,
const GCompileArgs &args,
const std::vector<ade::NodeHandle> &nodes) const;
virtual void addBackendPasses(ade::ExecutionEngineSetupContext &);
virtual ~Priv() = default;
};
#endif // GAPI_API_GBACKEND_PRIV_HPP
+64
View File
@@ -0,0 +1,64 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <cassert>
#include "opencv2/gapi/gcall.hpp"
#include "api/gcall_priv.hpp"
// GCall private implementation ////////////////////////////////////////////////
cv::GCall::Priv::Priv(const cv::GKernel &k)
: m_k(k)
{
}
// GCall public implementation /////////////////////////////////////////////////
cv::GCall::GCall(const cv::GKernel &k)
: m_priv(new Priv(k))
{
// Here we have a reference to GNode,
// and GNode has a reference to us. Cycle! Now see destructor.
m_priv->m_node = GNode::Call(*this);
}
cv::GCall::~GCall()
{
// When a GCall object is destroyed (and GCall::Priv is likely still alive,
// as there might be other references), reset m_node to break cycle.
m_priv->m_node = GNode();
}
void cv::GCall::setArgs(std::vector<GArg> &&args)
{
// FIXME: Check if argument number is matching kernel prototype
m_priv->m_args = std::move(args);
}
cv::GMat cv::GCall::yield(int output)
{
return cv::GMat(m_priv->m_node, output);
}
cv::GScalar cv::GCall::yieldScalar(int output)
{
return cv::GScalar(m_priv->m_node, output);
}
cv::detail::GArrayU cv::GCall::yieldArray(int output)
{
return cv::detail::GArrayU(m_priv->m_node, output);
}
cv::GCall::Priv& cv::GCall::priv()
{
return *m_priv;
}
const cv::GCall::Priv& cv::GCall::priv() const
{
return *m_priv;
}
+37
View File
@@ -0,0 +1,37 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GCALL_PRIV_HPP
#define OPENCV_GCALL_PRIV_HPP
#include <vector>
#include <unordered_map>
#include "opencv2/gapi/garg.hpp"
#include "opencv2/gapi/gcall.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "api/gnode.hpp"
namespace cv {
class GCall::Priv
{
public:
std::vector<GArg> m_args;
const GKernel m_k;
// FIXME: Document that there's no recursion here.
// TODO: Rename to "constructionNode" or smt to reflect its lifetime
GNode m_node;
explicit Priv(const GKernel &k);
};
}
#endif // OPENCV_GCALL_PRIV_HPP
+191
View File
@@ -0,0 +1,191 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <algorithm> // remove_if
#include <cctype> // isspace (non-locale version)
#include <ade/util/algorithm.hpp>
#include "opencv2/core/cvdef.h"
#include "logger.hpp" // GAPI_LOG
#include "opencv2/gapi/gcomputation.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "api/gcomputation_priv.hpp"
#include "api/gcall_priv.hpp"
#include "api/gnode_priv.hpp"
#include "compiler/gmodelbuilder.hpp"
#include "compiler/gcompiler.hpp"
// cv::GComputation private implementation /////////////////////////////////////
// <none>
// cv::GComputation public implementation //////////////////////////////////////
cv::GComputation::GComputation(const Generator& gen)
: m_priv(gen().m_priv)
{
}
cv::GComputation::GComputation(GMat in, GMat out)
: cv::GComputation(cv::GIn(in), cv::GOut(out))
{
}
cv::GComputation::GComputation(GMat in, GScalar out)
: cv::GComputation(cv::GIn(in), cv::GOut(out))
{
}
cv::GComputation::GComputation(GMat in1, GMat in2, GMat out)
: cv::GComputation(cv::GIn(in1, in2), cv::GOut(out))
{
}
cv::GComputation::GComputation(GMat in1, GMat in2, GScalar out)
: cv::GComputation(cv::GIn(in1, in2), cv::GOut(out))
{
}
cv::GComputation::GComputation(const std::vector<GMat> &ins,
const std::vector<GMat> &outs)
: m_priv(new Priv())
{
const auto wrap = [](cv::GMat m) { return GProtoArg(m); };
ade::util::transform(ins, std::back_inserter(m_priv->m_ins), wrap);
ade::util::transform(outs, std::back_inserter(m_priv->m_outs), wrap);
}
cv::GComputation::GComputation(cv::GProtoInputArgs &&ins,
cv::GProtoOutputArgs &&outs)
: m_priv(new Priv())
{
m_priv->m_ins = std::move(ins.m_args);
m_priv->m_outs = std::move(outs.m_args);
}
cv::GCompiled cv::GComputation::compile(GMetaArgs &&metas, GCompileArgs &&args)
{
// FIXME: Cache gcompiled per parameters here?
cv::gimpl::GCompiler comp(*this, std::move(metas), std::move(args));
return comp.compile();
}
void cv::GComputation::apply(GRunArgs &&ins, GRunArgsP &&outs, GCompileArgs &&args)
{
const auto in_metas = descr_of(ins);
// FIXME Graph should be recompiled when GCompileArgs have changed
if (m_priv->m_lastMetas != in_metas)
{
// FIXME: Had to construct temporary object as compile() takes && (r-value)
m_priv->m_lastCompiled = compile(GMetaArgs(in_metas), std::move(args));
m_priv->m_lastMetas = in_metas; // Update only here, if compile() was ok
}
m_priv->m_lastCompiled(std::move(ins), std::move(outs));
}
void cv::GComputation::apply(cv::Mat in, cv::Mat &out, GCompileArgs &&args)
{
apply(cv::gin(in), cv::gout(out), std::move(args));
// FIXME: The following doesn't work!
// Operation result is not replicated into user's object
// apply({GRunArg(in)}, {GRunArg(out)});
}
void cv::GComputation::apply(cv::Mat in, cv::Scalar &out, GCompileArgs &&args)
{
apply(cv::gin(in), cv::gout(out), std::move(args));
}
void cv::GComputation::apply(cv::Mat in1, cv::Mat in2, cv::Mat &out, GCompileArgs &&args)
{
apply(cv::gin(in1, in2), cv::gout(out), std::move(args));
}
void cv::GComputation::apply(cv::Mat in1, cv::Mat in2, cv::Scalar &out, GCompileArgs &&args)
{
apply(cv::gin(in1, in2), cv::gout(out), std::move(args));
}
void cv::GComputation::apply(const std::vector<cv::Mat> &ins,
const std::vector<cv::Mat> &outs,
GCompileArgs &&args)
{
GRunArgs call_ins;
GRunArgsP call_outs;
// Make a temporary copy of vector outs - cv::Mats are copies anyway
auto tmp = outs;
for (const cv::Mat &m : ins) { call_ins.emplace_back(m); }
for ( cv::Mat &m : tmp) { call_outs.emplace_back(&m); }
apply(std::move(call_ins), std::move(call_outs), std::move(args));
}
cv::GComputation::Priv& cv::GComputation::priv()
{
return *m_priv;
}
const cv::GComputation::Priv& cv::GComputation::priv() const
{
return *m_priv;
}
// Islands /////////////////////////////////////////////////////////////////////
void cv::gapi::island(const std::string &name,
GProtoInputArgs &&ins,
GProtoOutputArgs &&outs)
{
{
// Island must have a printable name.
// Forbid names which contain only spaces.
GAPI_Assert(!name.empty());
const auto first_printable_it = std::find_if_not(name.begin(), name.end(), isspace);
const bool likely_printable = first_printable_it != name.end();
GAPI_Assert(likely_printable);
}
// Even if the name contains spaces, keep it unmodified as user will
// then use this string to assign affinity, etc.
// First, set island tags on all operations from `ins` to `outs`
auto island = cv::gimpl::unrollExpr(ins.m_args, outs.m_args);
if (island.all_ops.empty())
{
util::throw_error(std::logic_error("Operation range is empty"));
}
for (auto &op_expr_node : island.all_ops)
{
auto &op_expr_node_p = op_expr_node.priv();
GAPI_Assert(op_expr_node.shape() == GNode::NodeShape::CALL);
const GCall& call = op_expr_node.call();
const GCall::Priv& call_p = call.priv();
if (!op_expr_node_p.m_island.empty())
{
util::throw_error(std::logic_error
( "Operation " + call_p.m_k.name
+ " is already assigned to island \""
+ op_expr_node_p.m_island + "\""));
}
else
{
op_expr_node_p.m_island = name;
GAPI_LOG_INFO(NULL,
"Assigned " << call_p.m_k.name << "_" << &call_p <<
" to island \"" << name << "\"");
}
}
// Note - this function only sets islands to all operations in
// expression tree, it is just a first step.
// The second step is assigning intermediate data objects to Islands,
// see passes::initIslands for details.
}
@@ -0,0 +1,29 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GCOMPUTATION_PRIV_HPP
#define OPENCV_GAPI_GCOMPUTATION_PRIV_HPP
#include "opencv2/gapi.hpp"
#include "opencv2/gapi/gcall.hpp"
#include "opencv2/gapi/util/variant.hpp"
namespace cv {
class GComputation::Priv
{
public:
GCompiled m_lastCompiled;
GMetaArgs m_lastMetas; // TODO: make GCompiled remember its metas?
GProtoArgs m_ins;
GProtoArgs m_outs;
};
}
#endif // OPENCV_GAPI_GCOMPUTATION_PRIV_HPP
+141
View File
@@ -0,0 +1,141 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <iostream> // cerr
#include <functional> // hash
#include <numeric> // accumulate
#include <ade/util/algorithm.hpp>
#include "opencv2/core/cvdef.h"
#include "logger.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "api/gbackend_priv.hpp"
// GKernelPackage public implementation ////////////////////////////////////////
void cv::gapi::GKernelPackage::remove(const cv::gapi::GBackend& backend)
{
m_backend_kernels.erase(backend);
}
bool cv::gapi::GKernelPackage::includesAPI(const std::string &id) const
{
// In current form not very efficient (n * log n)
auto it = std::find_if(m_backend_kernels.begin(),
m_backend_kernels.end(),
[&id](const M::value_type &p) {
return ade::util::contains(p.second, id);
});
return (it != m_backend_kernels.end());
}
std::size_t cv::gapi::GKernelPackage::size() const
{
return std::accumulate(m_backend_kernels.begin(),
m_backend_kernels.end(),
static_cast<std::size_t>(0u),
[](std::size_t acc, const M::value_type& v) {
return acc + v.second.size();
});
}
cv::gapi::GKernelPackage cv::gapi::combine(const GKernelPackage &lhs,
const GKernelPackage &rhs,
const cv::unite_policy policy)
{
if (policy == cv::unite_policy::REPLACE)
{
// REPLACE policy: if there is a collision, prefer RHS
// to LHS
// since OTHER package has a prefernece, start with its copy
GKernelPackage result(rhs);
// now iterate over LHS package and put kernel if and only
// if there's no such one
for (const auto &backend : lhs.m_backend_kernels)
{
for (const auto &kimpl : backend.second)
{
if (!result.includesAPI(kimpl.first))
result.m_backend_kernels[backend.first].insert(kimpl);
}
}
return result;
}
else if (policy == cv::unite_policy::KEEP)
{
// KEEP policy: if there is a collision, just keep two versions
// of a kernel
GKernelPackage result(lhs);
for (const auto &p : rhs.m_backend_kernels)
{
result.m_backend_kernels[p.first].insert(p.second.begin(),
p.second.end());
}
return result;
}
else GAPI_Assert(false);
return GKernelPackage();
}
std::pair<cv::gapi::GBackend, cv::GKernelImpl>
cv::gapi::GKernelPackage::lookup(const std::string &id,
const GLookupOrder &order) const
{
if (order.empty())
{
// If order is empty, return what comes first
auto it = std::find_if(m_backend_kernels.begin(),
m_backend_kernels.end(),
[&id](const M::value_type &p) {
return ade::util::contains(p.second, id);
});
if (it != m_backend_kernels.end())
{
// FIXME: Two lookups!
return std::make_pair(it->first, it->second.find(id)->second);
}
}
else
{
// There is order, so:
// 1. Limit search scope only to specified backends
// FIXME: Currently it is not configurable if search can fall-back
// to other backends (not listed in order) if kernel hasn't been found
// in the look-up list
// 2. Query backends in the specified order
for (const auto &selected_backend : order)
{
const auto kernels_it = m_backend_kernels.find(selected_backend);
if (kernels_it == m_backend_kernels.end())
{
GAPI_LOG_WARNING(NULL,
"Backend "
<< &selected_backend.priv() // FIXME: name instead
<< " was listed in lookup list but was not found "
"in the package");
continue;
}
if (ade::util::contains(kernels_it->second, id))
{
// FIXME: two lookups!
return std::make_pair(selected_backend, kernels_it->second.find(id)->second);
}
}
}
// If reached here, kernel was not found among selected backends.
util::throw_error(std::logic_error("Kernel " + id + " was not found"));
}
std::vector<cv::gapi::GBackend> cv::gapi::GKernelPackage::backends() const
{
std::vector<cv::gapi::GBackend> result;
for (const auto &p : m_backend_kernels) result.emplace_back(p.first);
return result;
}
+71
View File
@@ -0,0 +1,71 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <opencv2/gapi/opencv_includes.hpp>
#include <opencv2/gapi/own/mat.hpp> //gapi::own::Mat
#include "opencv2/gapi/gmat.hpp"
#include "api/gapi_priv.hpp" // GOrigin
// cv::GMat public implementation //////////////////////////////////////////////
cv::GMat::GMat()
: m_priv(new GOrigin(GShape::GMAT, GNode::Param()))
{
}
cv::GMat::GMat(const GNode &n, std::size_t out)
: m_priv(new GOrigin(GShape::GMAT, n, out))
{
}
cv::GOrigin& cv::GMat::priv()
{
return *m_priv;
}
const cv::GOrigin& cv::GMat::priv() const
{
return *m_priv;
}
cv::GMatDesc cv::descr_of(const cv::Mat &mat)
{
return GMatDesc{mat.depth(), mat.channels(), {mat.cols, mat.rows}};
}
cv::GMatDesc cv::gapi::own::descr_of(const cv::gapi::own::Mat &mat)
{
return GMatDesc{mat.depth(), mat.channels(), {mat.cols, mat.rows}};
}
namespace cv {
std::ostream& operator<<(std::ostream& os, const cv::GMatDesc &desc)
{
switch (desc.depth)
{
#define TT(X) case CV_##X: os << #X; break;
TT(8U);
TT(8S);
TT(16U);
TT(16S);
TT(32S);
TT(32F);
TT(64F);
#undef TT
default:
os << "(user type "
<< std::hex << desc.depth << std::dec
<< ")";
break;
}
os << "C" << desc.chan << " ";
os << desc.size.width << "x" << desc.size.height;
return os;
}
}
+88
View File
@@ -0,0 +1,88 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <cassert>
#include "api/gnode.hpp"
#include "api/gnode_priv.hpp"
// GNode private implementation
cv::GNode::Priv::Priv()
: m_shape(NodeShape::EMPTY)
{
}
cv::GNode::Priv::Priv(GCall c)
: m_shape(NodeShape::CALL), m_spec(c)
{
}
cv::GNode::Priv::Priv(ParamTag)
: m_shape(NodeShape::PARAM)
{
}
cv::GNode::Priv::Priv(ConstTag)
: m_shape(NodeShape::CONST_BOUNDED)
{
}
// GNode public implementation
cv::GNode::GNode()
: m_priv(new Priv())
{
}
cv::GNode::GNode(const GCall &c)
: m_priv(new Priv(c))
{
}
cv::GNode::GNode(ParamTag)
: m_priv(new Priv(Priv::ParamTag()))
{
}
cv::GNode::GNode(ConstTag)
: m_priv(new Priv(Priv::ConstTag()))
{
}
cv::GNode cv::GNode::Call(const GCall &c)
{
return GNode(c);
}
cv::GNode cv::GNode::Param()
{
return GNode(ParamTag());
}
cv::GNode cv::GNode::Const()
{
return GNode(ConstTag());
}
cv::GNode::Priv& cv::GNode::priv()
{
return *m_priv;
}
const cv::GNode::Priv& cv::GNode::priv() const
{
return *m_priv;
}
const cv::GNode::NodeShape& cv::GNode::shape() const
{
return m_priv->m_shape;
}
const cv::GCall& cv::GNode::call() const
{
return util::get<GCall>(m_priv->m_spec);
}
+58
View File
@@ -0,0 +1,58 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GNODE_HPP
#define OPENCV_GAPI_GNODE_HPP
#include <memory> // std::shared_ptr
namespace cv {
class GCall;
// TODO Move "internal" namespace
// TODO Expose details?
// This class won't be public
// data GNode = Call Operation [GNode]
// | Const <T>
// | Param <GMat|GParam>
class GNode
{
public:
class Priv;
// Constructors
GNode(); // Empty (invalid) constructor
static GNode Call (const GCall &c); // Call constructor
static GNode Param(); // Param constructor
static GNode Const();
// Internal use only
Priv& priv();
const Priv& priv() const;
enum class NodeShape: unsigned int;
const NodeShape& shape() const;
const GCall& call() const;
protected:
struct ParamTag {};
struct ConstTag {};
explicit GNode(const GCall &c);
explicit GNode(ParamTag unused);
explicit GNode(ConstTag unused);
std::shared_ptr<Priv> m_priv;
};
}
#endif // OPENCV_GAPI_GNODE_HPP
+52
View File
@@ -0,0 +1,52 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GNODE_PRIV_HPP
#define OPENCV_GNODE_PRIV_HPP
#include <string>
#include <vector>
#include <unordered_map>
#include "opencv2/gapi/util/variant.hpp"
#include "opencv2/gapi/gcall.hpp"
#include "opencv2/gapi/garg.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "api/gnode.hpp"
namespace cv {
enum class GNode::NodeShape: unsigned int
{
EMPTY,
CALL,
PARAM,
CONST_BOUNDED
};
class GNode::Priv
{
public:
// TODO: replace with optional?
typedef util::variant<util::monostate, GCall> NodeSpec;
const NodeShape m_shape;
const NodeSpec m_spec;
std::string m_island; // user-modifiable attribute
struct ParamTag {};
struct ConstTag {};
Priv(); // Empty (invalid) constructor
explicit Priv(GCall c); // Call conctrustor
explicit Priv(ParamTag u); // Param constructor
explicit Priv(ConstTag u); // Param constructor
};
}
#endif // OPENCV_GNODE_PRIV_HPP
+152
View File
@@ -0,0 +1,152 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <ade/util/algorithm.hpp>
#include "opencv2/gapi/util/throw.hpp"
#include "opencv2/gapi/garg.hpp"
#include "opencv2/gapi/gproto.hpp"
#include "api/gapi_priv.hpp"
#include "api/gproto_priv.hpp"
// FIXME: it should be a visitor!
// FIXME: Reimplement with traits?
const cv::GOrigin& cv::gimpl::proto::origin_of(const cv::GProtoArg &arg)
{
switch (arg.index())
{
case cv::GProtoArg::index_of<cv::GMat>():
return util::get<cv::GMat>(arg).priv();
case cv::GProtoArg::index_of<cv::GScalar>():
return util::get<cv::GScalar>(arg).priv();
case cv::GProtoArg::index_of<cv::detail::GArrayU>():
return util::get<cv::detail::GArrayU>(arg).priv();
default:
util::throw_error(std::logic_error("Unsupported GProtoArg type"));
}
}
const cv::GOrigin& cv::gimpl::proto::origin_of(const cv::GArg &arg)
{
// Generic, but not very efficient implementation
// FIXME: Walking a thin line here!!! Here we rely that GArg and
// GProtoArg share the same object and this is true while objects
// are reference-counted, so return value is not a reference to a tmp.
return origin_of(rewrap(arg));
}
bool cv::gimpl::proto::is_dynamic(const cv::GArg& arg)
{
// FIXME: refactor this method to be auto-generated from
// - GProtoArg variant parameter pack, and
// - traits over every type
switch (arg.kind)
{
case detail::ArgKind::GMAT:
case detail::ArgKind::GSCALAR:
case detail::ArgKind::GARRAY:
return true;
default:
return false;
}
}
cv::GRunArg cv::value_of(const cv::GOrigin &origin)
{
switch (origin.shape)
{
case GShape::GSCALAR: return GRunArg(util::get<cv::gapi::own::Scalar>(origin.value));
default: util::throw_error(std::logic_error("Unsupported shape for constant"));
}
}
cv::GProtoArg cv::gimpl::proto::rewrap(const cv::GArg &arg)
{
// FIXME: replace with a more generic any->variant
// (or variant<T> -> variant<U>) conversion?
switch (arg.kind)
{
case detail::ArgKind::GMAT: return GProtoArg(arg.get<cv::GMat>());
case detail::ArgKind::GSCALAR: return GProtoArg(arg.get<cv::GScalar>());
case detail::ArgKind::GARRAY: return GProtoArg(arg.get<cv::detail::GArrayU>());
default: util::throw_error(std::logic_error("Unsupported GArg type"));
}
}
cv::GMetaArg cv::descr_of(const cv::GRunArg &arg)
{
switch (arg.index())
{
case GRunArg::index_of<cv::Mat>():
return cv::GMetaArg(descr_of(util::get<cv::Mat>(arg)));
case GRunArg::index_of<cv::Scalar>():
return cv::GMetaArg(descr_of(util::get<cv::Scalar>(arg)));
case GRunArg::index_of<cv::gapi::own::Scalar>():
return cv::GMetaArg(descr_of(util::get<cv::gapi::own::Scalar>(arg)));
case GRunArg::index_of<cv::detail::VectorRef>():
return cv::GMetaArg(util::get<cv::detail::VectorRef>(arg).descr_of());
default: util::throw_error(std::logic_error("Unsupported GRunArg type"));
}
}
cv::GMetaArgs cv::descr_of(const cv::GRunArgs &args)
{
cv::GMetaArgs metas;
ade::util::transform(args, std::back_inserter(metas), [](const cv::GRunArg &arg){ return descr_of(arg); });
return metas;
}
cv::GMetaArg cv::descr_of(const cv::GRunArgP &argp)
{
switch (argp.index())
{
case GRunArgP::index_of<cv::Mat*>(): return GMetaArg(descr_of(*util::get<cv::Mat*>(argp)));
case GRunArgP::index_of<cv::gapi::own::Mat*>(): return GMetaArg(descr_of(*util::get<cv::gapi::own::Mat*>(argp)));
case GRunArgP::index_of<cv::Scalar*>(): return GMetaArg(descr_of(*util::get<cv::Scalar*>(argp)));
case GRunArgP::index_of<cv::gapi::own::Scalar*>(): return GMetaArg(descr_of(*util::get<cv::gapi::own::Scalar*>(argp)));
case GRunArgP::index_of<cv::detail::VectorRef>(): return GMetaArg(util::get<cv::detail::VectorRef>(argp).descr_of());
default: util::throw_error(std::logic_error("Unsupported GRunArgP type"));
}
}
namespace cv {
std::ostream& operator<<(std::ostream& os, const cv::GMetaArg &arg)
{
// FIXME: Implement via variant visitor
switch (arg.index())
{
case cv::GMetaArg::index_of<util::monostate>():
os << "(unresolved)";
break;
case cv::GMetaArg::index_of<cv::GMatDesc>():
os << util::get<cv::GMatDesc>(arg);
break;
case cv::GMetaArg::index_of<cv::GScalarDesc>():
os << util::get<cv::GScalarDesc>(arg);
break;
case cv::GMetaArg::index_of<cv::GArrayDesc>():
os << util::get<cv::GArrayDesc>(arg);
break;
default:
GAPI_Assert(false);
}
return os;
}
}
+35
View File
@@ -0,0 +1,35 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GPROTO_PRIV_HPP
#define OPENCV_GAPI_GPROTO_PRIV_HPP
#include "opencv2/gapi/gproto.hpp"
#include "opencv2/gapi/garg.hpp"
#include "api/gapi_priv.hpp"
namespace cv {
namespace gimpl {
namespace proto {
// These methods are used by GModelBuilder only
// FIXME: Document semantics
// FIXME: GAPI_EXPORTS because of tests only!
// FIXME: Possible dangling reference alert!!!
GAPI_EXPORTS const GOrigin& origin_of (const GProtoArg &arg);
GAPI_EXPORTS const GOrigin& origin_of (const GArg &arg);
bool is_dynamic(const GArg &arg);
GProtoArg rewrap (const GArg &arg);
} // proto
} // gimpl
} // cv
#endif // OPENCV_GAPI_GPROTO_PRIV_HPP
+69
View File
@@ -0,0 +1,69 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include "opencv2/gapi/gscalar.hpp"
#include "opencv2/gapi/own/convert.hpp"
#include "api/gapi_priv.hpp" // GOrigin
// cv::GScalar public implementation ///////////////////////////////////////////
cv::GScalar::GScalar()
: m_priv(new GOrigin(GShape::GSCALAR, cv::GNode::Param()))
{
}
cv::GScalar::GScalar(const GNode &n, std::size_t out)
: m_priv(new GOrigin(GShape::GSCALAR, n, out))
{
}
cv::GScalar::GScalar(const cv::gapi::own::Scalar& s)
: m_priv(new GOrigin(GShape::GSCALAR, cv::gimpl::ConstVal(s)))
{
}
cv::GScalar::GScalar(cv::gapi::own::Scalar&& s)
: m_priv(new GOrigin(GShape::GSCALAR, cv::gimpl::ConstVal(std::move(s))))
{
}
cv::GScalar::GScalar(const cv::Scalar& s)
: m_priv(new GOrigin(GShape::GSCALAR, cv::gimpl::ConstVal(to_own(s))))
{
}
cv::GScalar::GScalar(double v0)
: m_priv(new GOrigin(GShape::GSCALAR, cv::gimpl::ConstVal(cv::gapi::own::Scalar(v0))))
{
}
cv::GOrigin& cv::GScalar::priv()
{
return *m_priv;
}
const cv::GOrigin& cv::GScalar::priv() const
{
return *m_priv;
}
cv::GScalarDesc cv::descr_of(const cv::gapi::own::Scalar &)
{
return empty_scalar_desc();
}
cv::GScalarDesc cv::descr_of(const cv::Scalar& s)
{
return cv::descr_of(to_own(s));
}
namespace cv {
std::ostream& operator<<(std::ostream& os, const cv::GScalarDesc &)
{
os << "(scalar)";
return os;
}
}
+347
View File
@@ -0,0 +1,347 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include "opencv2/gapi/gcall.hpp"
#include "opencv2/gapi/gscalar.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "opencv2/gapi/core.hpp"
#include <tuple>
#include <numeric>
namespace cv { namespace gapi {
GMat add(const GMat& src1, const GMat& src2, int dtype)
{
return core::GAdd::on(src1, src2, dtype);
}
GMat addC(const GMat& src1, const GScalar& c, int dtype)
{
return core::GAddC::on(src1, c, dtype);
}
GMat addC(const GScalar& c, const GMat& src1, int dtype)
{
return core::GAddC::on(src1, c, dtype);
}
GMat sub(const GMat& src1, const GMat& src2, int dtype)
{
return core::GSub::on(src1, src2, dtype);
}
GMat subC(const GMat& src1, const GScalar& c, int dtype)
{
return core::GSubC::on(src1, c, dtype);
}
GMat subRC(const GScalar& c, const GMat& src, int dtype)
{
return core::GSubRC::on(c, src, dtype);
}
GMat mul(const GMat& src1, const GMat& src2, double scale, int dtype)
{
return core::GMul::on(src1, src2, scale, dtype);
}
GMat mulC(const GMat& src, double scale, int dtype)
{
return core::GMulCOld::on(src, scale, dtype);
}
GMat mulC(const GMat& src, const GScalar& multiplier, int dtype)
{
return core::GMulC::on(src, multiplier, dtype);
}
GMat mulC(const GScalar& multiplier, const GMat& src, int dtype)
{
return core::GMulC::on(src, multiplier, dtype);
}
GMat div(const GMat& src1, const GMat& src2, double scale, int dtype)
{
return core::GDiv::on(src1, src2, scale, dtype);
}
GMat divC(const GMat& src, const GScalar& divisor, double scale, int dtype)
{
return core::GDivC::on(src, divisor, scale, dtype);
}
GMat divRC(const GScalar& divident, const GMat& src, double scale, int dtype)
{
return core::GDivRC::on(divident, src, scale, dtype);
}
GScalar mean(const GMat& src)
{
return core::GMean::on(src);
}
GMat mask(const GMat& src, const GMat& mask)
{
return core::GMask::on(src, mask);
}
std::tuple<GMat, GMat> polarToCart(const GMat& magnitude, const GMat& angle,
bool angleInDegrees)
{
return core::GPolarToCart::on(magnitude, angle, angleInDegrees);
}
std::tuple<GMat, GMat> cartToPolar(const GMat& x, const GMat& y,
bool angleInDegrees)
{
return core::GCartToPolar::on(x, y, angleInDegrees);
}
GMat cmpGT(const GMat& src1, const GMat& src2)
{
return core::GCmpGT::on(src1, src2);
}
GMat cmpLT(const GMat& src1, const GMat& src2)
{
return core::GCmpLT::on(src1, src2);
}
GMat cmpGE(const GMat& src1, const GMat& src2)
{
return core::GCmpGE::on(src1, src2);
}
GMat cmpLE(const GMat& src1, const GMat& src2)
{
return core::GCmpLE::on(src1, src2);
}
GMat cmpEQ(const GMat& src1, const GMat& src2)
{
return core::GCmpEQ::on(src1, src2);
}
GMat cmpNE(const GMat& src1, const GMat& src2)
{
return core::GCmpNE::on(src1, src2);
}
GMat cmpGT(const GMat& src1, const GScalar& src2)
{
return core::GCmpGTScalar::on(src1, src2);
}
GMat cmpLT(const GMat& src1, const GScalar& src2)
{
return core::GCmpLTScalar::on(src1, src2);
}
GMat cmpGE(const GMat& src1, const GScalar& src2)
{
return core::GCmpGEScalar::on(src1, src2);
}
GMat cmpLE(const GMat& src1, const GScalar& src2)
{
return core::GCmpLEScalar::on(src1, src2);
}
GMat cmpEQ(const GMat& src1, const GScalar& src2)
{
return core::GCmpEQScalar::on(src1, src2);
}
GMat cmpNE(const GMat& src1, const GScalar& src2)
{
return core::GCmpNEScalar::on(src1, src2);
}
GMat min(const GMat& src1, const GMat& src2)
{
return core::GMin::on(src1, src2);
}
GMat max(const GMat& src1, const GMat& src2)
{
return core::GMax::on(src1, src2);
}
GMat absDiff(const GMat& src1, const GMat& src2)
{
return core::GAbsDiff::on(src1, src2);
}
GMat absDiffC(const GMat& src, const GScalar& c)
{
return core::GAbsDiffC::on(src, c);
}
GMat bitwise_and(const GMat& src1, const GMat& src2)
{
return core::GAnd::on(src1, src2);
}
GMat bitwise_and(const GMat& src1, const GScalar& src2)
{
return core::GAndS::on(src1, src2);
}
GMat bitwise_or(const GMat& src1, const GMat& src2)
{
return core::GOr::on(src1, src2);
}
GMat bitwise_or(const GMat& src1, const GScalar& src2)
{
return core::GOrS::on(src1, src2);
}
GMat bitwise_xor(const GMat& src1, const GMat& src2)
{
return core::GXor::on(src1, src2);
}
GMat bitwise_xor(const GMat& src1, const GScalar& src2)
{
return core::GXorS::on(src1, src2);
}
GMat bitwise_not(const GMat& src1)
{
return core::GNot::on(src1);
}
GMat select(const GMat& src1, const GMat& src2, const GMat& mask)
{
return core::GSelect::on(src1, src2, mask);
}
GScalar sum(const GMat& src)
{
return core::GSum::on(src);
}
GMat addWeighted(const GMat& src1, double alpha, const GMat& src2, double beta, double gamma, int dtype)
{
return core::GAddW::on(src1, alpha, src2, beta, gamma, dtype);
}
GScalar normL1(const GMat& src)
{
return core::GNormL1::on(src);
}
GScalar normL2(const GMat& src)
{
return core::GNormL2::on(src);
}
GScalar normInf(const GMat& src)
{
return core::GNormInf::on(src);
}
std::tuple<GMat, GMat> integral(const GMat& src, int sdepth, int sqdepth)
{
return core::GIntegral::on(src, sdepth, sqdepth);
}
GMat threshold(const GMat& src, const GScalar& thresh, const GScalar& maxval, int type)
{
GAPI_Assert(type != cv::THRESH_TRIANGLE && type != cv::THRESH_OTSU);
return core::GThreshold::on(src, thresh, maxval, type);
}
std::tuple<GMat, GScalar> threshold(const GMat& src, const GScalar& maxval, int type)
{
GAPI_Assert(type == cv::THRESH_TRIANGLE || type == cv::THRESH_OTSU);
return core::GThresholdOT::on(src, maxval, type);
}
GMat inRange(const GMat& src, const GScalar& threshLow, const GScalar& threshUp)
{
return core::GInRange::on(src, threshLow, threshUp);
}
std::tuple<GMat, GMat, GMat> split3(const GMat& src)
{
return core::GSplit3::on(src);
}
std::tuple<GMat, GMat, GMat, GMat> split4(const GMat& src)
{
return core::GSplit4::on(src);
}
GMat merge3(const GMat& src1, const GMat& src2, const GMat& src3)
{
return core::GMerge3::on(src1, src2, src3);
}
GMat merge4(const GMat& src1, const GMat& src2, const GMat& src3, const GMat& src4)
{
return core::GMerge4::on(src1, src2, src3, src4);
}
GMat resize(const GMat& src, const Size& dsize, double fx, double fy, int interpolation)
{
return core::GResize::on(src, dsize, fx, fy, interpolation);
}
GMat remap(const GMat& src, const Mat& map1, const Mat& map2,
int interpolation, int borderMode,
const Scalar& borderValue)
{
return core::GRemap::on(src, map1, map2, interpolation, borderMode, borderValue);
}
GMat flip(const GMat& src, int flipCode)
{
return core::GFlip::on(src, flipCode);
}
GMat crop(const GMat& src, const Rect& rect)
{
return core::GCrop::on(src, rect);
}
GMat concatHor(const GMat& src1, const GMat& src2)
{
return core::GConcatHor::on(src1, src2);
}
GMat concatHor(const std::vector<GMat>& v)
{
GAPI_Assert(v.size() >= 2);
return std::accumulate(v.begin()+1, v.end(), v[0], core::GConcatHor::on);
}
GMat concatVert(const GMat& src1, const GMat& src2)
{
return core::GConcatVert::on(src1, src2);
}
GMat concatVert(const std::vector<GMat>& v)
{
GAPI_Assert(v.size() >= 2);
return std::accumulate(v.begin()+1, v.end(), v[0], core::GConcatVert::on);
}
GMat LUT(const GMat& src, const Mat& lut)
{
return core::GLUT::on(src, lut);
}
GMat convertTo(const GMat& m, int rtype, double alpha, double beta)
{
return core::GConvertTo::on(m, rtype, alpha, beta);
}
} //namespace gapi
} //namespace cv
+142
View File
@@ -0,0 +1,142 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include "opencv2/gapi/gscalar.hpp"
#include "opencv2/gapi/gcall.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "opencv2/gapi/imgproc.hpp"
namespace cv { namespace gapi {
GMat sepFilter(const GMat& src, int ddepth, const Mat& kernelX, const Mat& kernelY, const Point& anchor,
const Scalar& delta, int borderType, const Scalar& borderVal)
{
return imgproc::GSepFilter::on(src, ddepth, kernelX, kernelY, anchor, delta, borderType, borderVal);
}
GMat filter2D(const GMat& src, int ddepth, const Mat& kernel, const Point& anchor, const Scalar& delta, int borderType,
const Scalar& bordVal)
{
return imgproc::GFilter2D::on(src, ddepth, kernel, anchor, delta, borderType, bordVal);
}
GMat boxFilter(const GMat& src, int dtype, const Size& ksize, const Point& anchor,
bool normalize, int borderType, const Scalar& bordVal)
{
return imgproc::GBoxFilter::on(src, dtype, ksize, anchor, normalize, borderType, bordVal);
}
GMat blur(const GMat& src, const Size& ksize, const Point& anchor,
int borderType, const Scalar& bordVal)
{
return imgproc::GBlur::on(src, ksize, anchor, borderType, bordVal);
}
GMat gaussianBlur(const GMat& src, const Size& ksize, double sigmaX, double sigmaY,
int borderType, const Scalar& bordVal)
{
return imgproc::GGaussBlur::on(src, ksize, sigmaX, sigmaY, borderType, bordVal);
}
GMat medianBlur(const GMat& src, int ksize)
{
return imgproc::GMedianBlur::on(src, ksize);
}
GMat erode(const GMat& src, const Mat& kernel, const Point& anchor, int iterations,
int borderType, const Scalar& borderValue )
{
return imgproc::GErode::on(src, kernel, anchor, iterations, borderType, borderValue);
}
GMat erode3x3(const GMat& src, int iterations,
int borderType, const Scalar& borderValue )
{
return erode(src, cv::Mat(), cv::Point(-1, -1), iterations, borderType, borderValue);
}
GMat dilate(const GMat& src, const Mat& kernel, const Point& anchor, int iterations,
int borderType, const Scalar& borderValue)
{
return imgproc::GDilate::on(src, kernel, anchor, iterations, borderType, borderValue);
}
GMat dilate3x3(const GMat& src, int iterations,
int borderType, const Scalar& borderValue)
{
return dilate(src, cv::Mat(), cv::Point(-1,-1), iterations, borderType, borderValue);
}
GMat sobel(const GMat& src, int ddepth, int dx, int dy, int ksize,
double scale, double delta,
int borderType, const Scalar& bordVal)
{
return imgproc::GSobel::on(src, ddepth, dx, dy, ksize, scale, delta, borderType, bordVal);
}
GMat equalizeHist(const GMat& src)
{
return imgproc::GEqHist::on(src);
}
GMat Canny(const GMat& src, double thr1, double thr2, int apertureSize, bool l2gradient)
{
return imgproc::GCanny::on(src, thr1, thr2, apertureSize, l2gradient);
}
GMat RGB2Gray(const GMat& src)
{
return imgproc::GRGB2Gray::on(src);
}
GMat RGB2Gray(const GMat& src, float rY, float gY, float bY)
{
return imgproc::GRGB2GrayCustom::on(src, rY, gY, bY);
}
GMat BGR2Gray(const GMat& src)
{
return imgproc::GBGR2Gray::on(src);
}
GMat RGB2YUV(const GMat& src)
{
return imgproc::GRGB2YUV::on(src);
}
GMat BGR2LUV(const GMat& src)
{
return imgproc::GBGR2LUV::on(src);
}
GMat LUV2BGR(const GMat& src)
{
return imgproc::GLUV2BGR::on(src);
}
GMat BGR2YUV(const GMat& src)
{
return imgproc::GBGR2YUV::on(src);
}
GMat YUV2BGR(const GMat& src)
{
return imgproc::GYUV2BGR::on(src);
}
GMat YUV2RGB(const GMat& src)
{
return imgproc::GYUV2RGB::on(src);
}
GMat RGB2Lab(const GMat& src)
{
return imgproc::GRGB2Lab::on(src);
}
} //namespace gapi
} //namespace cv
+211
View File
@@ -0,0 +1,211 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include "opencv2/gapi/imgproc.hpp"
#include "opencv2/gapi/core.hpp"
#include "opencv2/gapi/gscalar.hpp"
#include "opencv2/gapi/operators.hpp"
cv::GMat operator+(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::add(lhs, rhs);
}
cv::GMat operator+(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::addC(lhs, rhs);
}
cv::GMat operator+(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::addC(rhs, lhs);
}
cv::GMat operator-(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::sub(lhs, rhs);
}
cv::GMat operator-(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::subC(lhs, rhs);
}
cv::GMat operator-(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::subRC(lhs, rhs);
}
cv::GMat operator*(const cv::GMat& lhs, float rhs)
{
return cv::gapi::mulC(lhs, static_cast<double>(rhs));
}
cv::GMat operator*(float lhs, const cv::GMat& rhs)
{
return cv::gapi::mulC(rhs, static_cast<double>(lhs));
}
cv::GMat operator*(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::mulC(lhs, rhs);
}
cv::GMat operator*(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::mulC(rhs, lhs);
}
cv::GMat operator/(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::divC(lhs, rhs, 1.0);
}
cv::GMat operator/(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::div(lhs, rhs, 1.0);
}
cv::GMat operator/(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::divRC(lhs, rhs, 1.0);
}
cv::GMat operator&(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::bitwise_and(lhs, rhs);
}
cv::GMat operator&(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::bitwise_and(lhs, rhs);
}
cv::GMat operator&(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::bitwise_and(rhs, lhs);
}
cv::GMat operator|(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::bitwise_or(lhs, rhs);
}
cv::GMat operator|(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::bitwise_or(lhs, rhs);
}
cv::GMat operator|(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::bitwise_or(rhs, lhs);
}
cv::GMat operator^(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::bitwise_xor(lhs, rhs);
}
cv::GMat operator^(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::bitwise_xor(lhs, rhs);
}
cv::GMat operator^(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::bitwise_xor(rhs, lhs);
}
cv::GMat operator~(const cv::GMat& lhs)
{
return cv::gapi::bitwise_not(lhs);
}
cv::GMat operator>(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpGT(lhs, rhs);
}
cv::GMat operator>=(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpGE(lhs, rhs);
}
cv::GMat operator<(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpLT(lhs, rhs);
}
cv::GMat operator<=(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpLE(lhs, rhs);
}
cv::GMat operator==(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpEQ(lhs, rhs);
}
cv::GMat operator!=(const cv::GMat& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpNE(lhs, rhs);
}
cv::GMat operator>(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::cmpGT(lhs, rhs);
}
cv::GMat operator>=(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::cmpGE(lhs, rhs);
}
cv::GMat operator<(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::cmpLT(lhs, rhs);
}
cv::GMat operator<=(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::cmpLE(lhs, rhs);
}
cv::GMat operator==(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::cmpEQ(lhs, rhs);
}
cv::GMat operator!=(const cv::GMat& lhs, const cv::GScalar& rhs)
{
return cv::gapi::cmpNE(lhs, rhs);
}
cv::GMat operator>(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpLT(rhs, lhs);
}
cv::GMat operator>=(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpLE(rhs, lhs);
}
cv::GMat operator<(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpGT(rhs, lhs);
}
cv::GMat operator<=(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpGE(rhs, lhs);
}
cv::GMat operator==(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpEQ(rhs, lhs);
}
cv::GMat operator!=(const cv::GScalar& lhs, const cv::GMat& rhs)
{
return cv::gapi::cmpNE(rhs, lhs);
}
+2
View File
@@ -0,0 +1,2 @@
This directory contains various G-API backends, which provide scheduling
logic and kernel implementations for specific targets.
@@ -0,0 +1,103 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GBACKEND_HPP
#define OPENCV_GAPI_GBACKEND_HPP
#include <string>
#include <memory>
#include <ade/node.hpp>
#include "opencv2/gapi/garg.hpp"
#include "opencv2/gapi/own/mat.hpp"
#include "opencv2/gapi/util/optional.hpp"
#include "opencv2/gapi/own/scalar.hpp"
#include "compiler/gmodel.hpp"
namespace cv {
namespace gimpl {
// Forward declarations
struct Data;
struct RcDesc;
namespace magazine {
template<typename... Ts> struct Class
{
template<typename T> using MapT = std::unordered_map<int, T>;
template<typename T> MapT<T>& slot()
{
return std::get<ade::util::type_list_index<T, Ts...>::value>(slots);
}
template<typename T> const MapT<T>& slot() const
{
return std::get<ade::util::type_list_index<T, Ts...>::value>(slots);
}
private:
std::tuple<MapT<Ts>...> slots;
};
} // namespace magazine
using Mag = magazine::Class<cv::gapi::own::Mat, cv::gapi::own::Scalar, cv::detail::VectorRef>;
namespace magazine
{
void bindInArg (Mag& mag, const RcDesc &rc, const GRunArg &arg);
void bindOutArg(Mag& mag, const RcDesc &rc, const GRunArgP &arg);
void resetInternalData(Mag& mag, const Data &d);
cv::GRunArg getArg (const Mag& mag, const RcDesc &ref);
cv::GRunArgP getObjPtr ( Mag& mag, const RcDesc &rc);
void writeBack (const Mag& mag, const RcDesc &rc, GRunArgP &g_arg);
} // namespace magazine
namespace detail
{
template<typename... Ts> struct magazine
{
template<typename T> using MapT = std::unordered_map<int, T>;
template<typename T> MapT<T>& slot()
{
return std::get<util::type_list_index<T, Ts...>::value>(slots);
}
template<typename T> const MapT<T>& slot() const
{
return std::get<util::type_list_index<T, Ts...>::value>(slots);
}
private:
std::tuple<MapT<Ts>...> slots;
};
} // namespace detail
struct GRuntimeArgs
{
GRunArgs inObjs;
GRunArgsP outObjs;
};
template<typename T>
inline cv::util::optional<T> getCompileArg(const cv::GCompileArgs &args)
{
for (auto &compile_arg : args)
{
if (compile_arg.tag == cv::detail::CompileArgTag<T>::tag())
{
return cv::util::optional<T>(compile_arg.get<T>());
}
}
return cv::util::optional<T>();
}
}} // cv::gimpl
#endif // OPENCV_GAPI_GBACKEND_HPP
@@ -0,0 +1,18 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include "opencv2/gapi/gcompoundkernel.hpp" // compound::backend()
#include "api/gbackend_priv.hpp"
#include "compiler/gislandmodel.hpp" // GIslandExecutable
cv::gapi::GBackend cv::gapi::compound::backend()
{
// A pointer to dummy Priv is used to uniquely identify backends
static cv::gapi::GBackend this_backend(std::make_shared<cv::gapi::GBackend::Priv>());
return this_backend;
}
@@ -0,0 +1,45 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <ade/util/zip_range.hpp> // util::indexed
#include "opencv2/gapi/gcompoundkernel.hpp"
#include "compiler/gobjref.hpp"
// FIXME move to backends
cv::detail::GCompoundContext::GCompoundContext(const cv::GArgs& in_args)
{
m_args.resize(in_args.size());
for (const auto& it : ade::util::indexed(in_args))
{
const auto& i = ade::util::index(it);
const auto& in_arg = ade::util::value(it);
if (in_arg.kind != cv::detail::ArgKind::GOBJREF)
{
m_args[i] = in_arg;
}
else
{
const cv::gimpl::RcDesc &ref = in_arg.get<cv::gimpl::RcDesc>();
switch (ref.shape)
{
case GShape::GMAT : m_args[i] = GArg(GMat()); break;
case GShape::GSCALAR: m_args[i] = GArg(GScalar()); break;
case GShape::GARRAY :/* do nothing - as handled in a special way, see gcompoundkernel.hpp for details */; break;
default: GAPI_Assert(false);
}
}
}
GAPI_Assert(m_args.size() == in_args.size());
}
cv::detail::GCompoundKernel::GCompoundKernel(const F& f) : m_f(f)
{
}
void cv::detail::GCompoundKernel::apply(cv::detail::GCompoundContext& ctx) { m_f(ctx); }
@@ -0,0 +1,227 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <functional>
#include <unordered_set>
#include <ade/util/algorithm.hpp>
#include <ade/util/range.hpp>
#include <ade/util/zip_range.hpp>
#include <ade/util/chain_range.hpp>
#include <ade/typed_graph.hpp>
#include "opencv2/gapi/gcommon.hpp"
#include "opencv2/gapi/util/any.hpp"
#include "opencv2/gapi/gtype_traits.hpp"
#include "compiler/gobjref.hpp"
#include "compiler/gmodel.hpp"
#include "backends/cpu/gcpubackend.hpp"
#include "backends/cpu/gcpuimgproc.hpp"
#include "backends/cpu/gcpucore.hpp"
#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
// FIXME: Is there a way to take a typed graph (our GModel),
// and create a new typed graph _ATOP_ of that (by extending with a couple of
// new types?).
// Alternatively, is there a way to compose types graphs?
//
// If not, we need to introduce that!
using GCPUModel = ade::TypedGraph
< cv::gimpl::Unit
, cv::gimpl::Protocol
>;
// FIXME: Same issue with Typed and ConstTyped
using GConstGCPUModel = ade::ConstTypedGraph
< cv::gimpl::Unit
, cv::gimpl::Protocol
>;
namespace
{
class GCPUBackendImpl final: public cv::gapi::GBackend::Priv
{
virtual void unpackKernel(ade::Graph &graph,
const ade::NodeHandle &op_node,
const cv::GKernelImpl &impl) override
{
GCPUModel gm(graph);
auto cpu_impl = cv::util::any_cast<cv::GCPUKernel>(impl.opaque);
gm.metadata(op_node).set(cv::gimpl::Unit{cpu_impl});
}
virtual EPtr compile(const ade::Graph &graph,
const cv::GCompileArgs &,
const std::vector<ade::NodeHandle> &nodes) const override
{
return EPtr{new cv::gimpl::GCPUExecutable(graph, nodes)};
}
};
}
cv::gapi::GBackend cv::gapi::cpu::backend()
{
static cv::gapi::GBackend this_backend(std::make_shared<GCPUBackendImpl>());
return this_backend;
}
// GCPUExcecutable implementation //////////////////////////////////////////////
cv::gimpl::GCPUExecutable::GCPUExecutable(const ade::Graph &g,
const std::vector<ade::NodeHandle> &nodes)
: m_g(g), m_gm(m_g)
{
// Convert list of operations (which is topologically sorted already)
// into an execution script.
for (auto &nh : nodes)
{
switch (m_gm.metadata(nh).get<NodeType>().t)
{
case NodeType::OP: m_script.push_back({nh, GModel::collectOutputMeta(m_gm, nh)}); break;
case NodeType::DATA:
{
m_dataNodes.push_back(nh);
const auto &desc = m_gm.metadata(nh).get<Data>();
if (desc.storage == Data::Storage::CONST)
{
auto rc = RcDesc{desc.rc, desc.shape, desc.ctor};
magazine::bindInArg(m_res, rc, m_gm.metadata(nh).get<ConstValue>().arg);
}
//preallocate internal Mats in advance
if (desc.storage == Data::Storage::INTERNAL && desc.shape == GShape::GMAT)
{
const auto mat_desc = util::get<cv::GMatDesc>(desc.meta);
const auto type = CV_MAKETYPE(mat_desc.depth, mat_desc.chan);
m_res.slot<cv::gapi::own::Mat>()[desc.rc].create(mat_desc.size, type);
}
break;
}
default: util::throw_error(std::logic_error("Unsupported NodeType type"));
}
}
}
// FIXME: Document what it does
cv::GArg cv::gimpl::GCPUExecutable::packArg(const GArg &arg)
{
// No API placeholders allowed at this point
// FIXME: this check has to be done somewhere in compilation stage.
GAPI_Assert( arg.kind != cv::detail::ArgKind::GMAT
&& arg.kind != cv::detail::ArgKind::GSCALAR
&& arg.kind != cv::detail::ArgKind::GARRAY);
if (arg.kind != cv::detail::ArgKind::GOBJREF)
{
// All other cases - pass as-is, with no transformations to GArg contents.
return arg;
}
GAPI_Assert(arg.kind == cv::detail::ArgKind::GOBJREF);
// Wrap associated CPU object (either host or an internal one)
// FIXME: object can be moved out!!! GExecutor faced that.
const cv::gimpl::RcDesc &ref = arg.get<cv::gimpl::RcDesc>();
switch (ref.shape)
{
case GShape::GMAT: return GArg(m_res.slot<cv::gapi::own::Mat>() [ref.id]);
case GShape::GSCALAR: return GArg(m_res.slot<cv::gapi::own::Scalar>()[ref.id]);
// Note: .at() is intentional for GArray as object MUST be already there
// (and constructed by either bindIn/Out or resetInternal)
case GShape::GARRAY: return GArg(m_res.slot<cv::detail::VectorRef>().at(ref.id));
default:
util::throw_error(std::logic_error("Unsupported GShape type"));
break;
}
}
void cv::gimpl::GCPUExecutable::run(std::vector<InObj> &&input_objs,
std::vector<OutObj> &&output_objs)
{
// Update resources with run-time information - what this Island
// has received from user (or from another Island, or mix...)
// FIXME: Check input/output objects against GIsland protocol
for (auto& it : input_objs) magazine::bindInArg (m_res, it.first, it.second);
for (auto& it : output_objs) magazine::bindOutArg(m_res, it.first, it.second);
// Initialize (reset) internal data nodes with user structures
// before processing a frame (no need to do it for external data structures)
GModel::ConstGraph gm(m_g);
for (auto nh : m_dataNodes)
{
const auto &desc = gm.metadata(nh).get<Data>();
if ( desc.storage == Data::Storage::INTERNAL
&& !util::holds_alternative<util::monostate>(desc.ctor))
{
// FIXME: Note that compile-time constant data objects (like
// a value-initialized GArray<T>) also satisfy this condition
// and should be excluded, but now we just don't support it
magazine::resetInternalData(m_res, desc);
}
}
// OpenCV backend execution is not a rocket science at all.
// Simply invoke our kernels in the proper order.
GConstGCPUModel gcm(m_g);
for (auto &op_info : m_script)
{
const auto &op = m_gm.metadata(op_info.nh).get<Op>();
// Obtain our real execution unit
// TODO: Should kernels be copyable?
GCPUKernel k = gcm.metadata(op_info.nh).get<Unit>().k;
// Initialize kernel's execution context:
// - Input parameters
GCPUContext context;
context.m_args.reserve(op.args.size());
using namespace std::placeholders;
ade::util::transform(op.args,
std::back_inserter(context.m_args),
std::bind(&GCPUExecutable::packArg, this, _1));
// - Output parameters.
// FIXME: pre-allocate internal Mats, etc, according to the known meta
for (const auto &out_it : ade::util::indexed(op.outs))
{
// FIXME: Can the same GArg type resolution mechanism be reused here?
const auto out_port = ade::util::index(out_it);
const auto out_desc = ade::util::value(out_it);
context.m_results[out_port] = magazine::getObjPtr(m_res, out_desc);
}
// Now trigger the executable unit
k.apply(context);
//As Kernels are forbidden to allocate memory for (Mat) outputs,
//this code seems redundant, at least for Mats
//FIXME: unify with cv::detail::ensure_out_mats_not_reallocated
for (const auto &out_it : ade::util::indexed(op_info.expected_out_metas))
{
const auto out_index = ade::util::index(out_it);
const auto expected_meta = ade::util::value(out_it);
const auto out_meta = descr_of(context.m_results[out_index]);
if (expected_meta != out_meta)
{
util::throw_error
(std::logic_error
("Output meta doesn't "
"coincide with the generated meta\n"
"Expected: " + ade::util::to_string(expected_meta) + "\n"
"Actual : " + ade::util::to_string(out_meta)));
}
}
} // for(m_script)
for (auto &it : output_objs) magazine::writeBack(m_res, it.first, it.second);
}
@@ -0,0 +1,63 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GCPUBACKEND_HPP
#define OPENCV_GAPI_GCPUBACKEND_HPP
#include <map> // map
#include <unordered_map> // unordered_map
#include <tuple> // tuple
#include <ade/util/algorithm.hpp> // type_list_index
#include "opencv2/gapi/garg.hpp"
#include "opencv2/gapi/gproto.hpp"
#include "opencv2/gapi/cpu/gcpukernel.hpp"
#include "api/gapi_priv.hpp"
#include "backends/common/gbackend.hpp"
#include "compiler/gislandmodel.hpp"
namespace cv { namespace gimpl {
struct Unit
{
static const char *name() { return "HostKernel"; }
GCPUKernel k;
};
class GCPUExecutable final: public GIslandExecutable
{
const ade::Graph &m_g;
GModel::ConstGraph m_gm;
struct OperationInfo
{
ade::NodeHandle nh;
GMetaArgs expected_out_metas;
};
// Execution script, currently absolutely naive
std::vector<OperationInfo> m_script;
// List of all resources in graph (both internal and external)
std::vector<ade::NodeHandle> m_dataNodes;
// Actual data of all resources in graph (both internal and external)
Mag m_res;
GArg packArg(const GArg &arg);
public:
GCPUExecutable(const ade::Graph &graph,
const std::vector<ade::NodeHandle> &nodes);
virtual void run(std::vector<InObj> &&input_objs,
std::vector<OutObj> &&output_objs) override;
};
}}
#endif // OPENCV_GAPI_GBACKEND_HPP
+577
View File
@@ -0,0 +1,577 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include "precomp.hpp"
#include "opencv2/gapi/core.hpp"
#include "opencv2/gapi/cpu/core.hpp"
#include "backends/cpu/gcpucore.hpp"
GAPI_OCV_KERNEL(GCPUAdd, cv::gapi::core::GAdd)
{
static void run(const cv::Mat& a, const cv::Mat& b, int dtype, cv::Mat& out)
{
cv::add(a, b, out, cv::noArray(), dtype);
}
};
GAPI_OCV_KERNEL(GCPUAddC, cv::gapi::core::GAddC)
{
static void run(const cv::Mat& a, const cv::Scalar& b, int dtype, cv::Mat& out)
{
cv::add(a, b, out, cv::noArray(), dtype);
}
};
GAPI_OCV_KERNEL(GCPUSub, cv::gapi::core::GSub)
{
static void run(const cv::Mat& a, const cv::Mat& b, int dtype, cv::Mat& out)
{
cv::subtract(a, b, out, cv::noArray(), dtype);
}
};
GAPI_OCV_KERNEL(GCPUSubC, cv::gapi::core::GSubC)
{
static void run(const cv::Mat& a, const cv::Scalar& b, int dtype, cv::Mat& out)
{
cv::subtract(a, b, out, cv::noArray(), dtype);
}
};
GAPI_OCV_KERNEL(GCPUSubRC, cv::gapi::core::GSubRC)
{
static void run(const cv::Scalar& a, const cv::Mat& b, int dtype, cv::Mat& out)
{
cv::subtract(a, b, out, cv::noArray(), dtype);
}
};
GAPI_OCV_KERNEL(GCPUMul, cv::gapi::core::GMul)
{
static void run(const cv::Mat& a, const cv::Mat& b, double scale, int dtype, cv::Mat& out)
{
cv::multiply(a, b, out, scale, dtype);
}
};
GAPI_OCV_KERNEL(GCPUMulCOld, cv::gapi::core::GMulCOld)
{
static void run(const cv::Mat& a, double b, int dtype, cv::Mat& out)
{
cv::multiply(a, b, out, 1, dtype);
}
};
GAPI_OCV_KERNEL(GCPUMulC, cv::gapi::core::GMulC)
{
static void run(const cv::Mat& a, const cv::Scalar& b, int dtype, cv::Mat& out)
{
cv::multiply(a, b, out, 1, dtype);
}
};
GAPI_OCV_KERNEL(GCPUDiv, cv::gapi::core::GDiv)
{
static void run(const cv::Mat& a, const cv::Mat& b, double scale, int dtype, cv::Mat& out)
{
cv::divide(a, b, out, scale, dtype);
}
};
GAPI_OCV_KERNEL(GCPUDivC, cv::gapi::core::GDivC)
{
static void run(const cv::Mat& a, const cv::Scalar& b, double scale, int dtype, cv::Mat& out)
{
cv::divide(a, b, out, scale, dtype);
}
};
GAPI_OCV_KERNEL(GCPUDivRC, cv::gapi::core::GDivRC)
{
static void run(const cv::Scalar& a, const cv::Mat& b, double scale, int dtype, cv::Mat& out)
{
cv::divide(a, b, out, scale, dtype);
}
};
GAPI_OCV_KERNEL(GCPUMask, cv::gapi::core::GMask)
{
static void run(const cv::Mat& in, const cv::Mat& mask, cv::Mat& out)
{
out = cv::Mat::zeros(in.size(), in.type());
in.copyTo(out, mask);
}
};
GAPI_OCV_KERNEL(GCPUMean, cv::gapi::core::GMean)
{
static void run(const cv::Mat& in, cv::Scalar& out)
{
out = cv::mean(in);
}
};
GAPI_OCV_KERNEL(GCPUPolarToCart, cv::gapi::core::GPolarToCart)
{
static void run(const cv::Mat& magn, const cv::Mat& angle, bool angleInDegrees, cv::Mat& outx, cv::Mat& outy)
{
cv::polarToCart(magn, angle, outx, outy, angleInDegrees);
}
};
GAPI_OCV_KERNEL(GCPUCartToPolar, cv::gapi::core::GCartToPolar)
{
static void run(const cv::Mat& x, const cv::Mat& y, bool angleInDegrees, cv::Mat& outmagn, cv::Mat& outangle)
{
cv::cartToPolar(x, y, outmagn, outangle, angleInDegrees);
}
};
GAPI_OCV_KERNEL(GCPUCmpGT, cv::gapi::core::GCmpGT)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_GT);
}
};
GAPI_OCV_KERNEL(GCPUCmpGE, cv::gapi::core::GCmpGE)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_GE);
}
};
GAPI_OCV_KERNEL(GCPUCmpLE, cv::gapi::core::GCmpLE)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_LE);
}
};
GAPI_OCV_KERNEL(GCPUCmpLT, cv::gapi::core::GCmpLT)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_LT);
}
};
GAPI_OCV_KERNEL(GCPUCmpEQ, cv::gapi::core::GCmpEQ)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_EQ);
}
};
GAPI_OCV_KERNEL(GCPUCmpNE, cv::gapi::core::GCmpNE)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_NE);
}
};
GAPI_OCV_KERNEL(GCPUCmpGTScalar, cv::gapi::core::GCmpGTScalar)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_GT);
}
};
GAPI_OCV_KERNEL(GCPUCmpGEScalar, cv::gapi::core::GCmpGEScalar)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_GE);
}
};
GAPI_OCV_KERNEL(GCPUCmpLEScalar, cv::gapi::core::GCmpLEScalar)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_LE);
}
};
GAPI_OCV_KERNEL(GCPUCmpLTScalar, cv::gapi::core::GCmpLTScalar)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_LT);
}
};
GAPI_OCV_KERNEL(GCPUCmpEQScalar, cv::gapi::core::GCmpEQScalar)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_EQ);
}
};
GAPI_OCV_KERNEL(GCPUCmpNEScalar, cv::gapi::core::GCmpNEScalar)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::compare(a, b, out, cv::CMP_NE);
}
};
GAPI_OCV_KERNEL(GCPUAnd, cv::gapi::core::GAnd)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::bitwise_and(a, b, out);
}
};
GAPI_OCV_KERNEL(GCPUAndS, cv::gapi::core::GAndS)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::bitwise_and(a, b, out);
}
};
GAPI_OCV_KERNEL(GCPUOr, cv::gapi::core::GOr)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::bitwise_or(a, b, out);
}
};
GAPI_OCV_KERNEL(GCPUOrS, cv::gapi::core::GOrS)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::bitwise_or(a, b, out);
}
};
GAPI_OCV_KERNEL(GCPUXor, cv::gapi::core::GXor)
{
static void run(const cv::Mat& a, const cv::Mat& b, cv::Mat& out)
{
cv::bitwise_xor(a, b, out);
}
};
GAPI_OCV_KERNEL(GCPUXorS, cv::gapi::core::GXorS)
{
static void run(const cv::Mat& a, const cv::Scalar& b, cv::Mat& out)
{
cv::bitwise_xor(a, b, out);
}
};
GAPI_OCV_KERNEL(GCPUNot, cv::gapi::core::GNot)
{
static void run(const cv::Mat& a, cv::Mat& out)
{
cv::bitwise_not(a, out);
}
};
GAPI_OCV_KERNEL(GCPUSelect, cv::gapi::core::GSelect)
{
static void run(const cv::Mat& src1, const cv::Mat& src2, const cv::Mat& mask, cv::Mat& out)
{
src2.copyTo(out);
src1.copyTo(out, mask);
}
};
GAPI_OCV_KERNEL(GCPUMin, cv::gapi::core::GMin)
{
static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out)
{
out = cv::min(in1, in2);
}
};
GAPI_OCV_KERNEL(GCPUMax, cv::gapi::core::GMax)
{
static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out)
{
out = cv::max(in1, in2);
}
};
GAPI_OCV_KERNEL(GCPUAbsDiff, cv::gapi::core::GAbsDiff)
{
static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out)
{
cv::absdiff(in1, in2, out);
}
};
GAPI_OCV_KERNEL(GCPUAbsDiffC, cv::gapi::core::GAbsDiffC)
{
static void run(const cv::Mat& in1, const cv::Scalar& in2, cv::Mat& out)
{
cv::absdiff(in1, in2, out);
}
};
GAPI_OCV_KERNEL(GCPUSum, cv::gapi::core::GSum)
{
static void run(const cv::Mat& in, cv::Scalar& out)
{
out = cv::sum(in);
}
};
GAPI_OCV_KERNEL(GCPUAddW, cv::gapi::core::GAddW)
{
static void run(const cv::Mat& in1, double alpha, const cv::Mat& in2, double beta, double gamma, int dtype, cv::Mat& out)
{
cv::addWeighted(in1, alpha, in2, beta, gamma, out, dtype);
}
};
GAPI_OCV_KERNEL(GCPUNormL1, cv::gapi::core::GNormL1)
{
static void run(const cv::Mat& in, cv::Scalar& out)
{
out = cv::norm(in, cv::NORM_L1);
}
};
GAPI_OCV_KERNEL(GCPUNormL2, cv::gapi::core::GNormL2)
{
static void run(const cv::Mat& in, cv::Scalar& out)
{
out = cv::norm(in, cv::NORM_L2);
}
};
GAPI_OCV_KERNEL(GCPUNormInf, cv::gapi::core::GNormInf)
{
static void run(const cv::Mat& in, cv::Scalar& out)
{
out = cv::norm(in, cv::NORM_INF);
}
};
GAPI_OCV_KERNEL(GCPUIntegral, cv::gapi::core::GIntegral)
{
static void run(const cv::Mat& in, int sdepth, int sqdepth, cv::Mat& out, cv::Mat& outSq)
{
cv::integral(in, out, outSq, sdepth, sqdepth);
}
};
GAPI_OCV_KERNEL(GCPUThreshold, cv::gapi::core::GThreshold)
{
static void run(const cv::Mat& in, const cv::Scalar& a, const cv::Scalar& b, int type, cv::Mat& out)
{
cv::threshold(in, out, a.val[0], b.val[0], type);
}
};
GAPI_OCV_KERNEL(GCPUThresholdOT, cv::gapi::core::GThresholdOT)
{
static void run(const cv::Mat& in, const cv::Scalar& b, int type, cv::Mat& out, cv::Scalar& outScalar)
{
outScalar = cv::threshold(in, out, b.val[0], b.val[0], type);
}
};
GAPI_OCV_KERNEL(GCPUInRange, cv::gapi::core::GInRange)
{
static void run(const cv::Mat& in, const cv::Scalar& low, const cv::Scalar& up, cv::Mat& out)
{
cv::inRange(in, low, up, out);
}
};
GAPI_OCV_KERNEL(GCPUSplit3, cv::gapi::core::GSplit3)
{
static void run(const cv::Mat& in, cv::Mat &m1, cv::Mat &m2, cv::Mat &m3)
{
std::vector<cv::Mat> outMats = {m1, m2, m3};
cv::split(in, outMats);
// Write back FIXME: Write a helper or avoid this nonsence completely!
m1 = outMats[0];
m2 = outMats[1];
m3 = outMats[2];
}
};
GAPI_OCV_KERNEL(GCPUSplit4, cv::gapi::core::GSplit4)
{
static void run(const cv::Mat& in, cv::Mat &m1, cv::Mat &m2, cv::Mat &m3, cv::Mat &m4)
{
std::vector<cv::Mat> outMats = {m1, m2, m3, m4};
cv::split(in, outMats);
// Write back FIXME: Write a helper or avoid this nonsence completely!
m1 = outMats[0];
m2 = outMats[1];
m3 = outMats[2];
m4 = outMats[3];
}
};
GAPI_OCV_KERNEL(GCPUMerge3, cv::gapi::core::GMerge3)
{
static void run(const cv::Mat& in1, const cv::Mat& in2, const cv::Mat& in3, cv::Mat &out)
{
std::vector<cv::Mat> inMats = {in1, in2, in3};
cv::merge(inMats, out);
}
};
GAPI_OCV_KERNEL(GCPUMerge4, cv::gapi::core::GMerge4)
{
static void run(const cv::Mat& in1, const cv::Mat& in2, const cv::Mat& in3, const cv::Mat& in4, cv::Mat &out)
{
std::vector<cv::Mat> inMats = {in1, in2, in3, in4};
cv::merge(inMats, out);
}
};
GAPI_OCV_KERNEL(GCPUResize, cv::gapi::core::GResize)
{
static void run(const cv::Mat& in, cv::Size sz, double fx, double fy, int interp, cv::Mat &out)
{
cv::resize(in, out, sz, fx, fy, interp);
}
};
GAPI_OCV_KERNEL(GCPURemap, cv::gapi::core::GRemap)
{
static void run(const cv::Mat& in, const cv::Mat& x, const cv::Mat& y, int a, int b, cv::Scalar s, cv::Mat& out)
{
cv::remap(in, out, x, y, a, b, s);
}
};
GAPI_OCV_KERNEL(GCPUFlip, cv::gapi::core::GFlip)
{
static void run(const cv::Mat& in, int code, cv::Mat& out)
{
cv::flip(in, out, code);
}
};
GAPI_OCV_KERNEL(GCPUCrop, cv::gapi::core::GCrop)
{
static void run(const cv::Mat& in, cv::Rect rect, cv::Mat& out)
{
cv::Mat(in, rect).copyTo(out);
}
};
GAPI_OCV_KERNEL(GCPUConcatHor, cv::gapi::core::GConcatHor)
{
static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out)
{
cv::hconcat(in1, in2, out);
}
};
GAPI_OCV_KERNEL(GCPUConcatVert, cv::gapi::core::GConcatVert)
{
static void run(const cv::Mat& in1, const cv::Mat& in2, cv::Mat& out)
{
cv::vconcat(in1, in2, out);
}
};
GAPI_OCV_KERNEL(GCPULUT, cv::gapi::core::GLUT)
{
static void run(const cv::Mat& in, const cv::Mat& lut, cv::Mat& out)
{
cv::LUT(in, lut, out);
}
};
GAPI_OCV_KERNEL(GCPUConvertTo, cv::gapi::core::GConvertTo)
{
static void run(const cv::Mat& in, int rtype, double alpha, double beta, cv::Mat& out)
{
in.convertTo(out, rtype, alpha, beta);
}
};
cv::gapi::GKernelPackage cv::gapi::core::cpu::kernels()
{
static auto pkg = cv::gapi::kernels
< GCPUAdd
, GCPUAddC
, GCPUSub
, GCPUSubC
, GCPUSubRC
, GCPUMul
, GCPUMulC
, GCPUMulCOld
, GCPUDiv
, GCPUDivC
, GCPUDivRC
, GCPUMean
, GCPUMask
, GCPUPolarToCart
, GCPUCartToPolar
, GCPUCmpGT
, GCPUCmpGE
, GCPUCmpLE
, GCPUCmpLT
, GCPUCmpEQ
, GCPUCmpNE
, GCPUCmpGTScalar
, GCPUCmpGEScalar
, GCPUCmpLEScalar
, GCPUCmpLTScalar
, GCPUCmpEQScalar
, GCPUCmpNEScalar
, GCPUAnd
, GCPUAndS
, GCPUOr
, GCPUOrS
, GCPUXor
, GCPUXorS
, GCPUNot
, GCPUSelect
, GCPUMin
, GCPUMax
, GCPUAbsDiff
, GCPUAbsDiffC
, GCPUSum
, GCPUAddW
, GCPUNormL1
, GCPUNormL2
, GCPUNormInf
, GCPUIntegral
, GCPUThreshold
, GCPUThresholdOT
, GCPUInRange
, GCPUSplit3
, GCPUSplit4
, GCPUResize
, GCPUMerge3
, GCPUMerge4
, GCPURemap
, GCPUFlip
, GCPUCrop
, GCPUConcatHor
, GCPUConcatVert
, GCPULUT
, GCPUConvertTo
>();
return pkg;
}
@@ -0,0 +1,24 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GCPUCORE_HPP
#define OPENCV_GAPI_GCPUCORE_HPP
#include <map>
#include <string>
#include "opencv2/gapi/cpu/gcpukernel.hpp"
namespace cv { namespace gimpl {
// NB: This is what a "Kernel Package" from the original Wiki doc should be.
void loadCPUCore(std::map<std::string, cv::GCPUKernel> &kmap);
}
}
#endif // OPENCV_GAPI_GCPUCORE_HPP
@@ -0,0 +1,273 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include "precomp.hpp"
#include "opencv2/gapi/imgproc.hpp"
#include "opencv2/gapi/cpu/imgproc.hpp"
#include "backends/cpu/gcpuimgproc.hpp"
GAPI_OCV_KERNEL(GCPUSepFilter, cv::gapi::imgproc::GSepFilter)
{
static void run(const cv::Mat& in, int ddepth, const cv::Mat& kernX, const cv::Mat& kernY, const cv::Point& anchor, const cv::Scalar& delta,
int border, const cv::Scalar& bordVal, cv::Mat &out)
{
if( border == cv::BORDER_CONSTANT )
{
cv::Mat temp_in;
int width_add = (kernY.cols - 1) / 2;
int height_add = (kernX.rows - 1) / 2;
cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, border, bordVal);
cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows);
cv::sepFilter2D(temp_in(rect), out, ddepth, kernX, kernY, anchor, delta.val[0], border);
}
else
cv::sepFilter2D(in, out, ddepth, kernX, kernY, anchor, delta.val[0], border);
}
};
GAPI_OCV_KERNEL(GCPUBoxFilter, cv::gapi::imgproc::GBoxFilter)
{
static void run(const cv::Mat& in, int ddepth, const cv::Size& ksize, const cv::Point& anchor, bool normalize, int borderType, const cv::Scalar& bordVal, cv::Mat &out)
{
if( borderType == cv::BORDER_CONSTANT )
{
cv::Mat temp_in;
int width_add = (ksize.width - 1) / 2;
int height_add = (ksize.height - 1) / 2;
cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, borderType, bordVal);
cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows);
cv::boxFilter(temp_in(rect), out, ddepth, ksize, anchor, normalize, borderType);
}
else
cv::boxFilter(in, out, ddepth, ksize, anchor, normalize, borderType);
}
};
GAPI_OCV_KERNEL(GCPUBlur, cv::gapi::imgproc::GBlur)
{
static void run(const cv::Mat& in, const cv::Size& ksize, const cv::Point& anchor, int borderType, const cv::Scalar& bordVal, cv::Mat &out)
{
if( borderType == cv::BORDER_CONSTANT )
{
cv::Mat temp_in;
int width_add = (ksize.width - 1) / 2;
int height_add = (ksize.height - 1) / 2;
cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, borderType, bordVal);
cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows);
cv::blur(temp_in(rect), out, ksize, anchor, borderType);
}
else
cv::blur(in, out, ksize, anchor, borderType);
}
};
GAPI_OCV_KERNEL(GCPUFilter2D, cv::gapi::imgproc::GFilter2D)
{
static void run(const cv::Mat& in, int ddepth, const cv::Mat& k, const cv::Point& anchor, const cv::Scalar& delta, int border,
const cv::Scalar& bordVal, cv::Mat &out)
{
if( border == cv::BORDER_CONSTANT )
{
cv::Mat temp_in;
int width_add = (k.cols - 1) / 2;
int height_add = (k.rows - 1) / 2;
cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, border, bordVal );
cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows);
cv::filter2D(temp_in(rect), out, ddepth, k, anchor, delta.val[0], border);
}
else
cv::filter2D(in, out, ddepth, k, anchor, delta.val[0], border);
}
};
GAPI_OCV_KERNEL(GCPUGaussBlur, cv::gapi::imgproc::GGaussBlur)
{
static void run(const cv::Mat& in, const cv::Size& ksize, double sigmaX, double sigmaY, int borderType, const cv::Scalar& bordVal, cv::Mat &out)
{
if( borderType == cv::BORDER_CONSTANT )
{
cv::Mat temp_in;
int width_add = (ksize.width - 1) / 2;
int height_add = (ksize.height - 1) / 2;
cv::copyMakeBorder(in, temp_in, height_add, height_add, width_add, width_add, borderType, bordVal );
cv::Rect rect = cv::Rect(height_add, width_add, in.cols, in.rows);
cv::GaussianBlur(temp_in(rect), out, ksize, sigmaX, sigmaY, borderType);
}
else
cv::GaussianBlur(in, out, ksize, sigmaX, sigmaY, borderType);
}
};
GAPI_OCV_KERNEL(GCPUMedianBlur, cv::gapi::imgproc::GMedianBlur)
{
static void run(const cv::Mat& in, int ksize, cv::Mat &out)
{
cv::medianBlur(in, out, ksize);
}
};
GAPI_OCV_KERNEL(GCPUErode, cv::gapi::imgproc::GErode)
{
static void run(const cv::Mat& in, const cv::Mat& kernel, const cv::Point& anchor, int iterations, int borderType, const cv::Scalar& borderValue, cv::Mat &out)
{
cv::erode(in, out, kernel, anchor, iterations, borderType, borderValue);
}
};
GAPI_OCV_KERNEL(GCPUDilate, cv::gapi::imgproc::GDilate)
{
static void run(const cv::Mat& in, const cv::Mat& kernel, const cv::Point& anchor, int iterations, int borderType, const cv::Scalar& borderValue, cv::Mat &out)
{
cv::dilate(in, out, kernel, anchor, iterations, borderType, borderValue);
}
};
GAPI_OCV_KERNEL(GCPUSobel, cv::gapi::imgproc::GSobel)
{
static void run(const cv::Mat& in, int ddepth, int dx, int dy, int ksize, double scale, double delta, int borderType,
const cv::Scalar& bordVal, cv::Mat &out)
{
if( borderType == cv::BORDER_CONSTANT )
{
cv::Mat temp_in;
int add = (ksize - 1) / 2;
cv::copyMakeBorder(in, temp_in, add, add, add, add, borderType, bordVal );
cv::Rect rect = cv::Rect(add, add, in.cols, in.rows);
cv::Sobel(temp_in(rect), out, ddepth, dx, dy, ksize, scale, delta, borderType);
}
else
cv::Sobel(in, out, ddepth, dx, dy, ksize, scale, delta, borderType);
}
};
GAPI_OCV_KERNEL(GCPUEqualizeHist, cv::gapi::imgproc::GEqHist)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::equalizeHist(in, out);
}
};
GAPI_OCV_KERNEL(GCPUCanny, cv::gapi::imgproc::GCanny)
{
static void run(const cv::Mat& in, double thr1, double thr2, int apSize, bool l2gradient, cv::Mat &out)
{
cv::Canny(in, out, thr1, thr2, apSize, l2gradient);
}
};
GAPI_OCV_KERNEL(GCPURGB2YUV, cv::gapi::imgproc::GRGB2YUV)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_RGB2YUV);
}
};
GAPI_OCV_KERNEL(GCPUYUV2RGB, cv::gapi::imgproc::GYUV2RGB)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_YUV2RGB);
}
};
GAPI_OCV_KERNEL(GCPURGB2Lab, cv::gapi::imgproc::GRGB2Lab)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_RGB2Lab);
}
};
GAPI_OCV_KERNEL(GCPUBGR2LUV, cv::gapi::imgproc::GBGR2LUV)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_BGR2Luv);
}
};
GAPI_OCV_KERNEL(GCPUBGR2YUV, cv::gapi::imgproc::GBGR2YUV)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_BGR2YUV);
}
};
GAPI_OCV_KERNEL(GCPULUV2BGR, cv::gapi::imgproc::GLUV2BGR)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_Luv2BGR);
}
};
GAPI_OCV_KERNEL(GCPUYUV2BGR, cv::gapi::imgproc::GYUV2BGR)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_YUV2BGR);
}
};
GAPI_OCV_KERNEL(GCPURGB2Gray, cv::gapi::imgproc::GRGB2Gray)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_RGB2GRAY);
}
};
GAPI_OCV_KERNEL(GCPUBGR2Gray, cv::gapi::imgproc::GBGR2Gray)
{
static void run(const cv::Mat& in, cv::Mat &out)
{
cv::cvtColor(in, out, cv::COLOR_BGR2GRAY);
}
};
GAPI_OCV_KERNEL(GCPURGB2GrayCustom, cv::gapi::imgproc::GRGB2GrayCustom)
{
static void run(const cv::Mat& in, float rY, float bY, float gY, cv::Mat &out)
{
cv::Mat planes[3];
cv::split(in, planes);
out = planes[0]*rY + planes[1]*bY + planes[2]*gY;
}
};
cv::gapi::GKernelPackage cv::gapi::imgproc::cpu::kernels()
{
static auto pkg = cv::gapi::kernels
< GCPUFilter2D
, GCPUSepFilter
, GCPUBoxFilter
, GCPUBlur
, GCPUGaussBlur
, GCPUMedianBlur
, GCPUErode
, GCPUDilate
, GCPUSobel
, GCPUCanny
, GCPUEqualizeHist
, GCPURGB2YUV
, GCPUYUV2RGB
, GCPURGB2Lab
, GCPUBGR2LUV
, GCPUBGR2YUV
, GCPUYUV2BGR
, GCPULUV2BGR
, GCPUBGR2Gray
, GCPURGB2Gray
, GCPURGB2GrayCustom
>();
return pkg;
}
@@ -0,0 +1,23 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GCPUIMGPROC_HPP
#define OPENCV_GAPI_GCPUIMGPROC_HPP
#include <map>
#include <string>
#include "opencv2/gapi/cpu/gcpukernel.hpp"
namespace cv { namespace gimpl {
// NB: This is what a "Kernel Package" from the origianl Wiki doc should be.
void loadCPUImgProc(std::map<std::string, cv::GCPUKernel> &kmap);
}}
#endif // OPENCV_GAPI_GCPUIMGPROC_HPP
@@ -0,0 +1,50 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <cassert>
#include "opencv2/gapi/cpu/gcpukernel.hpp"
const cv::gapi::own::Mat& cv::GCPUContext::inMat(int input)
{
return inArg<cv::gapi::own::Mat>(input);
}
cv::gapi::own::Mat& cv::GCPUContext::outMatR(int output)
{
return *util::get<cv::gapi::own::Mat*>(m_results.at(output));
}
const cv::gapi::own::Scalar& cv::GCPUContext::inVal(int input)
{
return inArg<cv::gapi::own::Scalar>(input);
}
cv::gapi::own::Scalar& cv::GCPUContext::outValR(int output)
{
return *util::get<cv::gapi::own::Scalar*>(m_results.at(output));
}
cv::detail::VectorRef& cv::GCPUContext::outVecRef(int output)
{
return util::get<cv::detail::VectorRef>(m_results.at(output));
}
cv::GCPUKernel::GCPUKernel()
{
}
cv::GCPUKernel::GCPUKernel(const GCPUKernel::F &f)
: m_f(f)
{
}
void cv::GCPUKernel::apply(GCPUContext &ctx)
{
GAPI_Assert(m_f);
m_f(ctx);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,128 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_FLUID_BACKEND_HPP
#define OPENCV_GAPI_FLUID_BACKEND_HPP
#include "opencv2/gapi/garg.hpp"
#include "opencv2/gapi/gproto.hpp"
#include "opencv2/gapi/fluid/gfluidkernel.hpp"
#include "opencv2/gapi/fluid/gfluidbuffer.hpp"
// PRIVATE STUFF!
#include "backends/common/gbackend.hpp"
#include "compiler/gislandmodel.hpp"
namespace cv { namespace gimpl {
struct FluidUnit
{
static const char *name() { return "FluidKernel"; }
GFluidKernel k;
gapi::fluid::BorderOpt border;
int border_size;
int line_consumption;
double ratio;
};
struct FluidUseOwnBorderBuffer
{
static const char *name() { return "FluidUseOwnBorderBuffer"; }
bool use;
};
struct FluidData
{
static const char *name() { return "FluidData"; }
// FIXME: This structure starts looking like "FluidBuffer" meta
int latency = 0;
int skew = 0;
int max_consumption = 1;
int border_size = 0;
int lpi_write = 1;
gapi::fluid::BorderOpt border;
};
struct FluidAgent
{
public:
virtual ~FluidAgent() = default;
FluidAgent(const ade::Graph &g, ade::NodeHandle nh);
GFluidKernel k;
ade::NodeHandle op_handle; // FIXME: why it is here??//
std::string op_name;
// < 0 - not a buffer
// >= 0 - a buffer with RcID
std::vector<int> in_buffer_ids;
std::vector<int> out_buffer_ids;
cv::GArgs in_args;
std::vector<cv::gapi::fluid::View> in_views; // sparce list of IN views
std::vector<cv::gapi::fluid::Buffer*> out_buffers;
// FIXME Current assumption is that outputs have EQUAL SIZES
int m_outputLines = 0;
int m_producedLines = 0;
double m_ratio = 0.0f;
// Execution methods
void reset();
bool canWork() const;
bool canRead() const;
bool canWrite() const;
void doWork();
bool done() const;
void debug(std::ostream& os);
private:
// FIXME!!!
// move to another class
virtual int firstWindow() const = 0;
virtual int nextWindow() const = 0;
virtual int linesRead() const = 0;
};
class GFluidExecutable final: public GIslandExecutable
{
const ade::Graph &m_g;
GModel::ConstGraph m_gm;
std::vector<std::unique_ptr<FluidAgent>> m_agents;
std::vector<cv::gapi::fluid::Buffer> m_buffers;
using Magazine = detail::magazine<cv::gapi::own::Scalar>;
Magazine m_res;
std::size_t m_num_int_buffers; // internal buffers counter (m_buffers - num_scratch)
std::vector<std::size_t> m_scratch_users;
std::vector<cv::gapi::fluid::View> m_views;
std::vector<cv::gapi::own::Rect> m_outputRois;
std::unordered_map<int, std::size_t> m_id_map; // GMat id -> buffer idx map
void bindInArg (const RcDesc &rc, const GRunArg &arg);
void bindOutArg(const RcDesc &rc, const GRunArgP &arg);
void packArg (GArg &in_arg, const GArg &op_arg);
public:
GFluidExecutable(const ade::Graph &g,
const std::vector<ade::NodeHandle> &nodes,
const std::vector<cv::gapi::own::Rect> &outputRois);
virtual void run(std::vector<InObj> &&input_objs,
std::vector<OutObj> &&output_objs) override;
};
}} // cv::gimpl
#endif // OPENCV_GAPI_FLUID_BACKEND_HPP
@@ -0,0 +1,746 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <iomanip> // hex, dec (debug)
#include "opencv2/gapi/own/convert.hpp"
#include "opencv2/gapi/fluid/gfluidbuffer.hpp"
#include "backends/fluid/gfluidbuffer_priv.hpp"
#include "opencv2/gapi/opencv_includes.hpp"
#include "backends/fluid/gfluidutils.hpp" // saturate
namespace cv {
namespace gapi {
namespace fluid {
bool operator == (const fluid::Border& b1, const fluid::Border& b2)
{
return b1.type == b2.type && b1.value == b2.value;
}
} // namespace fluid
// Fluid BorderHandler implementation /////////////////////////////////////////////////
namespace {
template<typename T>
// Expected inputs:
// row - row buffer allocated with border in mind (have memory for both image and border pixels)
// length - size of the buffer with left and right borders included
void fillBorderReplicateRow(uint8_t* row, int length, int chan, int borderSize)
{
auto leftBorder = reinterpret_cast<T*>(row);
auto rightBorder = leftBorder + (length - borderSize) * chan;
for (int b = 0; b < borderSize; b++)
{
for (int c = 0; c < chan; c++)
{
leftBorder [b*chan + c] = leftBorder [borderSize*chan + c];
rightBorder[b*chan + c] = rightBorder[-chan + c];
}
}
}
template<typename T>
void fillBorderReflectRow(uint8_t* row, int length, int chan, int borderSize)
{
auto leftBorder = reinterpret_cast<T*>(row);
auto rightBorder = leftBorder + (length - borderSize) * chan;
for (int b = 0; b < borderSize; b++)
{
for (int c = 0; c < chan; c++)
{
leftBorder [b*chan + c] = leftBorder [(2*borderSize - b)*chan + c];
rightBorder[b*chan + c] = rightBorder[(-b - 2)*chan + c];
}
}
}
template<typename T>
void fillConstBorderRow(uint8_t* row, int length, int chan, int borderSize, cv::gapi::own::Scalar borderValue)
{
GAPI_DbgAssert(chan > 0 && chan <= 4);
auto leftBorder = reinterpret_cast<T*>(row);
auto rightBorder = leftBorder + (length - borderSize) * chan;
for (int b = 0; b < borderSize; b++)
{
for (int c = 0; c < chan; c++)
{
leftBorder [b*chan + c] = fluid::saturate<T>(borderValue[c], fluid::roundd);
rightBorder[b*chan + c] = fluid::saturate<T>(borderValue[c], fluid::roundd);
}
}
}
// Fills const border pixels in the whole mat
void fillBorderConstant(int borderSize, cv::gapi::own::Scalar borderValue, cv::Mat& mat)
{
// cv::Scalar can contain maximum 4 chan
GAPI_Assert(mat.channels() > 0 && mat.channels() <= 4);
auto getFillBorderRowFunc = [&](int type) {
switch(type)
{
case CV_8U: return &fillConstBorderRow< uint8_t>; break;
case CV_16S: return &fillConstBorderRow< int16_t>; break;
case CV_16U: return &fillConstBorderRow<uint16_t>; break;
case CV_32F: return &fillConstBorderRow< float >; break;
default: CV_Assert(false); return &fillConstBorderRow<uint8_t>;
}
};
auto fillBorderRow = getFillBorderRowFunc(mat.depth());
for (int y = 0; y < mat.rows; y++)
{
fillBorderRow(mat.ptr(y), mat.cols, mat.channels(), borderSize, borderValue);
}
}
} // anonymous namespace
fluid::BorderHandler::BorderHandler(int border_size)
{
CV_Assert(border_size > 0);
m_border_size = border_size;
}
template <int BorderType>
fluid::BorderHandlerT<BorderType>::BorderHandlerT(int border_size, int data_type)
: BorderHandler(border_size)
{
auto getFillBorderRowFunc = [&](int border, int dataType) {
if (border == cv::BORDER_REPLICATE)
{
switch(dataType)
{
case CV_8U: return &fillBorderReplicateRow< uint8_t>; break;
case CV_16S: return &fillBorderReplicateRow< int16_t>; break;
case CV_16U: return &fillBorderReplicateRow<uint16_t>; break;
case CV_32F: return &fillBorderReplicateRow< float >; break;
default: CV_Assert(!"Unsupported data type"); return &fillBorderReplicateRow<uint8_t>;
}
}
else if (border == cv::BORDER_REFLECT_101)
{
switch(dataType)
{
case CV_8U: return &fillBorderReflectRow< uint8_t>; break;
case CV_16S: return &fillBorderReflectRow< int16_t>; break;
case CV_16U: return &fillBorderReflectRow<uint16_t>; break;
case CV_32F: return &fillBorderReflectRow< float >; break;
default: CV_Assert(!"Unsupported data type"); return &fillBorderReflectRow<uint8_t>;
}
}
else
{
CV_Assert(!"Unsupported border type");
return &fillBorderReflectRow<uint8_t>;
}
};
m_fill_border_row = getFillBorderRowFunc(BorderType, data_type);
}
namespace {
template <int BorderType> int getBorderIdx(int log_idx, int desc_height);
template<> int getBorderIdx<cv::BORDER_REPLICATE>(int log_idx, int desc_height)
{
return log_idx < 0 ? 0 : desc_height - 1;
}
template<> int getBorderIdx<cv::BORDER_REFLECT_101>(int log_idx, int desc_height)
{
return log_idx < 0 ? -log_idx : 2*(desc_height - 1) - log_idx;
}
} // namespace
template <int BorderType>
const uint8_t* fluid::BorderHandlerT<BorderType>::inLineB(int log_idx, const BufferStorageWithBorder& data, int desc_height) const
{
auto idx = getBorderIdx<BorderType>(log_idx, desc_height);
return data.ptr(idx);
}
fluid::BorderHandlerT<cv::BORDER_CONSTANT>::BorderHandlerT(int border_size, cv::gapi::own::Scalar border_value, int data_type, int desc_width)
: BorderHandler(border_size), m_border_value(border_value)
{
m_const_border.create(1, desc_width + 2*m_border_size, data_type);
m_const_border = cv::gapi::own::to_ocv(border_value);
}
const uint8_t* fluid::BorderHandlerT<cv::BORDER_CONSTANT>::inLineB(int /*log_idx*/, const BufferStorageWithBorder& /*data*/, int /*desc_height*/) const
{
return m_const_border.ptr(0, m_border_size);
}
void fluid::BorderHandlerT<cv::BORDER_CONSTANT>::fillCompileTimeBorder(BufferStorageWithBorder& data) const
{
cv::gapi::fillBorderConstant(m_border_size, m_border_value, data.data());
}
template <int BorderType>
void fluid::BorderHandlerT<BorderType>::updateBorderPixels(BufferStorageWithBorder &data, int startLine, int nLines) const
{
auto& mat = data.data();
auto length = mat.cols;
auto chan = mat.channels();
for (int l = startLine; l < startLine + nLines; l++)
{
auto row = mat.ptr(data.physIdx(l));
m_fill_border_row(row, length, chan, m_border_size);
}
}
std::size_t fluid::BorderHandlerT<cv::BORDER_CONSTANT>::size() const
{
return m_const_border.total() * m_const_border.elemSize();
}
// Fluid BufferStorage implementation //////////////////////////////////////////
void fluid::BufferStorageWithBorder::create(int capacity, int desc_width, int dtype, int border_size, Border border)
{
auto width = (desc_width + 2*border_size);
m_data.create(capacity, width, dtype);
switch(border.type)
{
case cv::BORDER_CONSTANT:
m_borderHandler.reset(new BorderHandlerT<cv::BORDER_CONSTANT>(border_size, border.value, dtype, desc_width)); break;
case cv::BORDER_REPLICATE:
m_borderHandler.reset(new BorderHandlerT<cv::BORDER_REPLICATE>(border_size, dtype)); break;
case cv::BORDER_REFLECT_101:
m_borderHandler.reset(new BorderHandlerT<cv::BORDER_REFLECT_101>(border_size, dtype)); break;
default:
CV_Assert(false);
}
m_borderHandler->fillCompileTimeBorder(*this);
}
void fluid::BufferStorageWithoutBorder::create(int capacity, int desc_width, int dtype)
{
auto width = desc_width;
m_data.create(capacity, width, dtype);
m_is_virtual = true;
}
const uint8_t* fluid::BufferStorageWithBorder::inLineB(int log_idx, int desc_height) const
{
if (log_idx < 0 || log_idx >= desc_height)
{
return m_borderHandler->inLineB(log_idx, *this, desc_height);
}
else
{
return ptr(log_idx);
}
}
const uint8_t* fluid::BufferStorageWithoutBorder::inLineB(int log_idx, int /*desc_height*/) const
{
return ptr(log_idx);
}
static void copyWithoutBorder(const cv::Mat& src, int src_border_size, cv::Mat& dst, int dst_border_size, int startSrcLine, int startDstLine, int lpi)
{
// FIXME use cv::gapi::own::Rect when implement cv::gapi::own::Mat
auto subSrc = src(cv::Rect{src_border_size, startSrcLine, src.cols - 2*src_border_size, lpi});
auto subDst = dst(cv::Rect{dst_border_size, startDstLine, dst.cols - 2*dst_border_size, lpi});
subSrc.copyTo(subDst);
}
void fluid::BufferStorageWithoutBorder::copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const
{
for (int l = startLine; l < startLine + nLines; l++)
{
copyWithoutBorder(m_data, 0, dst.data(), dst.borderSize(), physIdx(l), dst.physIdx(l), 1);
}
}
void fluid::BufferStorageWithBorder::copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const
{
// Copy required lpi lines line by line (to avoid wrap if invoked for multiple lines)
for (int l = startLine; l < startLine + nLines; l++)
{
copyWithoutBorder(m_data, borderSize(), dst.data(), dst.borderSize(), physIdx(l), dst.physIdx(l), 1);
}
}
// FIXME? remember parent and remove src parameter?
void fluid::BufferStorageWithBorder::updateBeforeRead(int startLine, int nLines, const BufferStorage& src)
{
// TODO:
// Cover with tests!!
// (Ensure that there are no redundant copies done
// and only required (not fetched before) lines are copied)
GAPI_DbgAssert(startLine >= 0);
src.copyTo(*this, startLine, nLines);
m_borderHandler->updateBorderPixels(*this, startLine, nLines);
}
void fluid::BufferStorageWithoutBorder::updateBeforeRead(int /*startLine*/, int /*lpi*/, const BufferStorage& /*src*/)
{
/* nothing */
}
void fluid::BufferStorageWithBorder::updateAfterWrite(int startLine, int nLines)
{
// FIXME?
// Actually startLine + nLines can be > logical height so
// redundant end lines which will never be read
// can be filled in the ring buffer
m_borderHandler->updateBorderPixels(*this, startLine, nLines);
}
void fluid::BufferStorageWithoutBorder::updateAfterWrite(int /*startLine*/, int /*lpi*/)
{
/* nothing */
}
size_t fluid::BufferStorageWithBorder::size() const
{
return m_data.total()*m_data.elemSize() + m_borderHandler->size();
}
size_t fluid::BufferStorageWithoutBorder::size() const
{
return m_data.total()*m_data.elemSize();
}
namespace fluid {
namespace {
std::unique_ptr<fluid::BufferStorage> createStorage(int capacity, int desc_width, int type,
int border_size, fluid::BorderOpt border);
std::unique_ptr<fluid::BufferStorage> createStorage(int capacity, int desc_width, int type,
int border_size, fluid::BorderOpt border)
{
if (border)
{
std::unique_ptr<fluid::BufferStorageWithBorder> storage(new BufferStorageWithBorder);
storage->create(capacity, desc_width, type, border_size, border.value());
return std::move(storage);
}
std::unique_ptr<BufferStorageWithoutBorder> storage(new BufferStorageWithoutBorder);
storage->create(capacity, desc_width, type);
return std::move(storage);
}
std::unique_ptr<BufferStorage> createStorage(const cv::Mat& data, cv::gapi::own::Rect roi);
std::unique_ptr<BufferStorage> createStorage(const cv::Mat& data, cv::gapi::own::Rect roi)
{
std::unique_ptr<BufferStorageWithoutBorder> storage(new BufferStorageWithoutBorder);
storage->attach(data, roi);
return std::move(storage);
}
} // namespace
} // namespace fluid
// Fluid View implementation ///////////////////////////////////////////////////
void fluid::View::Priv::reset(int linesForFirstIteration)
{
GAPI_DbgAssert(m_p);
m_lines_next_iter = linesForFirstIteration;
m_read_caret = m_p->priv().readStart();
}
void fluid::View::Priv::readDone(int linesRead, int linesForNextIteration)
{
CV_DbgAssert(m_p);
m_read_caret += linesRead;
m_read_caret %= m_p->meta().size.height;
m_lines_next_iter = linesForNextIteration;
}
bool fluid::View::Priv::ready() const
{
auto lastWrittenLine = m_p->priv().writeStart() + m_p->linesReady();
// + bottom border
if (lastWrittenLine == m_p->meta().size.height) lastWrittenLine += m_border_size;
// + top border
lastWrittenLine += m_border_size;
auto lastRequiredLine = m_read_caret + m_lines_next_iter;
return lastWrittenLine >= lastRequiredLine;
}
fluid::ViewPrivWithoutOwnBorder::ViewPrivWithoutOwnBorder(const Buffer *parent, int borderSize)
{
CV_Assert(parent);
m_p = parent;
m_border_size = borderSize;
}
const uint8_t* fluid::ViewPrivWithoutOwnBorder::InLineB(int index) const
{
GAPI_DbgAssert(m_p);
const auto &p_priv = m_p->priv();
CV_Assert( index >= -m_border_size
&& index < -m_border_size + m_lines_next_iter);
const int log_idx = m_read_caret + index;
return p_priv.storage().inLineB(log_idx, m_p->meta().size.height);
}
fluid::ViewPrivWithOwnBorder::ViewPrivWithOwnBorder(const Buffer *parent, int lineConsumption, int borderSize, Border border)
{
GAPI_Assert(parent);
m_p = parent;
m_border_size = borderSize;
auto desc = m_p->meta();
int type = CV_MAKETYPE(desc.depth, desc.chan);
m_own_storage.create(lineConsumption, desc.size.width, type, borderSize, border);
}
void fluid::ViewPrivWithOwnBorder::prepareToRead()
{
int startLine = 0;
int nLines = 0;
if (m_read_caret == m_p->priv().readStart())
{
// Need to fetch full window on the first iteration
startLine = (m_read_caret > m_border_size) ? m_read_caret - m_border_size : 0;
nLines = m_lines_next_iter;
}
else
{
startLine = m_read_caret + m_border_size;
nLines = m_lines_next_iter - 2*m_border_size;
}
m_own_storage.updateBeforeRead(startLine, nLines, m_p->priv().storage());
}
std::size_t fluid::ViewPrivWithOwnBorder::size() const
{
GAPI_DbgAssert(m_p);
return m_own_storage.size();
}
const uint8_t* fluid::ViewPrivWithOwnBorder::InLineB(int index) const
{
GAPI_DbgAssert(m_p);
GAPI_Assert( index >= -m_border_size
&& index < -m_border_size + m_lines_next_iter);
const int log_idx = m_read_caret + index;
return m_own_storage.inLineB(log_idx, m_p->meta().size.height);
}
const uint8_t* fluid::View::InLineB(int index) const
{
return m_priv->InLineB(index);
}
fluid::View::operator bool() const
{
return m_priv != nullptr && m_priv->m_p != nullptr;
}
int fluid::View::length() const
{
return m_priv->m_p->length();
}
bool fluid::View::ready() const
{
return m_priv->ready();
}
int fluid::View::y() const
{
return m_priv->m_read_caret - m_priv->m_border_size;
}
cv::GMatDesc fluid::View::meta() const
{
// FIXME: cover with test!
return m_priv->m_p->meta();
}
fluid::View::Priv& fluid::View::priv()
{
return *m_priv;
}
const fluid::View::Priv& fluid::View::priv() const
{
return *m_priv;
}
// Fluid Buffer implementation /////////////////////////////////////////////////
fluid::Buffer::Priv::Priv(int read_start, cv::gapi::own::Rect roi)
: m_readStart(read_start)
, m_roi(roi)
{}
void fluid::Buffer::Priv::init(const cv::GMatDesc &desc,
int line_consumption,
int border_size,
int skew,
int wlpi,
int readStartPos,
cv::gapi::own::Rect roi)
{
GAPI_Assert(m_line_consumption == -1);
GAPI_Assert(line_consumption > 0);
m_line_consumption = line_consumption;
m_border_size = border_size;
m_skew = skew;
m_writer_lpi = wlpi;
m_desc = desc;
m_readStart = readStartPos;
m_roi = roi;
}
void fluid::Buffer::Priv::allocate(BorderOpt border)
{
GAPI_Assert(!m_storage);
// Init physical buffer
// FIXME? combine with skew?
auto maxRead = m_line_consumption + m_skew;
auto maxWritten = m_writer_lpi;
auto max = std::max(maxRead, maxWritten);
auto min = std::min(maxRead, maxWritten);
// FIXME:
// Fix the deadlock (completely)!!!
auto data_height = static_cast<int>(std::ceil((double)max / min) * min);
m_storage = createStorage(data_height,
m_desc.size.width,
CV_MAKETYPE(m_desc.depth, m_desc.chan),
m_border_size,
border);
// Finally, initialize carets
m_write_caret = 0;
}
void fluid::Buffer::Priv::bindTo(const cv::Mat &data, bool is_input)
{
// FIXME: move all these fields into a separate structure
GAPI_Assert(m_skew == 0);
GAPI_Assert(m_desc == cv::descr_of(data));
if ( is_input) CV_Assert(m_writer_lpi == 1);
m_storage = createStorage(data, m_roi);
m_is_input = is_input;
m_write_caret = is_input ? writeEnd(): writeStart();
// NB: views remain the same!
}
bool fluid::Buffer::Priv::full() const
{
int slowest_y = writeEnd();
if (!m_views.empty())
{
// reset with maximum possible value and then find minimum
slowest_y = m_desc.size.height;
for (const auto &v : m_views) slowest_y = std::min(slowest_y, v.y());
}
return m_write_caret + lpi() - slowest_y > m_storage->rows();
}
void fluid::Buffer::Priv::writeDone()
{
// There are possible optimizations which can be done to fill a border values
// in compile time of the graph (for example border is const),
// so there is no need to update border values after each write.
// If such optimizations weren't applied, fill border for lines
// which have been just written
m_storage->updateAfterWrite(m_write_caret, m_writer_lpi);
// Final write may produce less LPI, so
// write caret may exceed logical buffer size
m_write_caret += m_writer_lpi;
// FIXME: add consistency check!
}
void fluid::Buffer::Priv::reset()
{
m_write_caret = m_is_input ? writeEnd() : writeStart();
}
int fluid::Buffer::Priv::size() const
{
std::size_t view_sz = 0;
for (const auto &v : m_views) view_sz += v.priv().size();
auto total = view_sz;
if (m_storage) total += m_storage->size();
// FIXME: Change API to return size_t!!!
return static_cast<int>(total);
}
int fluid::Buffer::Priv::linesReady() const
{
if (m_is_input)
{
return m_storage->rows();
}
else
{
const int writes = std::min(m_write_caret - writeStart(), outputLines());
return writes;
}
}
uint8_t* fluid::Buffer::Priv::OutLineB(int index)
{
GAPI_Assert(index >= 0 && index < m_writer_lpi);
return m_storage->ptr(m_write_caret + index);
}
int fluid::Buffer::Priv::lpi() const
{
// FIXME:
// m_write_caret can be greater than m_writeRoi.y + m_writeRoi.height, so return value can be negative !!!
return std::min(writeEnd() - m_write_caret, m_writer_lpi);
}
fluid::Buffer::Buffer()
: m_priv(new Priv())
{
}
fluid::Buffer::Buffer(const cv::GMatDesc &desc)
: m_priv(new Priv())
{
int lineConsumption = 1;
int border = 0, skew = 0, wlpi = 1, readStart = 0;
cv::gapi::own::Rect roi = {0, 0, desc.size.width, desc.size.height};
m_priv->init(desc, lineConsumption, border, skew, wlpi, readStart, roi);
m_priv->allocate({});
}
fluid::Buffer::Buffer(const cv::GMatDesc &desc,
int max_line_consumption,
int border_size,
int skew,
int wlpi,
BorderOpt border)
: m_priv(new Priv())
{
int readStart = 0;
cv::gapi::own::Rect roi = {0, 0, desc.size.width, desc.size.height};
m_priv->init(desc, max_line_consumption, border_size, skew, wlpi, readStart, roi);
m_priv->allocate(border);
}
fluid::Buffer::Buffer(const cv::Mat &data, bool is_input)
: m_priv(new Priv())
{
int lineConsumption = 1;
int border = 0, skew = 0, wlpi = 1, readStart = 0;
cv::gapi::own::Rect roi{0, 0, data.cols, data.rows};
m_priv->init(descr_of(data), lineConsumption, border, skew, wlpi, readStart, roi);
m_priv->bindTo(data, is_input);
}
uint8_t* fluid::Buffer::Buffer::OutLineB(int index)
{
return m_priv->OutLineB(index);
}
int fluid::Buffer::linesReady() const
{
return m_priv->linesReady();
}
int fluid::Buffer::length() const
{
return meta().size.width;
}
int fluid::Buffer::lpi() const
{
return m_priv->lpi();
}
cv::GMatDesc fluid::Buffer::meta() const
{
return m_priv->meta();
}
fluid::View::View(Priv* p)
: m_priv(p)
{ /* nothing */ }
fluid::View fluid::Buffer::mkView(int lineConsumption, int borderSize, BorderOpt border, bool ownStorage)
{
// FIXME: logic outside of Priv (because View takes pointer to Buffer)
auto view = ownStorage ? View(new ViewPrivWithOwnBorder(this, lineConsumption, borderSize, border.value()))
: View(new ViewPrivWithoutOwnBorder(this, borderSize));
m_priv->addView(view);
return view;
}
void fluid::debugBufferPriv(const fluid::Buffer& buffer, std::ostream &os)
{
// FIXME Use cv::gapi::own Size and Rect with operator<<, when merged ADE-285
const auto& p = buffer.priv();
os << "Fluid buffer " << std::hex << &buffer << std::dec
<< " " << p.m_desc.size.width << " x " << p.m_desc.size.height << "]"
<< " readStart:" << p.m_readStart
<< " roi:" << "[" << p.m_roi.width << " x " << p.m_roi.height << " from (" << p.m_roi.x << ", " << p.m_roi.y << ")]"
<<" (phys " << "[" << p.storage().cols() << " x " << p.storage().rows() << "]" << ") :"
<< " w: " << p.m_write_caret
<< ", r: [";
for (const auto &v : p.m_views) { os << &v.priv() << ":" << v.y() << " "; }
os << "], avail: " << buffer.linesReady()
<< std::endl;
}
void fluid::Buffer::debug(std::ostream &os) const
{
debugBufferPriv(*this, os);
}
fluid::Buffer::Priv& fluid::Buffer::priv()
{
return *m_priv;
}
const fluid::Buffer::Priv& fluid::Buffer::priv() const
{
return *m_priv;
}
int fluid::Buffer::y() const
{
return m_priv->y();
}
} // namespace cv::gapi
} // namespace cv
@@ -0,0 +1,298 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_FLUID_BUFFER_PRIV_HPP
#define OPENCV_GAPI_FLUID_BUFFER_PRIV_HPP
#include <vector>
#include "opencv2/gapi/fluid/gfluidbuffer.hpp"
#include "opencv2/gapi/own/convert.hpp" // cv::gapi::own::to_ocv
namespace cv {
namespace gapi {
namespace fluid {
class BufferStorageWithBorder;
class BorderHandler
{
protected:
int m_border_size;
public:
BorderHandler(int border_size);
virtual ~BorderHandler() = default;
virtual const uint8_t* inLineB(int log_idx, const BufferStorageWithBorder &data, int desc_height) const = 0;
// Fills border pixels after buffer allocation (if possible (for const border))
virtual void fillCompileTimeBorder(BufferStorageWithBorder &) const { /* nothing */ }
// Fills required border lines
virtual void updateBorderPixels(BufferStorageWithBorder& /*data*/, int /*startLine*/, int /*lpi*/) const { /* nothing */ }
inline int borderSize() const { return m_border_size; }
virtual std::size_t size() const { return 0; }
};
template<int BorderType>
class BorderHandlerT : public BorderHandler
{
std::function<void(uint8_t*,int,int,int)> m_fill_border_row;
public:
BorderHandlerT(int border_size, int data_type);
virtual void updateBorderPixels(BufferStorageWithBorder& data, int startLine, int lpi) const override;
virtual const uint8_t* inLineB(int log_idx, const BufferStorageWithBorder &data, int desc_height) const override;
};
template<>
class BorderHandlerT<cv::BORDER_CONSTANT> : public BorderHandler
{
cv::gapi::own::Scalar m_border_value;
cv::Mat m_const_border;
public:
BorderHandlerT(int border_size, cv::gapi::own::Scalar border_value, int data_type, int desc_width);
virtual const uint8_t* inLineB(int log_idx, const BufferStorageWithBorder &data, int desc_height) const override;
virtual void fillCompileTimeBorder(BufferStorageWithBorder &) const override;
virtual std::size_t size() const override;
};
class BufferStorage
{
protected:
cv::Mat m_data;
public:
virtual void copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const = 0;
virtual ~BufferStorage() = default;
virtual const uint8_t* ptr(int idx) const = 0;
virtual uint8_t* ptr(int idx) = 0;
inline bool empty() const { return m_data.empty(); }
inline const cv::Mat& data() const { return m_data; }
inline cv::Mat& data() { return m_data; }
inline int rows() const { return m_data.rows; }
inline int cols() const { return m_data.cols; }
inline int type() const { return m_data.type(); }
virtual const uint8_t* inLineB(int log_idx, int desc_height) const = 0;
// FIXME? remember parent and remove src parameter?
virtual void updateBeforeRead(int startLine, int nLines, const BufferStorage& src) = 0;
virtual void updateAfterWrite(int startLine, int nLines) = 0;
virtual int physIdx(int logIdx) const = 0;
virtual size_t size() const = 0;
};
class BufferStorageWithoutBorder final : public BufferStorage
{
bool m_is_virtual = true;
cv::gapi::own::Rect m_roi;
public:
virtual void copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const override;
inline virtual const uint8_t* ptr(int idx) const override
{
GAPI_DbgAssert((m_is_virtual && m_roi == cv::gapi::own::Rect{}) || (!m_is_virtual && m_roi != cv::gapi::own::Rect{}));
return m_data.ptr(physIdx(idx), 0);
}
inline virtual uint8_t* ptr(int idx) override
{
GAPI_DbgAssert((m_is_virtual && m_roi == cv::gapi::own::Rect{}) || (!m_is_virtual && m_roi != cv::gapi::own::Rect{}));
return m_data.ptr(physIdx(idx), 0);
}
inline void attach(const cv::Mat& _data, const cv::gapi::own::Rect& _roi)
{
m_data = _data(cv::gapi::own::to_ocv(_roi));
m_roi = _roi;
m_is_virtual = false;
}
void create(int capacity, int desc_width, int type);
inline virtual const uint8_t* inLineB(int log_idx, int desc_height) const override;
virtual void updateBeforeRead(int startLine, int nLines, const BufferStorage& src) override;
virtual void updateAfterWrite(int startLine, int nLines) override;
inline virtual int physIdx(int logIdx) const override { return (logIdx - m_roi.y) % m_data.rows; }
virtual size_t size() const override;
};
class BufferStorageWithBorder final: public BufferStorage
{
std::unique_ptr<BorderHandler> m_borderHandler;
public:
inline int borderSize() const { return m_borderHandler->borderSize(); }
virtual void copyTo(BufferStorageWithBorder &dst, int startLine, int nLines) const override;
inline virtual const uint8_t* ptr(int idx) const override
{
return m_data.ptr(physIdx(idx), borderSize());
}
inline virtual uint8_t* ptr(int idx) override
{
return m_data.ptr(physIdx(idx), borderSize());
}
void create(int capacity, int desc_width, int type, int border_size, Border border);
virtual const uint8_t* inLineB(int log_idx, int desc_height) const override;
virtual void updateBeforeRead(int startLine, int nLines, const BufferStorage &src) override;
virtual void updateAfterWrite(int startLine, int nLines) override;
inline virtual int physIdx(int logIdx) const override { return logIdx % m_data.rows; }
virtual size_t size() const override;
};
// FIXME: GAPI_EXPORTS is used here only to access internal methods
// like readDone/writeDone in low-level tests
class GAPI_EXPORTS View::Priv
{
friend class View;
protected:
const Buffer *m_p = nullptr; // FIXME replace with weak_ptr
int m_read_caret = -1;
int m_lines_next_iter = -1;
int m_border_size = -1;
public:
virtual ~Priv() = default;
// API used by actors/backend
virtual void prepareToRead() = 0;
void readDone(int linesRead, int linesForNextIteration);
void reset(int linesForFirstIteration);
virtual std::size_t size() const = 0;
// Does the view have enough unread lines for next iteration
bool ready() const;
// API used (indirectly) by user code
virtual const uint8_t* InLineB(int index) const = 0;
};
class ViewPrivWithoutOwnBorder final : public View::Priv
{
public:
// API used by actors/backend
ViewPrivWithoutOwnBorder(const Buffer *p, int borderSize);
virtual void prepareToRead() override { /* nothing */ }
virtual std::size_t size() const override { return 0; }
// API used (indirectly) by user code
virtual const uint8_t* InLineB(int index) const override;
};
class ViewPrivWithOwnBorder final : public View::Priv
{
BufferStorageWithBorder m_own_storage;
public:
// API used by actors/backend
ViewPrivWithOwnBorder(const Buffer *p, int lineCapacity, int borderSize, Border border);
virtual void prepareToRead() override;
virtual std::size_t size() const override;
// API used (indirectly) by user code
virtual const uint8_t* InLineB(int index) const override;
};
void debugBufferPriv(const Buffer& buffer, std::ostream &os);
// FIXME: GAPI_EXPORTS is used here only to access internal methods
// like readDone/writeDone in low-level tests
class GAPI_EXPORTS Buffer::Priv
{
int m_line_consumption = -1;
int m_border_size = -1;
int m_skew = -1;
int m_writer_lpi = 1;
cv::GMatDesc m_desc = cv::GMatDesc{-1,-1,{-1,-1}};
bool m_is_input = false;
int m_write_caret = -1;
std::vector<View> m_views;
std::unique_ptr<BufferStorage> m_storage;
// Coordinate starting from which this buffer is assumed
// to be read (with border not being taken into account)
int m_readStart;
cv::gapi::own::Rect m_roi;
friend void debugBufferPriv(const Buffer& p, std::ostream &os);
public:
Priv() = default;
Priv(int read_start, cv::gapi::own::Rect roi);
inline const BufferStorage& storage() const { return *m_storage.get(); }
// API used by actors/backend
void init(const cv::GMatDesc &desc,
int line_consumption,
int border_size,
int skew,
int wlpi,
int readStart,
cv::gapi::own::Rect roi);
void allocate(BorderOpt border);
void bindTo(const cv::Mat &data, bool is_input);
void addView(const View& view) { m_views.push_back(view); }
const GMatDesc meta() const { return m_desc; }
bool full() const;
void writeDone();
void reset();
int size() const;
int linesReady() const;
inline int y() const { return m_write_caret; }
inline int writer_lpi() const { return m_writer_lpi; }
// API used (indirectly) by user code
uint8_t* OutLineB(int index = 0);
int lpi() const;
inline int readStart() const { return m_readStart; }
inline int writeStart() const { return m_roi.y; }
inline int writeEnd() const { return m_roi.y + m_roi.height; }
inline int outputLines() const { return m_roi.height; }
};
} // namespace cv::gapi::fluid
} // namespace cv::gapi
} // namespace cv
#endif // OPENCV_GAPI_FLUID_BUFFER_PRIV_HPP
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,19 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GFLUIDCORE_HPP
#define OPENCV_GAPI_GFLUIDCORE_HPP
#include "opencv2/gapi/fluid/gfluidkernel.hpp"
namespace cv { namespace gapi { namespace core { namespace fluid {
GAPI_EXPORTS GKernelPackage kernels();
}}}}
#endif // OPENCV_GAPI_GFLUIDCORE_HPP
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,19 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GFLUIDIMGPROC_HPP
#define OPENCV_GAPI_GFLUIDIMGPROC_HPP
#include "opencv2/gapi/fluid/gfluidkernel.hpp"
namespace cv { namespace gapi { namespace imgproc { namespace fluid {
GAPI_EXPORTS GKernelPackage kernels();
}}}}
#endif // OPENCV_GAPI_GFLUIDIMGPROC_HPP
@@ -0,0 +1,154 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef GFLUIDUTILS_HPP
#define GFLUIDUTILS_HPP
#include <limits>
#include <type_traits>
#include <opencv2/gapi/util/compiler_hints.hpp> //UNUSED
namespace cv {
namespace gapi {
namespace fluid {
//-----------------------------
//
// Numeric cast with saturation
//
//-----------------------------
template<typename DST, typename SRC>
static inline DST saturate(SRC x)
{
// only integral types please!
GAPI_DbgAssert(std::is_integral<DST>::value &&
std::is_integral<SRC>::value);
if (std::is_same<DST, SRC>::value)
return static_cast<DST>(x);
if (sizeof(DST) > sizeof(SRC))
return static_cast<DST>(x);
// compiler must recognize this saturation,
// so compile saturate<s16>(a + b) with adds
// instruction (e.g.: _mm_adds_epi16 if x86)
return x < std::numeric_limits<DST>::min()?
std::numeric_limits<DST>::min():
x > std::numeric_limits<DST>::max()?
std::numeric_limits<DST>::max():
static_cast<DST>(x);
}
// Note, that OpenCV rounds differently:
// - like std::round() for add, subtract
// - like std::rint() for multiply, divide
template<typename DST, typename SRC, typename R>
static inline DST saturate(SRC x, R round)
{
if (std::is_floating_point<DST>::value)
{
return static_cast<DST>(x);
}
else if (std::is_integral<SRC>::value)
{
GAPI_DbgAssert(std::is_integral<DST>::value &&
std::is_integral<SRC>::value);
return saturate<DST>(x);
}
else
{
GAPI_DbgAssert(std::is_integral<DST>::value &&
std::is_floating_point<SRC>::value);
#ifdef _WIN32
// Suppress warning about convering x to floating-point
// Note that x is already floating-point at this point
#pragma warning(disable: 4244)
#endif
int ix = static_cast<int>(round(x));
#ifdef _WIN32
#pragma warning(default: 4244)
#endif
return saturate<DST>(ix);
}
}
// explicit suffix 'd' for double type
static inline double ceild(double x) { return std::ceil(x); }
static inline double floord(double x) { return std::floor(x); }
static inline double roundd(double x) { return std::round(x); }
static inline double rintd(double x) { return std::rint(x); }
//--------------------------------
//
// Macros for mappig of data types
//
//--------------------------------
#define UNARY_(DST, SRC, OP, ...) \
if (cv::DataType<DST>::depth == dst.meta().depth && \
cv::DataType<SRC>::depth == src.meta().depth) \
{ \
GAPI_DbgAssert(dst.length() == src.length()); \
GAPI_DbgAssert(dst.meta().chan == src.meta().chan); \
\
OP<DST, SRC>(__VA_ARGS__); \
return; \
}
// especial unary operation: dst is always 8UC1 image
#define INRANGE_(DST, SRC, OP, ...) \
if (cv::DataType<DST>::depth == dst.meta().depth && \
cv::DataType<SRC>::depth == src.meta().depth) \
{ \
GAPI_DbgAssert(dst.length() == src.length()); \
GAPI_DbgAssert(dst.meta().chan == 1); \
\
OP<DST, SRC>(__VA_ARGS__); \
return; \
}
#define BINARY_(DST, SRC1, SRC2, OP, ...) \
if (cv::DataType<DST>::depth == dst.meta().depth && \
cv::DataType<SRC1>::depth == src1.meta().depth && \
cv::DataType<SRC2>::depth == src2.meta().depth) \
{ \
GAPI_DbgAssert(dst.length() == src1.length()); \
GAPI_DbgAssert(dst.length() == src2.length()); \
\
GAPI_DbgAssert(dst.meta().chan == src1.meta().chan); \
GAPI_DbgAssert(dst.meta().chan == src2.meta().chan); \
\
OP<DST, SRC1, SRC2>(__VA_ARGS__); \
return; \
}
// especial ternary operation: src3 has only one channel
#define SELECT_(DST, SRC1, SRC2, SRC3, OP, ...) \
if (cv::DataType<DST>::depth == dst.meta().depth && \
cv::DataType<SRC1>::depth == src1.meta().depth && \
cv::DataType<SRC2>::depth == src2.meta().depth && \
cv::DataType<SRC3>::depth == src3.meta().depth) \
{ \
GAPI_DbgAssert(dst.length() == src1.length()); \
GAPI_DbgAssert(dst.length() == src2.length()); \
GAPI_DbgAssert(dst.length() == src3.length()); \
\
GAPI_DbgAssert(dst.meta().chan == src1.meta().chan); \
GAPI_DbgAssert(dst.meta().chan == src2.meta().chan); \
GAPI_DbgAssert( 1 == src3.meta().chan); \
\
OP<DST, SRC1, SRC2, SRC3>(__VA_ARGS__); \
return; \
}
} // namespace fluid
} // namespace gapi
} // namespace cv
#endif // GFLUIDUTILS_HPP
+1
View File
@@ -0,0 +1 @@
This directory contains G-API graph compiler logic.
+131
View File
@@ -0,0 +1,131 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <ade/graph.hpp>
#include "opencv2/gapi/gproto.hpp" // descr_of
#include "opencv2/gapi/gcompiled.hpp"
#include "compiler/gcompiled_priv.hpp"
#include "backends/common/gbackend.hpp"
// GCompiled private implementation ////////////////////////////////////////////
void cv::GCompiled::Priv::setup(const GMetaArgs &_metaArgs,
const GMetaArgs &_outMetas,
std::unique_ptr<cv::gimpl::GExecutor> &&_pE)
{
m_metas = _metaArgs;
m_outMetas = _outMetas;
m_exec = std::move(_pE);
}
bool cv::GCompiled::Priv::isEmpty() const
{
return !m_exec;
}
void cv::GCompiled::Priv::run(cv::gimpl::GRuntimeArgs &&args)
{
// Strip away types since ADE knows nothing about that
// args will be taken by specific GBackendExecutables
checkArgs(args);
m_exec->run(std::move(args));
}
const cv::GMetaArgs& cv::GCompiled::Priv::metas() const
{
return m_metas;
}
const cv::GMetaArgs& cv::GCompiled::Priv::outMetas() const
{
return m_outMetas;
}
void cv::GCompiled::Priv::checkArgs(const cv::gimpl::GRuntimeArgs &args) const
{
const auto runtime_metas = descr_of(args.inObjs);
if (runtime_metas != m_metas)
{
util::throw_error(std::logic_error("This object was compiled "
"for different metadata!"));
// FIXME: Add details on what is actually wrong
}
}
const cv::gimpl::GModel::Graph& cv::GCompiled::Priv::model() const
{
GAPI_Assert(nullptr != m_exec);
return m_exec->model();
}
// GCompiled public implementation /////////////////////////////////////////////
cv::GCompiled::GCompiled()
: m_priv(new Priv())
{
}
cv::GCompiled::operator bool() const
{
return !m_priv->isEmpty();
}
void cv::GCompiled::operator() (GRunArgs &&ins, GRunArgsP &&outs)
{
// FIXME: Check that <outs> matches the protocol
m_priv->run(cv::gimpl::GRuntimeArgs{std::move(ins),std::move(outs)});
}
void cv::GCompiled::operator ()(cv::Mat in, cv::Mat &out)
{
(*this)(cv::gin(in), cv::gout(out));
}
void cv::GCompiled::operator() (cv::Mat in, cv::Scalar &out)
{
(*this)(cv::gin(in), cv::gout(out));
}
void cv::GCompiled::operator() (cv::Mat in1, cv::Mat in2, cv::Mat &out)
{
(*this)(cv::gin(in1, in2), cv::gout(out));
}
void cv::GCompiled::operator() (cv::Mat in1, cv::Mat in2, cv::Scalar &out)
{
(*this)(cv::gin(in1, in2), cv::gout(out));
}
void cv::GCompiled::operator ()(const std::vector<cv::Mat> &ins,
const std::vector<cv::Mat> &outs)
{
GRunArgs call_ins;
GRunArgsP call_outs;
// Make a temporary copy of vector outs - cv::Mats are copies anyway
auto tmp = outs;
for (const cv::Mat &m : ins) { call_ins.emplace_back(m); }
for ( cv::Mat &m : tmp) { call_outs.emplace_back(&m); }
(*this)(std::move(call_ins), std::move(call_outs));
}
const cv::GMetaArgs& cv::GCompiled::metas() const
{
return m_priv->metas();
}
const cv::GMetaArgs& cv::GCompiled::outMetas() const
{
return m_priv->outMetas();
}
cv::GCompiled::Priv& cv::GCompiled::priv()
{
return *m_priv;
}
@@ -0,0 +1,58 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GCOMPILED_PRIV_HPP
#define OPENCV_GAPI_GCOMPILED_PRIV_HPP
#include <memory> // unique_ptr
#include "opencv2/gapi/util/optional.hpp"
#include "compiler/gmodel.hpp"
#include "executor/gexecutor.hpp"
// NB: BTW, GCompiled is the only "public API" class which
// private part (implementaion) is hosted in the "compiler/" module.
//
// This file is here just to keep ADE hidden from the top-level APIs.
//
// As the thing becomes more complex, appropriate API and implementation
// part will be placed to api/ and compiler/ modules respectively.
namespace cv {
namespace gimpl
{
struct GRuntimeArgs;
};
// FIXME: GAPI_EXPORTS is here only due to tests and Windows linker issues
class GAPI_EXPORTS GCompiled::Priv
{
// NB: For now, a GCompiled keeps the original ade::Graph alive.
// If we want to go autonomous, we might to do something with this.
GMetaArgs m_metas; // passed by user
GMetaArgs m_outMetas; // inferred by compiler
std::unique_ptr<cv::gimpl::GExecutor> m_exec;
void checkArgs(const cv::gimpl::GRuntimeArgs &args) const;
public:
void setup(const GMetaArgs &metaArgs,
const GMetaArgs &outMetas,
std::unique_ptr<cv::gimpl::GExecutor> &&pE);
bool isEmpty() const;
void run(cv::gimpl::GRuntimeArgs &&args);
const GMetaArgs& metas() const;
const GMetaArgs& outMetas() const;
const cv::gimpl::GModel::Graph& model() const;
};
}
#endif // OPENCV_GAPI_GCOMPILED_PRIV_HPP
+273
View File
@@ -0,0 +1,273 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <vector>
#include <stack>
#include <unordered_map>
#include <ade/util/algorithm.hpp> // any_of
#include <ade/util/zip_range.hpp> // zip_range, indexed
#include <ade/graph.hpp>
#include <ade/passes/check_cycles.hpp>
#include "api/gcomputation_priv.hpp"
#include "api/gnode_priv.hpp" // FIXME: why it is here?
#include "api/gproto_priv.hpp" // FIXME: why it is here?
#include "api/gcall_priv.hpp" // FIXME: why it is here?
#include "api/gapi_priv.hpp" // FIXME: why it is here?
#include "api/gbackend_priv.hpp" // Backend basic API (newInstance, etc)
#include "compiler/gmodel.hpp"
#include "compiler/gmodelbuilder.hpp"
#include "compiler/gcompiler.hpp"
#include "compiler/gcompiled_priv.hpp"
#include "compiler/passes/passes.hpp"
#include "executor/gexecutor.hpp"
#include "backends/common/gbackend.hpp"
// <FIXME:>
#include "opencv2/gapi/cpu/core.hpp" // Also directly refer to Core
#include "opencv2/gapi/cpu/imgproc.hpp" // ...and Imgproc kernel implementations
// </FIXME:>
#include "opencv2/gapi/gcompoundkernel.hpp" // compound::backend()
#include "opencv2/core/cvdef.h"
#include "logger.hpp"
namespace
{
cv::gapi::GKernelPackage getKernelPackage(cv::GCompileArgs &args)
{
static auto ocv_pkg = combine(cv::gapi::core::cpu::kernels(),
cv::gapi::imgproc::cpu::kernels(),
cv::unite_policy::KEEP);
auto user_pkg = cv::gimpl::getCompileArg<cv::gapi::GKernelPackage>(args);
return combine(ocv_pkg, user_pkg.value_or(cv::gapi::GKernelPackage{}), cv::unite_policy::REPLACE);
}
cv::util::optional<std::string> getGraphDumpDirectory(cv::GCompileArgs& args)
{
auto dump_info = cv::gimpl::getCompileArg<cv::graph_dump_path>(args);
if (!dump_info.has_value())
{
const char* path = std::getenv("GRAPH_DUMP_PATH");
return path
? cv::util::make_optional(std::string(path))
: cv::util::optional<std::string>();
}
else
{
return cv::util::make_optional(dump_info.value().m_dump_path);
}
}
} // anonymous namespace
// GCompiler implementation ////////////////////////////////////////////////////
cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c,
GMetaArgs &&metas,
GCompileArgs &&args)
: m_c(c), m_metas(std::move(metas)), m_args(std::move(args))
{
using namespace std::placeholders;
m_all_kernels = getKernelPackage(m_args);
auto lookup_order = getCompileArg<gapi::GLookupOrder>(m_args).value_or(gapi::GLookupOrder());
auto dump_path = getGraphDumpDirectory(m_args);
m_e.addPassStage("init");
m_e.addPass("init", "check_cycles", ade::passes::CheckCycles());
m_e.addPass("init", "expand_kernels", std::bind(passes::expandKernels, _1,
m_all_kernels)); // NB: package is copied
m_e.addPass("init", "topo_sort", ade::passes::TopologicalSort());
m_e.addPass("init", "init_islands", passes::initIslands);
m_e.addPass("init", "check_islands", passes::checkIslands);
// TODO:
// - Check basic graph validity (i.e., all inputs are connected)
// - Complex dependencies (i.e. parent-child) unrolling
// - etc, etc, etc
// Remove GCompoundBackend to avoid calling setupBackend() with it in the list
m_all_kernels.remove(cv::gapi::compound::backend());
m_e.addPass("init", "resolve_kernels", std::bind(passes::resolveKernels, _1,
std::ref(m_all_kernels), // NB: and not copied here
lookup_order));
m_e.addPass("init", "check_islands_content", passes::checkIslandsContent);
m_e.addPassStage("meta");
m_e.addPass("meta", "initialize", std::bind(passes::initMeta, _1, std::ref(m_metas)));
m_e.addPass("meta", "propagate", passes::inferMeta);
m_e.addPass("meta", "finalize", passes::storeResultingMeta);
// moved to another stage, FIXME: two dumps?
// m_e.addPass("meta", "dump_dot", passes::dumpDotStdout);
// Special stage for backend-specific transformations
// FIXME: document passes hierarchy and order for backend developers
m_e.addPassStage("transform");
m_e.addPassStage("exec");
m_e.addPass("exec", "fuse_islands", passes::fuseIslands);
m_e.addPass("exec", "sync_islands", passes::syncIslandTags);
if (dump_path.has_value())
{
m_e.addPass("exec", "dump_dot", std::bind(passes::dumpGraph, _1,
dump_path.value()));
}
// Process backends at the last moment (after all G-API passes are added).
ade::ExecutionEngineSetupContext ectx(m_e);
auto backends = m_all_kernels.backends();
for (auto &b : backends)
{
b.priv().addBackendPasses(ectx);
}
}
void cv::gimpl::GCompiler::validateInputMeta()
{
if (m_metas.size() != m_c.priv().m_ins.size())
{
util::throw_error(std::logic_error
("COMPILE: GComputation interface / metadata mismatch! "
"(expected " + std::to_string(m_c.priv().m_ins.size()) + ", "
"got " + std::to_string(m_metas.size()) + " meta arguments)"));
}
const auto meta_matches = [](const GMetaArg &meta, const GProtoArg &proto) {
switch (proto.index())
{
// FIXME: Auto-generate methods like this from traits:
case GProtoArg::index_of<cv::GMat>():
return util::holds_alternative<cv::GMatDesc>(meta);
case GProtoArg::index_of<cv::GScalar>():
return util::holds_alternative<cv::GScalarDesc>(meta);
case GProtoArg::index_of<cv::detail::GArrayU>():
return util::holds_alternative<cv::GArrayDesc>(meta);
default:
GAPI_Assert(false);
}
return false; // should never happen
};
for (const auto &meta_arg_idx : ade::util::indexed(ade::util::zip(m_metas, m_c.priv().m_ins)))
{
const auto &meta = std::get<0>(ade::util::value(meta_arg_idx));
const auto &proto = std::get<1>(ade::util::value(meta_arg_idx));
if (!meta_matches(meta, proto))
{
const auto index = ade::util::index(meta_arg_idx);
util::throw_error(std::logic_error
("GComputation object type / metadata descriptor mismatch "
"(argument " + std::to_string(index) + ")"));
// FIXME: report what we've got and what we've expected
}
}
// All checks are ok
}
void cv::gimpl::GCompiler::validateOutProtoArgs()
{
for (const auto &out_pos : ade::util::indexed(m_c.priv().m_outs))
{
const auto &node = proto::origin_of(ade::util::value(out_pos)).node;
if (node.shape() != cv::GNode::NodeShape::CALL)
{
auto pos = ade::util::index(out_pos);
util::throw_error(std::logic_error
("Computation output " + std::to_string(pos) +
" is not a result of any operation"));
}
}
}
cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::generateGraph()
{
validateInputMeta();
validateOutProtoArgs();
// Generate ADE graph from expression-based computation
std::unique_ptr<ade::Graph> pG(new ade::Graph);
ade::Graph& g = *pG;
GModel::Graph gm(g);
cv::gimpl::GModel::init(gm);
cv::gimpl::GModelBuilder builder(g);
auto proto_slots = builder.put(m_c.priv().m_ins, m_c.priv().m_outs);
GAPI_LOG_INFO(NULL, "Generated graph: " << g.nodes().size() << " nodes" << std::endl);
// Store Computation's protocol in metadata
Protocol p;
std::tie(p.inputs, p.outputs, p.in_nhs, p.out_nhs) = proto_slots;
gm.metadata().set(p);
return pG;
}
void cv::gimpl::GCompiler::runPasses(ade::Graph &g)
{
m_e.runPasses(g);
GAPI_LOG_INFO(NULL, "All compiler passes are successful");
}
void cv::gimpl::GCompiler::compileIslands(ade::Graph &g)
{
GModel::Graph gm(g);
std::shared_ptr<ade::Graph> gptr(gm.metadata().get<IslandModel>().model);
GIslandModel::Graph gim(*gptr);
// Run topological sort on GIslandModel first
auto pass_ctx = ade::passes::PassContext{*gptr};
ade::passes::TopologicalSort{}(pass_ctx);
// Now compile islands
GIslandModel::compileIslands(gim, g, m_args);
}
cv::GCompiled cv::gimpl::GCompiler::produceCompiled(GPtr &&pg)
{
// This is the final compilation step. Here:
// - An instance of GExecutor is created. Depening on the platform,
// build configuration, etc, a GExecutor may be:
// - a naive single-thread graph interpreter;
// - a std::thread-based thing
// - a TBB-based thing, etc.
// - All this stuff is wrapped into a GCompiled object and returned
// to user.
// Note: this happens in the last pass ("compile_islands"):
// - Each GIsland of GIslandModel instantiates its own,
// backend-specific executable object
// - Every backend gets a subgraph to execute, and builds
// an execution plan for it (backend-specific execution)
// ...before call to produceCompiled();
const auto &outMetas = GModel::ConstGraph(*pg).metadata()
.get<OutputMeta>().outMeta;
std::unique_ptr<GExecutor> pE(new GExecutor(std::move(pg)));
// FIXME: select which executor will be actually used,
// make GExecutor abstract.
GCompiled compiled;
compiled.priv().setup(m_metas, outMetas, std::move(pE));
return compiled;
}
cv::GCompiled cv::gimpl::GCompiler::compile()
{
std::unique_ptr<ade::Graph> pG = generateGraph();
runPasses(*pG);
compileIslands(*pG);
return produceCompiled(std::move(pG));
}
+51
View File
@@ -0,0 +1,51 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GCOMPILER_HPP
#define OPENCV_GAPI_GCOMPILER_HPP
#include "opencv2/gapi/gcommon.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "opencv2/gapi/gcomputation.hpp"
#include <ade/execution_engine/execution_engine.hpp>
namespace cv { namespace gimpl {
// FIXME: exported for internal tests only!
class GAPI_EXPORTS GCompiler
{
const GComputation& m_c;
const GMetaArgs m_metas;
GCompileArgs m_args;
ade::ExecutionEngine m_e;
cv::gapi::GKernelPackage m_all_kernels;
void validateInputMeta();
void validateOutProtoArgs();
public:
explicit GCompiler(const GComputation &c,
GMetaArgs &&metas,
GCompileArgs &&args);
// The method which does everything...
GCompiled compile();
// But is actually composed of this:
using GPtr = std::unique_ptr<ade::Graph>;
GPtr generateGraph(); // Unroll GComputation into a GModel
void runPasses(ade::Graph &g); // Apply all G-API passes on a GModel
void compileIslands(ade::Graph &g); // Instantiate GIslandExecutables in GIslandModel
GCompiled produceCompiled(GPtr &&pg); // Produce GCompiled from processed GModel
};
}}
#endif // OPENCV_GAPI_GCOMPILER_HPP
+287
View File
@@ -0,0 +1,287 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <sstream>
#include <unordered_set>
#include <unordered_map>
#include <ade/util/checked_cast.hpp>
#include "api/gbackend_priv.hpp" // GBackend::Priv().compile()
#include "compiler/gmodel.hpp"
#include "compiler/gislandmodel.hpp"
#include "logger.hpp" // GAPI_LOG
namespace cv { namespace gimpl {
GIsland::GIsland(const gapi::GBackend &bknd,
ade::NodeHandle op,
util::optional<std::string> &&user_tag)
: m_backend(bknd)
, m_user_tag(std::move(user_tag))
{
m_all.insert(op);
m_in_ops.insert(op);
m_out_ops.insert(op);
}
// _ because of gcc4.8 wanings on ARM
GIsland::GIsland(const gapi::GBackend &_bknd,
node_set &&_all,
node_set &&_in_ops,
node_set &&_out_ops,
util::optional<std::string> &&_user_tag)
: m_backend(_bknd)
, m_all(std::move(_all))
, m_in_ops(std::move(_in_ops))
, m_out_ops(std::move(_out_ops))
, m_user_tag(std::move(_user_tag))
{
}
const GIsland::node_set& GIsland::contents() const
{
return m_all;
}
const GIsland::node_set& GIsland::in_ops() const
{
return m_in_ops;
}
const GIsland::node_set& GIsland::out_ops() const
{
return m_out_ops;
}
gapi::GBackend GIsland::backend() const
{
return m_backend;
}
bool GIsland::is_user_specified() const
{
return m_user_tag.has_value();
}
void GIsland::debug() const
{
std::stringstream stream;
stream << name() << " {{\n input ops: ";
for (const auto& nh : m_in_ops) stream << nh << "; ";
stream << "\n output ops: ";
for (const auto& nh : m_out_ops) stream << nh << "; ";
stream << "\n contents: ";
for (const auto& nh : m_all) stream << nh << "; ";
stream << "\n}}" << std::endl;
GAPI_LOG_INFO(NULL, stream.str());
}
GIsland::node_set GIsland::consumers(const ade::Graph &g,
const ade::NodeHandle &slot_nh) const
{
GIslandModel::ConstGraph gim(g);
auto data_nh = gim.metadata(slot_nh).get<DataSlot>().original_data_node;
GIsland::node_set result;
for (const auto& in_op : m_in_ops)
{
auto it = std::find(in_op->inNodes().begin(),
in_op->inNodes().end(),
data_nh);
if (it != in_op->inNodes().end())
result.insert(in_op);
}
return result;
}
ade::NodeHandle GIsland::producer(const ade::Graph &g,
const ade::NodeHandle &slot_nh) const
{
GIslandModel::ConstGraph gim(g);
auto data_nh = gim.metadata(slot_nh).get<DataSlot>().original_data_node;
for (const auto& out_op : m_out_ops)
{
auto it = std::find(out_op->outNodes().begin(),
out_op->outNodes().end(),
data_nh);
if (it != out_op->outNodes().end())
return out_op;
}
// Consistency: A GIsland requested for producer() of slot_nh should
// always had the appropriate GModel node handle in its m_out_ops vector.
GAPI_Assert(false);
return ade::NodeHandle();
}
std::string GIsland::name() const
{
if (is_user_specified())
return m_user_tag.value();
std::stringstream ss;
ss << "island_#" << std::hex << static_cast<const void*>(this);
return ss.str();
}
void GIslandModel::generateInitial(GIslandModel::Graph &g,
const ade::Graph &src_graph)
{
const GModel::ConstGraph src_g(src_graph);
// Initially GIslandModel is a 1:1 projection from GModel:
// 1) Every GModel::OP becomes a separate GIslandModel::FusedIsland;
// 2) Every GModel::DATA becomes GIslandModel::DataSlot;
// 3) Single-operation FusedIslands are connected with DataSlots in the
// same way as OPs and DATA (edges with the same metadata)
using node_set = std::unordered_set
< ade::NodeHandle
, ade::HandleHasher<ade::Node>
>;
using node_map = std::unordered_map
< ade::NodeHandle
, ade::NodeHandle
, ade::HandleHasher<ade::Node>
>;
node_set all_operations;
node_map data_to_slot;
// First, list all operations and build create DataSlots in <g>
for (auto src_nh : src_g.nodes())
{
switch (src_g.metadata(src_nh).get<NodeType>().t)
{
case NodeType::OP: all_operations.insert(src_nh); break;
case NodeType::DATA: data_to_slot[src_nh] = mkSlotNode(g, src_nh); break;
default: GAPI_Assert(false); break;
}
} // for (src_g.nodes)
// Now put single-op islands and connect it with DataSlots
for (auto src_op_nh : all_operations)
{
auto nh = mkIslandNode(g, src_g.metadata(src_op_nh).get<Op>().backend, src_op_nh, src_graph);
for (auto in_edge : src_op_nh->inEdges())
{
auto src_data_nh = in_edge->srcNode();
auto isl_slot_nh = data_to_slot.at(src_data_nh);
g.link(isl_slot_nh, nh); // no other data stored yet
}
for (auto out_edge : src_op_nh->outEdges())
{
auto dst_data_nh = out_edge->dstNode();
auto isl_slot_nh = data_to_slot.at(dst_data_nh);
g.link(nh, isl_slot_nh);
}
} // for(all_operations)
}
ade::NodeHandle GIslandModel::mkSlotNode(Graph &g, const ade::NodeHandle &data_nh)
{
auto nh = g.createNode();
g.metadata(nh).set(DataSlot{data_nh});
g.metadata(nh).set(NodeKind{NodeKind::SLOT});
return nh;
}
ade::NodeHandle GIslandModel::mkIslandNode(Graph &g, const gapi::GBackend& bknd, const ade::NodeHandle &op_nh, const ade::Graph &orig_g)
{
const GModel::ConstGraph src_g(orig_g);
util::optional<std::string> user_tag;
if (src_g.metadata(op_nh).contains<Island>())
{
user_tag = util::make_optional(src_g.metadata(op_nh).get<Island>().island);
}
auto nh = g.createNode();
std::shared_ptr<GIsland> island(new GIsland(bknd, op_nh, std::move(user_tag)));
g.metadata(nh).set(FusedIsland{std::move(island)});
g.metadata(nh).set(NodeKind{NodeKind::ISLAND});
return nh;
}
ade::NodeHandle GIslandModel::mkIslandNode(Graph &g, std::shared_ptr<GIsland>&& isl)
{
ade::NodeHandle nh = g.createNode();
g.metadata(nh).set(cv::gimpl::NodeKind{cv::gimpl::NodeKind::ISLAND});
g.metadata(nh).set<cv::gimpl::FusedIsland>({std::move(isl)});
return nh;
}
void GIslandModel::syncIslandTags(Graph &g, ade::Graph &orig_g)
{
GModel::Graph gm(orig_g);
for (auto nh : g.nodes())
{
if (NodeKind::ISLAND == g.metadata(nh).get<NodeKind>().k)
{
auto island = g.metadata(nh).get<FusedIsland>().object;
auto isl_tag = island->name();
for (const auto& orig_nh_inside : island->contents())
{
gm.metadata(orig_nh_inside).set(Island{isl_tag});
}
}
}
}
void GIslandModel::compileIslands(Graph &g, const ade::Graph &orig_g, const GCompileArgs &args)
{
GModel::ConstGraph gm(orig_g);
auto original_sorted = gm.metadata().get<ade::passes::TopologicalSortData>();
for (auto nh : g.nodes())
{
if (NodeKind::ISLAND == g.metadata(nh).get<NodeKind>().k)
{
auto island_obj = g.metadata(nh).get<FusedIsland>().object;
auto island_ops = island_obj->contents();
std::vector<ade::NodeHandle> topo_sorted_list;
ade::util::copy_if(original_sorted.nodes(),
std::back_inserter(topo_sorted_list),
[&](ade::NodeHandle sorted_nh) {
return ade::util::contains(island_ops, sorted_nh);
});
auto island_exe = island_obj->backend().priv()
.compile(orig_g, args, topo_sorted_list);
GAPI_Assert(nullptr != island_exe);
g.metadata(nh).set(IslandExec{std::move(island_exe)});
}
}
}
ade::NodeHandle GIslandModel::producerOf(const ConstGraph &g, ade::NodeHandle &data_nh)
{
for (auto nh : g.nodes())
{
// find a data slot...
if (NodeKind::SLOT == g.metadata(nh).get<NodeKind>().k)
{
// which is associated with the given data object...
if (data_nh == g.metadata(nh).get<DataSlot>().original_data_node)
{
// which probably has a produrer...
if (0u != nh->inNodes().size())
{
// ...then the answer is that producer
return nh->inNodes().front();
}
else return ade::NodeHandle(); // input data object?
// return empty to break the cycle
}
}
}
// No appropriate data slot found - probably, the object has been
// optimized out during fusion
return ade::NodeHandle();
}
} // namespace cv
} // namespace gimpl
+184
View File
@@ -0,0 +1,184 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GISLANDMODEL_HPP
#define OPENCV_GAPI_GISLANDMODEL_HPP
#include <unordered_set>
#include <memory> // shared_ptr
#include <ade/graph.hpp>
#include <ade/typed_graph.hpp>
#include <ade/passes/topological_sort.hpp>
#include "opencv2/gapi/util/optional.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "compiler/gobjref.hpp"
namespace cv { namespace gimpl {
// FIXME: GAPI_EXPORTS only because of tests!
class GAPI_EXPORTS GIsland
{
public:
using node_set = std::unordered_set
< ade::NodeHandle
, ade::HandleHasher<ade::Node>
>;
// Initial constructor (constructs a single-op Island)
GIsland(const gapi::GBackend &bknd,
ade::NodeHandle op,
util::optional<std::string>&& user_tag);
// Merged constructor
GIsland(const gapi::GBackend &bknd,
node_set &&all,
node_set &&in_ops,
node_set &&out_ops,
util::optional<std::string>&& user_tag);
const node_set& contents() const;
const node_set& in_ops() const;
const node_set& out_ops() const;
std::string name() const;
gapi::GBackend backend() const;
/**
* Returns all GModel operation node handles which are _reading_
* from a GModel data object associated (wrapped in) the given
* Slot object.
*
* @param g an ade::Graph with GIslandModel information inside
* @param slot_nh Slot object node handle of interest
* @return a set of GModel operation node handles
*/
node_set consumers(const ade::Graph &g,
const ade::NodeHandle &slot_nh) const;
/**
* Returns a GModel operation node handle which is _writing_
* to a GModel data object associated (wrapped in) the given
* Slot object.
*
* @param g an ade::Graph with GIslandModel information inside
* @param slot_nh Slot object node handle of interest
* @return a node handle of original GModel
*/
ade::NodeHandle producer(const ade::Graph &g,
const ade::NodeHandle &slot_nh) const;
void debug() const;
bool is_user_specified() const;
protected:
gapi::GBackend m_backend; // backend which handles this Island execution
node_set m_all; // everything (data + operations) within an island
node_set m_in_ops; // operations island begins with
node_set m_out_ops; // operations island ends with
// has island name IF specified by user. Empty for internal (inferred) islands
util::optional<std::string> m_user_tag;
};
// GIslandExecutable - a backend-specific thing which executes
// contents of an Island
// * Is instantiated by the last step of the Islands fusion procedure;
// * Is orchestrated by a GExecutor instance.
//
class GIslandExecutable
{
public:
using InObj = std::pair<RcDesc, cv::GRunArg>;
using OutObj = std::pair<RcDesc, cv::GRunArgP>;
// FIXME: now run() requires full input vector to be available.
// actually, parts of subgraph may execute even if there's no all data
// slots in place.
// TODO: Add partial execution capabilities
virtual void run(std::vector<InObj> &&input_objs,
std::vector<OutObj> &&output_objs) = 0;
virtual ~GIslandExecutable() = default;
};
// Couldn't reuse NodeType here - FIXME unify (move meta to a shared place)
struct NodeKind
{
static const char *name() { return "NodeKind"; }
enum { ISLAND, SLOT} k;
};
// FIXME: Rename to Island (as soon as current GModel::Island is renamed
// to IslandTag).
struct FusedIsland
{
static const char *name() { return "FusedIsland"; }
std::shared_ptr<GIsland> object;
};
struct DataSlot
{
static const char *name() { return "DataSlot"; }
ade::NodeHandle original_data_node; // direct link to GModel
};
struct IslandExec
{
static const char *name() { return "IslandExecutable"; }
std::shared_ptr<GIslandExecutable> object;
};
namespace GIslandModel
{
using Graph = ade::TypedGraph
< NodeKind
, FusedIsland
, DataSlot
, IslandExec
, ade::passes::TopologicalSortData
>;
// FIXME: derive from TypedGraph
using ConstGraph = ade::ConstTypedGraph
< NodeKind
, FusedIsland
, DataSlot
, IslandExec
, ade::passes::TopologicalSortData
>;
// Top-level function
void generateInitial(Graph &g, const ade::Graph &src_g);
// "Building blocks"
ade::NodeHandle mkSlotNode(Graph &g, const ade::NodeHandle &data_nh);
ade::NodeHandle mkIslandNode(Graph &g, const gapi::GBackend &bknd, const ade::NodeHandle &op_nh, const ade::Graph &orig_g);
ade::NodeHandle mkIslandNode(Graph &g, std::shared_ptr<GIsland>&& isl);
// GIslandModel API
void syncIslandTags(Graph &g, ade::Graph &orig_g);
void compileIslands(Graph &g, const ade::Graph &orig_g, const GCompileArgs &args);
// Debug routines
// producerOf - returns an Island handle which produces given data object
// from the original model (! don't mix with DataSlot)
// FIXME: GAPI_EXPORTS because of tests only!
ade::NodeHandle GAPI_EXPORTS producerOf(const ConstGraph &g, ade::NodeHandle &data_nh);
} // namespace GIslandModel
}} // namespace cv::gimpl
#endif // OPENCV_GAPI_GISLANDMODEL_HPP
+245
View File
@@ -0,0 +1,245 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <string>
#include <sstream> // used in GModel::log
#include <ade/util/zip_range.hpp> // util::indexed
#include <ade/util/checked_cast.hpp>
#include "opencv2/gapi/gproto.hpp"
#include "api/gnode_priv.hpp"
#include "compiler/gobjref.hpp"
#include "compiler/gmodel.hpp"
namespace cv { namespace gimpl {
ade::NodeHandle GModel::mkOpNode(GModel::Graph &g, const GKernel &k, const std::vector<GArg> &args, const std::string &island)
{
ade::NodeHandle op_h = g.createNode();
g.metadata(op_h).set(NodeType{NodeType::OP});
//These extra empty {} are to please GCC (-Wmissing-field-initializers)
g.metadata(op_h).set(Op{k, args, {}, {}, {}});
if (!island.empty())
g.metadata(op_h).set(Island{island});
return op_h;
}
ade::NodeHandle GModel::mkDataNode(GModel::Graph &g, const GOrigin& origin)
{
ade::NodeHandle op_h = g.createNode();
const auto id = g.metadata().get<DataObjectCounter>().GetNewId(origin.shape);
g.metadata(op_h).set(NodeType{NodeType::DATA});
GMetaArg meta;
Data::Storage storage = Data::Storage::INTERNAL; // By default, all objects are marked INTERNAL
if (origin.node.shape() == GNode::NodeShape::CONST_BOUNDED)
{
auto value = value_of(origin);
meta = descr_of(value);
storage = Data::Storage::CONST;
g.metadata(op_h).set(ConstValue{value});
}
g.metadata(op_h).set(Data{origin.shape, id, meta, origin.ctor, storage});
return op_h;
}
void GModel::linkIn(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t in_port)
{
// Check if input is already connected
for (const auto& in_e : opH->inEdges())
{
GAPI_Assert(g.metadata(in_e).get<Input>().port != in_port);
}
auto &op = g.metadata(opH).get<Op>();
auto &gm = g.metadata(objH).get<Data>();
// FIXME: check validity using kernel prototype
GAPI_Assert(in_port < op.args.size());
ade::EdgeHandle eh = g.link(objH, opH);
g.metadata(eh).set(Input{in_port});
// Replace an API object with a REF (G* -> GOBJREF)
op.args[in_port] = cv::GArg(RcDesc{gm.rc, gm.shape, {}});
}
void GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t out_port)
{
// FIXME: check validity using kernel prototype
// Check if output is already connected
for (const auto& out_e : opH->outEdges())
{
GAPI_Assert(g.metadata(out_e).get<Output>().port != out_port);
}
auto &op = g.metadata(opH).get<Op>();
auto &gm = g.metadata(objH).get<Data>();
GAPI_Assert(objH->inNodes().size() == 0u);
ade::EdgeHandle eh = g.link(opH, objH);
g.metadata(eh).set(Output{out_port});
// TODO: outs must be allocated according to kernel protocol!
const auto storage_with_port = ade::util::checked_cast<std::size_t>(out_port+1);
const auto min_out_size = std::max(op.outs.size(), storage_with_port);
op.outs.resize(min_out_size, RcDesc{-1,GShape::GMAT,{}}); // FIXME: Invalid shape instead?
op.outs[out_port] = RcDesc{gm.rc, gm.shape, {}};
}
std::vector<ade::NodeHandle> GModel::orderedInputs(Graph &g, ade::NodeHandle nh)
{
std::vector<ade::NodeHandle> sorted_in_nhs(nh->inEdges().size());
for (const auto& in_eh : nh->inEdges())
{
const auto port = g.metadata(in_eh).get<cv::gimpl::Input>().port;
GAPI_Assert(port < sorted_in_nhs.size());
sorted_in_nhs[port] = in_eh->srcNode();
}
return sorted_in_nhs;
}
std::vector<ade::NodeHandle> GModel::orderedOutputs(Graph &g, ade::NodeHandle nh)
{
std::vector<ade::NodeHandle> sorted_out_nhs(nh->outEdges().size());
for (const auto& out_eh : nh->outEdges())
{
const auto port = g.metadata(out_eh).get<cv::gimpl::Output>().port;
GAPI_Assert(port < sorted_out_nhs.size());
sorted_out_nhs[port] = out_eh->dstNode();
}
return sorted_out_nhs;
}
void GModel::init(Graph& g)
{
g.metadata().set(DataObjectCounter());
}
void GModel::log(Graph &g, ade::NodeHandle nh, std::string &&msg, ade::NodeHandle updater)
{
std::string s = std::move(msg);
if (updater != nullptr)
{
std::stringstream fmt;
fmt << " (via " << updater << ")";
s += fmt.str();
}
if (g.metadata(nh).contains<Journal>())
{
g.metadata(nh).get<Journal>().messages.push_back(s);
}
else
{
g.metadata(nh).set(Journal{{s}});
}
}
// FIXME:
// Unify with GModel::log(.. ade::NodeHandle ..)
void GModel::log(Graph &g, ade::EdgeHandle eh, std::string &&msg, ade::NodeHandle updater)
{
std::string s = std::move(msg);
if (updater != nullptr)
{
std::stringstream fmt;
fmt << " (via " << updater << ")";
s += fmt.str();
}
if (g.metadata(eh).contains<Journal>())
{
g.metadata(eh).get<Journal>().messages.push_back(s);
}
else
{
g.metadata(eh).set(Journal{{s}});
}
}
ade::NodeHandle GModel::detail::dataNodeOf(const ConstGraph &g, const GOrigin &origin)
{
// FIXME: Does it still work with graph transformations, e.g. redirectWriter()??
return g.metadata().get<Layout>().object_nodes.at(origin);
}
void GModel::redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
{
std::vector<ade::EdgeHandle> ehh(from->outEdges().begin(), from->outEdges().end());
for (auto e : ehh)
{
auto dst = e->dstNode();
auto input = g.metadata(e).get<Input>();
g.erase(e);
linkIn(g, dst, to, input.port);
}
}
void GModel::redirectWriter(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
{
GAPI_Assert(from->inEdges().size() == 1);
auto e = from->inEdges().front();
auto op = e->srcNode();
auto output = g.metadata(e).get<Output>();
g.erase(e);
linkOut(g, op, to, output.port);
}
GMetaArgs GModel::collectInputMeta(GModel::ConstGraph cg, ade::NodeHandle node)
{
GAPI_Assert(cg.metadata(node).get<NodeType>().t == NodeType::OP);
GMetaArgs in_meta_args(cg.metadata(node).get<Op>().args.size());
for (const auto &e : node->inEdges())
{
const auto& in_data = cg.metadata(e->srcNode()).get<Data>();
in_meta_args[cg.metadata(e).get<Input>().port] = in_data.meta;
}
return in_meta_args;
}
ade::EdgeHandle GModel::getInEdgeByPort(const GModel::ConstGraph& cg,
const ade::NodeHandle& nh,
std::size_t in_port)
{
auto inEdges = nh->inEdges();
const auto& edge = ade::util::find_if(inEdges, [&](ade::EdgeHandle eh) {
return cg.metadata(eh).get<Input>().port == in_port;
});
GAPI_Assert(edge != inEdges.end());
return *edge;
}
GMetaArgs GModel::collectOutputMeta(GModel::ConstGraph cg, ade::NodeHandle node)
{
GAPI_Assert(cg.metadata(node).get<NodeType>().t == NodeType::OP);
GMetaArgs out_meta_args(cg.metadata(node).get<Op>().outs.size());
for (const auto &e : node->outEdges())
{
const auto& out_data = cg.metadata(e->dstNode()).get<Data>();
out_meta_args[cg.metadata(e).get<Output>().port] = out_data.meta;
}
return out_meta_args;
}
bool GModel::isActive(const GModel::Graph &cg, const cv::gapi::GBackend &backend)
{
return ade::util::contains(cg.metadata().get<ActiveBackends>().backends,
backend);
}
}} // cv::gimpl
+251
View File
@@ -0,0 +1,251 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GMODEL_HPP
#define OPENCV_GAPI_GMODEL_HPP
#include <memory> // shared_ptr
#include <unordered_map>
#include <functional> // std::function
#include <ade/graph.hpp>
#include <ade/typed_graph.hpp>
#include <ade/passes/topological_sort.hpp>
// /!\ ATTENTION:
//
// No API includes like GMat, GNode, GCall here!
// This part of the system is API-unaware by its design.
//
#include "opencv2/gapi/garg.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "api/gapi_priv.hpp" // GShape
#include "api/gproto_priv.hpp" // origin_of
#include "backends/common/gbackend.hpp"
#include "compiler/gobjref.hpp"
#include "compiler/gislandmodel.hpp"
namespace cv { namespace gimpl {
// TODO: Document all metadata types
struct NodeType
{
static const char *name() { return "NodeType"; }
enum { OP, DATA } t;
};
struct Input
{
static const char *name() { return "Input"; }
std::size_t port;
};
struct Output
{
static const char *name() { return "Output"; }
std::size_t port;
};
struct Op
{
static const char *name() { return "Op"; }
cv::GKernel k;
std::vector<GArg> args; // TODO: Introduce a new type for internal args?
std::vector<RcDesc> outs; // TODO: Introduce a new type for resource references
cv::gapi::GBackend backend;
util::any opaque;
};
struct Data
{
static const char *name() { return "Data"; }
// FIXME: This is a _pure_ duplication of RcDesc now! (except storage)
GShape shape; // FIXME: Probably to be replaced by GMetaArg?
int rc;
GMetaArg meta;
HostCtor ctor; // T-specific helper to deal with unknown types in our code
// FIXME: Why rc+shape+meta is not represented as RcDesc here?
enum class Storage
{
INTERNAL, // data object is not listed in GComputation protocol
INPUT, // data object is listed in GComputation protocol as Input
OUTPUT, // data object is listed in GComputation protocol as Output
CONST, // data object is constant
};
Storage storage;
};
struct ConstValue
{
static const char *name() { return "ConstValue"; }
GRunArg arg;
};
// This metadata is valid for both DATA and OP kinds of nodes
// FIXME: Rename to IslandTag
struct Island
{
static const char *name() { return "Island"; }
std::string island; // can be set by user, otherwise is set by fusion
};
struct Protocol
{
static const char *name() { return "Protocol"; }
// TODO: Replace the whole thing with a "Protocol" object
std::vector<RcDesc> inputs;
std::vector<RcDesc> outputs;
std::vector<ade::NodeHandle> in_nhs;
std::vector<ade::NodeHandle> out_nhs;
};
struct OutputMeta
{
static const char *name() { return "OutputMeta"; }
GMetaArgs outMeta;
};
struct Journal
{
static const char *name() { return "Journal"; }
std::vector<std::string> messages;
};
// The mapping between user-side GMat/GScalar/... objects
// and its appropriate nodes. Can be stored in graph optionally
// (NOT used by any compiler or backends, introspection purposes
// only)
struct Layout
{
static const char *name() { return "Layout"; }
GOriginMap<ade::NodeHandle> object_nodes;
};
// Unique data object counter (per-type)
class DataObjectCounter
{
public:
static const char* name() { return "DataObjectCounter"; }
int GetNewId(GShape shape) { return m_next_data_id[shape]++; }
private:
std::unordered_map<cv::GShape, int> m_next_data_id;
};
// A projected graph of Islands (generated from graph of Operations)
struct IslandModel
{
static const char* name() { return "IslandModel"; }
std::shared_ptr<ade::Graph> model;
};
// List of backends selected for current graph execution
struct ActiveBackends
{
static const char *name() { return "ActiveBackends"; }
std::unordered_set<cv::gapi::GBackend> backends;
};
namespace GModel
{
using Graph = ade::TypedGraph
< NodeType
, Input
, Output
, Op
, Data
, ConstValue
, Island
, Protocol
, OutputMeta
, Journal
, ade::passes::TopologicalSortData
, DataObjectCounter
, Layout
, IslandModel
, ActiveBackends
>;
// FIXME: How to define it based on GModel???
using ConstGraph = ade::ConstTypedGraph
< NodeType
, Input
, Output
, Op
, Data
, ConstValue
, Island
, Protocol
, OutputMeta
, Journal
, ade::passes::TopologicalSortData
, DataObjectCounter
, Layout
, IslandModel
, ActiveBackends
>;
// User should initialize graph before using it
// GAPI_EXPORTS for tests
GAPI_EXPORTS void init (Graph& g);
ade::NodeHandle mkOpNode(Graph &g, const GKernel &k, const std::vector<GArg>& args, const std::string &island);
// FIXME: change it to take GMeta instead of GShape?
ade::NodeHandle mkDataNode(Graph &g, const GOrigin& origin);
// Adds a string message to a node. Any node can be subject of log, messages then
// appear in the dumped .dot file.x
void log(Graph &g, ade::NodeHandle op, std::string &&message, ade::NodeHandle updater = ade::NodeHandle());
void log(Graph &g, ade::EdgeHandle op, std::string &&message, ade::NodeHandle updater = ade::NodeHandle());
void linkIn (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t in_port);
void linkOut (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t out_port);
// FIXME: Align this GModel API properly, it is a mess now
namespace detail
{
// FIXME: GAPI_EXPORTS only because of tests!!!
GAPI_EXPORTS ade::NodeHandle dataNodeOf(const ConstGraph& g, const GOrigin &origin);
}
template<typename T> inline ade::NodeHandle dataNodeOf(const ConstGraph& g, T &&t)
{
return detail::dataNodeOf(g, cv::gimpl::proto::origin_of(GProtoArg{t}));
}
void linkIn (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t in_port);
void linkOut (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t out_port);
void redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to);
void redirectWriter (Graph &g, ade::NodeHandle from, ade::NodeHandle to);
std::vector<ade::NodeHandle> orderedInputs (Graph &g, ade::NodeHandle nh);
std::vector<ade::NodeHandle> orderedOutputs(Graph &g, ade::NodeHandle nh);
// Returns input meta array for given op node
// Array is sparse, as metadata for non-gapi input objects is empty
// TODO:
// Cover with tests!!
GMetaArgs collectInputMeta(GModel::ConstGraph cg, ade::NodeHandle node);
GMetaArgs collectOutputMeta(GModel::ConstGraph cg, ade::NodeHandle node);
ade::EdgeHandle getInEdgeByPort(const GModel::ConstGraph& cg, const ade::NodeHandle& nh, std::size_t in_port);
// Returns true if the given backend participates in the execution
bool isActive(const GModel::Graph &cg, const cv::gapi::GBackend &backend);
} // namespace GModel
}} // namespace cv::gimpl
#endif // OPENCV_GAPI_GMODEL_HPP
+303
View File
@@ -0,0 +1,303 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
////////////////////////////////////////////////////////////////////////////////
//
// FIXME: "I personally hate this file"
// - Dmitry
//
////////////////////////////////////////////////////////////////////////////////
#include <utility> // tuple
#include <stack> // stack
#include <vector> // vector
#include <unordered_set> // unordered_set
#include <type_traits> // is_same
#include <ade/util/zip_range.hpp> // util::indexed
#include "api/gapi_priv.hpp" // GOrigin
#include "api/gproto_priv.hpp" // descriptor_of and other GProtoArg-related
#include "api/gcall_priv.hpp"
#include "api/gnode_priv.hpp"
#include "compiler/gmodelbuilder.hpp"
namespace {
// TODO: move to helpers and cover with internal tests?
template<typename T> struct GVisited
{
typedef std::unordered_set<T> VTs;
bool visited(const T& t) const { return m_visited.find(t) != m_visited.end(); }
void visit (const T& t) { m_visited.insert(t); }
const VTs& visited() const { return m_visited; }
private:
VTs m_visited;
};
template<typename T, typename U = T> struct GVisitedTracker: protected GVisited<T>
{
typedef std::vector<U> TUs;
void visit(const T& t, const U& u) { GVisited<T>::visit(t); m_tracked.push_back(u); }
const TUs& tracked() const { return m_tracked; }
using GVisited<T>::visited;
private:
TUs m_tracked;
};
} // namespace
cv::gimpl::Unrolled cv::gimpl::unrollExpr(const GProtoArgs &ins,
const GProtoArgs &outs)
{
// FIXME: Who's gonna check if ins/outs are not EMPTY?
// FIXME: operator== for GObjects? (test if the same object or not)
using GObjId = const cv::GOrigin*;
GVisitedTracker<const GNode::Priv*, cv::GNode> ops;
GVisited<GObjId> reached_sources;
cv::GOriginSet origins;
// Cache input argument objects for a faster look-up
// While the only reliable way to identify a Data object is Origin
// (multiple data objects may refer to the same Origin as result of
// multuple yield() calls), input objects can be uniquely identified
// by its `priv` address. Here we rely on this to verify if the expression
// we unroll actually matches the protocol specified to us by user.
std::unordered_set<GObjId> in_objs_p;
for (const auto& in_obj : ins)
{
// Objects are guarnateed to remain alive while this method
// is working, so it is safe to keep pointers here and below
in_objs_p.insert(&proto::origin_of(in_obj));
}
// Recursive expression traversal
std::stack<cv::GProtoArg> data_objs(std::deque<cv::GProtoArg>(outs.begin(), outs.end()));
while (!data_objs.empty())
{
const auto obj = data_objs.top();
const auto &obj_p = proto::origin_of(obj);
data_objs.pop();
const auto &origin = obj_p;
origins.insert(origin); // TODO: Put Object description here later on
// If this Object is listed in the protocol, don't dive deeper (even
// if it is in fact a result of operation). Our computation is
// bounded by this data slot, so terminate this recursion path early.
if (in_objs_p.find(&obj_p) != in_objs_p.end())
{
reached_sources.visit(&obj_p);
continue;
}
const cv::GNode &node = origin.node;
switch (node.shape())
{
case cv::GNode::NodeShape::EMPTY:
// TODO: Own exception type?
util::throw_error(std::logic_error("Empty node reached!"));
break;
case cv::GNode::NodeShape::PARAM:
case cv::GNode::NodeShape::CONST_BOUNDED:
// No preceding operation to this data object - so the data object is either a GComputation
// parameter or a constant (compile-time) value
// Record it to check if protocol matches expression tree later
if (!reached_sources.visited(&obj_p))
reached_sources.visit(&obj_p);
break;
case cv::GNode::NodeShape::CALL:
if (!ops.visited(&node.priv()))
{
// This operation hasn't been visited yet - mark it so,
// then add its operands to stack to continue recursion.
ops.visit(&node.priv(), node);
const cv::GCall call = origin.node.call();
const cv::GCall::Priv& call_p = call.priv();
// Put the outputs object description of the node
// so that they are not lost if they are not consumed by other operations
for (const auto &it : ade::util::indexed(call_p.m_k.outShapes))
{
std::size_t port = ade::util::index(it);
GShape shape = ade::util::value(it);
GOrigin org { shape, node, port};
origins.insert(org);
}
for (const auto &arg : call_p.m_args)
{
if (proto::is_dynamic(arg))
{
data_objs.push(proto::rewrap(arg)); // Dive deeper
}
}
}
break;
default:
// Unsupported node shape
GAPI_Assert(false);
break;
}
}
// Check if protocol mentions data_objs which weren't reached during traversal
const auto missing_reached_sources = [&reached_sources](GObjId p) {
return reached_sources.visited().find(p) == reached_sources.visited().end();
};
if (ade::util::any_of(in_objs_p, missing_reached_sources))
{
// TODO: Own exception type or a return code?
util::throw_error(std::logic_error("Data object listed in Protocol "
"wasn\'t reached during unroll"));
}
// Check if there endpoint (parameter) data_objs which are not listed in protocol
const auto missing_in_proto = [&in_objs_p](GObjId p) {
return p->node.shape() != cv::GNode::NodeShape::CONST_BOUNDED &&
in_objs_p.find(p) == in_objs_p.end();
};
if (ade::util::any_of(reached_sources.visited(), missing_in_proto))
{
// TODO: Own exception type or a return code?
util::throw_error(std::logic_error("Data object reached during unroll "
"wasn\'t found in Protocol"));
}
return cv::gimpl::Unrolled{ops.tracked(), origins};
}
cv::gimpl::GModelBuilder::GModelBuilder(ade::Graph &g)
: m_g(g)
{
}
cv::gimpl::GModelBuilder::ProtoSlots
cv::gimpl::GModelBuilder::put(const GProtoArgs &ins, const GProtoArgs &outs)
{
const auto unrolled = cv::gimpl::unrollExpr(ins, outs);
// First, put all operations and its arguments into graph.
for (const auto &op_expr_node : unrolled.all_ops)
{
GAPI_Assert(op_expr_node.shape() == GNode::NodeShape::CALL);
const GCall& call = op_expr_node.call();
const GCall::Priv& call_p = call.priv();
ade::NodeHandle call_h = put_OpNode(op_expr_node);
for (const auto &it : ade::util::indexed(call_p.m_args))
{
const auto in_port = ade::util::index(it);
const auto& in_arg = ade::util::value(it);
if (proto::is_dynamic(in_arg))
{
ade::NodeHandle data_h = put_DataNode(proto::origin_of(in_arg));
cv::gimpl::GModel::linkIn(m_g, call_h, data_h, in_port);
}
}
}
// Then iterate via all "origins", instantiate (if not yet) Data graph nodes
// and connect these nodes with their producers in graph
for (const auto &origin : unrolled.all_data)
{
const cv::GNode& prod = origin.node;
GAPI_Assert(prod.shape() != cv::GNode::NodeShape::EMPTY);
ade::NodeHandle data_h = put_DataNode(origin);
if (prod.shape() == cv::GNode::NodeShape::CALL)
{
ade::NodeHandle call_h = put_OpNode(prod);
cv::gimpl::GModel::linkOut(m_g, call_h, data_h, origin.port);
}
}
// Mark graph data nodes as INPUTs and OUTPUTs respectively (according to the protocol)
for (const auto &arg : ins)
{
ade::NodeHandle nh = put_DataNode(proto::origin_of(arg));
m_g.metadata(nh).get<Data>().storage = Data::Storage::INPUT;
}
for (const auto &arg : outs)
{
ade::NodeHandle nh = put_DataNode(proto::origin_of(arg));
m_g.metadata(nh).get<Data>().storage = Data::Storage::OUTPUT;
}
// And, finally, store data object layout in meta
m_g.metadata().set(Layout{m_graph_data});
// After graph is generated, specify which data objects are actually
// computation entry/exit points.
using NodeDescr = std::pair<std::vector<RcDesc>,
std::vector<ade::NodeHandle> >;
const auto get_proto_slots = [&](const GProtoArgs &proto) -> NodeDescr
{
NodeDescr slots;
slots.first.reserve(proto.size());
slots.second.reserve(proto.size());
for (const auto &arg : proto)
{
ade::NodeHandle nh = put_DataNode(proto::origin_of(arg));
const auto &desc = m_g.metadata(nh).get<Data>();
//These extra empty {} are to please GCC (-Wmissing-field-initializers)
slots.first.push_back(RcDesc{desc.rc, desc.shape, {}});
slots.second.push_back(nh);
}
return slots;
};
auto in_slots = get_proto_slots(ins);
auto out_slots = get_proto_slots(outs);
return ProtoSlots{in_slots.first, out_slots.first,
in_slots.second, out_slots.second};
}
ade::NodeHandle cv::gimpl::GModelBuilder::put_OpNode(const cv::GNode &node)
{
const auto& node_p = node.priv();
const auto it = m_graph_ops.find(&node_p);
if (it == m_graph_ops.end())
{
GAPI_Assert(node.shape() == GNode::NodeShape::CALL);
const auto &call_p = node.call().priv();
auto nh = cv::gimpl::GModel::mkOpNode(m_g, call_p.m_k, call_p.m_args, node_p.m_island);
m_graph_ops[&node_p] = nh;
return nh;
}
else return it->second;
}
// FIXME: rename to get_DataNode (and same for Op)
ade::NodeHandle cv::gimpl::GModelBuilder::put_DataNode(const GOrigin &origin)
{
const auto it = m_graph_data.find(origin);
if (it == m_graph_data.end())
{
auto nh = cv::gimpl::GModel::mkDataNode(m_g, origin);
m_graph_data[origin] = nh;
return nh;
}
else return it->second;
}
@@ -0,0 +1,77 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GMODEL_BUILDER_HPP
#define OPENCV_GAPI_GMODEL_BUILDER_HPP
#include <map>
#include <unordered_map>
#include "opencv2/gapi/gproto.hpp"
#include "opencv2/gapi/gcall.hpp"
#include "api/gapi_priv.hpp"
#include "api/gnode.hpp"
#include "compiler/gmodel.hpp"
namespace cv { namespace gimpl {
struct Unrolled
{
std::vector<cv::GNode> all_ops;
GOriginSet all_data;
// NB.: Right now, as G-API operates with GMats only and that
// GMats have no type or dimensions (when a computation is built),
// track only origins (data links) with no any additional meta.
};
// FIXME: GAPI_EXPORTS only because of tests!!!
GAPI_EXPORTS Unrolled unrollExpr(const GProtoArgs &ins, const GProtoArgs &outs);
// This class generates an ADE graph with G-API specific metadata
// to represent user-specified computation in terms of graph model
//
// Resulting graph is built according to the following rules:
// - Every operation is a node
// - Every dynamic object (GMat) is a node
// - Edges between nodes represent producer/consumer relationships
// between operations and data objects.
// FIXME: GAPI_EXPORTS only because of tests!!!
class GAPI_EXPORTS GModelBuilder
{
GModel::Graph m_g;
// Mappings of G-API user framework entities to ADE node handles
std::unordered_map<const cv::GNode::Priv*, ade::NodeHandle> m_graph_ops;
GOriginMap<ade::NodeHandle> m_graph_data;
// Internal methods for mapping APIs into ADE during put()
ade::NodeHandle put_OpNode(const cv::GNode &node);
ade::NodeHandle put_DataNode(const cv::GOrigin &origin);
public:
explicit GModelBuilder(ade::Graph &g);
// TODO: replace GMat with a generic type
// TODO: Cover with tests! (as the rest of internal stuff)
// FIXME: Calling this method multiple times is currently UB
// TODO: add a semantic link between "ints" returned and in-model data IDs.
typedef std::tuple<std::vector<RcDesc>,
std::vector<RcDesc>,
std::vector<ade::NodeHandle>,
std::vector<ade::NodeHandle> > ProtoSlots;
ProtoSlots put(const GProtoArgs &ins, const GProtoArgs &outs);
protected:
ade::NodeHandle opNode(cv::GMat gmat);
};
}}
#endif // OPENCV_GAPI_GMODEL_BUILDER_HPP
+50
View File
@@ -0,0 +1,50 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GMATREF_HPP
#define OPENCV_GAPI_GMATREF_HPP
#include "opencv2/gapi/util/variant.hpp"
#include "opencv2/gapi/garg.hpp"
#include "api/gapi_priv.hpp" // GShape, HostCtor
namespace cv
{
namespace gimpl
{
struct RcDesc
{
int id; // id is unique but local to shape
GShape shape; // pair <id,shape> IS the unique ID
HostCtor ctor; // FIXME: is it really used here? Or in <Data>?
bool operator==(const RcDesc &rhs) const
{
// FIXME: ctor is not checked (should be?)
return id == rhs.id && shape == rhs.shape;
}
bool operator< (const RcDesc &rhs) const
{
return (id == rhs.id) ? shape < rhs.shape : id < rhs.id;
}
};
} // gimpl
namespace detail
{
template<> struct GTypeTraits<cv::gimpl::RcDesc>
{
static constexpr const ArgKind kind = ArgKind::GOBJREF;
};
}
} // cv
#endif // OPENCV_GAPI_GMATREF_HPP
@@ -0,0 +1,221 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <iostream> // cout
#include <sstream> // stringstream
#include <fstream> // ofstream
#include <ade/passes/check_cycles.hpp>
#include "opencv2/gapi/gproto.hpp"
#include "compiler/gmodel.hpp"
#include "compiler/gislandmodel.hpp"
#include "compiler/passes/passes.hpp"
namespace cv { namespace gimpl { namespace passes {
// TODO: FIXME: Ideally all this low-level stuff with accessing ADE APIs directly
// should be incapsulated somewhere into GModel, so here we'd operate not
// with raw nodes and edges, but with Operations and Data it produce/consume.
void dumpDot(const ade::Graph &g, std::ostream& os)
{
GModel::ConstGraph gr(g);
const std::unordered_map<cv::GShape, std::string> data_labels = {
{cv::GShape::GMAT, "GMat"},
{cv::GShape::GSCALAR, "GScalar"},
{cv::GShape::GARRAY, "GArray"},
};
auto format_op_label = [&gr](ade::NodeHandle nh) -> std::string {
std::stringstream ss;
const cv::GKernel k = gr.metadata(nh).get<Op>().k;
ss << k.name << "_" << nh;
return ss.str();
};
auto format_op = [&format_op_label](ade::NodeHandle nh) -> std::string {
return "\"" + format_op_label(nh) + "\"";
};
auto format_obj = [&gr, &data_labels](ade::NodeHandle nh) -> std::string {
std::stringstream ss;
const auto &data = gr.metadata(nh).get<Data>();
ss << data_labels.at(data.shape) << "_" << data.rc;
return ss.str();
};
auto format_log = [&gr](ade::NodeHandle nh, const std::string &obj_name) {
std::stringstream ss;
const auto &msgs = gr.metadata(nh).get<Journal>().messages;
ss << "xlabel=\"";
if (!obj_name.empty()) { ss << "*** " << obj_name << " ***:\n"; };
for (const auto &msg : msgs) { ss << msg << "\n"; }
ss << "\"";
return ss.str();
};
// FIXME:
// Unify with format_log
auto format_log_e = [&gr](ade::EdgeHandle nh) {
std::stringstream ss;
const auto &msgs = gr.metadata(nh).get<Journal>().messages;
for (const auto &msg : msgs) { ss << "\n" << msg; }
return ss.str();
};
auto sorted = gr.metadata().get<ade::passes::TopologicalSortData>();
os << "digraph GAPI_Computation {\n";
// Prior to dumping the graph itself, list Data and Op nodes individually
// and put type information in labels.
// Also prepare list of nodes in islands, if any
std::map<std::string, std::vector<std::string> > islands;
for (auto &nh : sorted.nodes())
{
const auto node_type = gr.metadata(nh).get<NodeType>().t;
if (NodeType::DATA == node_type)
{
const auto obj_data = gr.metadata(nh).get<Data>();
const auto obj_name = format_obj(nh);
os << obj_name << " [label=\"" << obj_name << "\n" << obj_data.meta << "\"";
if (gr.metadata(nh).contains<Journal>()) { os << ", " << format_log(nh, obj_name); }
os << " ]\n";
if (gr.metadata(nh).contains<Island>())
islands[gr.metadata(nh).get<Island>().island].push_back(obj_name);
}
else if (NodeType::OP == gr.metadata(nh).get<NodeType>().t)
{
const auto obj_name = format_op(nh);
const auto obj_name_label = format_op_label(nh);
os << obj_name << " [label=\"" << obj_name_label << "\"";
if (gr.metadata(nh).contains<Journal>()) { os << ", " << format_log(nh, obj_name_label); }
os << " ]\n";
if (gr.metadata(nh).contains<Island>())
islands[gr.metadata(nh).get<Island>().island].push_back(obj_name);
}
}
// Then, dump Islands (only nodes, operations and data, without links)
for (const auto &isl : islands)
{
os << "subgraph \"cluster " + isl.first << "\" {\n";
for(auto isl_node : isl.second) os << isl_node << ";\n";
os << "label=\"" << isl.first << "\";";
os << "}\n";
}
// Now dump the graph
for (auto &nh : sorted.nodes())
{
// FIXME: Alan Kay probably hates me.
switch (gr.metadata(nh).get<NodeType>().t)
{
case NodeType::DATA:
{
const auto obj_name = format_obj(nh);
for (const auto &eh : nh->outEdges())
{
os << obj_name << " -> " << format_op(eh->dstNode())
<< " [ label = \"in_port: "
<< gr.metadata(eh).get<Input>().port;
if (gr.metadata(eh).contains<Journal>()) { os << format_log_e(eh); }
os << "\" ] \n";
}
}
break;
case NodeType::OP:
{
for (const auto &eh : nh->outEdges())
{
os << format_op(nh) << " -> " << format_obj(eh->dstNode())
<< " [ label = \"out_port: "
<< gr.metadata(eh).get<Output>().port
<< " \" ]; \n";
}
}
break;
default: GAPI_Assert(false);
}
}
// And finally dump a GIslandModel (not connected with GModel directly,
// but projected in the same .dot file side-by-side)
auto pIG = gr.metadata().get<IslandModel>().model;
GIslandModel::Graph gim(*pIG);
for (auto nh : gim.nodes())
{
switch (gim.metadata(nh).get<NodeKind>().k)
{
case NodeKind::ISLAND:
{
const auto island = gim.metadata(nh).get<FusedIsland>().object;
const auto isl_name = "\"" + island->name() + "\"";
for (auto out_nh : nh->outNodes())
{
os << isl_name << " -> \"slot:"
<< format_obj(gim.metadata(out_nh).get<DataSlot>()
.original_data_node)
<< "\"\n";
}
}
break;
case NodeKind::SLOT:
{
const auto obj_name = format_obj(gim.metadata(nh).get<DataSlot>()
.original_data_node);
for (auto cons_nh : nh->outNodes())
{
os << "\"slot:" << obj_name << "\" -> \""
<< gim.metadata(cons_nh).get<FusedIsland>().object->name()
<< "\"\n";
}
}
break;
default:
GAPI_Assert(false);
break;
}
}
os << "}" << std::endl;
}
void dumpDot(ade::passes::PassContext &ctx, std::ostream& os)
{
dumpDot(ctx.graph, os);
}
void dumpDotStdout(ade::passes::PassContext &ctx)
{
dumpDot(ctx, std::cout);
}
void dumpDotToFile(ade::passes::PassContext &ctx, const std::string& dump_path)
{
std::ofstream dump_file(dump_path);
if (dump_file.is_open())
{
dumpDot(ctx, dump_file);
dump_file << std::endl;
}
}
void dumpGraph(ade::passes::PassContext &ctx, const std::string& dump_path)
{
dump_path.empty() ? dumpDotStdout(ctx) : dumpDotToFile(ctx, dump_path);
}
}}} // cv::gimpl::passes
+640
View File
@@ -0,0 +1,640 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <string>
#include <list> // list
#include <iomanip> // setw, etc
#include <fstream> // ofstream
#include <memory>
#include <functional>
#include <ade/util/algorithm.hpp> // contains
#include <ade/util/chain_range.hpp> // chain
#include "opencv2/gapi/util/optional.hpp" // util::optional
#include "opencv2/core/cvdef.h"
#include "logger.hpp" // GAPI_LOG
#include "compiler/gmodel.hpp"
#include "compiler/gislandmodel.hpp"
#include "compiler/passes/passes.hpp"
#include "compiler/passes/helpers.hpp"
#include "compiler/transactions.hpp"
////////////////////////////////////////////////////////////////////////////////
//
// N.B.
// Merge is a binary operation (LHS `Merge` RHS) where LHS may be arbitrary
//
// After every merge, the procedure starts from the beginning (in the topological
// order), thus trying to merge next "unmerged" island to the latest merged one.
//
////////////////////////////////////////////////////////////////////////////////
// Uncomment to dump more info on merge process
// FIXME: make it user-configurable run-time option
// #define DEBUG_MERGE
namespace cv
{
namespace gimpl
{
namespace
{
bool fusionIsTrivial(const ade::Graph &src_graph)
{
// Fusion is considered trivial if there only one
// active backend and no user-defined islands
// FIXME:
// Also check the cases backend can't handle
// (e.x. GScalar connecting two fluid ops should split the graph)
const GModel::ConstGraph g(src_graph);
const auto& active_backends = g.metadata().get<ActiveBackends>().backends;
return active_backends.size() == 1 &&
ade::util::all_of(g.nodes(), [&](ade::NodeHandle nh) {
return !g.metadata(nh).contains<Island>();
});
}
void fuseTrivial(GIslandModel::Graph &g, const ade::Graph &src_graph)
{
const GModel::ConstGraph src_g(src_graph);
const auto& backend = *src_g.metadata().get<ActiveBackends>().backends.cbegin();
const auto& proto = src_g.metadata().get<Protocol>();
GIsland::node_set all, in_ops, out_ops;
all.insert(src_g.nodes().begin(), src_g.nodes().end());
for (const auto nh : proto.in_nhs)
{
all.erase(nh);
in_ops.insert(nh->outNodes().begin(), nh->outNodes().end());
}
for (const auto nh : proto.out_nhs)
{
all.erase(nh);
out_ops.insert(nh->inNodes().begin(), nh->inNodes().end());
}
auto isl = std::make_shared<GIsland>(backend,
std::move(all),
std::move(in_ops),
std::move(out_ops),
util::optional<std::string>{});
auto ih = GIslandModel::mkIslandNode(g, std::move(isl));
for (const auto nh : proto.in_nhs)
{
auto slot = GIslandModel::mkSlotNode(g, nh);
g.link(slot, ih);
}
for (const auto nh : proto.out_nhs)
{
auto slot = GIslandModel::mkSlotNode(g, nh);
g.link(ih, slot);
}
}
struct MergeContext
{
using CycleCausers = std::pair< std::shared_ptr<GIsland>,
std::shared_ptr<GIsland> >;
struct CycleHasher final
{
std::size_t operator()(const CycleCausers& p) const
{
std::size_t a = std::hash<GIsland*>()(p.first.get());
std::size_t b = std::hash<GIsland*>()(p.second.get());
return a ^ (b << 1);
}
};
// Set of Islands pairs which cause a cycle if merged.
// Every new merge produces a new Island, and if Islands were
// merged (and thus dropped from GIslandModel), the objects may
// still be alive as included into this set.
std::unordered_set<CycleCausers, CycleHasher> cycle_causers;
};
bool canMerge(const GIslandModel::Graph &g,
const ade::NodeHandle a_nh,
const ade::NodeHandle /*slot_nh*/,
const ade::NodeHandle b_nh,
const MergeContext &ctx = MergeContext())
{
auto a_ptr = g.metadata(a_nh).get<FusedIsland>().object;
auto b_ptr = g.metadata(b_nh).get<FusedIsland>().object;
GAPI_Assert(a_ptr.get());
GAPI_Assert(b_ptr.get());
// Islands with different affinity can't be merged
if (a_ptr->backend() != b_ptr->backend())
return false;
// Islands which cause a cycle can't be merged as well
// (since the flag is set, the procedure already tried to
// merge these islands in the past)
if (ade::util::contains(ctx.cycle_causers, std::make_pair(a_ptr, b_ptr))||
ade::util::contains(ctx.cycle_causers, std::make_pair(b_ptr, a_ptr)))
return false;
// There may be user-defined islands. Initially user-defined
// islands also are built from single operations and then merged
// by this procedure, but there is some exceptions.
// User-specified island can't be merged to an internal island
if ( ( a_ptr->is_user_specified() && !b_ptr->is_user_specified())
|| (!a_ptr->is_user_specified() && b_ptr->is_user_specified()))
{
return false;
}
else if (a_ptr->is_user_specified() && b_ptr->is_user_specified())
{
// These islads are _different_ user-specified Islands
// FIXME: today it may only differ by name
if (a_ptr->name() != b_ptr->name())
return false;
}
// FIXME: add a backend-specified merge checker
return true;
}
inline bool isProducedBy(const ade::NodeHandle &slot,
const ade::NodeHandle &island)
{
// A data slot may have only 0 or 1 producer
if (slot->inNodes().size() == 0)
return false;
return slot->inNodes().front() == island;
}
inline bool isConsumedBy(const ade::NodeHandle &slot,
const ade::NodeHandle &island)
{
auto it = std::find_if(slot->outNodes().begin(),
slot->outNodes().end(),
[&](const ade::NodeHandle &nh) {
return nh == island;
});
return it != slot->outNodes().end();
}
/**
* Find a candidate Island for merge for the given Island nh.
*
* @param g Island Model where merge occurs
* @param nh GIsland node, either LHS or RHS of probable merge
* @param ctx Merge context, may contain some cached stuff to avoid
* double/triple/etc checking
* @return Tuple of Island handle, Data slot handle (which connects them),
* and a position of found handle with respect to nh (IN/OUT)
*/
std::tuple<ade::NodeHandle, ade::NodeHandle, Direction>
findCandidate(const GIslandModel::Graph &g,
ade::NodeHandle nh,
const MergeContext &ctx = MergeContext())
{
using namespace std::placeholders;
// Find a first matching candidate GIsland for merge
// among inputs
for (const auto& input_data_nh : nh->inNodes())
{
if (input_data_nh->inNodes().size() != 0)
{
// Data node must have a single producer only
GAPI_DbgAssert(input_data_nh->inNodes().size() == 1);
auto input_data_prod_nh = input_data_nh->inNodes().front();
if (canMerge(g, input_data_prod_nh, input_data_nh, nh, ctx))
return std::make_tuple(input_data_prod_nh,
input_data_nh,
Direction::In);
}
} // for(inNodes)
// Ok, now try to find it among the outputs
for (const auto& output_data_nh : nh->outNodes())
{
auto mergeTest = [&](ade::NodeHandle cons_nh) -> bool {
return canMerge(g, nh, output_data_nh, cons_nh, ctx);
};
auto cand_it = std::find_if(output_data_nh->outNodes().begin(),
output_data_nh->outNodes().end(),
mergeTest);
if (cand_it != output_data_nh->outNodes().end())
return std::make_tuple(*cand_it,
output_data_nh,
Direction::Out);
} // for(outNodes)
// Empty handle, no good candidates
return std::make_tuple(ade::NodeHandle(),
ade::NodeHandle(),
Direction::Invalid);
}
// A cancellable merge of two GIslands, "a" and "b", connected via "slot"
class MergeAction
{
ade::Graph &m_g;
const ade::Graph &m_orig_g;
GIslandModel::Graph m_gim;
ade::NodeHandle m_prod;
ade::NodeHandle m_slot;
ade::NodeHandle m_cons;
Change::List m_changes;
struct MergeObjects
{
using NS = GIsland::node_set;
NS all; // same as in GIsland
NS in_ops; // same as in GIsland
NS out_ops; // same as in GIsland
NS opt_interim_slots; // can be dropped (optimized out)
NS non_opt_interim_slots;// can't be dropped (extern. link)
};
MergeObjects identify() const;
public:
MergeAction(ade::Graph &g,
const ade::Graph &orig_g,
ade::NodeHandle prod,
ade::NodeHandle slot,
ade::NodeHandle cons)
: m_g(g)
, m_orig_g(orig_g)
, m_gim(GIslandModel::Graph(m_g))
, m_prod(prod)
, m_slot(slot)
, m_cons(cons)
{
}
void tryMerge(); // Try to merge islands Prod and Cons
void rollback(); // Roll-back changes if merge has been done but broke the model
void commit(); // Fix changes in the model after successful merge
};
// Merge proceduce is a replacement of two Islands, Prod and Cons,
// connected via !Slot!, with a new Island, which contain all Prod
// nodes + all Cons nodes, and reconnected in the graph properly:
//
// Merge(Prod, !Slot!, Cons)
//
// [Slot 2]
// :
// v
// ... [Slot 0] -> Prod -> !Slot! -> Cons -> [Slot 3] -> ...
// ... [Slot 1] -' : '-> [Slot 4] -> ...
// V
// ...
// results into:
//
// ... [Slot 0] -> Merged -> [Slot 3] ...
// ... [Slot 1] : :-> [Slot 4] ...
// ... [Slot 2] ' '-> !Slot! ...
//
// The rules are the following:
// 1) All Prod input slots become Merged input slots;
// - Example: Slot 0 Slot 1
// 2) Any Cons input slots which come from Islands different to Prod
// also become Merged input slots;
// - Example: Slot 2
// 3) All Cons output slots become Merged output slots;
// - Example: Slot 3, Slot 4
// 4) All Prod output slots which are not consumed by Cons
// also become Merged output slots;
// - (not shown on the example)
// 5) If the !Slot! which connects Prod and Cons is consumed
// exclusively by Cons, it is optimized out (dropped) from the model;
// 6) If the !Slot! is used by consumers other by Cons, it
// becomes an extra output of Merged
// 7) !Slot! may be not the only connection between Prod and Cons,
// but as a result of merge operation, all such connections
// should be handles as described for !Slot!
MergeAction::MergeObjects MergeAction::identify() const
{
auto lhs = m_gim.metadata(m_prod).get<FusedIsland>().object;
auto rhs = m_gim.metadata(m_cons).get<FusedIsland>().object;
GIsland::node_set interim_slots;
GIsland::node_set merged_all(lhs->contents());
merged_all.insert(rhs->contents().begin(), rhs->contents().end());
GIsland::node_set merged_in_ops(lhs->in_ops()); // (1)
for (auto cons_in_slot_nh : m_cons->inNodes()) // (2)
{
if (isProducedBy(cons_in_slot_nh, m_prod))
{
interim_slots.insert(cons_in_slot_nh);
// at this point, interim_slots are not sync with merged_all
// (merged_all will be updated with interim_slots which
// will be optimized out).
}
else
{
const auto extra_in_ops = rhs->consumers(m_g, cons_in_slot_nh);
merged_in_ops.insert(extra_in_ops.begin(), extra_in_ops.end());
}
}
GIsland::node_set merged_out_ops(rhs->out_ops()); // (3)
for (auto prod_out_slot_nh : m_prod->outNodes()) // (4)
{
if (!isConsumedBy(prod_out_slot_nh, m_cons))
{
merged_out_ops.insert(lhs->producer(m_g, prod_out_slot_nh));
}
}
// (5,6,7)
GIsland::node_set opt_interim_slots;
GIsland::node_set non_opt_interim_slots;
auto is_non_opt = [&](const ade::NodeHandle &slot_nh) {
// If a data object associated with this slot is a part
// of GComputation _output_ protocol, it can't be optimzied out
const auto data_nh = m_gim.metadata(slot_nh).get<DataSlot>().original_data_node;
const auto &data = GModel::ConstGraph(m_orig_g).metadata(data_nh).get<Data>();
if (data.storage == Data::Storage::OUTPUT)
return true;
// Otherwise, a non-optimizeable data slot is the one consumed
// by some other island than "cons"
const auto it = std::find_if(slot_nh->outNodes().begin(),
slot_nh->outNodes().end(),
[&](ade::NodeHandle &&nh)
{return nh != m_cons;});
return it != slot_nh->outNodes().end();
};
for (auto slot_nh : interim_slots)
{
// Put all intermediate data nodes (which are BOTH optimized
// and not-optimized out) to Island contents.
merged_all.insert(m_gim.metadata(slot_nh)
.get<DataSlot>().original_data_node);
GIsland::node_set &dst = is_non_opt(slot_nh)
? non_opt_interim_slots // there are consumers other than m_cons
: opt_interim_slots; // there's no consumers other than m_cons
dst.insert(slot_nh);
}
// (4+6).
// BTW, (4) could be "All Prod output slots read NOT ONLY by Cons"
for (auto non_opt_slot_nh : non_opt_interim_slots)
{
merged_out_ops.insert(lhs->producer(m_g, non_opt_slot_nh));
}
return MergeObjects{
merged_all, merged_in_ops, merged_out_ops,
opt_interim_slots, non_opt_interim_slots
};
}
// FIXME(DM): Probably this procedure will be refactored dramatically one day...
void MergeAction::tryMerge()
{
// _: Understand the contents and I/O connections of a new merged Island
MergeObjects mo = identify();
auto lhs_obj = m_gim.metadata(m_prod).get<FusedIsland>().object;
auto rhs_obj = m_gim.metadata(m_cons).get<FusedIsland>().object;
GAPI_Assert( ( lhs_obj->is_user_specified() && rhs_obj->is_user_specified())
|| (!lhs_obj->is_user_specified() && !rhs_obj->is_user_specified()));
cv::util::optional<std::string> maybe_user_tag;
if (lhs_obj->is_user_specified() && rhs_obj->is_user_specified())
{
GAPI_Assert(lhs_obj->name() == rhs_obj->name());
maybe_user_tag = cv::util::make_optional(lhs_obj->name());
}
// A: Create a new Island and add it to the graph
auto backend = m_gim.metadata(m_prod).get<FusedIsland>()
.object->backend();
auto merged = std::make_shared<GIsland>(backend,
std::move(mo.all),
std::move(mo.in_ops),
std::move(mo.out_ops),
std::move(maybe_user_tag));
// FIXME: move this debugging to some user-controllable log-level
#ifdef DEBUG_MERGE
merged->debug();
#endif
auto new_nh = GIslandModel::mkIslandNode(m_gim, std::move(merged));
m_changes.enqueue<Change::NodeCreated>(new_nh);
// B: Disconnect all Prod's input Slots from Prod,
// connect it to Merged
std::vector<ade::EdgeHandle> input_edges(m_prod->inEdges().begin(),
m_prod->inEdges().end());
for (auto in_edge : input_edges)
{
m_changes.enqueue<Change::NewLink>(m_g, in_edge->srcNode(), new_nh);
m_changes.enqueue<Change::DropLink>(m_g, m_prod, in_edge);
}
// C: Disconnect all Cons' output Slots from Cons,
// connect it to Merged
std::vector<ade::EdgeHandle> output_edges(m_cons->outEdges().begin(),
m_cons->outEdges().end());
for (auto out_edge : output_edges)
{
m_changes.enqueue<Change::NewLink>(m_g, new_nh, out_edge->dstNode());
m_changes.enqueue<Change::DropLink>(m_g, m_cons, out_edge);
}
// D: Process the intermediate slots (betweed Prod n Cons).
// D/1 - Those which are optimized out are just removed from the model
for (auto opt_slot_nh : mo.opt_interim_slots)
{
GAPI_Assert(1 == opt_slot_nh->inNodes().size() );
GAPI_Assert(m_prod == opt_slot_nh->inNodes().front());
std::vector<ade::EdgeHandle> edges_to_drop;
ade::util::copy(ade::util::chain(opt_slot_nh->inEdges(),
opt_slot_nh->outEdges()),
std::back_inserter(edges_to_drop));
for (auto eh : edges_to_drop)
{
m_changes.enqueue<Change::DropLink>(m_g, opt_slot_nh, eh);
}
m_changes.enqueue<Change::DropNode>(opt_slot_nh);
}
// D/2 - Those which are used externally are connected to new nh
// as outputs.
for (auto non_opt_slot_nh : mo.non_opt_interim_slots)
{
GAPI_Assert(1 == non_opt_slot_nh->inNodes().size() );
GAPI_Assert(m_prod == non_opt_slot_nh->inNodes().front());
m_changes.enqueue<Change::DropLink>(m_g, non_opt_slot_nh,
non_opt_slot_nh->inEdges().front());
std::vector<ade::EdgeHandle> edges_to_probably_drop
(non_opt_slot_nh->outEdges().begin(),
non_opt_slot_nh->outEdges().end());;
for (auto eh : edges_to_probably_drop)
{
if (eh->dstNode() == m_cons)
{
// drop only edges to m_cons, as there's other consumers
m_changes.enqueue<Change::DropLink>(m_g, non_opt_slot_nh, eh);
}
}
m_changes.enqueue<Change::NewLink>(m_g, new_nh, non_opt_slot_nh);
}
// E. All Prod's output edges which are directly related to Merge (e.g.
// connected to Cons) were processed on step (D).
// Relink the remaining output links
std::vector<ade::EdgeHandle> prod_extra_out_edges
(m_prod->outEdges().begin(),
m_prod->outEdges().end());
for (auto extra_out : prod_extra_out_edges)
{
m_changes.enqueue<Change::NewLink>(m_g, new_nh, extra_out->dstNode());
m_changes.enqueue<Change::DropLink>(m_g, m_prod, extra_out);
}
// F. All Cons' input edges which are directly related to merge (e.g.
// connected to Prod) were processed on step (D) as well,
// remaining should become Merged island's input edges
std::vector<ade::EdgeHandle> cons_extra_in_edges
(m_cons->inEdges().begin(),
m_cons->inEdges().end());
for (auto extra_in : cons_extra_in_edges)
{
m_changes.enqueue<Change::NewLink>(m_g, extra_in->srcNode(), new_nh);
m_changes.enqueue<Change::DropLink>(m_g, m_cons, extra_in);
}
// G. Finally, drop the original Island nodes. DropNode() does
// the sanity check for us (both nodes should have 0 edges).
m_changes.enqueue<Change::DropNode>(m_prod);
m_changes.enqueue<Change::DropNode>(m_cons);
}
void MergeAction::rollback()
{
m_changes.rollback(m_g);
}
void MergeAction::commit()
{
m_changes.commit(m_g);
}
#ifdef DEBUG_MERGE
void merge_debug(const ade::Graph &g, int iteration)
{
std::stringstream filename;
filename << "fusion_" << static_cast<const void*>(&g)
<< "_" << std::setw(4) << std::setfill('0') << iteration
<< ".dot";
std::ofstream ofs(filename.str());
passes::dumpDot(g, ofs);
}
#endif
void fuseGeneral(ade::Graph& im, const ade::Graph& g)
{
GIslandModel::Graph gim(im);
MergeContext mc;
bool there_was_a_merge = false;
std::size_t iteration = 0u;
do
{
there_was_a_merge = false;
// FIXME: move this debugging to some user-controllable log level
#ifdef DEBUG_MERGE
GAPI_LOG_INFO(NULL, "Before next merge attempt " << iteration << "...");
merge_debug(g, iteration);
#endif
iteration++;
auto sorted = pass_helpers::topoSort(im);
for (auto nh : sorted)
{
if (NodeKind::ISLAND == gim.metadata(nh).get<NodeKind>().k)
{
ade::NodeHandle cand_nh;
ade::NodeHandle cand_slot;
Direction dir = Direction::Invalid;
std::tie(cand_nh, cand_slot, dir) = findCandidate(gim, nh, mc);
if (cand_nh != nullptr && dir != Direction::Invalid)
{
auto lhs_nh = (dir == Direction::In ? cand_nh : nh);
auto rhs_nh = (dir == Direction::Out ? cand_nh : nh);
auto l_obj = gim.metadata(lhs_nh).get<FusedIsland>().object;
auto r_obj = gim.metadata(rhs_nh).get<FusedIsland>().object;
GAPI_LOG_INFO(NULL, r_obj->name() << " can be merged into " << l_obj->name());
// Try to do a merge. If merge was succesfull, check if the
// graph have cycles (cycles are prohibited at this point).
// If there are cycles, roll-back the merge and mark a pair of
// these Islands with a special tag - "cycle-causing".
MergeAction action(im, g, lhs_nh, cand_slot, rhs_nh);
action.tryMerge();
if (pass_helpers::hasCycles(im))
{
GAPI_LOG_INFO(NULL,
"merge(" << l_obj->name() << "," << r_obj->name() <<
") caused cycle, rolling back...");
action.rollback();
// don't try to merge these two islands next time (findCandidate will use that)
mc.cycle_causers.insert({l_obj, r_obj});
}
else
{
GAPI_LOG_INFO(NULL,
"merge(" << l_obj->name() << "," << r_obj->name() <<
") was successful!");
action.commit();
#ifdef DEBUG_MERGE
GIslandModel::syncIslandTags(gim, g);
#endif
there_was_a_merge = true;
break; // start do{}while from the beginning
}
} // if(can merge)
} // if(ISLAND)
} // for(all nodes)
}
while (there_was_a_merge);
}
} // anonymous namespace
void passes::fuseIslands(ade::passes::PassContext &ctx)
{
std::shared_ptr<ade::Graph> gptr(new ade::Graph);
GIslandModel::Graph gim(*gptr);
if (fusionIsTrivial(ctx.graph))
{
fuseTrivial(gim, ctx.graph);
}
else
{
GIslandModel::generateInitial(gim, ctx.graph);
fuseGeneral(*gptr.get(), ctx.graph);
}
GModel::Graph(ctx.graph).metadata().set(IslandModel{std::move(gptr)});
}
void passes::syncIslandTags(ade::passes::PassContext &ctx)
{
GModel::Graph gm(ctx.graph);
std::shared_ptr<ade::Graph> gptr(gm.metadata().get<IslandModel>().model);
GIslandModel::Graph gim(*gptr);
GIslandModel::syncIslandTags(gim, ctx.graph);
}
}} // namespace cv::gimpl
@@ -0,0 +1,120 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <algorithm> // copy
#include <unordered_map>
#include <unordered_set>
#include <ade/util/filter_range.hpp>
#include "opencv2/gapi/own/assert.hpp" // GAPI_Assert
#include "compiler/passes/helpers.hpp"
namespace {
namespace Cycles
{
// FIXME: This code is taken directly from ADE.
// export a bool(ade::Graph) function with pass instead
enum class TraverseState
{
visiting,
visited,
};
using state_t = std::unordered_map<ade::Node*, TraverseState>;
bool inline checkCycle(state_t& state, const ade::NodeHandle& node)
{
GAPI_Assert(nullptr != node);
state[node.get()] = TraverseState::visiting;
for (auto adj: node->outNodes())
{
auto it = state.find(adj.get());
if (state.end() == it) // not visited
{
// FIXME: use std::stack instead on-stack recursion
if (checkCycle(state, adj))
{
return true; // detected! (deeper frame)
}
}
else if (TraverseState::visiting == it->second)
{
return true; // detected! (this frame)
}
}
state[node.get()] = TraverseState::visited;
return false; // not detected
}
bool inline hasCycles(const ade::Graph &graph)
{
state_t state;
bool detected = false;
for (auto node: graph.nodes())
{
if (state.end() == state.find(node.get()))
{
// not yet visited during recursion
detected |= checkCycle(state, node);
if (detected) break;
}
}
return detected;
}
} // namespace Cycles
namespace TopoSort
{
using sorted_t = std::vector<ade::NodeHandle>;
using visited_t = std::unordered_set<ade::Node*>;
struct NonEmpty final
{
bool operator()(const ade::NodeHandle& node) const
{
return nullptr != node;
}
};
void inline visit(sorted_t& sorted, visited_t& visited, const ade::NodeHandle& node)
{
if (visited.end() == visited.find(node.get()))
{
for (auto adj: node->inNodes())
{
visit(sorted, visited, adj);
}
sorted.push_back(node);
visited.insert(node.get());
}
}
sorted_t inline topoSort(const ade::Graph &g)
{
sorted_t sorted;
visited_t visited;
for (auto node: g.nodes())
{
visit(sorted, visited, node);
}
auto r = ade::util::filter<NonEmpty>(ade::util::toRange(sorted));
return sorted_t(r.begin(), r.end());
}
} // namespace TopoSort
} // anonymous namespace
bool cv::gimpl::pass_helpers::hasCycles(const ade::Graph &g)
{
return Cycles::hasCycles(g);
}
std::vector<ade::NodeHandle> cv::gimpl::pass_helpers::topoSort(const ade::Graph &g)
{
return TopoSort::topoSort(g);
}
@@ -0,0 +1,31 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_COMPILER_PASSES_HELPERS_HPP
#define OPENCV_GAPI_COMPILER_PASSES_HELPERS_HPP
// FIXME: DROP THIS and REUSE ADE utilities
// (which serve as passes already but are not exposed as standalone functions)
#include <vector>
#include <ade/passes/pass_base.hpp>
#include <ade/node.hpp> // FIXME: Forward declarations instead?
#include <ade/graph.hpp>
namespace cv {
namespace gimpl {
namespace pass_helpers {
bool hasCycles(const ade::Graph &graph);
std::vector<ade::NodeHandle> topoSort(const ade::Graph &graph);
} // namespace pass_helpers
} // namespace gimpl
} // name
#endif // OPENCV_GAPI_COMPILER_PASSES_HELPERS_HPP
@@ -0,0 +1,231 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <sstream>
#include <stack>
#include <ade/util/chain_range.hpp>
#include <ade/graph.hpp>
#include "compiler/gmodel.hpp"
#include "compiler/passes/passes.hpp"
namespace
{
bool is_within_same_island(const cv::gimpl::GModel::Graph &gr,
const ade::NodeHandle &dataNode,
const std::string &island)
{
// A data node is within the same island as it's reader node
// if and only if data object's producer island (if there's a producer)
// is the same as the specified one.
//
// An object may have only a single producer, but multiple consumers,
// and these consumers may be assigned to different Islands.
// Since "initIslands" traversal direction is op-to-args, i.e. reverse,
// a single Data object may be visited twice during Islands initialization.
//
// In this case, Data object is part of Island A if and only if:
// - Data object's producer is part of Island A,
// - AND any of Data obejct's consumers is part of Island A.
//
// Op["island0"] --> Data[ ? ] --> Op["island0"]
// :
// '---------> Op["island1"]
//
// In the above example, Data object is assigned to "island0" as
// it is surrounded by operations assigned to "island0"
using namespace cv::gimpl;
if ( gr.metadata(dataNode).contains<Island>()
&& gr.metadata(dataNode).get<Island>().island != island)
return false;
if (dataNode->inNodes().empty())
return false;
GAPI_Assert(dataNode->inNodes().size() == 1u);
const auto prod_h = dataNode->inNodes().front();
// FIXME: ADE should have something like get_or<> or get<>(default)
GAPI_Assert(gr.metadata(prod_h).get<NodeType>().t == NodeType::OP);
return ( gr.metadata(prod_h).contains<Island>()
&& gr.metadata(prod_h).get<Island>().island == island)
&& (ade::util::any_of(dataNode->outNodes(), [&](ade::NodeHandle cons_h)
{
return ( gr.metadata(cons_h).contains<Island>()
&& gr.metadata(cons_h).get<Island>().island == island);
}));
}
} // anonymous namespace
// Initially only Operations have Island tag. This pass adds Island tag
// to all data objects within an Island.
// A data object is considered within an Island if and only if
// its reader and writer are assigned to this Island (see above).
void cv::gimpl::passes::initIslands(ade::passes::PassContext &ctx)
{
GModel::Graph gr(ctx.graph);
for (const auto &nh : gr.nodes())
{
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
{
if (gr.metadata(nh).contains<Island>())
{
const auto island = gr.metadata(nh).get<Island>().island;
// It is enough to check only input nodes
for (const auto &in_data_node : nh->inNodes())
{
if (is_within_same_island(gr, in_data_node, island))
{
gr.metadata(in_data_node).set(Island{island});
}
} // for(in_data_node)
} // if (contains<Island>)
} // if (OP)
} // for (nodes)
}
// There should be no multiple (disconnected) islands with the same name.
// This may occur if user assigns the same islands name to multiple ranges
// in the graph.
// FIXME: How it could be avoided on an earlier stage?
void cv::gimpl::passes::checkIslands(ade::passes::PassContext &ctx)
{
GModel::ConstGraph gr(ctx.graph);
// The algorithm is teh following:
//
// 1. Put all Tagged nodes (both Operations and Data) into a set
// 2. Initialize Visited set as (empty)
// 3. Initialize Traversal stack as (empty)
// 4. Initialize Islands map (String -> Integer) as (empty)
// 5. For every Tagged node from a set
// a. Skip if it is Visited
// b. For every input/output node:
// * if it is tagged with the same island:
// - add it to Traversal stack
// - remove from Tagged nodes if it is t
// c. While (stack is not empty):
// - Take a node from Stack
// - Repeat (b)
// d. Increment Islands map [this island] by 1
//
//
// If whatever Island has counter is more than 1, it is a disjoint
// one (i.e. there's two islands with the same name).
using node_set = std::unordered_set
< ade::NodeHandle
, ade::HandleHasher<ade::Node>
>;
node_set tagged_nodes;
node_set visited_tagged_nodes;
std::unordered_map<std::string, int> island_counters;
for (const auto &nh : gr.nodes())
{
if (gr.metadata(nh).contains<Island>())
{
tagged_nodes.insert(nh);
island_counters[gr.metadata(nh).get<Island>().island] = 0;
}
}
// Make a copy to allow list modifications during traversal
for (const auto &tagged_nh : tagged_nodes)
{
if (visited_tagged_nodes.end() != ade::util::find(visited_tagged_nodes, tagged_nh))
continue;
// Run the recursive traversal process as described in 5/a-d.
// This process is like a flood-fill traversal for island.
// If there's to distint successful flood-fills happened for the same island
// name, there are two islands with this name.
std::stack<ade::NodeHandle> stack;
stack.push(tagged_nh);
while (!stack.empty())
{
const auto this_nh = stack.top();
stack.pop();
// Since _this_ node is visited, it is a part of processed island
// so mark it as visited to skip in other recursive processes
visited_tagged_nodes.insert(this_nh);
GAPI_DbgAssert(gr.metadata(this_nh).contains<Island>());
GAPI_DbgAssert( gr.metadata(this_nh ).get<Island>().island
== gr.metadata(tagged_nh).get<Island>().island);
const auto &this_island = gr.metadata(this_nh).get<Island>().island;
for (const auto neighbor_nh : ade::util::chain(this_nh->inNodes(), this_nh->outNodes()))
{
if ( gr.metadata(neighbor_nh).contains<Island>()
&& gr.metadata(neighbor_nh).get<Island>().island == this_island
&& !visited_tagged_nodes.count(neighbor_nh))
{
stack.push(neighbor_nh);
}
} // for (neighbor)
} // while (stack)
// Flood-fill is over, now increment island counter for this island
island_counters[gr.metadata(tagged_nh).get<Island>().island]++;
} // for(tagged)
bool check_failed = false;
std::stringstream ss;
for (const auto &ic : island_counters)
{
GAPI_Assert(ic.second > 0);
if (ic.second > 1)
{
check_failed = true;
ss << "\"" << ic.first << "\"(" << ic.second << ") ";
}
}
if (check_failed)
{
util::throw_error
(std::logic_error("There are multiple distinct islands "
"with the same name: [" + ss.str() + "], "
"please check your cv::gapi::island() parameters!"));
}
}
void cv::gimpl::passes::checkIslandsContent(ade::passes::PassContext &ctx)
{
GModel::ConstGraph gr(ctx.graph);
std::unordered_map<std::string, cv::gapi::GBackend> backends_of_islands;
for (const auto& nh : gr.nodes())
{
if (NodeType::OP == gr.metadata(nh).get<NodeType>().t &&
gr.metadata(nh).contains<Island>())
{
const auto island = gr.metadata(nh).get<Island>().island;
auto island_backend_it = backends_of_islands.find(island);
const auto& op = gr.metadata(nh).get<Op>();
if (island_backend_it != backends_of_islands.end())
{
// Check that backend of the operation coincides with the backend of the island
// Backend of the island is determined by the backend of the first operation from this island
if (island_backend_it->second != op.backend)
{
util::throw_error(std::logic_error(island + " contains kernels " + op.k.name +
" with different backend"));
}
}
else
{
backends_of_islands.emplace(island, op.backend);
}
}
}
}
@@ -0,0 +1,155 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <ade/util/zip_range.hpp> // util::indexed
#include <ade/graph.hpp>
#include <ade/passes/check_cycles.hpp>
#include "opencv2/gapi/gcompoundkernel.hpp" // compound::backend()
#include "compiler/gmodel.hpp"
#include "compiler/passes/passes.hpp"
#include "api/gbackend_priv.hpp"
#include "backends/common/gbackend.hpp"
#include "compiler/gmodelbuilder.hpp"
#include "logger.hpp" // GAPI_LOG
namespace
{
struct ImplInfo
{
cv::GKernelImpl impl;
cv::GArgs in_args;
};
// Generaly the algorithm is following
//
// 1. Get GCompoundKernel implementation
// 2. Create GCompoundContext
// 3. Run GCompoundKernel with GCompoundContext
// 4. Build subgraph from imputs/outputs GCompoundKernel
// 5. Replace compound node to subgraph
void expand(ade::Graph& g, ade::NodeHandle nh, const ImplInfo& impl_info)
{
cv::gimpl::GModel::Graph gr(g);
auto compound_impl = cv::util::any_cast<cv::detail::GCompoundKernel>(impl_info.impl.opaque);
// GCompoundContext instantiates its own objects
// in accordance with the RcDescs from in_args
cv::detail::GCompoundContext context(impl_info.in_args);
compound_impl.apply(context);
cv::GProtoArgs ins, outs;
ins.reserve(context.m_args.size());
outs.reserve(context.m_results.size());
// Inputs can be non-dynamic types.
// Such inputs are not used when building a graph
for (const auto& arg : context.m_args)
{
if (cv::gimpl::proto::is_dynamic(arg))
{
ins.emplace_back(cv::gimpl::proto::rewrap(arg));
}
}
ade::util::transform(context.m_results, std::back_inserter(outs), &cv::gimpl::proto::rewrap);
cv::gimpl::GModelBuilder builder(g);
// Build the subgraph graph which will need to replace the compound node
const auto& proto_slots = builder.put(ins, outs);
const auto& in_nhs = std::get<2>(proto_slots);
const auto& out_nhs = std::get<3>(proto_slots);
auto sorted_in_nhs = cv::gimpl::GModel::orderedInputs(gr, nh);
auto sorted_out_nhs = cv::gimpl::GModel::orderedOutputs(gr, nh);
// Reconnect expanded kernels from graph data objects
// to subgraph data objects, then drop that graph data objects
for (const auto& it : ade::util::zip(in_nhs, sorted_in_nhs))
{
const auto& subgr_in_nh = std::get<0>(it);
const auto& comp_in_nh = std::get<1>(it);
cv::gimpl::GModel::redirectReaders(gr, subgr_in_nh, comp_in_nh);
gr.erase(subgr_in_nh);
}
gr.erase(nh);
for (const auto& it : ade::util::zip(out_nhs, sorted_out_nhs))
{
const auto& subgr_out_nh = std::get<0>(it);
const auto& comp_out_nh = std::get<1>(it);
cv::gimpl::GModel::redirectWriter(gr, subgr_out_nh, comp_out_nh);
gr.erase(subgr_out_nh);
}
}
}
// This pass, given the kernel package, selects a kernel implementation
// for every operation in the graph
void cv::gimpl::passes::resolveKernels(ade::passes::PassContext &ctx,
const gapi::GKernelPackage &kernels,
const gapi::GLookupOrder &order)
{
std::unordered_set<cv::gapi::GBackend> active_backends;
GModel::Graph gr(ctx.graph);
for (const auto &nh : gr.nodes())
{
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
{
auto &op = gr.metadata(nh).get<Op>();
cv::gapi::GBackend selected_backend;
cv::GKernelImpl selected_impl;
std::tie(selected_backend, selected_impl)
= kernels.lookup(op.k.name, order);
selected_backend.priv().unpackKernel(ctx.graph, nh, selected_impl);
op.backend = selected_backend;
active_backends.insert(selected_backend);
}
}
gr.metadata().set(ActiveBackends{active_backends});
}
void cv::gimpl::passes::expandKernels(ade::passes::PassContext &ctx, const gapi::GKernelPackage &kernels)
{
GModel::Graph gr(ctx.graph);
// Repeat the loop while there are compound kernels.
// Restart procedure after every successfull unrolling
bool has_compound_kernel = true;
while (has_compound_kernel)
{
has_compound_kernel = false;
for (const auto& nh : gr.nodes())
{
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
{
const auto& op = gr.metadata(nh).get<Op>();
cv::gapi::GBackend selected_backend;
cv::GKernelImpl selected_impl;
std::tie(selected_backend, selected_impl) = kernels.lookup(op.k.name);
if (selected_backend == cv::gapi::compound::backend())
{
has_compound_kernel = true;
expand(ctx.graph, nh, ImplInfo{selected_impl, op.args});
break;
}
}
}
}
GAPI_LOG_INFO(NULL, "Final graph: " << ctx.graph.nodes().size() << " nodes" << std::endl);
}
+123
View File
@@ -0,0 +1,123 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <ade/util/zip_range.hpp> // util::indexed
#include <ade/graph.hpp>
#include <ade/passes/check_cycles.hpp>
#include "compiler/gmodel.hpp"
#include "compiler/passes/passes.hpp"
#include "logger.hpp" // GAPI_LOG
// Iterate over all nodes and initialize meta of objects taken from the
// outside (i.e., computation input/output arguments)
void cv::gimpl::passes::initMeta(ade::passes::PassContext &ctx, const GMetaArgs &metas)
{
GModel::Graph gr(ctx.graph);
const auto &proto = gr.metadata().get<Protocol>();
for (const auto& it : ade::util::indexed(proto.in_nhs))
{
auto& data = gr.metadata(ade::util::value(it)).get<Data>();
data.meta = metas.at(ade::util::index(it));
}
}
// Iterate over all operations in the topological order, trigger kernels
// validate() function, update output objects metadata.
void cv::gimpl::passes::inferMeta(ade::passes::PassContext &ctx)
{
// FIXME: ADE pass dependency on topo_sort?
// FIXME: ADE pass dependency on initMeta?
GModel::Graph gr(ctx.graph);
const auto sorted = gr.metadata().get<ade::passes::TopologicalSortData>() ;
for (const auto &nh : sorted.nodes())
{
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
{
const auto& op = gr.metadata(nh).get<Op>();
GAPI_Assert(op.k.outMeta != nullptr);
// Prepare operation's input metadata vector
// Note that it's size is usually different from nh.inEdges.size(),
// and its element count is equal to operation's arguments count.
GMetaArgs input_meta_args(op.args.size());
// Iterate through input edges, update input_meta_args's slots
// appropriately. Not all of them will be updated due to (see above).
GAPI_Assert(nh->inEdges().size() > 0);
for (const auto &in_eh : nh->inEdges())
{
const auto& input_port = gr.metadata(in_eh).get<Input>().port;
const auto& input_nh = in_eh->srcNode();
GAPI_Assert(gr.metadata(input_nh).get<NodeType>().t == NodeType::DATA);
const auto& input_meta = gr.metadata(input_nh).get<Data>().meta;
if (util::holds_alternative<util::monostate>(input_meta))
{
// No meta in an input argument - a fatal error
// (note graph is traversed here in topoligcal order)
util::throw_error(std::logic_error("Fatal: input object's metadata "
"not found!"));
// FIXME: Add more details!!!
}
input_meta_args.at(input_port) = input_meta;
}
// Now ask kernel for it's output meta.
// Resulting out_args may have a larger size than op.outs, since some
// outputs could stay unused (unconnected)
const GMetaArgs out_metas = op.k.outMeta(input_meta_args, op.args);
// Walk through operation's outputs, update meta of output objects
// appropriately
GAPI_Assert(nh->outEdges().size() > 0);
for (const auto &out_eh : nh->outEdges())
{
const auto &output_port = gr.metadata(out_eh).get<Output>().port;
const auto &output_nh = out_eh->dstNode();
GAPI_Assert(gr.metadata(output_nh).get<NodeType>().t == NodeType::DATA);
auto &output_meta = gr.metadata(output_nh).get<Data>().meta;
if (!util::holds_alternative<util::monostate>(output_meta))
{
GAPI_LOG_INFO(NULL,
"!!! Output object has an initialized meta - "
"how it is possible today?" << std::endl; );
if (output_meta != out_metas.at(output_port))
{
util::throw_error(std::logic_error("Fatal: meta mismatch"));
// FIXME: New exception type?
// FIXME: More details!
}
}
// Store meta in graph
output_meta = out_metas.at(output_port);
}
} // if(OP)
} // for(sorted)
}
// After all metadata in graph is infered, store a vector of inferred metas
// for computation output values.
void cv::gimpl::passes::storeResultingMeta(ade::passes::PassContext &ctx)
{
GModel::Graph gr(ctx.graph);
const auto &proto = gr.metadata().get<Protocol>();
GMetaArgs output_metas(proto.out_nhs.size());
for (const auto& it : ade::util::indexed(proto.out_nhs))
{
auto& data = gr.metadata(ade::util::value(it)).get<Data>();
output_metas[ade::util::index(it)] = data.meta;
}
gr.metadata().set(OutputMeta{output_metas});
}
@@ -0,0 +1,58 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_COMPILER_PASSES_HPP
#define OPENCV_GAPI_COMPILER_PASSES_HPP
#include <ostream>
#include <ade/passes/pass_base.hpp>
#include "opencv2/gapi/garg.hpp"
#include "opencv2/gapi/gcommon.hpp"
// Forward declarations - external
namespace ade {
class Graph;
namespace passes {
struct PassContext;
}
}
namespace cv {
namespace gimpl { namespace passes {
void dumpDot(const ade::Graph &g, std::ostream& os);
void dumpDot(ade::passes::PassContext &ctx, std::ostream& os);
void dumpDotStdout(ade::passes::PassContext &ctx);
void dumpGraph(ade::passes::PassContext &ctx, const std::string& dump_path);
void dumpDotToFile(ade::passes::PassContext &ctx, const std::string& dump_path);
void initIslands(ade::passes::PassContext &ctx);
void checkIslands(ade::passes::PassContext &ctx);
void checkIslandsContent(ade::passes::PassContext &ctx);
void initMeta(ade::passes::PassContext &ctx, const GMetaArgs &metas);
void inferMeta(ade::passes::PassContext &ctx);
void storeResultingMeta(ade::passes::PassContext &ctx);
void expandKernels(ade::passes::PassContext &ctx,
const gapi::GKernelPackage& kernels);
void resolveKernels(ade::passes::PassContext &ctx,
const gapi::GKernelPackage &kernels,
const gapi::GLookupOrder &order);
void fuseIslands(ade::passes::PassContext &ctx);
void syncIslandTags(ade::passes::PassContext &ctx);
}} // namespace gimpl::passes
} // namespace cv
#endif // OPENCV_GAPI_COMPILER_PASSES_HPP
+147
View File
@@ -0,0 +1,147 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_COMPILER_TRANSACTIONS_HPP
#define OPENCV_GAPI_COMPILER_TRANSACTIONS_HPP
#include <algorithm> // find_if
#include <functional>
#include <list>
#include <ade/graph.hpp>
#include "opencv2/core/base.hpp"
enum class Direction: int {Invalid, In, Out};
////////////////////////////////////////////////////////////////////////////
////
// TODO: Probably it can be moved to ADE
namespace Change
{
struct Base
{
virtual void commit (ade::Graph & ) {};
virtual void rollback(ade::Graph & ) {};
virtual ~Base() = default;
};
class NodeCreated final: public Base
{
ade::NodeHandle m_node;
public:
explicit NodeCreated(const ade::NodeHandle &nh) : m_node(nh) {}
virtual void rollback(ade::Graph &g) override { g.erase(m_node); }
};
// NB: Drops all metadata stored in the EdgeHandle,
// which is not restored even in the rollback
// FIXME: either add a way for users to preserve meta manually
// or extend ADE to manipulate with meta such way
class DropLink final: public Base
{
ade::NodeHandle m_node;
Direction m_dir;
ade::NodeHandle m_sibling;
public:
DropLink(ade::Graph &g,
const ade::NodeHandle &node,
const ade::EdgeHandle &edge)
: m_node(node), m_dir(node == edge->srcNode()
? Direction::Out
: Direction::In)
{
m_sibling = (m_dir == Direction::In
? edge->srcNode()
: edge->dstNode());
g.erase(edge);
}
virtual void rollback(ade::Graph &g) override
{
switch(m_dir)
{
case Direction::In: g.link(m_sibling, m_node); break;
case Direction::Out: g.link(m_node, m_sibling); break;
default: CV_Assert(false);
}
}
};
class NewLink final: public Base
{
ade::EdgeHandle m_edge;
public:
NewLink(ade::Graph &g,
const ade::NodeHandle &prod,
const ade::NodeHandle &cons)
: m_edge(g.link(prod, cons))
{
}
virtual void rollback(ade::Graph &g) override
{
g.erase(m_edge);
}
};
class DropNode final: public Base
{
ade::NodeHandle m_node;
public:
explicit DropNode(const ade::NodeHandle &nh)
: m_node(nh)
{
// According to the semantic, node should be disconnected
// manually before it is dropped
CV_Assert(m_node->inEdges().size() == 0);
CV_Assert(m_node->outEdges().size() == 0);
}
virtual void commit(ade::Graph &g) override
{
g.erase(m_node);
}
};
class List
{
std::list< std::unique_ptr<Base> > m_changes;
public:
template<typename T, typename ...Args>
void enqueue(Args&&... args)
{
std::unique_ptr<Base> p(new T(args...));
m_changes.push_back(std::move(p));
}
void commit(ade::Graph &g)
{
// Commit changes in the forward order
for (auto& ch : m_changes) ch->commit(g);
}
void rollback(ade::Graph &g)
{
// Rollback changes in the reverse order
for (auto it = m_changes.rbegin(); it != m_changes.rend(); ++it)
{
(*it)->rollback(g);
}
}
};
} // namespace Change
////////////////////////////////////////////////////////////////////////////
#endif // OPENCV_GAPI_COMPILER_TRANSACTIONS_HPP
+211
View File
@@ -0,0 +1,211 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#include <iostream>
#include <ade/util/zip_range.hpp>
#include "opencv2/core/types.hpp"
#include "executor/gexecutor.hpp"
cv::gimpl::GExecutor::GExecutor(std::unique_ptr<ade::Graph> &&g_model)
: m_orig_graph(std::move(g_model))
, m_island_graph(GModel::Graph(*m_orig_graph).metadata()
.get<IslandModel>().model)
, m_gm(*m_orig_graph)
, m_gim(*m_island_graph)
{
// NB: Right now GIslandModel is acyclic, so for a naive execution,
// simple unrolling to a list of triggers is enough
// Naive execution model is similar to current CPU (OpenCV) plugin
// execution model:
// 1. Allocate all internal resources first (NB - CPU plugin doesn't do it)
// 2. Put input/output GComputation arguments to the storage
// 3. For every Island, prepare vectors of input/output parameter descs
// 4. Iterate over a list of operations (sorted in the topological order)
// 5. For every operation, form a list of input/output data objects
// 6. Run GIslandExecutable
// 7. writeBack
m_ops.reserve(m_gim.nodes().size());
auto sorted = m_gim.metadata().get<ade::passes::TopologicalSortData>();
for (auto nh : sorted.nodes())
{
switch (m_gim.metadata(nh).get<NodeKind>().k)
{
case NodeKind::ISLAND:
{
std::vector<RcDesc> input_rcs;
std::vector<RcDesc> output_rcs;
input_rcs.reserve(nh->inNodes().size());
output_rcs.reserve(nh->outNodes().size());
auto xtract = [&](ade::NodeHandle slot_nh, std::vector<RcDesc> &vec) {
const auto orig_data_nh
= m_gim.metadata(slot_nh).get<DataSlot>().original_data_node;
const auto &orig_data_info
= m_gm.metadata(orig_data_nh).get<Data>();
vec.emplace_back(RcDesc{ orig_data_info.rc
, orig_data_info.shape
, orig_data_info.ctor});
};
// (3)
for (auto in_slot_nh : nh->inNodes()) xtract(in_slot_nh, input_rcs);
for (auto out_slot_nh : nh->outNodes()) xtract(out_slot_nh, output_rcs);
m_ops.emplace_back(OpDesc{ std::move(input_rcs)
, std::move(output_rcs)
, m_gim.metadata(nh).get<IslandExec>().object});
}
break;
case NodeKind::SLOT:
{
const auto orig_data_nh
= m_gim.metadata(nh).get<DataSlot>().original_data_node;
// (1)
initResource(orig_data_nh);
m_slots.emplace_back(DataDesc{nh, orig_data_nh});
}
break;
default:
GAPI_Assert(false);
break;
} // switch(kind)
} // for(gim nodes)
}
void cv::gimpl::GExecutor::initResource(const ade::NodeHandle &orig_nh)
{
const Data &d = m_gm.metadata(orig_nh).get<Data>();
if ( d.storage != Data::Storage::INTERNAL
&& d.storage != Data::Storage::CONST)
return;
// INTERNALS+CONST only! no need to allocate/reset output objects
// to as it is bound externally (e.g. already in the m_res)
switch (d.shape)
{
case GShape::GMAT:
{
const auto desc = util::get<cv::GMatDesc>(d.meta);
const auto type = CV_MAKETYPE(desc.depth, desc.chan);
m_res.slot<cv::gapi::own::Mat>()[d.rc].create(desc.size, type);
}
break;
case GShape::GSCALAR:
if (d.storage == Data::Storage::CONST)
{
auto rc = RcDesc{d.rc, d.shape, d.ctor};
magazine::bindInArg(m_res, rc, m_gm.metadata(orig_nh).get<ConstValue>().arg);
}
break;
case GShape::GARRAY:
// Constructed on Reset, do nothing here
break;
default:
GAPI_Assert(false);
}
}
void cv::gimpl::GExecutor::run(cv::gimpl::GRuntimeArgs &&args)
{
// (2)
const auto proto = m_gm.metadata().get<Protocol>();
// Basic check if input/output arguments are correct
// FIXME: Move to GCompiled (do once for all GExecutors)
if (proto.inputs.size() != args.inObjs.size()) // TODO: Also check types
{
util::throw_error(std::logic_error
("Computation's input protocol doesn\'t "
"match actual arguments!"));
}
if (proto.outputs.size() != args.outObjs.size()) // TODO: Also check types
{
util::throw_error(std::logic_error
("Computation's output protocol doesn\'t "
"match actual arguments!"));
}
namespace util = ade::util;
//ensure that output Mat parameters are correctly allocated
for (auto index : util::iota(proto.out_nhs.size()) ) //FIXME: avoid copy of NodeHandle and GRunRsltComp ?
{
auto& nh = proto.out_nhs.at(index);
const Data &d = m_gm.metadata(nh).get<Data>();
if (d.shape == GShape::GMAT)
{
using cv::util::get;
const auto desc = get<cv::GMatDesc>(d.meta);
const auto type = CV_MAKETYPE(desc.depth, desc.chan);
auto& out_mat = *get<cv::Mat*>(args.outObjs.at(index));
out_mat.create(cv::gapi::own::to_ocv(desc.size), type);
}
}
// Update storage with user-passed objects
for (auto it : ade::util::zip(ade::util::toRange(proto.inputs),
ade::util::toRange(args.inObjs)))
{
magazine::bindInArg(m_res, std::get<0>(it), std::get<1>(it));
}
for (auto it : ade::util::zip(ade::util::toRange(proto.outputs),
ade::util::toRange(args.outObjs)))
{
magazine::bindOutArg(m_res, std::get<0>(it), std::get<1>(it));
}
// Reset internal data
for (auto &sd : m_slots)
{
const auto& data = m_gm.metadata(sd.data_nh).get<Data>();
magazine::resetInternalData(m_res, data);
}
// Run the script
for (auto &op : m_ops)
{
// (5)
using InObj = GIslandExecutable::InObj;
using OutObj = GIslandExecutable::OutObj;
std::vector<InObj> in_objs;
std::vector<OutObj> out_objs;
in_objs.reserve (op.in_objects.size());
out_objs.reserve(op.out_objects.size());
for (const auto &rc : op.in_objects)
{
in_objs.emplace_back(InObj{rc, magazine::getArg(m_res, rc)});
}
for (const auto &rc : op.out_objects)
{
out_objs.emplace_back(OutObj{rc, magazine::getObjPtr(m_res, rc)});
}
// (6)
op.isl_exec->run(std::move(in_objs), std::move(out_objs));
}
// (7)
for (auto it : ade::util::zip(ade::util::toRange(proto.outputs),
ade::util::toRange(args.outObjs)))
{
magazine::writeBack(m_res, std::get<0>(it), std::get<1>(it));
}
}
const cv::gimpl::GModel::Graph& cv::gimpl::GExecutor::model() const
{
return m_gm;
}
+94
View File
@@ -0,0 +1,94 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef OPENCV_GAPI_GEXECUTOR_HPP
#define OPENCV_GAPI_GEXECUTOR_HPP
#include <memory> // unique_ptr, shared_ptr
#include <utility> // tuple, required by magazine
#include <unordered_map> // required by magazine
#include <ade/graph.hpp>
#include "backends/common/gbackend.hpp"
namespace cv {
namespace gimpl {
// Graph-level executor interface.
//
// This class specifies API for a "super-executor" which orchestrates
// the overall Island graph execution.
//
// Every Island (subgraph) execution is delegated to a particular
// backend and is done opaquely to the GExecutor.
//
// Inputs to a GExecutor instance are:
// - GIslandModel - a high-level graph model which may be seen as a
// "procedure" to execute.
// - GModel - a low-level graph of operations (from which a GIslandModel
// is projected)
// - GComputation runtime arguments - vectors of input/output objects
//
// Every GExecutor is responsible for
// a. Maintaining non-island (intermediate) data objects within graph
// b. Providing GIslandExecutables with input/output data according to
// their protocols
// c. Triggering execution of GIslandExecutables when task/data dependencies
// are met.
//
// By default G-API stores all data on host, and cross-Island
// exchange happens via host buffers (and CV data objects).
//
// Today's exchange data objects are:
// - cv::Mat - for image buffers
// - cv::Scalar - for single values (with up to four components inside)
// - cv::detail::VectorRef - an untyped wrapper over std::vector<T>
//
class GExecutor
{
protected:
std::unique_ptr<ade::Graph> m_orig_graph;
std::shared_ptr<ade::Graph> m_island_graph;
cv::gimpl::GModel::Graph m_gm; // FIXME: make const?
cv::gimpl::GIslandModel::Graph m_gim; // FIXME: make const?
// FIXME: Naive executor details are here for now
// but then it should be moved to another place
struct OpDesc
{
std::vector<RcDesc> in_objects;
std::vector<RcDesc> out_objects;
std::shared_ptr<GIslandExecutable> isl_exec;
};
std::vector<OpDesc> m_ops;
struct DataDesc
{
ade::NodeHandle slot_nh;
ade::NodeHandle data_nh;
};
std::vector<DataDesc> m_slots;
Mag m_res;
void initResource(const ade::NodeHandle &orig_nh); // FIXME: shouldn't it be RcDesc?
public:
explicit GExecutor(std::unique_ptr<ade::Graph> &&g_model);
void run(cv::gimpl::GRuntimeArgs &&args);
const GModel::Graph& model() const; // FIXME: make it ConstGraph?
};
} // namespace gimpl
} // namespace cv
#endif // OPENCV_GAPI_GEXECUTOR_HPP
+24
View File
@@ -0,0 +1,24 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef __OPENCV_GAPI_LOGGER_HPP__
#define __OPENCV_GAPI_LOGGER_HPP__
#if 1
#include "opencv2/core/utils/logger.hpp"
#define GAPI_LOG_INFO(tag, ...) CV_LOG_INFO(tag, __VA_ARGS__)
#define GAPI_LOG_WARNING(tag, ...) CV_LOG_WARNING(tag, __VA_ARGS__)
#else
#define GAPI_LOG_INFO(tag, ...)
#define GAPI_LOG_WARNING(tag, ...)
#endif
#endif // __OPENCV_GAPI_LOGGER_HPP__
+19
View File
@@ -0,0 +1,19 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
//
// Copyright (C) 2018 Intel Corporation
#ifndef __OPENCV_GAPI_PRECOMP_HPP__
#define __OPENCV_GAPI_PRECOMP_HPP__
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/gapi.hpp"
#include "opencv2/gapi/gkernel.hpp"
#include "opencv2/gapi/core.hpp"
#include "opencv2/gapi/imgproc.hpp"
#endif // __OPENCV_GAPI_PRECOMP_HPP__