DL-next groundwork

Initial groundwork / tests for the op-forward displaylist

Test: hwuiunit --gtest_filter=CanvasOpBuffer.*
Change-Id: I6a09d9841c964a67fde8203b979de3fd3fbd2026
This commit is contained in:
John Reck
2020-10-29 20:53:51 -04:00
committed by Nader Jawad
parent e31e5987f9
commit 013127b1b5
11 changed files with 967 additions and 0 deletions

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

210
libs/hwui/canvas/OpBuffer.h Normal file
View File

@@ -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

View File

@@ -100,6 +100,12 @@ public:
void getSkBitmap(SkBitmap* outBitmap);
SkBitmap getSkBitmap() {
SkBitmap ret;
getSkBitmap(&ret);
return ret;
}
int getAshmemFd() const;
size_t getAllocationByteCount() const;

View File

@@ -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 */

View File

@@ -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());
}