Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
12 KiB
305 lines
12 KiB
// 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>(); |
|
}
|
|
|