Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Tetragramm
GitHub Repository: Tetragramm/opencv
Path: blob/master/modules/gapi/src/backends/fluid/gfluidbackend.cpp
16344 views
1
// This file is part of OpenCV project.
2
// It is subject to the license terms in the LICENSE file found in the top-level directory
3
// of this distribution and at http://opencv.org/license.html.
4
//
5
// Copyright (C) 2018 Intel Corporation
6
7
8
#include "precomp.hpp"
9
10
#include <functional>
11
#include <iostream>
12
#include <iomanip> // std::fixed, std::setprecision
13
#include <unordered_set>
14
#include <stack>
15
16
#include <ade/util/algorithm.hpp>
17
#include <ade/util/chain_range.hpp>
18
#include <ade/util/range.hpp>
19
#include <ade/util/zip_range.hpp>
20
21
#include <ade/typed_graph.hpp>
22
#include <ade/execution_engine/execution_engine.hpp>
23
24
#include "opencv2/gapi/gcommon.hpp"
25
#include "logger.hpp"
26
27
#include "opencv2/gapi/own/convert.hpp"
28
#include "opencv2/gapi/gmat.hpp" //for version of descr_of
29
// PRIVATE STUFF!
30
#include "compiler/gobjref.hpp"
31
#include "compiler/gmodel.hpp"
32
33
#include "backends/fluid/gfluidbuffer_priv.hpp"
34
#include "backends/fluid/gfluidbackend.hpp"
35
#include "backends/fluid/gfluidimgproc.hpp"
36
#include "backends/fluid/gfluidcore.hpp"
37
38
#include "api/gbackend_priv.hpp" // FIXME: Make it part of Backend SDK!
39
40
// FIXME: Is there a way to take a typed graph (our GModel),
41
// and create a new typed graph _ATOP_ of that (by extending with a couple of
42
// new types?).
43
// Alternatively, is there a way to compose types graphs?
44
//
45
// If not, we need to introduce that!
46
using GFluidModel = ade::TypedGraph
47
< cv::gimpl::FluidUnit
48
, cv::gimpl::FluidData
49
, cv::gimpl::Protocol
50
, cv::gimpl::FluidUseOwnBorderBuffer
51
>;
52
53
// FIXME: Same issue with Typed and ConstTyped
54
using GConstFluidModel = ade::ConstTypedGraph
55
< cv::gimpl::FluidUnit
56
, cv::gimpl::FluidData
57
, cv::gimpl::Protocol
58
, cv::gimpl::FluidUseOwnBorderBuffer
59
>;
60
61
// FluidBackend middle-layer implementation ////////////////////////////////////
62
namespace
63
{
64
class GFluidBackendImpl final: public cv::gapi::GBackend::Priv
65
{
66
virtual void unpackKernel(ade::Graph &graph,
67
const ade::NodeHandle &op_node,
68
const cv::GKernelImpl &impl) override
69
{
70
GFluidModel fm(graph);
71
auto fluid_impl = cv::util::any_cast<cv::GFluidKernel>(impl.opaque);
72
fm.metadata(op_node).set(cv::gimpl::FluidUnit{fluid_impl, {}, 0, 0, 0.0});
73
}
74
75
virtual EPtr compile(const ade::Graph &graph,
76
const cv::GCompileArgs &args,
77
const std::vector<ade::NodeHandle> &nodes) const override
78
{
79
using namespace cv::gimpl;
80
GModel::ConstGraph g(graph);
81
auto isl_graph = g.metadata().get<IslandModel>().model;
82
GIslandModel::Graph gim(*isl_graph);
83
84
const auto num_islands = std::count_if
85
(gim.nodes().begin(), gim.nodes().end(),
86
[&](const ade::NodeHandle &nh) {
87
return gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND;
88
});
89
90
const auto out_rois = cv::gimpl::getCompileArg<cv::GFluidOutputRois>(args);
91
if (num_islands > 1 && out_rois.has_value())
92
cv::util::throw_error(std::logic_error("GFluidOutputRois feature supports only one-island graphs"));
93
94
auto rois = out_rois.value_or(cv::GFluidOutputRois());
95
return EPtr{new cv::gimpl::GFluidExecutable(graph, nodes, std::move(rois.rois))};
96
}
97
98
virtual void addBackendPasses(ade::ExecutionEngineSetupContext &ectx) override;
99
100
};
101
}
102
103
cv::gapi::GBackend cv::gapi::fluid::backend()
104
{
105
static cv::gapi::GBackend this_backend(std::make_shared<GFluidBackendImpl>());
106
return this_backend;
107
}
108
109
// FluidAgent implementation ///////////////////////////////////////////////////
110
111
namespace cv { namespace gimpl {
112
struct FluidFilterAgent : public FluidAgent
113
{
114
private:
115
virtual int firstWindow() const override;
116
virtual int nextWindow() const override;
117
virtual int linesRead() const override;
118
public:
119
using FluidAgent::FluidAgent;
120
virtual void setInHeight(int) override { /* nothing */ }
121
};
122
123
struct FluidResizeAgent : public FluidAgent
124
{
125
private:
126
virtual int firstWindow() const override;
127
virtual int nextWindow() const override;
128
virtual int linesRead() const override;
129
public:
130
using FluidAgent::FluidAgent;
131
virtual void setInHeight(int) override { /* nothing */ }
132
};
133
134
struct FluidUpscaleAgent : public FluidAgent
135
{
136
private:
137
virtual int firstWindow() const override;
138
virtual int nextWindow() const override;
139
virtual int linesRead() const override;
140
141
int m_inH;
142
public:
143
using FluidAgent::FluidAgent;
144
virtual void setInHeight(int h) override { m_inH = h; }
145
};
146
}} // namespace cv::gimpl
147
148
cv::gimpl::FluidAgent::FluidAgent(const ade::Graph &g, ade::NodeHandle nh)
149
: k(GConstFluidModel(g).metadata(nh).get<FluidUnit>().k) // init(0)
150
, op_handle(nh) // init(1)
151
, op_name(GModel::ConstGraph(g).metadata(nh).get<Op>().k.name) // init(2)
152
{
153
std::set<int> out_w;
154
std::set<int> out_h;
155
GModel::ConstGraph cm(g);
156
for (auto out_data : nh->outNodes())
157
{
158
const auto &d = cm.metadata(out_data).get<Data>();
159
cv::GMatDesc d_meta = cv::util::get<cv::GMatDesc>(d.meta);
160
out_w.insert(d_meta.size.width);
161
out_h.insert(d_meta.size.height);
162
}
163
164
// Different output sizes are not supported
165
GAPI_Assert(out_w.size() == 1 && out_h.size() == 1);
166
}
167
168
void cv::gimpl::FluidAgent::reset()
169
{
170
m_producedLines = 0;
171
172
auto lines = firstWindow();
173
for (auto &v : in_views)
174
{
175
if (v)
176
{
177
v.priv().reset(lines);
178
}
179
}
180
}
181
182
namespace {
183
static int calcGcd (int n1, int n2)
184
{
185
return (n2 == 0) ? n1 : calcGcd (n2, n1 % n2);
186
}
187
188
// This is an empiric formula and this is not 100% guaranteed
189
// that it produces correct results in all possible cases
190
// FIXME:
191
// prove correctness or switch to some trusted method
192
//
193
// When performing resize input/output pixels form a cyclic
194
// pattern where inH/gcd input pixels are mapped to outH/gcd
195
// output pixels (pattern repeats gcd times).
196
//
197
// Output pixel can partually cover some of the input pixels.
198
// There are 3 possible cases:
199
//
200
// :___ ___: :___ _:_ ___: :___ __: ___ :__ ___:
201
// |___|___| |___|_:_|___| |___|__:|___|:__|___|
202
// : : : : : : : : :
203
//
204
// 1) No partial coverage, max window = scaleFactor;
205
// 2) Partial coverage occurs on the one side of the output pixel,
206
// max window = scaleFactor + 1;
207
// 3) Partial coverage occurs at both sides of the output pixel,
208
// max window = scaleFactor + 2;
209
//
210
// Type of the coverage is determined by remainder of
211
// inPeriodH/outPeriodH division, but it's an heuristic
212
// (howbeit didn't found the proof of the opposite so far).
213
214
static int calcResizeWindow(int inH, int outH)
215
{
216
GAPI_Assert(inH >= outH);
217
auto gcd = calcGcd(inH, outH);
218
int inPeriodH = inH/gcd;
219
int outPeriodH = outH/gcd;
220
int scaleFactor = inPeriodH / outPeriodH;
221
222
switch ((inPeriodH) % (outPeriodH))
223
{
224
case 0: return scaleFactor; break;
225
case 1: return scaleFactor + 1; break;
226
default: return scaleFactor + 2;
227
}
228
}
229
230
static int maxLineConsumption(const cv::GFluidKernel& k, int inH, int outH, int lpi)
231
{
232
switch (k.m_kind)
233
{
234
case cv::GFluidKernel::Kind::Filter: return k.m_window + lpi - 1; break;
235
case cv::GFluidKernel::Kind::Resize:
236
{
237
if (inH >= outH)
238
{
239
// FIXME:
240
// This is a suboptimal value, can be reduced
241
return calcResizeWindow(inH, outH) * lpi;
242
}
243
else
244
{
245
// FIXME:
246
// This is a suboptimal value, can be reduced
247
return (inH == 1) ? 1 : 2 + lpi - 1;
248
}
249
} break;
250
default: GAPI_Assert(false); return 0;
251
}
252
}
253
254
static int borderSize(const cv::GFluidKernel& k)
255
{
256
switch (k.m_kind)
257
{
258
case cv::GFluidKernel::Kind::Filter: return (k.m_window - 1) / 2; break;
259
// Resize never reads from border pixels
260
case cv::GFluidKernel::Kind::Resize: return 0; break;
261
default: GAPI_Assert(false); return 0;
262
}
263
}
264
265
double inCoord(int outIdx, double ratio)
266
{
267
return outIdx * ratio;
268
}
269
270
int windowStart(int outIdx, double ratio)
271
{
272
return static_cast<int>(inCoord(outIdx, ratio) + 1e-3);
273
}
274
275
int windowEnd(int outIdx, double ratio)
276
{
277
return static_cast<int>(std::ceil(inCoord(outIdx + 1, ratio) - 1e-3));
278
}
279
280
double inCoordUpscale(int outCoord, double ratio)
281
{
282
// Calculate the projection of output pixel's center
283
return (outCoord + 0.5) * ratio - 0.5;
284
}
285
286
int upscaleWindowStart(int outCoord, double ratio)
287
{
288
int start = static_cast<int>(inCoordUpscale(outCoord, ratio));
289
GAPI_DbgAssert(start >= 0);
290
return start;
291
}
292
293
int upscaleWindowEnd(int outCoord, double ratio, int inSz)
294
{
295
int end = static_cast<int>(std::ceil(inCoordUpscale(outCoord, ratio)) + 1);
296
if (end > inSz)
297
{
298
end = inSz;
299
}
300
return end;
301
}
302
} // anonymous namespace
303
304
int cv::gimpl::FluidFilterAgent::firstWindow() const
305
{
306
return k.m_window + k.m_lpi - 1;
307
}
308
309
int cv::gimpl::FluidFilterAgent::nextWindow() const
310
{
311
int lpi = std::min(k.m_lpi, m_outputLines - m_producedLines - k.m_lpi);
312
return k.m_window - 1 + lpi;
313
}
314
315
int cv::gimpl::FluidFilterAgent::linesRead() const
316
{
317
return k.m_lpi;
318
}
319
320
int cv::gimpl::FluidResizeAgent::firstWindow() const
321
{
322
auto outIdx = out_buffers[0]->priv().y();
323
auto lpi = std::min(m_outputLines - m_producedLines, k.m_lpi);
324
return windowEnd(outIdx + lpi - 1, m_ratio) - windowStart(outIdx, m_ratio);
325
}
326
327
int cv::gimpl::FluidResizeAgent::nextWindow() const
328
{
329
auto outIdx = out_buffers[0]->priv().y();
330
auto lpi = std::min(m_outputLines - m_producedLines - k.m_lpi, k.m_lpi);
331
auto nextStartIdx = outIdx + 1 + k.m_lpi - 1;
332
auto nextEndIdx = nextStartIdx + lpi - 1;
333
return windowEnd(nextEndIdx, m_ratio) - windowStart(nextStartIdx, m_ratio);
334
}
335
336
int cv::gimpl::FluidResizeAgent::linesRead() const
337
{
338
auto outIdx = out_buffers[0]->priv().y();
339
return windowStart(outIdx + 1 + k.m_lpi - 1, m_ratio) - windowStart(outIdx, m_ratio);
340
}
341
342
int cv::gimpl::FluidUpscaleAgent::firstWindow() const
343
{
344
auto outIdx = out_buffers[0]->priv().y();
345
auto lpi = std::min(m_outputLines - m_producedLines, k.m_lpi);
346
return upscaleWindowEnd(outIdx + lpi - 1, m_ratio, m_inH) - upscaleWindowStart(outIdx, m_ratio);
347
}
348
349
int cv::gimpl::FluidUpscaleAgent::nextWindow() const
350
{
351
auto outIdx = out_buffers[0]->priv().y();
352
auto lpi = std::min(m_outputLines - m_producedLines - k.m_lpi, k.m_lpi);
353
auto nextStartIdx = outIdx + 1 + k.m_lpi - 1;
354
auto nextEndIdx = nextStartIdx + lpi - 1;
355
return upscaleWindowEnd(nextEndIdx, m_ratio, m_inH) - upscaleWindowStart(nextStartIdx, m_ratio);
356
}
357
358
int cv::gimpl::FluidUpscaleAgent::linesRead() const
359
{
360
auto outIdx = out_buffers[0]->priv().y();
361
return upscaleWindowStart(outIdx + 1 + k.m_lpi - 1, m_ratio) - upscaleWindowStart(outIdx, m_ratio);
362
}
363
364
bool cv::gimpl::FluidAgent::canRead() const
365
{
366
// An agent can work if every input buffer have enough data to start
367
for (const auto& in_view : in_views)
368
{
369
if (in_view)
370
{
371
if (!in_view.ready())
372
return false;
373
}
374
}
375
return true;
376
}
377
378
bool cv::gimpl::FluidAgent::canWrite() const
379
{
380
// An agent can work if there is space to write in its output
381
// allocated buffers
382
GAPI_DbgAssert(!out_buffers.empty());
383
auto out_begin = out_buffers.begin();
384
auto out_end = out_buffers.end();
385
if (k.m_scratch) out_end--;
386
for (auto it = out_begin; it != out_end; ++it)
387
{
388
if ((*it)->priv().full())
389
{
390
return false;
391
}
392
}
393
return true;
394
}
395
396
bool cv::gimpl::FluidAgent::canWork() const
397
{
398
return canRead() && canWrite();
399
}
400
401
void cv::gimpl::FluidAgent::doWork()
402
{
403
GAPI_DbgAssert(m_outputLines > m_producedLines);
404
for (auto& in_view : in_views)
405
{
406
if (in_view) in_view.priv().prepareToRead();
407
}
408
409
k.m_f(in_args, out_buffers);
410
411
for (auto& in_view : in_views)
412
{
413
if (in_view) in_view.priv().readDone(linesRead(), nextWindow());
414
}
415
416
for (auto out_buf : out_buffers)
417
{
418
out_buf->priv().writeDone();
419
// FIXME WARNING: Scratch buffers rotated here too!
420
}
421
422
m_producedLines += k.m_lpi;
423
}
424
425
bool cv::gimpl::FluidAgent::done() const
426
{
427
// m_producedLines is a multiple of LPI, while original
428
// height may be not.
429
return m_producedLines >= m_outputLines;
430
}
431
432
void cv::gimpl::FluidAgent::debug(std::ostream &os)
433
{
434
os << "Fluid Agent " << std::hex << this
435
<< " (" << op_name << ") --"
436
<< " canWork=" << std::boolalpha << canWork()
437
<< " canRead=" << std::boolalpha << canRead()
438
<< " canWrite=" << std::boolalpha << canWrite()
439
<< " done=" << done()
440
<< " lines=" << std::dec << m_producedLines << "/" << m_outputLines
441
<< " {{\n";
442
for (auto out_buf : out_buffers)
443
{
444
out_buf->debug(os);
445
}
446
std::cout << "}}" << std::endl;
447
}
448
449
// GCPUExcecutable implementation //////////////////////////////////////////////
450
451
void cv::gimpl::GFluidExecutable::initBufferRois(std::vector<int>& readStarts, std::vector<cv::gapi::own::Rect>& rois)
452
{
453
GConstFluidModel fg(m_g);
454
auto proto = m_gm.metadata().get<Protocol>();
455
std::stack<ade::NodeHandle> nodesToVisit;
456
457
if (proto.outputs.size() != m_outputRois.size())
458
{
459
GAPI_Assert(m_outputRois.size() == 0);
460
return;
461
}
462
463
// First, initialize rois for output nodes, add them to traversal stack
464
for (const auto& it : ade::util::indexed(proto.out_nhs))
465
{
466
const auto idx = ade::util::index(it);
467
const auto nh = ade::util::value(it);
468
469
const auto &d = m_gm.metadata(nh).get<Data>();
470
471
// This is not our output
472
if (m_id_map.count(d.rc) == 0)
473
{
474
continue;
475
}
476
477
if (d.shape == GShape::GMAT)
478
{
479
auto desc = util::get<GMatDesc>(d.meta);
480
if (m_outputRois[idx] == cv::gapi::own::Rect{})
481
{
482
m_outputRois[idx] = cv::gapi::own::Rect{0, 0, desc.size.width, desc.size.height};
483
}
484
485
// Only slices are supported at the moment
486
GAPI_Assert(m_outputRois[idx].x == 0);
487
GAPI_Assert(m_outputRois[idx].width == desc.size.width);
488
489
auto id = m_id_map.at(d.rc);
490
readStarts[id] = 0;
491
rois[id] = m_outputRois[idx];
492
nodesToVisit.push(nh);
493
}
494
}
495
496
// Perform a wide search from each of the output nodes
497
// And extend roi of buffers by border_size
498
// Each node can be visited multiple times
499
// (if node has been already visited, the check that inferred rois are the same is performed)
500
while (!nodesToVisit.empty())
501
{
502
const auto startNode = nodesToVisit.top();
503
nodesToVisit.pop();
504
505
if (!startNode->inNodes().empty())
506
{
507
GAPI_Assert(startNode->inNodes().size() == 1);
508
const auto& oh = startNode->inNodes().front();
509
510
const auto& data = m_gm.metadata(startNode).get<Data>();
511
// only GMats participate in the process so it's valid to obtain GMatDesc
512
const auto& meta = util::get<GMatDesc>(data.meta);
513
514
for (const auto& inNode : oh->inNodes())
515
{
516
const auto& in_data = m_gm.metadata(inNode).get<Data>();
517
518
if (in_data.shape == GShape::GMAT && fg.metadata(inNode).contains<FluidData>())
519
{
520
const auto& in_meta = util::get<GMatDesc>(in_data.meta);
521
const auto& fd = fg.metadata(inNode).get<FluidData>();
522
523
auto adjFilterRoi = [](cv::gapi::own::Rect produced, int b, int max_height) {
524
// Extend with border roi which should be produced, crop to logical image size
525
cv::gapi::own::Rect roi = {produced.x, produced.y - b, produced.width, produced.height + 2*b};
526
cv::gapi::own::Rect fullImg{ 0, 0, produced.width, max_height };
527
return roi & fullImg;
528
};
529
530
auto adjResizeRoi = [](cv::gapi::own::Rect produced, cv::gapi::own::Size inSz, cv::gapi::own::Size outSz) {
531
auto map = [](int outCoord, int producedSz, int inSize, int outSize) {
532
double ratio = (double)inSize / outSize;
533
int w0 = 0, w1 = 0;
534
if (ratio >= 1.0)
535
{
536
w0 = windowStart(outCoord, ratio);
537
w1 = windowEnd (outCoord + producedSz - 1, ratio);
538
}
539
else
540
{
541
w0 = upscaleWindowStart(outCoord, ratio);
542
w1 = upscaleWindowEnd(outCoord + producedSz - 1, ratio, inSize);
543
}
544
return std::make_pair(w0, w1);
545
};
546
547
auto mapY = map(produced.y, produced.height, inSz.height, outSz.height);
548
auto y0 = mapY.first;
549
auto y1 = mapY.second;
550
551
auto mapX = map(produced.x, produced.width, inSz.width, outSz.width);
552
auto x0 = mapX.first;
553
auto x1 = mapX.second;
554
555
cv::gapi::own::Rect roi = {x0, y0, x1 - x0, y1 - y0};
556
return roi;
557
};
558
559
cv::gapi::own::Rect produced = rois[m_id_map.at(data.rc)];
560
561
cv::gapi::own::Rect resized;
562
switch (fg.metadata(oh).get<FluidUnit>().k.m_kind)
563
{
564
case GFluidKernel::Kind::Filter: resized = produced; break;
565
case GFluidKernel::Kind::Resize: resized = adjResizeRoi(produced, in_meta.size, meta.size); break;
566
default: GAPI_Assert(false);
567
}
568
569
int readStart = resized.y;
570
cv::gapi::own::Rect roi = adjFilterRoi(resized, fd.border_size, in_meta.size.height);
571
572
auto in_id = m_id_map.at(in_data.rc);
573
if (rois[in_id] == cv::gapi::own::Rect{})
574
{
575
readStarts[in_id] = readStart;
576
rois[in_id] = roi;
577
// Continue traverse on internal (w.r.t Island) data nodes only.
578
if (fd.internal) nodesToVisit.push(inNode);
579
}
580
else
581
{
582
GAPI_Assert(readStarts[in_id] == readStart);
583
GAPI_Assert(rois[in_id] == roi);
584
}
585
} // if (in_data.shape == GShape::GMAT)
586
} // for (const auto& inNode : oh->inNodes())
587
} // if (!startNode->inNodes().empty())
588
} // while (!nodesToVisit.empty())
589
}
590
591
cv::gimpl::GFluidExecutable::GFluidExecutable(const ade::Graph &g,
592
const std::vector<ade::NodeHandle> &nodes,
593
const std::vector<cv::gapi::own::Rect> &outputRois)
594
: m_g(g), m_gm(m_g), m_nodes(nodes), m_outputRois(outputRois)
595
{
596
GConstFluidModel fg(m_g);
597
598
// Initialize vector of data buffers, build list of operations
599
// FIXME: There _must_ be a better way to [query] count number of DATA nodes
600
std::size_t mat_count = 0;
601
std::size_t last_agent = 0;
602
std::map<std::size_t, ade::NodeHandle> all_gmat_ids;
603
604
auto grab_mat_nh = [&](ade::NodeHandle nh) {
605
auto rc = m_gm.metadata(nh).get<Data>().rc;
606
if (m_id_map.count(rc) == 0)
607
{
608
all_gmat_ids[mat_count] = nh;
609
m_id_map[rc] = mat_count++;
610
}
611
};
612
613
for (const auto &nh : m_nodes)
614
{
615
switch (m_gm.metadata(nh).get<NodeType>().t)
616
{
617
case NodeType::DATA:
618
if (m_gm.metadata(nh).get<Data>().shape == GShape::GMAT)
619
grab_mat_nh(nh);
620
break;
621
622
case NodeType::OP:
623
{
624
const auto& fu = fg.metadata(nh).get<FluidUnit>();
625
switch (fu.k.m_kind)
626
{
627
case GFluidKernel::Kind::Filter: m_agents.emplace_back(new FluidFilterAgent(m_g, nh)); break;
628
case GFluidKernel::Kind::Resize:
629
{
630
if (fu.ratio >= 1.0)
631
{
632
m_agents.emplace_back(new FluidResizeAgent(m_g, nh));
633
}
634
else
635
{
636
m_agents.emplace_back(new FluidUpscaleAgent(m_g, nh));
637
}
638
} break;
639
default: GAPI_Assert(false);
640
}
641
// NB.: in_buffer_ids size is equal to Arguments size, not Edges size!!!
642
m_agents.back()->in_buffer_ids.resize(m_gm.metadata(nh).get<Op>().args.size(), -1);
643
for (auto eh : nh->inEdges())
644
{
645
// FIXME Only GMats are currently supported (which can be represented
646
// as fluid buffers
647
if (m_gm.metadata(eh->srcNode()).get<Data>().shape == GShape::GMAT)
648
{
649
const auto in_port = m_gm.metadata(eh).get<Input>().port;
650
const int in_buf = m_gm.metadata(eh->srcNode()).get<Data>().rc;
651
652
m_agents.back()->in_buffer_ids[in_port] = in_buf;
653
grab_mat_nh(eh->srcNode());
654
}
655
}
656
// FIXME: Assumption that all operation outputs MUST be connected
657
m_agents.back()->out_buffer_ids.resize(nh->outEdges().size(), -1);
658
for (auto eh : nh->outEdges())
659
{
660
const auto& data = m_gm.metadata(eh->dstNode()).get<Data>();
661
const auto out_port = m_gm.metadata(eh).get<Output>().port;
662
const int out_buf = data.rc;
663
664
m_agents.back()->out_buffer_ids[out_port] = out_buf;
665
if (data.shape == GShape::GMAT) grab_mat_nh(eh->dstNode());
666
}
667
if (fu.k.m_scratch)
668
m_scratch_users.push_back(last_agent);
669
last_agent++;
670
break;
671
}
672
default: GAPI_Assert(false);
673
}
674
}
675
676
// Check that IDs form a continiuos set (important for further indexing)
677
GAPI_Assert(m_id_map.size() > 0);
678
GAPI_Assert(m_id_map.size() == static_cast<size_t>(mat_count));
679
680
// Actually initialize Fluid buffers
681
GAPI_LOG_INFO(NULL, "Initializing " << mat_count << " fluid buffer(s)" << std::endl);
682
m_num_int_buffers = mat_count;
683
const std::size_t num_scratch = m_scratch_users.size();
684
685
std::vector<int> readStarts(mat_count);
686
std::vector<cv::gapi::own::Rect> rois(mat_count);
687
688
initBufferRois(readStarts, rois);
689
690
// NB: Allocate ALL buffer object at once, and avoid any further reallocations
691
// (since raw pointers-to-elements are taken)
692
m_buffers.resize(m_num_int_buffers + num_scratch);
693
for (const auto &it : all_gmat_ids)
694
{
695
auto id = it.first;
696
auto nh = it.second;
697
const auto & d = m_gm.metadata(nh).get<Data>();
698
const auto &fd = fg.metadata(nh).get<FluidData>();
699
const auto meta = cv::util::get<GMatDesc>(d.meta);
700
701
m_buffers[id].priv().init(meta, fd.lpi_write, readStarts[id], rois[id]);
702
703
// TODO:
704
// Introduce Storage::INTERNAL_GRAPH and Storage::INTERNAL_ISLAND?
705
if (fd.internal == true)
706
{
707
m_buffers[id].priv().allocate(fd.border, fd.border_size, fd.max_consumption, fd.skew);
708
std::stringstream stream;
709
m_buffers[id].debug(stream);
710
GAPI_LOG_INFO(NULL, stream.str());
711
}
712
}
713
714
// After buffers are allocated, repack: ...
715
for (auto &agent : m_agents)
716
{
717
// a. Agent input parameters with View pointers (creating Views btw)
718
const auto &op = m_gm.metadata(agent->op_handle).get<Op>();
719
const auto &fu = fg.metadata(agent->op_handle).get<FluidUnit>();
720
agent->in_args.resize(op.args.size());
721
agent->in_views.resize(op.args.size());
722
for (auto it : ade::util::zip(ade::util::iota(op.args.size()),
723
ade::util::toRange(agent->in_buffer_ids)))
724
{
725
auto in_idx = std::get<0>(it);
726
auto buf_idx = std::get<1>(it);
727
728
if (buf_idx >= 0)
729
{
730
// IF there is input buffer, register a view (every unique
731
// reader has its own), and store it in agent Args
732
gapi::fluid::Buffer &buffer = m_buffers.at(m_id_map.at(buf_idx));
733
734
auto inEdge = GModel::getInEdgeByPort(m_g, agent->op_handle, in_idx);
735
auto ownStorage = fg.metadata(inEdge).get<FluidUseOwnBorderBuffer>().use;
736
737
gapi::fluid::View view = buffer.mkView(fu.line_consumption, fu.border_size, fu.border, ownStorage);
738
// NB: It is safe to keep ptr as view lifetime is buffer lifetime
739
agent->in_views[in_idx] = view;
740
agent->in_args[in_idx] = GArg(view);
741
agent->m_ratio = fu.ratio;
742
}
743
else
744
{
745
// Copy(FIXME!) original args as is
746
agent->in_args[in_idx] = op.args[in_idx];
747
}
748
}
749
750
// cache input height to avoid costly meta() call
751
// (actually cached and used only in upscale)
752
if (agent->in_views[0])
753
{
754
agent->setInHeight(agent->in_views[0].meta().size.height);
755
}
756
757
// b. Agent output parameters with Buffer pointers.
758
agent->out_buffers.resize(agent->op_handle->outEdges().size(), nullptr);
759
for (auto it : ade::util::zip(ade::util::iota(agent->out_buffers.size()),
760
ade::util::toRange(agent->out_buffer_ids)))
761
{
762
auto out_idx = std::get<0>(it);
763
auto buf_idx = m_id_map.at(std::get<1>(it));
764
agent->out_buffers.at(out_idx) = &m_buffers.at(buf_idx);
765
agent->m_outputLines = m_buffers.at(buf_idx).priv().outputLines();
766
}
767
}
768
769
// After parameters are there, initialize scratch buffers
770
if (num_scratch)
771
{
772
GAPI_LOG_INFO(NULL, "Initializing " << num_scratch << " scratch buffer(s)" << std::endl);
773
std::size_t last_scratch_id = 0;
774
775
for (auto i : m_scratch_users)
776
{
777
auto &agent = m_agents.at(i);
778
GAPI_Assert(agent->k.m_scratch);
779
780
// Collect input metas to trigger scratch buffer initialization
781
// Array is sparse (num of elements == num of GArgs, not edges)
782
GMetaArgs in_metas(agent->in_args.size());
783
for (auto eh : agent->op_handle->inEdges())
784
{
785
const auto& in_data = m_gm.metadata(eh->srcNode()).get<Data>();
786
in_metas[m_gm.metadata(eh).get<Input>().port] = in_data.meta;
787
}
788
789
// Trigger Scratch buffer initialization method
790
const std::size_t new_scratch_idx = m_num_int_buffers + last_scratch_id;
791
792
agent->k.m_is(in_metas, agent->in_args, m_buffers.at(new_scratch_idx));
793
std::stringstream stream;
794
m_buffers[new_scratch_idx].debug(stream);
795
GAPI_LOG_INFO(NULL, stream.str());
796
agent->out_buffers.emplace_back(&m_buffers[new_scratch_idx]);
797
last_scratch_id++;
798
}
799
}
800
801
std::size_t total_size = 0;
802
for (const auto &i : ade::util::indexed(m_buffers))
803
{
804
// Check that all internal and scratch buffers are allocated
805
const auto idx = ade::util::index(i);
806
const auto b = ade::util::value(i);
807
if (idx >= m_num_int_buffers ||
808
fg.metadata(all_gmat_ids[idx]).get<FluidData>().internal == true)
809
{
810
GAPI_Assert(b.priv().size() > 0);
811
}
812
813
// Buffers which will be bound to real images may have size of 0 at this moment
814
// (There can be non-zero sized const border buffer allocated in such buffers)
815
total_size += b.priv().size();
816
}
817
GAPI_LOG_INFO(NULL, "Internal buffers: " << std::fixed << std::setprecision(2) << static_cast<float>(total_size)/1024 << " KB\n");
818
}
819
820
// FIXME: Document what it does
821
void cv::gimpl::GFluidExecutable::bindInArg(const cv::gimpl::RcDesc &rc, const GRunArg &arg)
822
{
823
switch (rc.shape)
824
{
825
case GShape::GMAT: m_buffers[m_id_map.at(rc.id)].priv().bindTo(util::get<cv::gapi::own::Mat>(arg), true); break;
826
case GShape::GSCALAR: m_res.slot<cv::gapi::own::Scalar>()[rc.id] = util::get<cv::gapi::own::Scalar>(arg); break;
827
default: util::throw_error(std::logic_error("Unsupported GShape type"));
828
}
829
}
830
831
void cv::gimpl::GFluidExecutable::bindOutArg(const cv::gimpl::RcDesc &rc, const GRunArgP &arg)
832
{
833
// Only GMat is supported as return type
834
switch (rc.shape)
835
{
836
case GShape::GMAT:
837
{
838
cv::GMatDesc desc = m_buffers[m_id_map.at(rc.id)].meta();
839
auto &outMat = *util::get<cv::gapi::own::Mat*>(arg);
840
GAPI_Assert(outMat.data != nullptr);
841
GAPI_Assert(descr_of(outMat) == desc && "Output argument was not preallocated as it should be ?");
842
m_buffers[m_id_map.at(rc.id)].priv().bindTo(outMat, false);
843
break;
844
}
845
default: util::throw_error(std::logic_error("Unsupported return GShape type"));
846
}
847
}
848
849
void cv::gimpl::GFluidExecutable::packArg(cv::GArg &in_arg, const cv::GArg &op_arg)
850
{
851
GAPI_Assert(op_arg.kind != cv::detail::ArgKind::GMAT
852
&& op_arg.kind != cv::detail::ArgKind::GSCALAR);
853
854
if (op_arg.kind == cv::detail::ArgKind::GOBJREF)
855
{
856
const cv::gimpl::RcDesc &ref = op_arg.get<cv::gimpl::RcDesc>();
857
if (ref.shape == GShape::GSCALAR)
858
{
859
in_arg = GArg(m_res.slot<cv::gapi::own::Scalar>()[ref.id]);
860
}
861
}
862
}
863
864
void cv::gimpl::GFluidExecutable::run(std::vector<InObj> &&input_objs,
865
std::vector<OutObj> &&output_objs)
866
{
867
// Bind input buffers from parameters
868
for (auto& it : input_objs) bindInArg(it.first, it.second);
869
for (auto& it : output_objs) bindOutArg(it.first, it.second);
870
871
// Reset Buffers and Agents state before we go
872
for (auto &buffer : m_buffers)
873
buffer.priv().reset();
874
875
for (auto &agent : m_agents)
876
{
877
agent->reset();
878
// Pass input cv::Scalar's to agent argument
879
const auto& op = m_gm.metadata(agent->op_handle).get<Op>();
880
for (const auto& it : ade::util::indexed(op.args))
881
{
882
const auto& arg = ade::util::value(it);
883
packArg(agent->in_args[ade::util::index(it)], arg);
884
}
885
}
886
887
// Explicitly reset Scratch buffers, if any
888
for (auto scratch_i : m_scratch_users)
889
{
890
auto &agent = m_agents[scratch_i];
891
GAPI_DbgAssert(agent->k.m_scratch);
892
agent->k.m_rs(*agent->out_buffers.back());
893
}
894
895
// Now start executing our stuff!
896
// Fluid execution is:
897
// - run through list of Agents from Left to Right
898
// - for every Agent:
899
// - if all input Buffers have enough data to fulfill
900
// Agent's window - trigger Agent
901
// - on trigger, Agent takes all input lines from input buffers
902
// and produces a single output line
903
// - once Agent finishes, input buffers get "readDone()",
904
// and output buffers get "writeDone()"
905
// - if there's not enough data, Agent is skipped
906
// Yes, THAT easy!
907
bool complete = true;
908
do {
909
complete = true;
910
bool work_done=false;
911
for (auto &agent : m_agents)
912
{
913
// agent->debug(std::cout);
914
if (!agent->done())
915
{
916
if (agent->canWork())
917
{
918
agent->doWork(); work_done=true;
919
}
920
if (!agent->done()) complete = false;
921
}
922
}
923
GAPI_Assert(work_done || complete);
924
} while (!complete); // FIXME: number of iterations can be calculated statically
925
}
926
927
// FIXME: these passes operate on graph global level!!!
928
// Need to fix this for heterogeneous (island-based) processing
929
void GFluidBackendImpl::addBackendPasses(ade::ExecutionEngineSetupContext &ectx)
930
{
931
using namespace cv::gimpl;
932
933
// FIXME: all passes were moved to "exec" stage since Fluid
934
// should check Islands configuration first (which is now quite
935
// limited), and only then continue with all other passes.
936
//
937
// The passes/stages API must be streamlined!
938
ectx.addPass("exec", "init_fluid_data", [](ade::passes::PassContext &ctx)
939
{
940
GModel::Graph g(ctx.graph);
941
if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
942
return;
943
944
auto isl_graph = g.metadata().get<IslandModel>().model;
945
GIslandModel::Graph gim(*isl_graph);
946
947
GFluidModel fg(ctx.graph);
948
949
const auto setFluidData = [&](ade::NodeHandle nh, bool internal) {
950
FluidData fd;
951
fd.internal = internal;
952
fg.metadata(nh).set(fd);
953
};
954
955
for (const auto& nh : gim.nodes())
956
{
957
if (gim.metadata(nh).get<NodeKind>().k == NodeKind::ISLAND)
958
{
959
const auto isl = gim.metadata(nh).get<FusedIsland>().object;
960
if (isl->backend() == cv::gapi::fluid::backend())
961
{
962
// add FluidData to all data nodes inside island
963
for (const auto node : isl->contents())
964
{
965
if (g.metadata(node).get<NodeType>().t == NodeType::DATA)
966
setFluidData(node, true);
967
}
968
969
// add FluidData to slot if it's read/written by fluid
970
std::vector<ade::NodeHandle> io_handles;
971
for (const auto &in_op : isl->in_ops())
972
{
973
ade::util::copy(in_op->inNodes(), std::back_inserter(io_handles));
974
}
975
for (const auto &out_op : isl->out_ops())
976
{
977
ade::util::copy(out_op->outNodes(), std::back_inserter(io_handles));
978
}
979
for (const auto &io_node : io_handles)
980
{
981
if (!fg.metadata(io_node).contains<FluidData>())
982
setFluidData(io_node, false);
983
}
984
} // if (fluid backend)
985
} // if (ISLAND)
986
} // for (gim.nodes())
987
});
988
// FIXME:
989
// move to unpackKernel method
990
// when https://gitlab-icv.inn.intel.com/G-API/g-api/merge_requests/66 is merged
991
ectx.addPass("exec", "init_fluid_unit_borders", [](ade::passes::PassContext &ctx)
992
{
993
GModel::Graph g(ctx.graph);
994
if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
995
return;
996
997
GFluidModel fg(ctx.graph);
998
999
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1000
for (auto node : sorted)
1001
{
1002
if (fg.metadata(node).contains<FluidUnit>())
1003
{
1004
// FIXME: check that op has only one data node on input
1005
auto &fu = fg.metadata(node).get<FluidUnit>();
1006
const auto &op = g.metadata(node).get<Op>();
1007
1008
// Trigger user-defined "getBorder" callback
1009
fu.border = fu.k.m_b(GModel::collectInputMeta(fg, node), op.args);
1010
}
1011
}
1012
});
1013
ectx.addPass("exec", "init_fluid_units", [](ade::passes::PassContext &ctx)
1014
{
1015
GModel::Graph g(ctx.graph);
1016
if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1017
return;
1018
1019
GFluidModel fg(ctx.graph);
1020
1021
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1022
for (auto node : sorted)
1023
{
1024
if (fg.metadata(node).contains<FluidUnit>())
1025
{
1026
std::set<int> in_hs, out_ws, out_hs;
1027
1028
for (const auto& in : node->inNodes())
1029
{
1030
const auto& d = g.metadata(in).get<Data>();
1031
if (d.shape == cv::GShape::GMAT)
1032
{
1033
const auto& meta = cv::util::get<cv::GMatDesc>(d.meta);
1034
in_hs.insert(meta.size.height);
1035
}
1036
}
1037
1038
for (const auto& out : node->outNodes())
1039
{
1040
const auto& d = g.metadata(out).get<Data>();
1041
if (d.shape == cv::GShape::GMAT)
1042
{
1043
const auto& meta = cv::util::get<cv::GMatDesc>(d.meta);
1044
out_ws.insert(meta.size.width);
1045
out_hs.insert(meta.size.height);
1046
}
1047
}
1048
1049
GAPI_Assert(in_hs.size() == 1 && out_ws.size() == 1 && out_hs.size() == 1);
1050
1051
auto in_h = *in_hs .cbegin();
1052
auto out_h = *out_hs.cbegin();
1053
1054
auto &fu = fg.metadata(node).get<FluidUnit>();
1055
fu.ratio = (double)in_h / out_h;
1056
1057
int line_consumption = maxLineConsumption(fu.k, in_h, out_h, fu.k.m_lpi);
1058
int border_size = borderSize(fu.k);
1059
1060
fu.border_size = border_size;
1061
fu.line_consumption = line_consumption;
1062
1063
GModel::log(g, node, "Line consumption: " + std::to_string(fu.line_consumption));
1064
GModel::log(g, node, "Border size: " + std::to_string(fu.border_size));
1065
}
1066
}
1067
});
1068
ectx.addPass("exec", "init_line_consumption", [](ade::passes::PassContext &ctx)
1069
{
1070
GModel::Graph g(ctx.graph);
1071
if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1072
return;
1073
1074
GFluidModel fg(ctx.graph);
1075
for (const auto node : g.nodes())
1076
{
1077
if (fg.metadata(node).contains<FluidUnit>())
1078
{
1079
const auto &fu = fg.metadata(node).get<FluidUnit>();
1080
1081
for (auto in_data_node : node->inNodes())
1082
{
1083
auto &fd = fg.metadata(in_data_node).get<FluidData>();
1084
1085
// Update (not Set) fields here since a single data node may be
1086
// accessed by multiple consumers
1087
fd.max_consumption = std::max(fu.line_consumption, fd.max_consumption);
1088
fd.border_size = std::max(fu.border_size, fd.border_size);
1089
1090
GModel::log(g, in_data_node, "Line consumption: " + std::to_string(fd.max_consumption)
1091
+ " (upd by " + std::to_string(fu.line_consumption) + ")", node);
1092
GModel::log(g, in_data_node, "Border size: " + std::to_string(fd.border_size), node);
1093
}
1094
}
1095
}
1096
});
1097
ectx.addPass("exec", "calc_latency", [](ade::passes::PassContext &ctx)
1098
{
1099
GModel::Graph g(ctx.graph);
1100
if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1101
return;
1102
1103
GFluidModel fg(ctx.graph);
1104
1105
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1106
for (auto node : sorted)
1107
{
1108
if (fg.metadata(node).contains<FluidUnit>())
1109
{
1110
const auto &fu = fg.metadata(node).get<FluidUnit>();
1111
1112
const int own_latency = fu.line_consumption - fu.border_size;
1113
GModel::log(g, node, "LPI: " + std::to_string(fu.k.m_lpi));
1114
1115
// Output latency is max(input_latency) + own_latency
1116
int in_latency = 0;
1117
for (auto in_data_node : node->inNodes())
1118
{
1119
// FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
1120
in_latency = std::max(in_latency, fg.metadata(in_data_node).get<FluidData>().latency);
1121
}
1122
const int out_latency = in_latency + own_latency;
1123
1124
for (auto out_data_node : node->outNodes())
1125
{
1126
// FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
1127
auto &fd = fg.metadata(out_data_node).get<FluidData>();
1128
fd.latency = out_latency;
1129
fd.lpi_write = fu.k.m_lpi;
1130
GModel::log(g, out_data_node, "Latency: " + std::to_string(out_latency));
1131
}
1132
}
1133
}
1134
});
1135
ectx.addPass("exec", "calc_skew", [](ade::passes::PassContext &ctx)
1136
{
1137
GModel::Graph g(ctx.graph);
1138
if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1139
return;
1140
1141
GFluidModel fg(ctx.graph);
1142
1143
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1144
for (auto node : sorted)
1145
{
1146
if (fg.metadata(node).contains<FluidUnit>())
1147
{
1148
int max_latency = 0;
1149
for (auto in_data_node : node->inNodes())
1150
{
1151
// FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
1152
max_latency = std::max(max_latency, fg.metadata(in_data_node).get<FluidData>().latency);
1153
}
1154
for (auto in_data_node : node->inNodes())
1155
{
1156
// FIXME: ASSERT(DATA), ASSERT(FLUIDDATA)
1157
auto &fd = fg.metadata(in_data_node).get<FluidData>();
1158
1159
// Update (not Set) fields here since a single data node may be
1160
// accessed by multiple consumers
1161
fd.skew = std::max(fd.skew, max_latency - fd.latency);
1162
1163
GModel::log(g, in_data_node, "Skew: " + std::to_string(fd.skew), node);
1164
}
1165
}
1166
}
1167
});
1168
1169
ectx.addPass("exec", "init_buffer_borders", [](ade::passes::PassContext &ctx)
1170
{
1171
GModel::Graph g(ctx.graph);
1172
if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1173
return;
1174
1175
GFluidModel fg(ctx.graph);
1176
auto sorted = g.metadata().get<ade::passes::TopologicalSortData>().nodes();
1177
for (auto node : sorted)
1178
{
1179
if (fg.metadata(node).contains<FluidData>())
1180
{
1181
auto &fd = fg.metadata(node).get<FluidData>();
1182
1183
// Assign border stuff to FluidData
1184
1185
// In/out data nodes are bound to user data directly,
1186
// so cannot be extended with a border
1187
if (fd.internal == true)
1188
{
1189
// For now border of the buffer's storage is the border
1190
// of the first reader whose border size is the same.
1191
// FIXME: find more clever strategy of border picking
1192
// (it can be a border which is common for majority of the
1193
// readers, also we can calculate the number of lines which
1194
// will be copied by views on each iteration and base our choice
1195
// on this criteria)
1196
auto readers = node->outNodes();
1197
const auto &candidate = ade::util::find_if(readers, [&](ade::NodeHandle nh) {
1198
return fg.metadata(nh).contains<FluidUnit>() &&
1199
fg.metadata(nh).get<FluidUnit>().border_size == fd.border_size;
1200
});
1201
1202
GAPI_Assert(candidate != readers.end());
1203
1204
const auto &fu = fg.metadata(*candidate).get<FluidUnit>();
1205
fd.border = fu.border;
1206
}
1207
1208
if (fd.border)
1209
{
1210
GModel::log(g, node, "Border type: " + std::to_string(fd.border->type), node);
1211
}
1212
}
1213
}
1214
});
1215
ectx.addPass("exec", "init_view_borders", [](ade::passes::PassContext &ctx)
1216
{
1217
GModel::Graph g(ctx.graph);
1218
if (!GModel::isActive(g, cv::gapi::fluid::backend())) // FIXME: Rearchitect this!
1219
return;
1220
1221
GFluidModel fg(ctx.graph);
1222
for (auto node : g.nodes())
1223
{
1224
if (fg.metadata(node).contains<FluidData>())
1225
{
1226
auto &fd = fg.metadata(node).get<FluidData>();
1227
for (auto out_edge : node->outEdges())
1228
{
1229
const auto dstNode = out_edge->dstNode();
1230
if (fg.metadata(dstNode).contains<FluidUnit>())
1231
{
1232
const auto &fu = fg.metadata(dstNode).get<FluidUnit>();
1233
1234
// There is no need in own storage for view if it's border is
1235
// the same as the buffer's (view can have equal or smaller border
1236
// size in this case)
1237
if (fu.border_size == 0 ||
1238
(fu.border && fd.border && (*fu.border == *fd.border)))
1239
{
1240
GAPI_Assert(fu.border_size <= fd.border_size);
1241
fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{false});
1242
}
1243
else
1244
{
1245
fg.metadata(out_edge).set(FluidUseOwnBorderBuffer{true});
1246
GModel::log(g, out_edge, "OwnBufferStorage: true");
1247
}
1248
}
1249
}
1250
}
1251
}
1252
});
1253
}
1254
1255