From 499d8adb75f18113c1fd5ec36cbfbe5fac9b993a Mon Sep 17 00:00:00 2001 From: Anatoliy Talamanov Date: Thu, 23 Sep 2021 22:59:40 +0300 Subject: [PATCH] Merge pull request #20705 from TolyaTalamanov:at/handle-reshape-in-gexecutor G-API: Handle reshape for generic case in GExecutor * Handle reshape for generic case for GExecutor * Add initResources * Add tests * Refactor reshape method --- modules/gapi/src/executor/gexecutor.cpp | 17 +- .../test/internal/gapi_int_executor_tests.cpp | 217 ++++++++++++++++++ 2 files changed, 230 insertions(+), 4 deletions(-) diff --git a/modules/gapi/src/executor/gexecutor.cpp b/modules/gapi/src/executor/gexecutor.cpp index 2ca675c7df..ad9d380b4e 100644 --- a/modules/gapi/src/executor/gexecutor.cpp +++ b/modules/gapi/src/executor/gexecutor.cpp @@ -7,8 +7,6 @@ #include "precomp.hpp" -#include - #include #include @@ -411,7 +409,8 @@ bool cv::gimpl::GExecutor::canReshape() const { // FIXME: Introduce proper reshaping support on GExecutor level // for all cases! - return (m_ops.size() == 1) && m_ops[0].isl_exec->canReshape(); + return std::all_of(m_ops.begin(), m_ops.end(), + [](const OpDesc& op) { return op.isl_exec->canReshape(); }); } void cv::gimpl::GExecutor::reshape(const GMetaArgs& inMetas, const GCompileArgs& args) @@ -421,7 +420,17 @@ void cv::gimpl::GExecutor::reshape(const GMetaArgs& inMetas, const GCompileArgs& ade::passes::PassContext ctx{g}; passes::initMeta(ctx, inMetas); passes::inferMeta(ctx, true); - m_ops[0].isl_exec->reshape(g, args); + + // NB: Before reshape islands need to re-init resources for every slot. + for (auto slot : m_slots) + { + initResource(slot.slot_nh, slot.data_nh); + } + + for (auto& op : m_ops) + { + op.isl_exec->reshape(g, args); + } } void cv::gimpl::GExecutor::prepareForNewStream() diff --git a/modules/gapi/test/internal/gapi_int_executor_tests.cpp b/modules/gapi/test/internal/gapi_int_executor_tests.cpp index 04afa1fda8..90a338a3d0 100644 --- a/modules/gapi/test/internal/gapi_int_executor_tests.cpp +++ b/modules/gapi/test/internal/gapi_int_executor_tests.cpp @@ -6,10 +6,158 @@ #include "../test_precomp.hpp" +#include "../gapi_mock_kernels.hpp" namespace opencv_test { +namespace +{ + +class GMockExecutable final: public cv::gimpl::GIslandExecutable +{ + virtual inline bool canReshape() const override { + return m_priv->m_can_reshape; + } + + virtual void reshape(ade::Graph&, const GCompileArgs&) override + { + m_priv->m_reshape_counter++; + } + virtual void handleNewStream() override { } + virtual void run(std::vector&&, std::vector&&) { } + virtual bool allocatesOutputs() const override + { + return true; + } + + virtual cv::RMat allocate(const cv::GMatDesc&) const override + { + m_priv->m_allocate_counter++; + return cv::RMat(); + } + + // NB: GMockBackendImpl creates new unique_ptr + // on every compile call. Need to share counters between instances in order + // to validate it in tests. + struct Priv + { + bool m_can_reshape; + int m_reshape_counter; + int m_allocate_counter; + }; + + std::shared_ptr m_priv; + +public: + GMockExecutable(bool can_reshape = true) + : m_priv(new Priv{can_reshape, 0, 0}) + { + }; + + void setReshape(bool can_reshape) { m_priv->m_can_reshape = can_reshape; } + + int getReshapeCounter() const { return m_priv->m_reshape_counter; } + int getAllocateCounter() const { return m_priv->m_allocate_counter; } +}; + +class GMockBackendImpl final: public cv::gapi::GBackend::Priv +{ + virtual void unpackKernel(ade::Graph &, + const ade::NodeHandle &, + const cv::GKernelImpl &) override { } + + virtual EPtr compile(const ade::Graph &, + const cv::GCompileArgs &, + const std::vector &) const override + { + ++m_compile_counter; + return EPtr{new GMockExecutable(m_exec)}; + } + + mutable int m_compile_counter = 0; + GMockExecutable m_exec; + + virtual bool controlsMerge() const override { + return true; + } + + virtual bool allowsMerge(const cv::gimpl::GIslandModel::Graph &, + const ade::NodeHandle &, + const ade::NodeHandle &, + const ade::NodeHandle &) const override { + return false; + } + +public: + GMockBackendImpl(const GMockExecutable& exec) : m_exec(exec) { }; + int getCompileCounter() const { return m_compile_counter; } +}; + +class GMockFunctor : public gapi::cpu::GOCVFunctor +{ +public: + GMockFunctor(cv::gapi::GBackend backend, + const char* id, + const Meta &meta, + const Impl& impl) + : gapi::cpu::GOCVFunctor(id, meta, impl), m_backend(backend) + { + } + + cv::gapi::GBackend backend() const override { return m_backend; } + +private: + cv::gapi::GBackend m_backend; +}; + +template +GMockFunctor mock_kernel(const cv::gapi::GBackend& backend, Callable c) +{ + using P = cv::detail::OCVCallHelper; + return GMockFunctor{ backend + , K::id() + , &K::getOutMeta + , std::bind(&P::callFunctor, std::placeholders::_1, c) + }; +} + +void dummyFooImpl(const cv::Mat&, cv::Mat&) { }; +void dummyBarImpl(const cv::Mat&, const cv::Mat&, cv::Mat&) { }; + +struct GExecutorReshapeTest: public ::testing::Test +{ + GExecutorReshapeTest() + : comp([](){ + cv::GMat in; + cv::GMat out = I::Bar::on(I::Foo::on(in), in); + return cv::GComputation(in, out); + }) + { + backend_impl1 = std::make_shared(island1); + backend1 = cv::gapi::GBackend{backend_impl1}; + backend_impl2 = std::make_shared(island2); + backend2 = cv::gapi::GBackend{backend_impl2}; + auto kernel1 = mock_kernel(backend1, dummyFooImpl); + auto kernel2 = mock_kernel(backend2, dummyBarImpl); + pkg = cv::gapi::kernels(kernel1, kernel2); + in_mat1 = cv::Mat::eye(32, 32, CV_8UC1); + in_mat2 = cv::Mat::eye(64, 64, CV_8UC1); + } + + cv::GComputation comp; + GMockExecutable island1; + std::shared_ptr backend_impl1; + cv::gapi::GBackend backend1; + GMockExecutable island2; + std::shared_ptr backend_impl2; + cv::gapi::GBackend backend2; + cv::gapi::GKernelPackage pkg; + cv::Mat in_mat1, in_mat2, out_mat;; +}; + +} // anonymous namespace + // FIXME: avoid code duplication // The below graph and config is taken from ComplexIslands test suite TEST(GExecutor, SmokeTest) @@ -77,6 +225,75 @@ TEST(GExecutor, SmokeTest) // with breakdown worked) } +TEST_F(GExecutorReshapeTest, ReshapeInsteadOfRecompile) +{ + // NB: Initial state + EXPECT_EQ(0, backend_impl1->getCompileCounter()); + EXPECT_EQ(0, backend_impl2->getCompileCounter()); + EXPECT_EQ(0, island1.getReshapeCounter()); + EXPECT_EQ(0, island2.getReshapeCounter()); + + // NB: First compilation. + comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg)); + EXPECT_EQ(1, backend_impl1->getCompileCounter()); + EXPECT_EQ(1, backend_impl2->getCompileCounter()); + EXPECT_EQ(0, island1.getReshapeCounter()); + EXPECT_EQ(0, island2.getReshapeCounter()); + + // NB: GMockBackendImpl implements "reshape" method, + // so it won't be recompiled if the meta is changed. + comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg)); + EXPECT_EQ(1, backend_impl1->getCompileCounter()); + EXPECT_EQ(1, backend_impl2->getCompileCounter()); + EXPECT_EQ(1, island1.getReshapeCounter()); + EXPECT_EQ(1, island2.getReshapeCounter()); +} + +TEST_F(GExecutorReshapeTest, OneBackendNotReshapable) +{ + // NB: Make first island not reshapable + island1.setReshape(false); + + // NB: Initial state + EXPECT_EQ(0, backend_impl1->getCompileCounter()); + EXPECT_EQ(0, island1.getReshapeCounter()); + EXPECT_EQ(0, backend_impl2->getCompileCounter()); + EXPECT_EQ(0, island2.getReshapeCounter()); + + // NB: First compilation. + comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg)); + EXPECT_EQ(1, backend_impl1->getCompileCounter()); + EXPECT_EQ(1, backend_impl2->getCompileCounter()); + EXPECT_EQ(0, island1.getReshapeCounter()); + EXPECT_EQ(0, island2.getReshapeCounter()); + + // NB: Since one of islands isn't reshapable + // the entire graph isn't reshapable as well. + comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg)); + EXPECT_EQ(2, backend_impl1->getCompileCounter()); + EXPECT_EQ(2, backend_impl2->getCompileCounter()); + EXPECT_EQ(0, island1.getReshapeCounter()); + EXPECT_EQ(0, island2.getReshapeCounter()); +} + +TEST_F(GExecutorReshapeTest, ReshapeCallAllocate) +{ + // NB: Initial state + EXPECT_EQ(0, island1.getAllocateCounter()); + EXPECT_EQ(0, island1.getReshapeCounter()); + + // NB: First compilation. + comp.apply(cv::gin(in_mat1), cv::gout(out_mat), cv::compile_args(pkg)); + EXPECT_EQ(1, island1.getAllocateCounter()); + EXPECT_EQ(0, island1.getReshapeCounter()); + + // NB: The entire graph is reshapable, so it won't be recompiled, but reshaped. + // Check that reshape call "allocate" to reallocate buffers. + comp.apply(cv::gin(in_mat2), cv::gout(out_mat), cv::compile_args(pkg)); + EXPECT_EQ(2, island1.getAllocateCounter()); + EXPECT_EQ(1, island1.getReshapeCounter()); +} + // FIXME: Add explicit tests on GMat/GScalar/GArray being connectors // between executed islands -- GitLab