mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 23:33:05 +04:00
G-API: Introduce streaming::desync and infer(ROI)
- desync() is a new (and for now, the only one) intrinsic which splits the graph execution into asynchronous parts when running in Streaming mode; - desync() makes no effect when compiling in Traditional mode; - Added tests on desync() working in various scenarios; - Extended GStreamingExecutor to support desync(); also extended GStreamingCompiled() with a new version of pull() returning a vector of optional values; - Fixed various issues with storing the type information & proper construction callbacks for GArray<> and GOpaque; - Introduced a new infer(Roi,GMat) overload with a sample; - Introduced an internal API for Islands to control fusion procedure (to fuse or not to fuse); - Introduced handleStopStream() callback for island executables; - Added GCompileArgs to metadata of the graph (required for other features).
This commit is contained in:
@@ -238,6 +238,11 @@ cv::gimpl::GCompiler::GCompiler(const cv::GComputation &c,
|
||||
// (no compound backend present here)
|
||||
m_e.addPass("kernels", "check_islands_content", passes::checkIslandsContent);
|
||||
|
||||
// Special stage for intrinsics handling
|
||||
m_e.addPassStage("intrin");
|
||||
m_e.addPass("intrin", "desync", passes::intrinDesync);
|
||||
m_e.addPass("intrin", "finalizeIntrin", passes::intrinFinalize);
|
||||
|
||||
//Input metas may be empty when a graph is compiled for streaming
|
||||
m_e.addPassStage("meta");
|
||||
if (!m_metas.empty())
|
||||
@@ -384,6 +389,9 @@ cv::gimpl::GCompiler::GPtr cv::gimpl::GCompiler::generateGraph()
|
||||
{
|
||||
GModel::Graph(*g).metadata().set(OriginalInputMeta{m_metas});
|
||||
}
|
||||
// FIXME: remove m_args, remove GCompileArgs from backends' method signatures,
|
||||
// rework backends to access GCompileArgs from graph metadata
|
||||
GModel::Graph(*g).metadata().set(CompileArgs{m_args});
|
||||
return g;
|
||||
}
|
||||
|
||||
|
||||
@@ -175,13 +175,26 @@ void GIslandModel::generateInitial(GIslandModel::Graph &g,
|
||||
{
|
||||
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
|
||||
auto isl_new_eh = g.link(isl_slot_nh, nh); // no other data stored yet
|
||||
// Propagate some special metadata from the GModel to GIslandModel
|
||||
// TODO: Make it a single place (a function) for both inputs/outputs?
|
||||
// (since it is duplicated in the below code block)
|
||||
if (src_g.metadata(in_edge).contains<DesyncEdge>())
|
||||
{
|
||||
const auto idx = src_g.metadata(in_edge).get<DesyncEdge>().index;
|
||||
g.metadata(isl_new_eh).set(DesyncIslEdge{idx});
|
||||
}
|
||||
}
|
||||
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);
|
||||
auto isl_new_eh = g.link(nh, isl_slot_nh);
|
||||
if (src_g.metadata(out_edge).contains<DesyncEdge>())
|
||||
{
|
||||
const auto idx = src_g.metadata(out_edge).get<DesyncEdge>().index;
|
||||
g.metadata(isl_new_eh).set(DesyncIslEdge{idx});
|
||||
}
|
||||
}
|
||||
} // for(all_operations)
|
||||
}
|
||||
@@ -254,6 +267,9 @@ void GIslandModel::syncIslandTags(Graph &g, ade::Graph &orig_g)
|
||||
void GIslandModel::compileIslands(Graph &g, const ade::Graph &orig_g, const GCompileArgs &args)
|
||||
{
|
||||
GModel::ConstGraph gm(orig_g);
|
||||
if (gm.metadata().contains<HasIntrinsics>()) {
|
||||
util::throw_error(std::logic_error("FATAL: The graph has unresolved intrinsics"));
|
||||
}
|
||||
|
||||
auto original_sorted = gm.metadata().get<ade::passes::TopologicalSortData>();
|
||||
for (auto nh : g.nodes())
|
||||
|
||||
@@ -142,6 +142,14 @@ public:
|
||||
// at that stage.
|
||||
virtual void handleNewStream() {}; // do nothing here by default
|
||||
|
||||
// This method is called for every IslandExecutable when
|
||||
// the stream-based execution is stopped.
|
||||
// All processing is guaranteed to be stopped by this moment,
|
||||
// with no pending or running 'run()' processes ran in background.
|
||||
// FIXME: This method is tightly bound to the GStreamingExecutor
|
||||
// now.
|
||||
virtual void handleStopStream() {} // do nothing here by default
|
||||
|
||||
virtual ~GIslandExecutable() = default;
|
||||
};
|
||||
|
||||
@@ -222,8 +230,19 @@ struct IslandsCompiled
|
||||
static const char *name() { return "IslandsCompiled"; }
|
||||
};
|
||||
|
||||
// This flag marks an edge in an GIslandModel as "desynchronized"
|
||||
// i.e. it starts a new desynchronized subgraph
|
||||
struct DesyncIslEdge
|
||||
{
|
||||
static const char *name() { return "DesynchronizedIslandEdge"; }
|
||||
|
||||
// Projection from GModel/DesyncEdge.index
|
||||
int index;
|
||||
};
|
||||
|
||||
namespace GIslandModel
|
||||
{
|
||||
|
||||
using Graph = ade::TypedGraph
|
||||
< NodeKind
|
||||
, FusedIsland
|
||||
@@ -232,6 +251,7 @@ namespace GIslandModel
|
||||
, Emitter
|
||||
, Sink
|
||||
, IslandsCompiled
|
||||
, DesyncIslEdge
|
||||
, ade::passes::TopologicalSortData
|
||||
>;
|
||||
|
||||
@@ -244,6 +264,7 @@ namespace GIslandModel
|
||||
, Emitter
|
||||
, Sink
|
||||
, IslandsCompiled
|
||||
, DesyncIslEdge
|
||||
, ade::passes::TopologicalSortData
|
||||
>;
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ ade::NodeHandle GModel::mkDataNode(GModel::Graph &g, const GShape shape)
|
||||
return data_h;
|
||||
}
|
||||
|
||||
void GModel::linkIn(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t in_port)
|
||||
ade::EdgeHandle 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())
|
||||
@@ -96,9 +96,11 @@ void GModel::linkIn(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::si
|
||||
|
||||
// Replace an API object with a REF (G* -> GOBJREF)
|
||||
op.args[in_port] = cv::GArg(RcDesc{gm.rc, gm.shape, {}});
|
||||
|
||||
return eh;
|
||||
}
|
||||
|
||||
void GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t out_port)
|
||||
ade::EdgeHandle GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::size_t out_port)
|
||||
{
|
||||
// FIXME: check validity using kernel prototype
|
||||
|
||||
@@ -121,6 +123,8 @@ void GModel::linkOut(Graph &g, ade::NodeHandle opH, ade::NodeHandle objH, std::s
|
||||
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, {}};
|
||||
|
||||
return eh;
|
||||
}
|
||||
|
||||
std::vector<ade::NodeHandle> GModel::orderedInputs(const ConstGraph &g, ade::NodeHandle nh)
|
||||
@@ -210,26 +214,29 @@ ade::NodeHandle GModel::detail::dataNodeOf(const ConstLayoutGraph &g, const GOri
|
||||
return g.metadata().get<Layout>().object_nodes.at(origin);
|
||||
}
|
||||
|
||||
void GModel::redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
|
||||
std::vector<ade::EdgeHandle> GModel::redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
|
||||
{
|
||||
std::vector<ade::EdgeHandle> ehh(from->outEdges().begin(), from->outEdges().end());
|
||||
std::vector<ade::EdgeHandle> ohh;
|
||||
ohh.reserve(ehh.size());
|
||||
for (auto e : ehh)
|
||||
{
|
||||
auto dst = e->dstNode();
|
||||
auto input = g.metadata(e).get<Input>();
|
||||
g.erase(e);
|
||||
linkIn(g, dst, to, input.port);
|
||||
ohh.push_back(linkIn(g, dst, to, input.port));
|
||||
}
|
||||
return ohh;
|
||||
}
|
||||
|
||||
void GModel::redirectWriter(Graph &g, ade::NodeHandle from, ade::NodeHandle to)
|
||||
ade::EdgeHandle 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);
|
||||
return linkOut(g, op, to, output.port);
|
||||
}
|
||||
|
||||
GMetaArgs GModel::collectInputMeta(const GModel::ConstGraph &cg, ade::NodeHandle node)
|
||||
|
||||
@@ -211,6 +211,58 @@ struct CustomMetaFunction
|
||||
CM customOutMeta;
|
||||
};
|
||||
|
||||
// This is a general flag indicating that this GModel has intrinsics.
|
||||
// In the beginning of the compilation, it is a quick check to
|
||||
// indicate there are intrinsics.
|
||||
//
|
||||
// In the end of the compilation, having this flag is fatal -- all
|
||||
// intrinsics must be resolved.
|
||||
struct HasIntrinsics
|
||||
{
|
||||
static const char *name() { return "HasIntrinsicsFlag"; }
|
||||
};
|
||||
|
||||
// This is a special tag for both DATA and OP nodes indicating
|
||||
// which desynchronized path this node belongs to.
|
||||
// This tag is set by a special complex pass intrinDesync/accept.
|
||||
struct DesyncPath
|
||||
{
|
||||
static const char *name() { return "DesynchronizedPath"; }
|
||||
|
||||
// A zero-based index of the desynchronized path in the graph.
|
||||
// Set by intrinDesync() compiler pass
|
||||
int index;
|
||||
};
|
||||
|
||||
// This is a special tag for graph Edges indicating that this
|
||||
// particular edge starts a desynchronized path in the graph.
|
||||
// At the execution stage, the data coming "through" these edges
|
||||
// (virtually, of course, since our GModel edges never transfer the
|
||||
// actual data, they just represent these transfers) is desynchronized
|
||||
// from the rest of the pipeline, i.e. may be "lost" (stay unconsumed
|
||||
// and then overwritten with some new data when streaming).
|
||||
struct DesyncEdge
|
||||
{
|
||||
static const char *name() { return "DesynchronizedEdge"; }
|
||||
|
||||
// A zero-based index of the desynchronized path in the graph.
|
||||
// Set by intrinDesync/apply() compiler pass
|
||||
int index;
|
||||
};
|
||||
|
||||
// This flag marks the island graph as "desynchronized"
|
||||
struct Desynchronized
|
||||
{
|
||||
static const char *name() { return "Desynchronized"; }
|
||||
};
|
||||
|
||||
// Reference to compile args of the computation
|
||||
struct CompileArgs
|
||||
{
|
||||
static const char *name() { return "CompileArgs"; }
|
||||
GCompileArgs args;
|
||||
};
|
||||
|
||||
namespace GModel
|
||||
{
|
||||
using Graph = ade::TypedGraph
|
||||
@@ -232,6 +284,11 @@ namespace GModel
|
||||
, CustomMetaFunction
|
||||
, Streaming
|
||||
, Deserialized
|
||||
, HasIntrinsics
|
||||
, DesyncPath
|
||||
, DesyncEdge
|
||||
, Desynchronized
|
||||
, CompileArgs
|
||||
>;
|
||||
|
||||
// FIXME: How to define it based on GModel???
|
||||
@@ -254,6 +311,11 @@ namespace GModel
|
||||
, CustomMetaFunction
|
||||
, Streaming
|
||||
, Deserialized
|
||||
, HasIntrinsics
|
||||
, DesyncPath
|
||||
, DesyncEdge
|
||||
, Desynchronized
|
||||
, CompileArgs
|
||||
>;
|
||||
|
||||
// FIXME:
|
||||
@@ -278,11 +340,11 @@ namespace GModel
|
||||
// Clears logged messages of a node.
|
||||
GAPI_EXPORTS void log_clear(Graph &g, ade::NodeHandle node);
|
||||
|
||||
GAPI_EXPORTS void linkIn (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t in_port);
|
||||
GAPI_EXPORTS void linkOut (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t out_port);
|
||||
GAPI_EXPORTS ade::EdgeHandle linkIn (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t in_port);
|
||||
GAPI_EXPORTS ade::EdgeHandle linkOut (Graph &g, ade::NodeHandle op, ade::NodeHandle obj, std::size_t out_port);
|
||||
|
||||
GAPI_EXPORTS void redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to);
|
||||
GAPI_EXPORTS void redirectWriter (Graph &g, ade::NodeHandle from, ade::NodeHandle to);
|
||||
GAPI_EXPORTS std::vector<ade::EdgeHandle> redirectReaders(Graph &g, ade::NodeHandle from, ade::NodeHandle to);
|
||||
GAPI_EXPORTS ade::EdgeHandle redirectWriter (Graph &g, ade::NodeHandle from, ade::NodeHandle to);
|
||||
|
||||
GAPI_EXPORTS std::vector<ade::NodeHandle> orderedInputs (const ConstGraph &g, ade::NodeHandle nh);
|
||||
GAPI_EXPORTS std::vector<ade::NodeHandle> orderedOutputs(const ConstGraph &g, ade::NodeHandle nh);
|
||||
|
||||
@@ -134,12 +134,19 @@ cv::gimpl::Unrolled cv::gimpl::unrollExpr(const GProtoArgs &ins,
|
||||
|
||||
// Put the outputs object description of the node
|
||||
// so that they are not lost if they are not consumed by other operations
|
||||
GAPI_Assert(call_p.m_k.outCtors.size() == call_p.m_k.outShapes.size());
|
||||
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, {}, origin.kind };
|
||||
// FIXME: then use ZIP
|
||||
HostCtor ctor = call_p.m_k.outCtors[port];
|
||||
|
||||
// NB: Probably this fixes all other "missing host ctor"
|
||||
// problems.
|
||||
// TODO: Clean-up the old workarounds if it really is.
|
||||
GOrigin org {shape, node, port, std::move(ctor), origin.kind};
|
||||
origins.insert(org);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,15 +16,9 @@ namespace cv
|
||||
|
||||
namespace gimpl
|
||||
{
|
||||
// Union type for various user-defined type constructors (GArray<T>, GOpaque<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
|
||||
, detail::ConstructOpaque
|
||||
>;
|
||||
// HostCtor was there, but then moved to public
|
||||
// Redeclare here to avoid changing tons of code
|
||||
using HostCtor = cv::detail::HostCtor;
|
||||
|
||||
using ConstVal = util::variant
|
||||
< util::monostate
|
||||
|
||||
@@ -69,6 +69,11 @@ bool cv::GStreamingCompiled::Priv::pull(cv::GRunArgsP &&outs)
|
||||
return m_exec->pull(std::move(outs));
|
||||
}
|
||||
|
||||
bool cv::GStreamingCompiled::Priv::pull(cv::GOptRunArgsP &&outs)
|
||||
{
|
||||
return m_exec->pull(std::move(outs));
|
||||
}
|
||||
|
||||
bool cv::GStreamingCompiled::Priv::try_pull(cv::GRunArgsP &&outs)
|
||||
{
|
||||
return m_exec->try_pull(std::move(outs));
|
||||
@@ -113,6 +118,7 @@ bool cv::GStreamingCompiled::pull(cv::GRunArgsP &&outs)
|
||||
|
||||
std::tuple<bool, cv::GRunArgs> cv::GStreamingCompiled::pull()
|
||||
{
|
||||
// FIXME: Why it is not @ priv??
|
||||
GRunArgs run_args;
|
||||
GRunArgsP outs;
|
||||
const auto& out_shapes = m_priv->outShapes();
|
||||
@@ -144,6 +150,11 @@ std::tuple<bool, cv::GRunArgs> cv::GStreamingCompiled::pull()
|
||||
return std::make_tuple(is_over, run_args);
|
||||
}
|
||||
|
||||
bool cv::GStreamingCompiled::pull(cv::GOptRunArgsP &&outs)
|
||||
{
|
||||
return m_priv->pull(std::move(outs));
|
||||
}
|
||||
|
||||
bool cv::GStreamingCompiled::try_pull(cv::GRunArgsP &&outs)
|
||||
{
|
||||
return m_priv->try_pull(std::move(outs));
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
void setSource(GRunArgs &&args);
|
||||
void start();
|
||||
bool pull(cv::GRunArgsP &&outs);
|
||||
bool pull(cv::GOptRunArgsP &&outs);
|
||||
bool try_pull(cv::GRunArgsP &&outs);
|
||||
void stop();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <opencv2/gapi/util/optional.hpp> // util::optional
|
||||
#include "logger.hpp" // GAPI_LOG
|
||||
|
||||
#include "api/gbackend_priv.hpp" // for canMerge()
|
||||
#include "compiler/gmodel.hpp"
|
||||
#include "compiler/gislandmodel.hpp"
|
||||
#include "compiler/passes/passes.hpp"
|
||||
@@ -54,11 +55,28 @@ namespace
|
||||
// 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);
|
||||
if (g.metadata().contains<Desynchronized>()) {
|
||||
// Fusion of a graph having a desynchronized path is
|
||||
// definitely non-trivial
|
||||
return false;
|
||||
}
|
||||
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>();
|
||||
});
|
||||
if (active_backends.size() != 1u) {
|
||||
// More than 1 backend involved - non-trivial
|
||||
return false;
|
||||
}
|
||||
const auto& has_island_tags = [&](ade::NodeHandle nh) {
|
||||
return g.metadata(nh).contains<Island>();
|
||||
};
|
||||
if (ade::util::any_of(g.nodes(), has_island_tags)) {
|
||||
// There are user-defined islands - non-trivial
|
||||
return false;
|
||||
}
|
||||
if (active_backends.begin()->priv().controlsMerge()) {
|
||||
// If the only backend controls Island Fusion on its own - non-trivial
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void fuseTrivial(GIslandModel::Graph &g, const ade::Graph &src_graph)
|
||||
@@ -125,9 +143,9 @@ namespace
|
||||
};
|
||||
|
||||
bool canMerge(const GIslandModel::Graph &g,
|
||||
const ade::NodeHandle a_nh,
|
||||
const ade::NodeHandle /*slot_nh*/,
|
||||
const ade::NodeHandle b_nh,
|
||||
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;
|
||||
@@ -142,8 +160,8 @@ namespace
|
||||
// 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)))
|
||||
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
|
||||
@@ -163,7 +181,13 @@ namespace
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: add a backend-specified merge checker
|
||||
// If available, run the backend-specified merge checker
|
||||
const auto &this_backend_p = a_ptr->backend().priv();
|
||||
if ( this_backend_p.controlsMerge()
|
||||
&& !this_backend_p.allowsMerge(g, a_nh, slot_nh, b_nh))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -205,10 +229,31 @@ namespace
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
|
||||
// Before checking for candidates, find and ban neighbor nodes
|
||||
// (input or outputs) which are connected via desynchronized
|
||||
// edges.
|
||||
GIsland::node_set nodes_with_desync_edges;
|
||||
for (const auto& in_eh : nh->inEdges()) {
|
||||
if (g.metadata(in_eh).contains<DesyncIslEdge>()) {
|
||||
nodes_with_desync_edges.insert(in_eh->srcNode());
|
||||
}
|
||||
}
|
||||
for (const auto& output_data_nh : nh->outNodes()) {
|
||||
for (const auto &out_reader_eh : output_data_nh->outEdges()) {
|
||||
if (g.metadata(out_reader_eh).contains<DesyncIslEdge>()) {
|
||||
nodes_with_desync_edges.insert(out_reader_eh->dstNode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find a first matching candidate GIsland for merge
|
||||
// among inputs
|
||||
for (const auto& input_data_nh : nh->inNodes())
|
||||
for (const auto& in_eh : nh->inEdges())
|
||||
{
|
||||
if (ade::util::contains(nodes_with_desync_edges, in_eh->srcNode())) {
|
||||
continue; // desync edges can never be fused
|
||||
}
|
||||
const auto& input_data_nh = in_eh->srcNode();
|
||||
if (input_data_nh->inNodes().size() != 0)
|
||||
{
|
||||
// Data node must have a single producer only
|
||||
@@ -224,14 +269,17 @@ namespace
|
||||
// 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 mergeTest = [&](ade::EdgeHandle cons_eh) -> bool {
|
||||
if (ade::util::contains(nodes_with_desync_edges, cons_eh->dstNode())) {
|
||||
return false; // desync edges can never be fused
|
||||
}
|
||||
return canMerge(g, nh, output_data_nh, cons_eh->dstNode(), ctx);
|
||||
};
|
||||
auto cand_it = std::find_if(output_data_nh->outNodes().begin(),
|
||||
output_data_nh->outNodes().end(),
|
||||
auto cand_it = std::find_if(output_data_nh->outEdges().begin(),
|
||||
output_data_nh->outEdges().end(),
|
||||
mergeTest);
|
||||
if (cand_it != output_data_nh->outNodes().end())
|
||||
return std::make_tuple(*cand_it,
|
||||
if (cand_it != output_data_nh->outEdges().end())
|
||||
return std::make_tuple((*cand_it)->dstNode(),
|
||||
output_data_nh,
|
||||
Direction::Out);
|
||||
} // for(outNodes)
|
||||
@@ -251,6 +299,7 @@ namespace
|
||||
ade::NodeHandle m_slot;
|
||||
ade::NodeHandle m_cons;
|
||||
|
||||
using Change = ChangeT<DesyncIslEdge>;
|
||||
Change::List m_changes;
|
||||
|
||||
struct MergeObjects
|
||||
@@ -423,10 +472,10 @@ namespace
|
||||
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));
|
||||
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();
|
||||
@@ -440,7 +489,9 @@ namespace
|
||||
m_prod->inEdges().end());
|
||||
for (auto in_edge : input_edges)
|
||||
{
|
||||
m_changes.enqueue<Change::NewLink>(m_g, in_edge->srcNode(), new_nh);
|
||||
// FIXME: Introduce a Relink primitive instead?
|
||||
// (combining the both actions into one?)
|
||||
m_changes.enqueue<Change::NewLink>(m_g, in_edge->srcNode(), new_nh, in_edge);
|
||||
m_changes.enqueue<Change::DropLink>(m_g, m_prod, in_edge);
|
||||
}
|
||||
|
||||
@@ -450,7 +501,7 @@ namespace
|
||||
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::NewLink>(m_g, new_nh, out_edge->dstNode(), out_edge);
|
||||
m_changes.enqueue<Change::DropLink>(m_g, m_cons, out_edge);
|
||||
}
|
||||
|
||||
@@ -491,6 +542,10 @@ namespace
|
||||
m_changes.enqueue<Change::DropLink>(m_g, non_opt_slot_nh, eh);
|
||||
}
|
||||
}
|
||||
// FIXME: No metadata copied here (from where??)
|
||||
// For DesyncIslEdges it still works, as these tags are
|
||||
// placed to Data->Op edges and this one is an Op->Data
|
||||
// edge.
|
||||
m_changes.enqueue<Change::NewLink>(m_g, new_nh, non_opt_slot_nh);
|
||||
}
|
||||
|
||||
@@ -502,7 +557,7 @@ namespace
|
||||
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::NewLink>(m_g, new_nh, extra_out->dstNode(), extra_out);
|
||||
m_changes.enqueue<Change::DropLink>(m_g, m_prod, extra_out);
|
||||
}
|
||||
|
||||
@@ -514,7 +569,7 @@ namespace
|
||||
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::NewLink>(m_g, extra_in->srcNode(), new_nh, extra_in);
|
||||
m_changes.enqueue<Change::DropLink>(m_g, m_cons, extra_in);
|
||||
}
|
||||
|
||||
@@ -557,10 +612,10 @@ namespace
|
||||
there_was_a_merge = false;
|
||||
|
||||
// FIXME: move this debugging to some user-controllable log level
|
||||
#ifdef DEBUG_MERGE
|
||||
#ifdef DEBUG_MERGE
|
||||
GAPI_LOG_INFO(NULL, "Before next merge attempt " << iteration << "...");
|
||||
merge_debug(g, iteration);
|
||||
#endif
|
||||
#endif
|
||||
iteration++;
|
||||
auto sorted = pass_helpers::topoSort(im);
|
||||
for (auto nh : sorted)
|
||||
@@ -600,9 +655,9 @@ namespace
|
||||
"merge(" << l_obj->name() << "," << r_obj->name() <<
|
||||
") was successful!");
|
||||
action.commit();
|
||||
#ifdef DEBUG_MERGE
|
||||
#ifdef DEBUG_MERGE
|
||||
GIslandModel::syncIslandTags(gim, g);
|
||||
#endif
|
||||
#endif
|
||||
there_was_a_merge = true;
|
||||
break; // start do{}while from the beginning
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
// 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) 2020 Intel Corporation
|
||||
|
||||
|
||||
#include "precomp.hpp"
|
||||
|
||||
#include <ade/util/algorithm.hpp>
|
||||
#include <ade/util/zip_range.hpp>
|
||||
#include <opencv2/gapi/streaming/desync.hpp>// GDesync intrinsic
|
||||
|
||||
#include "compiler/gmodel.hpp"
|
||||
#include "compiler/passes/passes.hpp"
|
||||
|
||||
namespace desync {
|
||||
namespace {
|
||||
|
||||
// Drop the desynchronized node `nh` from the graph, reconnect the
|
||||
// graph structure properly. This is a helper function which is used
|
||||
// in both drop(g) and apply(g) passes.
|
||||
//
|
||||
// @return a vector of new edge handles connecting the "main" graph
|
||||
// with its desynchronized part.
|
||||
std::vector<ade::EdgeHandle> drop(cv::gimpl::GModel::Graph &g,
|
||||
ade::NodeHandle nh) {
|
||||
using namespace cv::gimpl;
|
||||
|
||||
// What we need to do here:
|
||||
// 1. Connect the readers of its produced data objects
|
||||
// to the input data objects of desync;
|
||||
// 2. Drop the data object it produces.
|
||||
// 3. Drop the desync operation itself;
|
||||
std::vector<ade::NodeHandle> in_data_objs = GModel::orderedInputs(g, nh);
|
||||
std::vector<ade::NodeHandle> out_data_objs = GModel::orderedOutputs(g, nh);
|
||||
std::vector<ade::EdgeHandle> new_links;
|
||||
GAPI_Assert(in_data_objs.size() == out_data_objs.size());
|
||||
GAPI_DbgAssert(ade::util::all_of
|
||||
(out_data_objs,
|
||||
[&](const ade::NodeHandle &oh) {
|
||||
return g.metadata(oh).contains<Data>();
|
||||
}));
|
||||
// (1)
|
||||
for (auto &&it: ade::util::zip(ade::util::toRange(in_data_objs),
|
||||
ade::util::toRange(out_data_objs))) {
|
||||
auto these_new_links = GModel::redirectReaders(g,
|
||||
std::get<1>(it),
|
||||
std::get<0>(it));
|
||||
new_links.insert(new_links.end(),
|
||||
these_new_links.begin(),
|
||||
these_new_links.end());
|
||||
}
|
||||
// (2)
|
||||
for (auto &&old_out_nh : out_data_objs) {
|
||||
g.erase(old_out_nh);
|
||||
}
|
||||
// (3)
|
||||
g.erase(nh);
|
||||
|
||||
return new_links;
|
||||
}
|
||||
|
||||
// Tracing a desynchronizing subgraph is somewhat tricky and happens
|
||||
// in both directions: downwards and upwards.
|
||||
//
|
||||
// The downward process is the basic one: we start with a "desync"
|
||||
// OP node and go down to the graph using the "output" edges. We check
|
||||
// if all nodes on this path [can] belong to this desynchronized path
|
||||
// and don't overlap with others.
|
||||
//
|
||||
// An important contract to maintain is that the desynchronized part
|
||||
// can't have any input references from the "main" graph part or any
|
||||
// other desynchronized part in the graph. This contract is validated
|
||||
// by checking every node's input which must belong to the same
|
||||
// desynchronized part.
|
||||
//
|
||||
// Here is the pitfall of this check:
|
||||
//
|
||||
// v
|
||||
// GMat_0
|
||||
// v
|
||||
// +----------+
|
||||
// | desync() | <- This point originates the traceDown process
|
||||
// +----------+
|
||||
// v
|
||||
// GMat_0' <- This node will be tagged for this desync at
|
||||
// :--------. step 0/1
|
||||
// v : <- The order how output nodes are visited is not
|
||||
// +----------+ : specified, we can visit Op2() first (as there
|
||||
// | Op1() | : is a direct link) bypassing visiting and tagging
|
||||
// +----------+ : Op1() and GMat_1
|
||||
// v :
|
||||
// GMat_1 :
|
||||
// : .---'
|
||||
// v v <- When we visit Op2() via the 2nd edge on this
|
||||
// +----------+ graph, we check if all inputs belong to the same
|
||||
// | Op2() | desynchronized graph and GMat_1 fails this check
|
||||
// +----------+ (since the traceDown() process haven't visited
|
||||
// it yet).
|
||||
//
|
||||
// Cases like this originate the traceUp() process: if we find an
|
||||
// input node in our desynchronized path which doesn't belong to this
|
||||
// path YET, it is not 100% a problem, and we need to trace it back
|
||||
// (upwards) to see if it is really a case.
|
||||
|
||||
// This recursive function checks the desync_id in the graph upwards.
|
||||
// The process doesn't continue for nodes which have a valid
|
||||
// desync_id already.
|
||||
// The process only continues for nodes which have no desync_id
|
||||
// assigned. If there's no such nodes anymore, the procedure is
|
||||
// considered complete and a list of nodes to tag is returned to the
|
||||
// caller.
|
||||
//
|
||||
// If NO inputs of this node have a valid desync_id, the desync
|
||||
// invariant is broken and the function throws.
|
||||
void traceUp(cv::gimpl::GModel::Graph &g,
|
||||
const ade::NodeHandle &nh,
|
||||
int desync_id,
|
||||
std::vector<ade::NodeHandle> &path) {
|
||||
using namespace cv::gimpl;
|
||||
|
||||
GAPI_Assert(!nh->inNodes().empty()
|
||||
&& "traceUp: a desynchronized part of the graph is not isolated?");
|
||||
|
||||
if (g.metadata(nh).contains<DesyncPath>()) {
|
||||
// We may face nodes which have DesyncPath already visited during
|
||||
// this recursive process (e.g. via some other output or branch in the
|
||||
// subgraph)
|
||||
if (g.metadata(nh).get<DesyncPath>().index != desync_id) {
|
||||
GAPI_Assert(false && "Desynchronization can't be nested!");
|
||||
}
|
||||
return; // This object belongs to the desync path - exit early.
|
||||
}
|
||||
|
||||
// Regardless of the result, put this nh to the path
|
||||
path.push_back(nh);
|
||||
|
||||
// Check if the input nodes are OK
|
||||
std::vector<ade::NodeHandle> nodes_to_trace;
|
||||
nodes_to_trace.reserve(nh->inNodes().size());
|
||||
for (auto &&in_nh : nh->inNodes()) {
|
||||
if (g.metadata(in_nh).contains<DesyncPath>()) {
|
||||
// We may face nodes which have DesyncPath already visited during
|
||||
// this recursive process (e.g. via some other output or branch in the
|
||||
// subgraph)
|
||||
GAPI_Assert(g.metadata(in_nh).get<DesyncPath>().index == desync_id
|
||||
&& "Desynchronization can't be nested!");
|
||||
} else {
|
||||
nodes_to_trace.push_back(in_nh);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are nodes to trace, continue the recursion
|
||||
for (auto &&up_nh : nodes_to_trace) {
|
||||
traceUp(g, up_nh, desync_id, path);
|
||||
}
|
||||
}
|
||||
|
||||
// This recursive function propagates the desync_id down to the graph
|
||||
// starting at nh, and also checks:
|
||||
// - if this desync path is isolated;
|
||||
// - if this desync path is not overlapped.
|
||||
// It also originates the traceUp() process at the points of
|
||||
// uncertainty (as described in the comment above).
|
||||
void traceDown(cv::gimpl::GModel::Graph &g,
|
||||
const ade::NodeHandle &nh,
|
||||
int desync_id) {
|
||||
using namespace cv::gimpl;
|
||||
|
||||
if (g.metadata(nh).contains<DesyncPath>()) {
|
||||
// We may face nodes which have DesyncPath already visited during
|
||||
// this recursive process (e.g. via some other output or branch in the
|
||||
// subgraph)
|
||||
GAPI_Assert(g.metadata(nh).get<DesyncPath>().index == desync_id
|
||||
&& "Desynchronization can't be nested!");
|
||||
} else {
|
||||
g.metadata(nh).set(DesyncPath{desync_id});
|
||||
}
|
||||
|
||||
// All inputs of this data object must belong to the same
|
||||
// desync path.
|
||||
for (auto &&in_nh : nh->inNodes()) {
|
||||
// If an input object is not assigned to this desync path,
|
||||
// it does not means that the object doesn't belong to
|
||||
// this path. Check it.
|
||||
std::vector<ade::NodeHandle> path_up;
|
||||
traceUp(g, in_nh, desync_id, path_up);
|
||||
// We get here on success. Just set the proper tags for
|
||||
// the identified input path.
|
||||
for (auto &&up_nh : path_up) {
|
||||
g.metadata(up_nh).set(DesyncPath{desync_id});
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate the tag & check down
|
||||
for (auto &&out_nh : nh->outNodes()) {
|
||||
traceDown(g, out_nh, desync_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Streaming case: ensure the graph has proper isolation of the
|
||||
// desynchronized parts, set proper Edge metadata hints for
|
||||
// GStreamingExecutable
|
||||
void apply(cv::gimpl::GModel::Graph &g) {
|
||||
using namespace cv::gimpl;
|
||||
|
||||
// Stage 0. Trace down the desync operations in the graph.
|
||||
// Tag them with their unique (per graph) identifiers.
|
||||
int total_desync = 0;
|
||||
for (auto &&nh : g.nodes()) {
|
||||
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
|
||||
const auto &op = g.metadata(nh).get<Op>();
|
||||
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
|
||||
GAPI_Assert(!g.metadata(nh).contains<DesyncPath>()
|
||||
&& "Desynchronization can't be nested!");
|
||||
const int this_desync_id = total_desync++;
|
||||
g.metadata(nh).set(DesyncPath{this_desync_id});
|
||||
for (auto &&out_nh: nh->outNodes()) {
|
||||
traceDown(g, out_nh, this_desync_id);
|
||||
}
|
||||
} // if (desync)
|
||||
} // if(OP)
|
||||
} // for(nodes)
|
||||
|
||||
// Tracing is done for all desync ops in the graph now.
|
||||
// Stage 1. Drop the desync operations from the graph, but mark
|
||||
// the desynchronized edges a special way.
|
||||
// The desynchronized edge is the edge which connects a main
|
||||
// subgraph data with a desynchronized subgraph data.
|
||||
std::vector<ade::NodeHandle> nodes(g.nodes().begin(), g.nodes().end());
|
||||
for (auto &&nh : nodes) {
|
||||
if (nh == nullptr) {
|
||||
// Some nodes could be dropped already during the procedure
|
||||
// thanks ADE their NodeHandles updated automatically
|
||||
continue;
|
||||
}
|
||||
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
|
||||
const auto &op = g.metadata(nh).get<Op>();
|
||||
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
|
||||
auto index = g.metadata(nh).get<DesyncPath>().index;
|
||||
auto new_links = drop(g, nh);
|
||||
for (auto &&eh : new_links) {
|
||||
g.metadata(eh).set(DesyncEdge{index});
|
||||
}
|
||||
} // if (desync)
|
||||
} // if (Op)
|
||||
} // for(nodes)
|
||||
|
||||
// Stage 2. Put a synchronized tag if there were changes applied
|
||||
if (total_desync > 0) {
|
||||
g.metadata().set(Desynchronized{});
|
||||
}
|
||||
}
|
||||
|
||||
// Probably the simplest case: desync makes no sense in the regular
|
||||
// compilation process, so just drop all its occurences in the graph,
|
||||
// reconnecting nodes properly.
|
||||
void drop(cv::gimpl::GModel::Graph &g) {
|
||||
// FIXME: LOG here that we're dropping the desync operations as
|
||||
// they have no sense when compiling in the regular mode.
|
||||
using namespace cv::gimpl;
|
||||
std::vector<ade::NodeHandle> nodes(g.nodes().begin(), g.nodes().end());
|
||||
for (auto &&nh : nodes) {
|
||||
if (nh == nullptr) {
|
||||
// Some nodes could be dropped already during the procedure
|
||||
// thanks ADE their NodeHandles updated automatically
|
||||
continue;
|
||||
}
|
||||
if (g.metadata(nh).get<NodeType>().t == NodeType::OP) {
|
||||
const auto &op = g.metadata(nh).get<Op>();
|
||||
if (op.k.name == cv::gapi::streaming::detail::GDesync::id()) {
|
||||
drop(g, nh);
|
||||
} // if (desync)
|
||||
} // if (Op)
|
||||
} // for(nodes)
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
} // namespace desync
|
||||
|
||||
void cv::gimpl::passes::intrinDesync(ade::passes::PassContext &ctx) {
|
||||
GModel::Graph gr(ctx.graph);
|
||||
if (!gr.metadata().contains<HasIntrinsics>())
|
||||
return;
|
||||
|
||||
gr.metadata().contains<Streaming>()
|
||||
? desync::apply(gr) // Streaming compilation
|
||||
: desync::drop(gr); // Regular compilation
|
||||
}
|
||||
|
||||
// Clears the HasIntrinsics flag if all intrinsics have been handled.
|
||||
void cv::gimpl::passes::intrinFinalize(ade::passes::PassContext &ctx) {
|
||||
GModel::Graph gr(ctx.graph);
|
||||
for (auto &&nh : gr.nodes()) {
|
||||
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP) {
|
||||
const auto &op = gr.metadata(nh).get<Op>();
|
||||
if (is_intrinsic(op.k.name)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If reached here, really clear the flag
|
||||
gr.metadata().erase<HasIntrinsics>();
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <opencv2/gapi/gcompoundkernel.hpp> // compound::backend()
|
||||
#include <opencv2/gapi/gkernel.hpp> // GKernelPackage
|
||||
#include <opencv2/gapi/infer.hpp> // GNetPackage
|
||||
#include <opencv2/gapi/streaming/desync.hpp>// GDesync intrinsic
|
||||
|
||||
#include "compiler/gmodel.hpp"
|
||||
#include "compiler/passes/passes.hpp"
|
||||
@@ -24,6 +25,20 @@
|
||||
#include "logger.hpp" // GAPI_LOG
|
||||
#include "api/gproto_priv.hpp" // is_dynamic, rewrap
|
||||
|
||||
namespace
|
||||
{
|
||||
// FIXME: This may be not the right design choice, but so far it works
|
||||
const std::vector<std::string> known_intrinsics = {
|
||||
cv::gapi::streaming::detail::GDesync::id()
|
||||
};
|
||||
}
|
||||
bool cv::gimpl::is_intrinsic(const std::string &s) {
|
||||
// FIXME: This search might be better in time once we start using string
|
||||
return std::find(known_intrinsics.begin(),
|
||||
known_intrinsics.end(),
|
||||
s) != known_intrinsics.end();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
struct ImplInfo
|
||||
@@ -130,8 +145,13 @@ void cv::gimpl::passes::bindNetParams(ade::passes::PassContext &ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// This pass, given the kernel package, selects a kernel implementation
|
||||
// for every operation in the graph
|
||||
// This pass, given the kernel package, selects a kernel
|
||||
// implementation for every operation in the graph
|
||||
//
|
||||
// Starting OpenCV 4.3, G-API may have some special "intrinsic"
|
||||
// operations. Those can be implemented by backends as regular
|
||||
// kernels, but if not, they are handled by the framework itself in
|
||||
// its optimization/execution passes.
|
||||
void cv::gimpl::passes::resolveKernels(ade::passes::PassContext &ctx,
|
||||
const gapi::GKernelPackage &kernels)
|
||||
{
|
||||
@@ -142,7 +162,25 @@ void cv::gimpl::passes::resolveKernels(ade::passes::PassContext &ctx,
|
||||
{
|
||||
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
|
||||
{
|
||||
// If the operation is known to be intrinsic and is NOT
|
||||
// implemented in the package, just skip it - there should
|
||||
// be some pass which handles it.
|
||||
auto &op = gr.metadata(nh).get<Op>();
|
||||
if (is_intrinsic(op.k.name) && !kernels.includesAPI(op.k.name)) {
|
||||
gr.metadata().set(HasIntrinsics{});
|
||||
continue;
|
||||
}
|
||||
// FIXME: And this logic is terribly wrong. The right
|
||||
// thing is to assign an intrinsic to a particular island
|
||||
// if and only if it is:
|
||||
// (a) surrounded by nodes of backend X, AND
|
||||
// (b) is supported by backend X.
|
||||
// Here we may have multiple backends supporting an
|
||||
// intrinsic but only one of those gets selected. And
|
||||
// this is exactly a situation we need multiple versions
|
||||
// of the same kernel to be presented in the kernel
|
||||
// package (as it was designed originally).
|
||||
|
||||
cv::gapi::GBackend selected_backend;
|
||||
cv::GKernelImpl selected_impl;
|
||||
std::tie(selected_backend, selected_impl) = kernels.lookup(op.k.name);
|
||||
@@ -181,6 +219,12 @@ void cv::gimpl::passes::expandKernels(ade::passes::PassContext &ctx, const gapi:
|
||||
if (gr.metadata(nh).get<NodeType>().t == NodeType::OP)
|
||||
{
|
||||
const auto& op = gr.metadata(nh).get<Op>();
|
||||
// FIXME: Essentially the same problem as in the above resolveKernels
|
||||
if (is_intrinsic(op.k.name) && !kernels.includesAPI(op.k.name)) {
|
||||
// Note: There's no need to set HasIntrinsics flag here
|
||||
// since resolveKernels would do it later.
|
||||
continue;
|
||||
}
|
||||
|
||||
cv::gapi::GBackend selected_backend;
|
||||
cv::GKernelImpl selected_impl;
|
||||
|
||||
@@ -31,7 +31,11 @@ namespace gapi {
|
||||
struct GNetPackage;
|
||||
} // namespace gapi
|
||||
|
||||
namespace gimpl { namespace passes {
|
||||
namespace gimpl {
|
||||
|
||||
bool is_intrinsic(const std::string &op_name);
|
||||
|
||||
namespace passes {
|
||||
|
||||
void dumpDot(const ade::Graph &g, std::ostream& os);
|
||||
void dumpDot(ade::passes::PassContext &ctx, std::ostream& os);
|
||||
@@ -66,6 +70,9 @@ void applyTransformations(ade::passes::PassContext &ctx,
|
||||
|
||||
void addStreaming(ade::passes::PassContext &ctx);
|
||||
|
||||
void intrinDesync(ade::passes::PassContext &ctx);
|
||||
void intrinFinalize(ade::passes::PassContext &ctx);
|
||||
|
||||
}} // namespace gimpl::passes
|
||||
|
||||
} // namespace cv
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <ade/graph.hpp>
|
||||
|
||||
#include "opencv2/gapi/util/util.hpp" // Seq
|
||||
#include "opencv2/gapi/own/assert.hpp"
|
||||
|
||||
enum class Direction: int {Invalid, In, Out};
|
||||
@@ -21,8 +22,50 @@ enum class Direction: int {Invalid, In, Out};
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
////
|
||||
// TODO: Probably it can be moved to ADE
|
||||
template<class H, class... Metatypes>
|
||||
class Preserved
|
||||
{
|
||||
using S = typename cv::detail::MkSeq<sizeof...(Metatypes)>::type;
|
||||
std::tuple<cv::util::optional<Metatypes>...> m_data;
|
||||
|
||||
namespace Change
|
||||
template<class T>
|
||||
cv::util::optional<T> get(ade::ConstTypedGraph<Metatypes...> g, H h) {
|
||||
return g.metadata(h).template contains<T>()
|
||||
? cv::util::make_optional(g.metadata(h).template get<T>())
|
||||
: cv::util::optional<T>{};
|
||||
}
|
||||
template<std::size_t Id>
|
||||
int set(ade::TypedGraph<Metatypes...> &g, H &h) {
|
||||
const auto &opt = std::get<Id>(m_data);
|
||||
if (opt.has_value())
|
||||
g.metadata(h).set(opt.value());
|
||||
return 0;
|
||||
}
|
||||
template<int... IIs>
|
||||
void copyTo_impl(ade::TypedGraph<Metatypes...> &g, H h, cv::detail::Seq<IIs...>) {
|
||||
int unused[] = {0, set<IIs>(g, h)...};
|
||||
(void) unused;
|
||||
}
|
||||
public:
|
||||
Preserved(const ade::Graph &g, H h) {
|
||||
ade::ConstTypedGraph<Metatypes...> tg(g);
|
||||
m_data = std::make_tuple(get<Metatypes>(tg, h)...);
|
||||
}
|
||||
void copyTo(ade::Graph &g, H h) {
|
||||
ade::TypedGraph<Metatypes...> tg(g);
|
||||
copyTo_impl(tg, h, S{});
|
||||
}
|
||||
};
|
||||
// Do nothing if there's no metadata
|
||||
template<class H>
|
||||
class Preserved<H> {
|
||||
public:
|
||||
Preserved(const ade::Graph &, H) {}
|
||||
void copyTo(ade::Graph &, H) {}
|
||||
};
|
||||
|
||||
template<class... Metatypes>
|
||||
struct ChangeT
|
||||
{
|
||||
struct Base
|
||||
{
|
||||
@@ -31,6 +74,8 @@ namespace Change
|
||||
virtual ~Base() = default;
|
||||
};
|
||||
|
||||
template<typename H> using Preserved = ::Preserved<H, Metatypes...>;
|
||||
|
||||
class NodeCreated final: public Base
|
||||
{
|
||||
ade::NodeHandle m_node;
|
||||
@@ -39,11 +84,7 @@ namespace Change
|
||||
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
|
||||
// FIXME: maybe extend ADE to clone/copy the whole metadata?
|
||||
class DropLink final: public Base
|
||||
{
|
||||
ade::NodeHandle m_node;
|
||||
@@ -51,13 +92,15 @@ namespace Change
|
||||
|
||||
ade::NodeHandle m_sibling;
|
||||
|
||||
Preserved<ade::EdgeHandle> m_meta;
|
||||
|
||||
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_node(node)
|
||||
, m_dir(node == edge->srcNode() ? Direction::Out : Direction::In)
|
||||
, m_meta(g, edge)
|
||||
{
|
||||
m_sibling = (m_dir == Direction::In
|
||||
? edge->srcNode()
|
||||
@@ -67,12 +110,17 @@ namespace Change
|
||||
|
||||
virtual void rollback(ade::Graph &g) override
|
||||
{
|
||||
// FIXME: Need to preserve metadata here!
|
||||
// GIslandModel edges now have metadata
|
||||
ade::EdgeHandle eh;
|
||||
switch(m_dir)
|
||||
{
|
||||
case Direction::In: g.link(m_sibling, m_node); break;
|
||||
case Direction::Out: g.link(m_node, m_sibling); break;
|
||||
case Direction::In: eh = g.link(m_sibling, m_node); break;
|
||||
case Direction::Out: eh = g.link(m_node, m_sibling); break;
|
||||
default: GAPI_Assert(false);
|
||||
}
|
||||
GAPI_Assert(eh != nullptr);
|
||||
m_meta.copyTo(g, eh);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,10 +130,15 @@ namespace Change
|
||||
|
||||
public:
|
||||
NewLink(ade::Graph &g,
|
||||
const ade::NodeHandle &prod,
|
||||
const ade::NodeHandle &cons)
|
||||
const ade::NodeHandle &prod,
|
||||
const ade::NodeHandle &cons,
|
||||
const ade::EdgeHandle ©_from = ade::EdgeHandle())
|
||||
: m_edge(g.link(prod, cons))
|
||||
{
|
||||
if (copy_from != nullptr)
|
||||
{
|
||||
Preserved<ade::EdgeHandle>(g, copy_from).copyTo(g, m_edge);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void rollback(ade::Graph &g) override
|
||||
@@ -141,7 +194,7 @@ namespace Change
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace Change
|
||||
}; // struct Change
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#endif // OPENCV_GAPI_COMPILER_TRANSACTIONS_HPP
|
||||
|
||||
Reference in New Issue
Block a user