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:
committed by
Alexander Alekhin
parent
852f061b26
commit
29e88e50ff
@@ -0,0 +1 @@
|
||||
This directory contains implementation of G-API frontend (public API classes).
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
This directory contains G-API graph compiler logic.
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
Reference in New Issue
Block a user