diff options
author | 2020-10-29 20:53:51 -0400 | |
---|---|---|
committer | 2020-11-05 17:19:02 -0800 | |
commit | 013127b1b5d1ead2e926b9dbb13ac477eb8a8ae7 (patch) | |
tree | b08fcd9afc305ebae7265a86cc7954760ea08681 | |
parent | e31e5987f9de30efc2ab55aa5335ddff91d9840c (diff) |
DL-next groundwork
Initial groundwork / tests for the op-forward displaylist
Test: hwuiunit --gtest_filter=CanvasOpBuffer.*
Change-Id: I6a09d9841c964a67fde8203b979de3fd3fbd2026
-rw-r--r-- | libs/hwui/Android.bp | 3 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasOpBuffer.cpp | 25 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasOpBuffer.h | 60 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasOpRasterizer.cpp | 50 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasOpRasterizer.h | 64 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasOpTypes.h | 46 | ||||
-rw-r--r-- | libs/hwui/canvas/CanvasOps.h | 126 | ||||
-rw-r--r-- | libs/hwui/canvas/OpBuffer.h | 210 | ||||
-rw-r--r-- | libs/hwui/hwui/Bitmap.h | 6 | ||||
-rw-r--r-- | libs/hwui/tests/common/CallCountingCanvas.h | 168 | ||||
-rw-r--r-- | libs/hwui/tests/unit/CanvasOpTests.cpp | 209 |
11 files changed, 967 insertions, 0 deletions
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 155bb6ba8f75..4ed5457a2a7f 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -429,6 +429,8 @@ cc_defaults { whole_static_libs: ["libskia"], srcs: [ + "canvas/CanvasOpBuffer.cpp", + "canvas/CanvasOpRasterizer.cpp", "pipeline/skia/SkiaDisplayList.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", "pipeline/skia/RenderNodeDrawable.cpp", @@ -604,6 +606,7 @@ cc_test { "tests/unit/ABitmapTests.cpp", "tests/unit/CacheManagerTests.cpp", "tests/unit/CanvasContextTests.cpp", + "tests/unit/CanvasOpTests.cpp", "tests/unit/CommonPoolTests.cpp", "tests/unit/DamageAccumulatorTests.cpp", "tests/unit/DeferredLayerUpdaterTests.cpp", diff --git a/libs/hwui/canvas/CanvasOpBuffer.cpp b/libs/hwui/canvas/CanvasOpBuffer.cpp new file mode 100644 index 000000000000..7054e47eac89 --- /dev/null +++ b/libs/hwui/canvas/CanvasOpBuffer.cpp @@ -0,0 +1,25 @@ +/* + * 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 "CanvasOpBuffer.h" + +#include "CanvasOps.h" + +namespace android::uirenderer { + +template class OpBuffer<CanvasOpType, CanvasOpContainer>; + +} // namespace android::uirenderer diff --git a/libs/hwui/canvas/CanvasOpBuffer.h b/libs/hwui/canvas/CanvasOpBuffer.h new file mode 100644 index 000000000000..b80faeb1a65b --- /dev/null +++ b/libs/hwui/canvas/CanvasOpBuffer.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#pragma once + +#include <SkMatrix.h> + +#include "CanvasOpTypes.h" +#include "OpBuffer.h" + +namespace android::uirenderer { + +template <CanvasOpType T> +struct CanvasOp; + +template <CanvasOpType T> +class CanvasOpContainer { +private: + BE_OPBUFFERS_FRIEND(); + + OpBufferItemHeader<CanvasOpType> header; + // TODO: Figure out some magic to make this not be here when it's identity (or not used) + SkMatrix mTransform; + CanvasOp<T> mImpl; + +public: + CanvasOpContainer(CanvasOp<T>&& impl, const SkMatrix& transform = SkMatrix::I()) + : mTransform(transform), mImpl(std::move(impl)) {} + + uint32_t size() const { return header.size; } + CanvasOpType type() const { return header.type; } + + const SkMatrix& transform() const { return mTransform; } + + CanvasOp<T>* operator->() noexcept { return &mImpl; } +}; + +extern template class OpBuffer<CanvasOpType, CanvasOpContainer>; +class CanvasOpBuffer final : public OpBuffer<CanvasOpType, CanvasOpContainer> { +public: + template <CanvasOpType T> + void push(CanvasOp<T>&& op) { + push_container(CanvasOpContainer<T>(std::move(op))); + } +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/canvas/CanvasOpRasterizer.cpp b/libs/hwui/canvas/CanvasOpRasterizer.cpp new file mode 100644 index 000000000000..97c418a3e8d0 --- /dev/null +++ b/libs/hwui/canvas/CanvasOpRasterizer.cpp @@ -0,0 +1,50 @@ +/* + * 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 "CanvasOpRasterizer.h" + +#include <SkCanvas.h> +#include <log/log.h> + +#include <vector> + +#include "CanvasOpBuffer.h" +#include "CanvasOps.h" + +namespace android::uirenderer { + +void rasterizeCanvasBuffer(const CanvasOpBuffer& source, SkCanvas* destination) { + // Tracks the global transform from the current display list back toward the display space + // Push on beginning a RenderNode draw, pop on ending one + std::vector<SkMatrix> globalMatrixStack; + SkMatrix& currentGlobalTransform = globalMatrixStack.emplace_back(SkMatrix::I()); + + source.for_each([&]<CanvasOpType T>(CanvasOpContainer<T> * op) { + if constexpr (T == CanvasOpType::BeginZ || T == CanvasOpType::EndZ) { + // Do beginZ or endZ + LOG_ALWAYS_FATAL("TODO"); + return; + } else { + // Generic OP + // First apply the current transformation + destination->setMatrix(SkMatrix::Concat(currentGlobalTransform, op->transform())); + // Now draw it + (*op)->draw(destination); + } + }); +} + +} // namespace android::uirenderer diff --git a/libs/hwui/canvas/CanvasOpRasterizer.h b/libs/hwui/canvas/CanvasOpRasterizer.h new file mode 100644 index 000000000000..c2235ab84d56 --- /dev/null +++ b/libs/hwui/canvas/CanvasOpRasterizer.h @@ -0,0 +1,64 @@ +/* + * 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. + */ + +#pragma once + +#include <hwui/Bitmap.h> + +#include <SkBitmap.h> +#include <SkCanvas.h> + +#include "CanvasOps.h" + +#include <experimental/type_traits> +#include <variant> + +namespace android::uirenderer { + +class CanvasOpBuffer; + +void rasterizeCanvasBuffer(const CanvasOpBuffer& source, SkCanvas* destination); + +class ImmediateModeRasterizer { +public: + explicit ImmediateModeRasterizer(std::unique_ptr<SkCanvas>&& canvas) { + mCanvas = canvas.get(); + mOwnership = std::move(canvas); + } + + explicit ImmediateModeRasterizer(std::shared_ptr<SkCanvas> canvas) { + mCanvas = canvas.get(); + mOwnership = std::move(canvas); + } + + explicit ImmediateModeRasterizer(Bitmap& bitmap) { + mCanvas = &(mOwnership.emplace<SkCanvas>(bitmap.getSkBitmap())); + } + + template <CanvasOpType T> + void draw(const CanvasOp<T>& op) { + if constexpr (CanvasOpTraits::can_draw<CanvasOp<T>>) { + op.draw(mCanvas); + } + } + +private: + SkCanvas* mCanvas; + // Just here to keep mCanvas alive. Thankfully we never need to actually look inside this... + std::variant<SkCanvas, std::shared_ptr<SkCanvas>, std::unique_ptr<SkCanvas>> mOwnership; +}; + +} // namespace android::uirenderer diff --git a/libs/hwui/canvas/CanvasOpTypes.h b/libs/hwui/canvas/CanvasOpTypes.h new file mode 100644 index 000000000000..2d4f2f579717 --- /dev/null +++ b/libs/hwui/canvas/CanvasOpTypes.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#pragma once + +#include <inttypes.h> + +namespace android::uirenderer { + +enum class CanvasOpType : int8_t { + // State ops + // TODO: Eliminate the end ops by having the start include the end-at position + Save, + SaveLayer, + SaveBehind, + Restore, + BeginZ, + EndZ, + + // Clip ops + ClipRect, + ClipPath, + + // Drawing ops + DrawColor, + DrawRect, + + // TODO: Rest + + COUNT // must be last +}; + +} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/canvas/CanvasOps.h b/libs/hwui/canvas/CanvasOps.h new file mode 100644 index 000000000000..a31a91c35268 --- /dev/null +++ b/libs/hwui/canvas/CanvasOps.h @@ -0,0 +1,126 @@ +/* + * 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. + */ + +#pragma once + +#include <SkAndroidFrameworkUtils.h> +#include <SkCanvas.h> +#include <SkPath.h> +#include <log/log.h> + +#include "CanvasOpTypes.h" + +#include <experimental/type_traits> + +namespace android::uirenderer { + +template <CanvasOpType T> +struct CanvasOp; + +struct CanvasOpTraits { + CanvasOpTraits() = delete; + + template<class T> + using draw_t = decltype(std::integral_constant<void (T::*)(SkCanvas*) const, &T::draw>{}); + + template <class T> + static constexpr bool can_draw = std::experimental::is_detected_v<draw_t, T>; +}; + +#define ASSERT_DRAWABLE() private: constexpr void _check_drawable() \ + { static_assert(CanvasOpTraits::can_draw<std::decay_t<decltype(*this)>>); } + +// ---------------------------------------------- +// State Ops +// --------------------------------------------- + +template <> +struct CanvasOp<CanvasOpType::Save> { + void draw(SkCanvas* canvas) const { canvas->save(); } + ASSERT_DRAWABLE() +}; + +template <> +struct CanvasOp<CanvasOpType::SaveLayer> { + SkCanvas::SaveLayerRec saveLayerRec; + void draw(SkCanvas* canvas) const { canvas->saveLayer(saveLayerRec); } + ASSERT_DRAWABLE() +}; + +template <> +struct CanvasOp<CanvasOpType::SaveBehind> { + SkRect bounds; + void draw(SkCanvas* canvas) const { SkAndroidFrameworkUtils::SaveBehind(canvas, &bounds); } + ASSERT_DRAWABLE() +}; + +template <> +struct CanvasOp<CanvasOpType::Restore> { + void draw(SkCanvas* canvas) const { canvas->restore(); } + ASSERT_DRAWABLE() +}; + +template <> +struct CanvasOp<CanvasOpType::BeginZ> { +}; +template <> +struct CanvasOp<CanvasOpType::EndZ> {}; + +// ---------------------------------------------- +// Clip Ops +// --------------------------------------------- + +template <> +struct CanvasOp<CanvasOpType::ClipRect> { + SkRect rect; + SkClipOp clipOp; + void draw(SkCanvas* canvas) const { canvas->clipRect(rect, clipOp); } + ASSERT_DRAWABLE() +}; + +template <> +struct CanvasOp<CanvasOpType::ClipPath> { + SkPath path; + SkClipOp op; + void draw(SkCanvas* canvas) const { canvas->clipPath(path, op, true); } + ASSERT_DRAWABLE() +}; + +// ---------------------------------------------- +// Drawing Ops +// --------------------------------------------- + +template <> +struct CanvasOp<CanvasOpType::DrawColor> { + SkColor4f color; + SkBlendMode mode; + void draw(SkCanvas* canvas) const { canvas->drawColor(color, mode); } + ASSERT_DRAWABLE() +}; + +template <> +struct CanvasOp<CanvasOpType::DrawRect> { + SkRect rect; + SkPaint paint; + void draw(SkCanvas* canvas) const { canvas->drawRect(rect, paint); } + ASSERT_DRAWABLE() +}; + + +// cleanup our macros +#undef ASSERT_DRAWABLE + +} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/canvas/OpBuffer.h b/libs/hwui/canvas/OpBuffer.h new file mode 100644 index 000000000000..398e090b8cfa --- /dev/null +++ b/libs/hwui/canvas/OpBuffer.h @@ -0,0 +1,210 @@ +/* + * 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. + */ + +#pragma once + +#include <algorithm> +#include <array> +#include <cinttypes> +#include <cstddef> +#include <cstdlib> +#include <type_traits> +#include <utility> + +namespace android::uirenderer { + +template <typename T> +struct OpBufferItemHeader { + T type : 8; + uint32_t size : 24; +}; + +struct OpBufferAllocationHeader { + // Used size, including header size + size_t used = 0; + // Capacity, including header size + size_t capacity = 0; + // Offset relative to `this` at which the first item is + size_t startOffset = 0; + // Offset relative to `this` at which the last item is + size_t endOffset = 0; +}; + +#define BE_OPBUFFERS_FRIEND() \ + template <typename ItemTypes, template <ItemTypes> typename, typename, typename> \ + friend class OpBuffer + +template <typename ItemTypes, template <ItemTypes> typename ItemContainer, + typename BufferHeader = OpBufferAllocationHeader, + typename ItemTypesSequence = std::make_index_sequence<static_cast<int>(ItemTypes::COUNT)>> +class OpBuffer { + // Instead of re-aligning individual inserts, just pad the size of everything + // to a multiple of pointer alignment. This assumes we never work with doubles. + // Which we don't. + static constexpr size_t Alignment = alignof(void*); + + static constexpr size_t PadAlign(size_t size) { + return (size + (Alignment - 1)) & -Alignment; + } + + static constexpr auto STARTING_SIZE = PadAlign(sizeof(BufferHeader)); + +public: + using ItemHeader = OpBufferItemHeader<ItemTypes>; + + OpBuffer() = default; + + // Prevent copying by default + OpBuffer(const OpBuffer&) = delete; + void operator=(const OpBuffer&) = delete; + + OpBuffer(OpBuffer&& other) { + mBuffer = other.mBuffer; + other.mBuffer = nullptr; + } + + void operator=(OpBuffer&& other) { + destroy(); + mBuffer = other.mBuffer; + other.mBuffer = nullptr; + } + + ~OpBuffer() { + destroy(); + } + + constexpr size_t capacity() const { return mBuffer ? mBuffer->capacity : 0; } + + constexpr size_t size() const { return mBuffer ? mBuffer->used : 0; } + + constexpr size_t remaining() const { return capacity() - size(); } + + // TODO: Add less-copy'ing variants of this. emplace_back? deferred initialization? + template <ItemTypes T> + void push_container(ItemContainer<T>&& op) { + static_assert(alignof(ItemContainer<T>) <= Alignment); + static_assert(offsetof(ItemContainer<T>, header) == 0); + + constexpr auto padded_size = PadAlign(sizeof(ItemContainer<T>)); + if (remaining() < padded_size) { + resize(std::max(padded_size, capacity()) * 2); + } + mBuffer->endOffset = mBuffer->used; + mBuffer->used += padded_size; + + void* allocateAt = reinterpret_cast<uint8_t*>(mBuffer) + mBuffer->endOffset; + auto temp = new (allocateAt) ItemContainer<T>{std::move(op)}; + temp->header = {.type = T, .size = padded_size}; + } + + void resize(size_t newsize) { + // Add the header size to newsize + const size_t adjustedSize = newsize + STARTING_SIZE; + + if (adjustedSize < size()) { + // todo: throw? + return; + } + if (newsize == 0) { + free(mBuffer); + mBuffer = nullptr; + } else { + if (mBuffer) { + mBuffer = reinterpret_cast<BufferHeader*>(realloc(mBuffer, adjustedSize)); + mBuffer->capacity = adjustedSize; + } else { + mBuffer = new (malloc(adjustedSize)) BufferHeader(); + mBuffer->capacity = adjustedSize; + mBuffer->used = STARTING_SIZE; + mBuffer->startOffset = STARTING_SIZE; + } + } + } + + template <typename F> + void for_each(F&& f) const { + for_each(std::forward<F>(f), ItemTypesSequence{}); + } + + void clear(); + + ItemHeader* first() const { return isEmpty() ? nullptr : itemAt(mBuffer->startOffset); } + + ItemHeader* last() const { return isEmpty() ? nullptr : itemAt(mBuffer->endOffset); } + +private: + template <typename F, std::size_t... I> + void for_each(F&& f, std::index_sequence<I...>) const { + // Validate we're not empty + if (isEmpty()) return; + + // Setup the jump table, mapping from each type to a springboard that invokes the template + // function with the appropriate concrete type + using F_PTR = decltype(&f); + using THUNK = void (*)(F_PTR, void*); + static constexpr auto jump = std::array<THUNK, sizeof...(I)>{[](F_PTR fp, void* t) { + (*fp)(reinterpret_cast<ItemContainer<static_cast<ItemTypes>(I)>*>(t)); + }...}; + + // 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; + 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 + // `f` + auto it = (void*)current; + current += header->size; + jump[static_cast<int>(header->type)](&f, it); + } + } + + void destroy() { + clear(); + resize(0); + } + + bool offsetIsValid(size_t offset) const { + return offset >= mBuffer->startOffset && offset < mBuffer->used; + } + + ItemHeader* itemAt(size_t offset) const { + if (!offsetIsValid(offset)) return nullptr; + return reinterpret_cast<ItemHeader*>(reinterpret_cast<uint8_t*>(mBuffer) + offset); + } + + bool isEmpty() const { return mBuffer == nullptr || mBuffer->used == STARTING_SIZE; } + + BufferHeader* mBuffer = nullptr; +}; + +template <typename ItemTypes, template <ItemTypes> typename ItemContainer, typename BufferHeader, + typename ItemTypeSequence> +void OpBuffer<ItemTypes, ItemContainer, BufferHeader, ItemTypeSequence>::clear() { + + // Don't need to do anything if we don't have a buffer + if (!mBuffer) return; + + for_each([](auto op) { + using T = std::remove_reference_t<decltype(*op)>; + op->~T(); + }); + mBuffer->used = STARTING_SIZE; + mBuffer->startOffset = STARTING_SIZE; + mBuffer->endOffset = 0; +} + +} // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 6ece7ef9f329..94a047c06ced 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -100,6 +100,12 @@ public: void getSkBitmap(SkBitmap* outBitmap); + SkBitmap getSkBitmap() { + SkBitmap ret; + getSkBitmap(&ret); + return ret; + } + int getAshmemFd() const; size_t getAllocationByteCount() const; diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h new file mode 100644 index 000000000000..a96557141ef5 --- /dev/null +++ b/libs/hwui/tests/common/CallCountingCanvas.h @@ -0,0 +1,168 @@ +/* + * 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. + */ + +#pragma once + +#include <SkCanvasVirtualEnforcer.h> +#include <SkNoDrawCanvas.h> + +namespace android { +namespace uirenderer { +namespace test { + +class CallCountingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> { +private: + int START_MARKER; +public: + CallCountingCanvas() : SkCanvasVirtualEnforcer<SkNoDrawCanvas>(1, 1) {} + + int sumTotalDrawCalls() { + // Dirty hack assumes we're nothing but ints between START_MARKET and END_MARKER + int* cur = &START_MARKER + 1; + int* end = &END_MARKER; + int sum = 0; + while (cur != end) { + sum += *cur; + cur++; + } + return sum; + } + + int drawPaintCount = 0; + void onDrawPaint(const SkPaint& paint) override { + drawPaintCount++; + } + + int drawBehindCount = 0; + void onDrawBehind(const SkPaint&) override { + drawBehindCount++; + } + + int drawRectCount = 0; + void onDrawRect(const SkRect& rect, const SkPaint& paint) override { + drawRectCount++; + } + + int drawRRectCount = 0; + void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override { + drawRRectCount++; + } + + int drawDRRectCount = 0; + void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) override { + drawDRRectCount++; + } + + int drawOvalCount = 0; + void onDrawOval(const SkRect& rect, const SkPaint& paint) override { + drawOvalCount++; + } + + int drawArcCount = 0; + void onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, + const SkPaint& paint) override { + drawArcCount++; + } + + int drawPathCount = 0; + void onDrawPath(const SkPath& path, const SkPaint& paint) override { + drawPaintCount++; + } + + int drawRegionCount = 0; + void onDrawRegion(const SkRegion& region, const SkPaint& paint) override { + drawRegionCount++; + } + + int drawTextBlobCount = 0; + void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) override { + drawTextBlobCount++; + } + + int drawPatchCount = 0; + void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode mode, + const SkPaint& paint) override { + drawPatchCount++; + } + + int drawPoints = 0; + void onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) override { + drawPoints++; + } + + int drawImageCount = 0; + void onDrawImage(const SkImage* image, SkScalar dx, SkScalar dy, + const SkPaint* paint) override { + drawImageCount++; + } + + int drawImageRectCount = 0; + void onDrawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SkCanvas::SrcRectConstraint constraint) override { + drawImageRectCount++; + } + + int drawImageNineCount = 0; + void onDrawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) override { + drawImageNineCount++; + } + + int drawImageLatticeCount = 0; + void onDrawImageLattice(const SkImage* image, const SkCanvas::Lattice& lattice, + const SkRect& dst, const SkPaint* paint) override { + drawImageLatticeCount++; + } + + int drawAtlasCount = 0; + void onDrawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect rect[], + const SkColor colors[], int count, SkBlendMode mode, const SkRect* cull, + const SkPaint* paint) override { + drawAtlasCount++; + } + + int drawAnnotationCount = 0; + void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override { + drawAnnotationCount++; + } + + int drawShadowRecCount = 0; + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override { + drawShadowRecCount++; + } + + int drawDrawableCount = 0; + void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { + drawDrawableCount++; + } + + int drawPictureCount = 0; + void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, + const SkPaint* paint) override { + drawPictureCount++; + } + +private: + int END_MARKER; +}; + +} /* namespace test */ +} /* namespace uirenderer */ +} /* namespace android */
\ No newline at end of file diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp new file mode 100644 index 000000000000..0815d15d29f2 --- /dev/null +++ b/libs/hwui/tests/unit/CanvasOpTests.cpp @@ -0,0 +1,209 @@ +/* + * 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/CanvasOpBuffer.h> +#include <canvas/CanvasOps.h> +#include <canvas/CanvasOpRasterizer.h> + +#include <tests/common/CallCountingCanvas.h> + +using namespace android; +using namespace android::uirenderer; +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>; + +template<typename T> +static int countItems(const T& t) { + int count = 0; + t.for_each([&](auto i) { + count++; + }); + 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, simplePush) { + CanvasOpBuffer buffer; + EXPECT_EQ(buffer.size(), 0); + buffer.push<Op::Save>({}); + buffer.push<Op::Save>({}); + buffer.push<Op::Restore>({}); + EXPECT_GT(buffer.size(), 0); + + int saveCount = 0; + int restoreCount = 0; + int otherCount = 0; + + buffer.for_each([&](auto op) { + switch (op->type()) { + case Op::Save: + saveCount++; + break; + case Op::Restore: + restoreCount++; + break; + default: + otherCount++; + break; + } + }); + + EXPECT_EQ(saveCount, 2); + EXPECT_EQ(restoreCount, 1); + EXPECT_EQ(otherCount, 0); + + buffer.clear(); + int itemCount = 0; + buffer.for_each([&](auto op) { + itemCount++; + }); + EXPECT_EQ(itemCount, 0); + buffer.resize(0); + EXPECT_EQ(buffer.size(), 0); +} + +TEST(CanvasOp, simpleDrawRect) { + CanvasOpBuffer buffer; + EXPECT_EQ(buffer.size(), 0); + buffer.push(CanvasOp<Op::DrawRect> { + .paint = SkPaint{}, + .rect = SkRect::MakeEmpty() + }); + + CallCountingCanvas canvas; + EXPECT_EQ(0, canvas.sumTotalDrawCalls()); + rasterizeCanvasBuffer(buffer, &canvas); + EXPECT_EQ(1, canvas.drawRectCount); + EXPECT_EQ(1, canvas.sumTotalDrawCalls()); +} + +TEST(CanvasOp, immediateRendering) { + auto canvas = std::make_shared<CallCountingCanvas>(); + + EXPECT_EQ(0, canvas->sumTotalDrawCalls()); + ImmediateModeRasterizer rasterizer{canvas}; + auto op = CanvasOp<Op::DrawRect> { + .paint = SkPaint{}, + .rect = SkRect::MakeEmpty() + }; + EXPECT_TRUE(CanvasOpTraits::can_draw<decltype(op)>); + rasterizer.draw(op); + EXPECT_EQ(1, canvas->drawRectCount); + EXPECT_EQ(1, canvas->sumTotalDrawCalls()); +}
\ No newline at end of file |