DL-next groundwork
Initial groundwork / tests for the op-forward displaylist Test: hwuiunit --gtest_filter=CanvasOpBuffer.* Change-Id: I6a09d9841c964a67fde8203b979de3fd3fbd2026
This commit is contained in:
@@ -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",
|
||||
|
||||
25
libs/hwui/canvas/CanvasOpBuffer.cpp
Normal file
25
libs/hwui/canvas/CanvasOpBuffer.cpp
Normal 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
|
||||
60
libs/hwui/canvas/CanvasOpBuffer.h
Normal file
60
libs/hwui/canvas/CanvasOpBuffer.h
Normal 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
|
||||
50
libs/hwui/canvas/CanvasOpRasterizer.cpp
Normal file
50
libs/hwui/canvas/CanvasOpRasterizer.cpp
Normal 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
|
||||
64
libs/hwui/canvas/CanvasOpRasterizer.h
Normal file
64
libs/hwui/canvas/CanvasOpRasterizer.h
Normal 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
|
||||
46
libs/hwui/canvas/CanvasOpTypes.h
Normal file
46
libs/hwui/canvas/CanvasOpTypes.h
Normal 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
|
||||
126
libs/hwui/canvas/CanvasOps.h
Normal file
126
libs/hwui/canvas/CanvasOps.h
Normal 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
210
libs/hwui/canvas/OpBuffer.h
Normal 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
|
||||
@@ -100,6 +100,12 @@ public:
|
||||
|
||||
void getSkBitmap(SkBitmap* outBitmap);
|
||||
|
||||
SkBitmap getSkBitmap() {
|
||||
SkBitmap ret;
|
||||
getSkBitmap(&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int getAshmemFd() const;
|
||||
size_t getAllocationByteCount() const;
|
||||
|
||||
|
||||
168
libs/hwui/tests/common/CallCountingCanvas.h
Normal file
168
libs/hwui/tests/common/CallCountingCanvas.h
Normal 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 */
|
||||
209
libs/hwui/tests/unit/CanvasOpTests.cpp
Normal file
209
libs/hwui/tests/unit/CanvasOpTests.cpp
Normal 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());
|
||||
}
|
||||
Reference in New Issue
Block a user