Loading libs/hwui/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -618,6 +618,7 @@ cc_test { "tests/unit/LayerUpdateQueueTests.cpp", "tests/unit/LinearAllocatorTests.cpp", "tests/unit/MatrixTests.cpp", "tests/unit/OpBufferTests.cpp", "tests/unit/PathInterpolatorTests.cpp", "tests/unit/RenderNodeDrawableTests.cpp", "tests/unit/RenderNodeTests.cpp", Loading libs/hwui/canvas/OpBuffer.h +81 −2 Original line number Diff line number Diff line Loading @@ -144,7 +144,86 @@ public: ItemHeader* last() const { return isEmpty() ? nullptr : itemAt(mBuffer->endOffset); } class sentinal { public: explicit sentinal(const uint8_t* end) : end(end) {} private: const uint8_t* const end; }; sentinal end() const { return sentinal{end_ptr()}; } template <ItemTypes T> class filtered_iterator { public: explicit filtered_iterator(uint8_t* start, const uint8_t* end) : mCurrent(start), mEnd(end) { ItemHeader* header = reinterpret_cast<ItemHeader*>(mCurrent); if (header->type != T) { advance(); } } filtered_iterator& operator++() { advance(); return *this; } // Although this iterator self-terminates, we need a placeholder to compare against // to make for-each loops happy bool operator!=(const sentinal& other) const { return mCurrent != mEnd; } ItemContainer<T>& operator*() { return *reinterpret_cast<ItemContainer<T>*>(mCurrent); } private: void advance() { ItemHeader* header = reinterpret_cast<ItemHeader*>(mCurrent); do { mCurrent += header->size; header = reinterpret_cast<ItemHeader*>(mCurrent); } while (mCurrent != mEnd && header->type != T); } uint8_t* mCurrent; const uint8_t* const mEnd; }; template <ItemTypes T> class filtered_view { public: explicit filtered_view(uint8_t* start, const uint8_t* end) : mStart(start), mEnd(end) {} filtered_iterator<T> begin() const { return filtered_iterator<T>{mStart, mEnd}; } sentinal end() const { return sentinal{mEnd}; } private: uint8_t* mStart; const uint8_t* const mEnd; }; template <ItemTypes T> filtered_view<T> filter() const { return filtered_view<T>{start_ptr(), end_ptr()}; } private: uint8_t* start_ptr() const { return reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->startOffset; } const uint8_t* end_ptr() const { return reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->used; } template <typename F, std::size_t... I> void for_each(F&& f, std::index_sequence<I...>) const { // Validate we're not empty Loading @@ -159,8 +238,8 @@ private: }...}; // Do the actual iteration of each item uint8_t* current = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->startOffset; uint8_t* end = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->used; uint8_t* current = start_ptr(); const uint8_t* end = end_ptr(); while (current != end) { auto header = reinterpret_cast<ItemHeader*>(current); // `f` could be a destructor, so ensure all accesses to the OP happen prior to invoking Loading libs/hwui/tests/unit/CanvasOpTests.cpp +0 −104 Original line number Diff line number Diff line Loading @@ -36,50 +36,6 @@ using namespace android::uirenderer::test; // We lazy using Op = CanvasOpType; enum MockTypes { Lifecycle, COUNT }; template<MockTypes T> struct MockOp; template<MockTypes T> struct MockOpContainer { OpBufferItemHeader<MockTypes> header; MockOp<T> impl; }; struct LifecycleTracker { int ctor_count = 0; int dtor_count = 0; int alive() { return ctor_count - dtor_count; } }; template<> struct MockOp<MockTypes::Lifecycle> { MockOp() = delete; void operator=(const MockOp&) = delete; MockOp(LifecycleTracker* tracker) : tracker(tracker) { tracker->ctor_count += 1; } MockOp(const MockOp& other) { tracker = other.tracker; tracker->ctor_count += 1; } ~MockOp() { tracker->dtor_count += 1; } LifecycleTracker* tracker = nullptr; }; using MockBuffer = OpBuffer<MockTypes, MockOpContainer>; class CanvasOpCountingReceiver { public: template <CanvasOpType T> Loading @@ -104,62 +60,6 @@ static int countItems(const T& t) { return count; } TEST(CanvasOp, lifecycleCheck) { LifecycleTracker tracker; { MockBuffer buffer; buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { .impl = MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(tracker.alive(), 1); buffer.clear(); EXPECT_EQ(tracker.alive(), 0); } EXPECT_EQ(tracker.alive(), 0); } TEST(CanvasOp, lifecycleCheckMove) { LifecycleTracker tracker; { MockBuffer buffer; buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { .impl = MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(tracker.alive(), 1); { MockBuffer other(std::move(buffer)); EXPECT_EQ(tracker.alive(), 1); EXPECT_EQ(buffer.size(), 0); EXPECT_GT(other.size(), 0); EXPECT_EQ(1, countItems(other)); EXPECT_EQ(0, countItems(buffer)); other.push_container(MockOpContainer<MockTypes::Lifecycle> { .impl = MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(2, countItems(other)); EXPECT_EQ(2, tracker.alive()); buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { .impl = MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(1, countItems(buffer)); EXPECT_EQ(3, tracker.alive()); buffer = std::move(other); EXPECT_EQ(2, countItems(buffer)); EXPECT_EQ(2, tracker.alive()); } EXPECT_EQ(2, countItems(buffer)); EXPECT_EQ(2, tracker.alive()); buffer.clear(); EXPECT_EQ(0, countItems(buffer)); EXPECT_EQ(0, tracker.alive()); } EXPECT_EQ(tracker.alive(), 0); } TEST(CanvasOp, verifyConst) { CanvasOpBuffer buffer; buffer.push<Op::DrawColor>({ Loading Loading @@ -708,7 +608,3 @@ TEST(CanvasOp, frontendSaveCount) { EXPECT_EQ(1, receiver[Op::Save]); EXPECT_EQ(1, receiver[Op::Restore]); } TEST(CanvasOp, frontendTransform) { } No newline at end of file libs/hwui/tests/unit/OpBufferTests.cpp 0 → 100644 +190 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <gtest/gtest.h> #include <canvas/OpBuffer.h> using namespace android; using namespace android::uirenderer; enum MockTypes { Lifecycle, NoOp, IntHolder, COUNT }; using Op = MockTypes; template<MockTypes T> struct MockOp; template<MockTypes T> struct MockOpContainer { OpBufferItemHeader<MockTypes> header; MockOp<T> impl; MockOpContainer(MockOp<T>&& impl) : impl(std::move(impl)) {} }; struct LifecycleTracker { int ctor_count = 0; int dtor_count = 0; int alive() { return ctor_count - dtor_count; } }; template<> struct MockOp<MockTypes::Lifecycle> { MockOp() = delete; void operator=(const MockOp&) = delete; MockOp(LifecycleTracker* tracker) : tracker(tracker) { tracker->ctor_count += 1; } MockOp(const MockOp& other) { tracker = other.tracker; tracker->ctor_count += 1; } ~MockOp() { tracker->dtor_count += 1; } LifecycleTracker* tracker = nullptr; }; template<> struct MockOp<MockTypes::NoOp> {}; template<> struct MockOp<MockTypes::IntHolder> { int value = -1; }; struct MockBuffer : public OpBuffer<MockTypes, MockOpContainer> { template <MockTypes T> void push(MockOp<T>&& op) { push_container(MockOpContainer<T>{std::move(op)}); } }; template<typename T> static int countItems(const T& t) { int count = 0; t.for_each([&](auto i) { count++; }); return count; } TEST(OpBuffer, lifecycleCheck) { LifecycleTracker tracker; { MockBuffer buffer; buffer.push_container(MockOpContainer<Op::Lifecycle> { MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(tracker.alive(), 1); buffer.clear(); EXPECT_EQ(tracker.alive(), 0); } EXPECT_EQ(tracker.alive(), 0); } TEST(OpBuffer, lifecycleCheckMove) { LifecycleTracker tracker; { MockBuffer buffer; buffer.push_container(MockOpContainer<Op::Lifecycle> { MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(tracker.alive(), 1); { MockBuffer other(std::move(buffer)); EXPECT_EQ(tracker.alive(), 1); EXPECT_EQ(buffer.size(), 0); EXPECT_GT(other.size(), 0); EXPECT_EQ(1, countItems(other)); EXPECT_EQ(0, countItems(buffer)); other.push_container(MockOpContainer<MockTypes::Lifecycle> { MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(2, countItems(other)); EXPECT_EQ(2, tracker.alive()); buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(1, countItems(buffer)); EXPECT_EQ(3, tracker.alive()); buffer = std::move(other); EXPECT_EQ(2, countItems(buffer)); EXPECT_EQ(2, tracker.alive()); } EXPECT_EQ(2, countItems(buffer)); EXPECT_EQ(2, tracker.alive()); buffer.clear(); EXPECT_EQ(0, countItems(buffer)); EXPECT_EQ(0, tracker.alive()); } EXPECT_EQ(tracker.alive(), 0); } TEST(OpBuffer, verifyConst) { MockBuffer buffer; buffer.push<Op::IntHolder>({42}); buffer.for_each([](auto op) { static_assert(std::is_const_v<std::remove_reference_t<decltype(*op)>>, "Expected container to be const"); }); } TEST(OpBuffer, filterView) { MockBuffer buffer; buffer.push<Op::NoOp>({}); buffer.push<Op::IntHolder>({0}); buffer.push<Op::IntHolder>({1}); buffer.push<Op::NoOp>({}); buffer.push<Op::NoOp>({}); buffer.push<Op::IntHolder>({2}); buffer.push<Op::NoOp>({}); buffer.push<Op::NoOp>({}); buffer.push<Op::NoOp>({}); buffer.push<Op::NoOp>({}); int index = 0; for (const auto& it : buffer.filter<Op::IntHolder>()) { ASSERT_EQ(Op::IntHolder, it.header.type); EXPECT_EQ(index, it.impl.value); index++; } EXPECT_EQ(index, 3); int count = 0; for (const auto& it : buffer.filter<Op::NoOp>()) { ASSERT_EQ(Op::NoOp, it.header.type); count++; } EXPECT_EQ(count, 7); } Loading
libs/hwui/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -618,6 +618,7 @@ cc_test { "tests/unit/LayerUpdateQueueTests.cpp", "tests/unit/LinearAllocatorTests.cpp", "tests/unit/MatrixTests.cpp", "tests/unit/OpBufferTests.cpp", "tests/unit/PathInterpolatorTests.cpp", "tests/unit/RenderNodeDrawableTests.cpp", "tests/unit/RenderNodeTests.cpp", Loading
libs/hwui/canvas/OpBuffer.h +81 −2 Original line number Diff line number Diff line Loading @@ -144,7 +144,86 @@ public: ItemHeader* last() const { return isEmpty() ? nullptr : itemAt(mBuffer->endOffset); } class sentinal { public: explicit sentinal(const uint8_t* end) : end(end) {} private: const uint8_t* const end; }; sentinal end() const { return sentinal{end_ptr()}; } template <ItemTypes T> class filtered_iterator { public: explicit filtered_iterator(uint8_t* start, const uint8_t* end) : mCurrent(start), mEnd(end) { ItemHeader* header = reinterpret_cast<ItemHeader*>(mCurrent); if (header->type != T) { advance(); } } filtered_iterator& operator++() { advance(); return *this; } // Although this iterator self-terminates, we need a placeholder to compare against // to make for-each loops happy bool operator!=(const sentinal& other) const { return mCurrent != mEnd; } ItemContainer<T>& operator*() { return *reinterpret_cast<ItemContainer<T>*>(mCurrent); } private: void advance() { ItemHeader* header = reinterpret_cast<ItemHeader*>(mCurrent); do { mCurrent += header->size; header = reinterpret_cast<ItemHeader*>(mCurrent); } while (mCurrent != mEnd && header->type != T); } uint8_t* mCurrent; const uint8_t* const mEnd; }; template <ItemTypes T> class filtered_view { public: explicit filtered_view(uint8_t* start, const uint8_t* end) : mStart(start), mEnd(end) {} filtered_iterator<T> begin() const { return filtered_iterator<T>{mStart, mEnd}; } sentinal end() const { return sentinal{mEnd}; } private: uint8_t* mStart; const uint8_t* const mEnd; }; template <ItemTypes T> filtered_view<T> filter() const { return filtered_view<T>{start_ptr(), end_ptr()}; } private: uint8_t* start_ptr() const { return reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->startOffset; } const uint8_t* end_ptr() const { return reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->used; } template <typename F, std::size_t... I> void for_each(F&& f, std::index_sequence<I...>) const { // Validate we're not empty Loading @@ -159,8 +238,8 @@ private: }...}; // Do the actual iteration of each item uint8_t* current = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->startOffset; uint8_t* end = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->used; uint8_t* current = start_ptr(); const uint8_t* end = end_ptr(); while (current != end) { auto header = reinterpret_cast<ItemHeader*>(current); // `f` could be a destructor, so ensure all accesses to the OP happen prior to invoking Loading
libs/hwui/tests/unit/CanvasOpTests.cpp +0 −104 Original line number Diff line number Diff line Loading @@ -36,50 +36,6 @@ using namespace android::uirenderer::test; // We lazy using Op = CanvasOpType; enum MockTypes { Lifecycle, COUNT }; template<MockTypes T> struct MockOp; template<MockTypes T> struct MockOpContainer { OpBufferItemHeader<MockTypes> header; MockOp<T> impl; }; struct LifecycleTracker { int ctor_count = 0; int dtor_count = 0; int alive() { return ctor_count - dtor_count; } }; template<> struct MockOp<MockTypes::Lifecycle> { MockOp() = delete; void operator=(const MockOp&) = delete; MockOp(LifecycleTracker* tracker) : tracker(tracker) { tracker->ctor_count += 1; } MockOp(const MockOp& other) { tracker = other.tracker; tracker->ctor_count += 1; } ~MockOp() { tracker->dtor_count += 1; } LifecycleTracker* tracker = nullptr; }; using MockBuffer = OpBuffer<MockTypes, MockOpContainer>; class CanvasOpCountingReceiver { public: template <CanvasOpType T> Loading @@ -104,62 +60,6 @@ static int countItems(const T& t) { return count; } TEST(CanvasOp, lifecycleCheck) { LifecycleTracker tracker; { MockBuffer buffer; buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { .impl = MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(tracker.alive(), 1); buffer.clear(); EXPECT_EQ(tracker.alive(), 0); } EXPECT_EQ(tracker.alive(), 0); } TEST(CanvasOp, lifecycleCheckMove) { LifecycleTracker tracker; { MockBuffer buffer; buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { .impl = MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(tracker.alive(), 1); { MockBuffer other(std::move(buffer)); EXPECT_EQ(tracker.alive(), 1); EXPECT_EQ(buffer.size(), 0); EXPECT_GT(other.size(), 0); EXPECT_EQ(1, countItems(other)); EXPECT_EQ(0, countItems(buffer)); other.push_container(MockOpContainer<MockTypes::Lifecycle> { .impl = MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(2, countItems(other)); EXPECT_EQ(2, tracker.alive()); buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { .impl = MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(1, countItems(buffer)); EXPECT_EQ(3, tracker.alive()); buffer = std::move(other); EXPECT_EQ(2, countItems(buffer)); EXPECT_EQ(2, tracker.alive()); } EXPECT_EQ(2, countItems(buffer)); EXPECT_EQ(2, tracker.alive()); buffer.clear(); EXPECT_EQ(0, countItems(buffer)); EXPECT_EQ(0, tracker.alive()); } EXPECT_EQ(tracker.alive(), 0); } TEST(CanvasOp, verifyConst) { CanvasOpBuffer buffer; buffer.push<Op::DrawColor>({ Loading Loading @@ -708,7 +608,3 @@ TEST(CanvasOp, frontendSaveCount) { EXPECT_EQ(1, receiver[Op::Save]); EXPECT_EQ(1, receiver[Op::Restore]); } TEST(CanvasOp, frontendTransform) { } No newline at end of file
libs/hwui/tests/unit/OpBufferTests.cpp 0 → 100644 +190 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <gtest/gtest.h> #include <canvas/OpBuffer.h> using namespace android; using namespace android::uirenderer; enum MockTypes { Lifecycle, NoOp, IntHolder, COUNT }; using Op = MockTypes; template<MockTypes T> struct MockOp; template<MockTypes T> struct MockOpContainer { OpBufferItemHeader<MockTypes> header; MockOp<T> impl; MockOpContainer(MockOp<T>&& impl) : impl(std::move(impl)) {} }; struct LifecycleTracker { int ctor_count = 0; int dtor_count = 0; int alive() { return ctor_count - dtor_count; } }; template<> struct MockOp<MockTypes::Lifecycle> { MockOp() = delete; void operator=(const MockOp&) = delete; MockOp(LifecycleTracker* tracker) : tracker(tracker) { tracker->ctor_count += 1; } MockOp(const MockOp& other) { tracker = other.tracker; tracker->ctor_count += 1; } ~MockOp() { tracker->dtor_count += 1; } LifecycleTracker* tracker = nullptr; }; template<> struct MockOp<MockTypes::NoOp> {}; template<> struct MockOp<MockTypes::IntHolder> { int value = -1; }; struct MockBuffer : public OpBuffer<MockTypes, MockOpContainer> { template <MockTypes T> void push(MockOp<T>&& op) { push_container(MockOpContainer<T>{std::move(op)}); } }; template<typename T> static int countItems(const T& t) { int count = 0; t.for_each([&](auto i) { count++; }); return count; } TEST(OpBuffer, lifecycleCheck) { LifecycleTracker tracker; { MockBuffer buffer; buffer.push_container(MockOpContainer<Op::Lifecycle> { MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(tracker.alive(), 1); buffer.clear(); EXPECT_EQ(tracker.alive(), 0); } EXPECT_EQ(tracker.alive(), 0); } TEST(OpBuffer, lifecycleCheckMove) { LifecycleTracker tracker; { MockBuffer buffer; buffer.push_container(MockOpContainer<Op::Lifecycle> { MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(tracker.alive(), 1); { MockBuffer other(std::move(buffer)); EXPECT_EQ(tracker.alive(), 1); EXPECT_EQ(buffer.size(), 0); EXPECT_GT(other.size(), 0); EXPECT_EQ(1, countItems(other)); EXPECT_EQ(0, countItems(buffer)); other.push_container(MockOpContainer<MockTypes::Lifecycle> { MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(2, countItems(other)); EXPECT_EQ(2, tracker.alive()); buffer.push_container(MockOpContainer<MockTypes::Lifecycle> { MockOp<MockTypes::Lifecycle>{&tracker} }); EXPECT_EQ(1, countItems(buffer)); EXPECT_EQ(3, tracker.alive()); buffer = std::move(other); EXPECT_EQ(2, countItems(buffer)); EXPECT_EQ(2, tracker.alive()); } EXPECT_EQ(2, countItems(buffer)); EXPECT_EQ(2, tracker.alive()); buffer.clear(); EXPECT_EQ(0, countItems(buffer)); EXPECT_EQ(0, tracker.alive()); } EXPECT_EQ(tracker.alive(), 0); } TEST(OpBuffer, verifyConst) { MockBuffer buffer; buffer.push<Op::IntHolder>({42}); buffer.for_each([](auto op) { static_assert(std::is_const_v<std::remove_reference_t<decltype(*op)>>, "Expected container to be const"); }); } TEST(OpBuffer, filterView) { MockBuffer buffer; buffer.push<Op::NoOp>({}); buffer.push<Op::IntHolder>({0}); buffer.push<Op::IntHolder>({1}); buffer.push<Op::NoOp>({}); buffer.push<Op::NoOp>({}); buffer.push<Op::IntHolder>({2}); buffer.push<Op::NoOp>({}); buffer.push<Op::NoOp>({}); buffer.push<Op::NoOp>({}); buffer.push<Op::NoOp>({}); int index = 0; for (const auto& it : buffer.filter<Op::IntHolder>()) { ASSERT_EQ(Op::IntHolder, it.header.type); EXPECT_EQ(index, it.impl.value); index++; } EXPECT_EQ(index, 3); int count = 0; for (const auto& it : buffer.filter<Op::NoOp>()) { ASSERT_EQ(Op::NoOp, it.header.type); count++; } EXPECT_EQ(count, 7); }