Reland "[DisplayList] Allow random access to ops through indexing" (flutter/engine#54676)
Now also fixes: https://github.com/flutter/flutter/issues/153737 Being able to reorder rendering commands leads to optimization opportunities in the graphics package. A graphics package being fed from a DisplayList either has to take the commands in the order given or implement their own storage format for the rendering data. With this new dispatching mechanism, the graphics package can both query basic information about the recorded ops and even dispatch them by the index into the list. Query information includes either the "category" of the op (clip/transform/render, etc.) or a specific op type enum. The package can dispatch some categories (or ops) immediately and remember other categories (or ops) along with their state for dispatching later.
This commit is contained in:
parent
606b1fc4bf
commit
28ee6cf0bb
@ -220,6 +220,60 @@ static void BM_DisplayListDispatchDefault(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void BM_DisplayListDispatchByIndexDefault(
|
||||||
|
benchmark::State& state,
|
||||||
|
DisplayListDispatchBenchmarkType type) {
|
||||||
|
bool prepare_rtree = NeedPrepareRTree(type);
|
||||||
|
DisplayListBuilder builder(prepare_rtree);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
InvokeAllOps(builder);
|
||||||
|
}
|
||||||
|
auto display_list = builder.Build();
|
||||||
|
DlOpReceiverIgnore receiver;
|
||||||
|
while (state.KeepRunning()) {
|
||||||
|
DlIndex end = display_list->GetRecordCount();
|
||||||
|
for (DlIndex i = 0u; i < end; i++) {
|
||||||
|
display_list->Dispatch(receiver, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BM_DisplayListDispatchByIteratorDefault(
|
||||||
|
benchmark::State& state,
|
||||||
|
DisplayListDispatchBenchmarkType type) {
|
||||||
|
bool prepare_rtree = NeedPrepareRTree(type);
|
||||||
|
DisplayListBuilder builder(prepare_rtree);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
InvokeAllOps(builder);
|
||||||
|
}
|
||||||
|
auto display_list = builder.Build();
|
||||||
|
DlOpReceiverIgnore receiver;
|
||||||
|
while (state.KeepRunning()) {
|
||||||
|
for (DlIndex i : *display_list) {
|
||||||
|
display_list->Dispatch(receiver, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void BM_DisplayListDispatchByVectorDefault(
|
||||||
|
benchmark::State& state,
|
||||||
|
DisplayListDispatchBenchmarkType type) {
|
||||||
|
bool prepare_rtree = NeedPrepareRTree(type);
|
||||||
|
DisplayListBuilder builder(prepare_rtree);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
InvokeAllOps(builder);
|
||||||
|
}
|
||||||
|
auto display_list = builder.Build();
|
||||||
|
DlOpReceiverIgnore receiver;
|
||||||
|
while (state.KeepRunning()) {
|
||||||
|
std::vector<DlIndex> indices =
|
||||||
|
display_list->GetCulledIndices(display_list->bounds());
|
||||||
|
for (DlIndex index : indices) {
|
||||||
|
display_list->Dispatch(receiver, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void BM_DisplayListDispatchCull(benchmark::State& state,
|
static void BM_DisplayListDispatchCull(benchmark::State& state,
|
||||||
DisplayListDispatchBenchmarkType type) {
|
DisplayListDispatchBenchmarkType type) {
|
||||||
bool prepare_rtree = NeedPrepareRTree(type);
|
bool prepare_rtree = NeedPrepareRTree(type);
|
||||||
@ -236,6 +290,26 @@ static void BM_DisplayListDispatchCull(benchmark::State& state,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void BM_DisplayListDispatchByVectorCull(
|
||||||
|
benchmark::State& state,
|
||||||
|
DisplayListDispatchBenchmarkType type) {
|
||||||
|
bool prepare_rtree = NeedPrepareRTree(type);
|
||||||
|
DisplayListBuilder builder(prepare_rtree);
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
InvokeAllOps(builder);
|
||||||
|
}
|
||||||
|
auto display_list = builder.Build();
|
||||||
|
SkRect rect = SkRect::MakeLTRB(0, 0, 100, 100);
|
||||||
|
EXPECT_FALSE(rect.contains(display_list->bounds()));
|
||||||
|
DlOpReceiverIgnore receiver;
|
||||||
|
while (state.KeepRunning()) {
|
||||||
|
std::vector<DlIndex> indices = display_list->GetCulledIndices(rect);
|
||||||
|
for (DlIndex index : indices) {
|
||||||
|
display_list->Dispatch(receiver, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BENCHMARK_CAPTURE(BM_DisplayListBuilderDefault,
|
BENCHMARK_CAPTURE(BM_DisplayListBuilderDefault,
|
||||||
kDefault,
|
kDefault,
|
||||||
DisplayListBuilderBenchmarkType::kDefault)
|
DisplayListBuilderBenchmarkType::kDefault)
|
||||||
@ -370,4 +444,24 @@ BENCHMARK_CAPTURE(BM_DisplayListDispatchCull,
|
|||||||
DisplayListDispatchBenchmarkType::kCulledWithRtree)
|
DisplayListDispatchBenchmarkType::kCulledWithRtree)
|
||||||
->Unit(benchmark::kMicrosecond);
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
|
BENCHMARK_CAPTURE(BM_DisplayListDispatchByIndexDefault,
|
||||||
|
kDefaultNoRtree,
|
||||||
|
DisplayListDispatchBenchmarkType::kDefaultNoRtree)
|
||||||
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
|
BENCHMARK_CAPTURE(BM_DisplayListDispatchByIteratorDefault,
|
||||||
|
kDefaultNoRtree,
|
||||||
|
DisplayListDispatchBenchmarkType::kDefaultNoRtree)
|
||||||
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
|
BENCHMARK_CAPTURE(BM_DisplayListDispatchByVectorDefault,
|
||||||
|
kDefaultNoRtree,
|
||||||
|
DisplayListDispatchBenchmarkType::kDefaultNoRtree)
|
||||||
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
|
BENCHMARK_CAPTURE(BM_DisplayListDispatchByVectorCull,
|
||||||
|
kCulledWithRtree,
|
||||||
|
DisplayListDispatchBenchmarkType::kCulledWithRtree)
|
||||||
|
->Unit(benchmark::kMicrosecond);
|
||||||
|
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
@ -29,6 +29,21 @@ DisplayList::DisplayList()
|
|||||||
root_is_unbounded_(false),
|
root_is_unbounded_(false),
|
||||||
max_root_blend_mode_(DlBlendMode::kClear) {}
|
max_root_blend_mode_(DlBlendMode::kClear) {}
|
||||||
|
|
||||||
|
// Eventually we should rework DisplayListBuilder to compute these and
|
||||||
|
// deliver the vector alongside the storage.
|
||||||
|
static std::vector<size_t> MakeOffsets(const DisplayListStorage& storage,
|
||||||
|
size_t byte_count) {
|
||||||
|
std::vector<size_t> offsets;
|
||||||
|
const uint8_t* start = storage.get();
|
||||||
|
const uint8_t* end = start + byte_count;
|
||||||
|
const uint8_t* ptr = start;
|
||||||
|
while (ptr < end) {
|
||||||
|
offsets.push_back(ptr - start);
|
||||||
|
ptr += reinterpret_cast<const DLOp*>(ptr)->size;
|
||||||
|
}
|
||||||
|
return offsets;
|
||||||
|
}
|
||||||
|
|
||||||
DisplayList::DisplayList(DisplayListStorage&& storage,
|
DisplayList::DisplayList(DisplayListStorage&& storage,
|
||||||
size_t byte_count,
|
size_t byte_count,
|
||||||
uint32_t op_count,
|
uint32_t op_count,
|
||||||
@ -44,6 +59,7 @@ DisplayList::DisplayList(DisplayListStorage&& storage,
|
|||||||
bool root_is_unbounded,
|
bool root_is_unbounded,
|
||||||
sk_sp<const DlRTree> rtree)
|
sk_sp<const DlRTree> rtree)
|
||||||
: storage_(std::move(storage)),
|
: storage_(std::move(storage)),
|
||||||
|
offsets_(MakeOffsets(storage_, byte_count)),
|
||||||
byte_count_(byte_count),
|
byte_count_(byte_count),
|
||||||
op_count_(op_count),
|
op_count_(op_count),
|
||||||
nested_byte_count_(nested_byte_count),
|
nested_byte_count_(nested_byte_count),
|
||||||
@ -73,86 +89,114 @@ uint32_t DisplayList::next_unique_id() {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Culler {
|
struct SaveInfo {
|
||||||
public:
|
SaveInfo(DlIndex previous_restore_index, bool save_was_needed)
|
||||||
virtual ~Culler() = default;
|
: previous_restore_index(previous_restore_index),
|
||||||
virtual bool init(DispatchContext& context) = 0;
|
save_was_needed(save_was_needed) {}
|
||||||
virtual void update(DispatchContext& context) = 0;
|
|
||||||
|
DlIndex previous_restore_index;
|
||||||
|
bool save_was_needed;
|
||||||
};
|
};
|
||||||
class NopCuller final : public Culler {
|
|
||||||
public:
|
|
||||||
static NopCuller instance;
|
|
||||||
|
|
||||||
~NopCuller() = default;
|
void DisplayList::RTreeResultsToIndexVector(
|
||||||
|
std::vector<DlIndex>& indices,
|
||||||
bool init(DispatchContext& context) override {
|
const std::vector<int>& rtree_results) const {
|
||||||
// Setting next_render_index to 0 means that
|
FML_DCHECK(rtree_);
|
||||||
// all rendering ops will be at or after that
|
auto cur_rect = rtree_results.begin();
|
||||||
// index so they will execute and all restore
|
auto end_rect = rtree_results.end();
|
||||||
// indices will be after it as well so all
|
if (cur_rect >= end_rect) {
|
||||||
// clip and transform operations will execute.
|
return;
|
||||||
context.next_render_index = 0;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
void update(DispatchContext& context) override {}
|
DlIndex next_render_index = rtree_->id(*cur_rect++);
|
||||||
};
|
DlIndex next_restore_index = std::numeric_limits<DlIndex>::max();
|
||||||
NopCuller NopCuller::instance = NopCuller();
|
std::vector<SaveInfo> save_infos;
|
||||||
class VectorCuller final : public Culler {
|
for (DlIndex index = 0u; index < offsets_.size(); index++) {
|
||||||
public:
|
while (index > next_render_index) {
|
||||||
VectorCuller(const DlRTree* rtree, const std::vector<int>& rect_indices)
|
if (cur_rect < end_rect) {
|
||||||
: rtree_(rtree), cur_(rect_indices.begin()), end_(rect_indices.end()) {}
|
next_render_index = rtree_->id(*cur_rect++);
|
||||||
|
} else {
|
||||||
~VectorCuller() = default;
|
// Nothing left to render.
|
||||||
|
// Nothing left to do, but match our restores from the stack.
|
||||||
bool init(DispatchContext& context) override {
|
while (!save_infos.empty()) {
|
||||||
if (cur_ < end_) {
|
SaveInfo& info = save_infos.back();
|
||||||
context.next_render_index = rtree_->id(*cur_++);
|
// stack top boolean tells us whether the local variable
|
||||||
return true;
|
// next_restore_index should be executed. The local variable
|
||||||
} else {
|
// then gets reset to the value stored in the stack top
|
||||||
// Setting next_render_index to MAX_INT means that
|
if (info.save_was_needed) {
|
||||||
// all rendering ops will be "before" that index and
|
FML_DCHECK(next_restore_index < offsets_.size());
|
||||||
// they will skip themselves and all clip and transform
|
indices.push_back(next_restore_index);
|
||||||
// ops will see that the next render index is not
|
}
|
||||||
// before the next restore index (even if both are MAX_INT)
|
next_restore_index = info.previous_restore_index;
|
||||||
// and so they will also not execute.
|
save_infos.pop_back();
|
||||||
// None of this really matters because returning false
|
|
||||||
// here should cause the Dispatch operation to abort,
|
|
||||||
// but this value is conceptually correct if that short
|
|
||||||
// circuit optimization isn't used.
|
|
||||||
context.next_render_index = std::numeric_limits<int>::max();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void update(DispatchContext& context) override {
|
|
||||||
if (++context.cur_index > context.next_render_index) {
|
|
||||||
while (cur_ < end_) {
|
|
||||||
context.next_render_index = rtree_->id(*cur_++);
|
|
||||||
if (context.next_render_index >= context.cur_index) {
|
|
||||||
// It should be rare that we have duplicate indices
|
|
||||||
// but if we do, then having a while loop is a cheap
|
|
||||||
// insurance for those cases.
|
|
||||||
// The main cause of duplicate indices is when a
|
|
||||||
// DrawDisplayListOp was added to this DisplayList and
|
|
||||||
// both are computing an R-Tree, in which case the
|
|
||||||
// builder method will forward all of the child
|
|
||||||
// DisplayList's rects to this R-Tree with the same
|
|
||||||
// op_index.
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
context.next_render_index = std::numeric_limits<int>::max();
|
}
|
||||||
|
const uint8_t* ptr = storage_.get() + offsets_[index];
|
||||||
|
const DLOp* op = reinterpret_cast<const DLOp*>(ptr);
|
||||||
|
switch (GetOpCategory(op->type)) {
|
||||||
|
case DisplayListOpCategory::kAttribute:
|
||||||
|
// Attributes are always needed
|
||||||
|
indices.push_back(index);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DisplayListOpCategory::kTransform:
|
||||||
|
case DisplayListOpCategory::kClip:
|
||||||
|
if (next_render_index < next_restore_index) {
|
||||||
|
indices.push_back(index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DisplayListOpCategory::kRendering:
|
||||||
|
case DisplayListOpCategory::kSubDisplayList:
|
||||||
|
if (index == next_render_index) {
|
||||||
|
indices.push_back(index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DisplayListOpCategory::kSave:
|
||||||
|
case DisplayListOpCategory::kSaveLayer: {
|
||||||
|
bool needed = (next_render_index < next_restore_index);
|
||||||
|
save_infos.emplace_back(next_restore_index, needed);
|
||||||
|
switch (op->type) {
|
||||||
|
case DisplayListOpType::kSave:
|
||||||
|
case DisplayListOpType::kSaveLayer:
|
||||||
|
case DisplayListOpType::kSaveLayerBackdrop:
|
||||||
|
next_restore_index =
|
||||||
|
static_cast<const SaveOpBase*>(op)->restore_index;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
FML_UNREACHABLE();
|
||||||
|
}
|
||||||
|
if (needed) {
|
||||||
|
indices.push_back(index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DisplayListOpCategory::kRestore: {
|
||||||
|
FML_DCHECK(!save_infos.empty());
|
||||||
|
FML_DCHECK(index == next_restore_index);
|
||||||
|
SaveInfo& info = save_infos.back();
|
||||||
|
next_restore_index = info.previous_restore_index;
|
||||||
|
if (info.save_was_needed) {
|
||||||
|
indices.push_back(index);
|
||||||
|
}
|
||||||
|
save_infos.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DisplayListOpCategory::kInvalidCategory:
|
||||||
|
FML_UNREACHABLE();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private:
|
|
||||||
const DlRTree* rtree_;
|
|
||||||
std::vector<int>::const_iterator cur_;
|
|
||||||
std::vector<int>::const_iterator end_;
|
|
||||||
};
|
|
||||||
|
|
||||||
void DisplayList::Dispatch(DlOpReceiver& receiver) const {
|
void DisplayList::Dispatch(DlOpReceiver& receiver) const {
|
||||||
const uint8_t* ptr = storage_.get();
|
const uint8_t* base = storage_.get();
|
||||||
Dispatch(receiver, ptr, ptr + byte_count_, NopCuller::instance);
|
for (size_t offset : offsets_) {
|
||||||
|
DispatchOneOp(receiver, base + offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayList::Dispatch(DlOpReceiver& receiver,
|
void DisplayList::Dispatch(DlOpReceiver& receiver,
|
||||||
@ -167,52 +211,36 @@ void DisplayList::Dispatch(DlOpReceiver& receiver,
|
|||||||
}
|
}
|
||||||
if (!has_rtree() || cull_rect.contains(bounds())) {
|
if (!has_rtree() || cull_rect.contains(bounds())) {
|
||||||
Dispatch(receiver);
|
Dispatch(receiver);
|
||||||
return;
|
} else {
|
||||||
|
auto op_indices = GetCulledIndices(cull_rect);
|
||||||
|
const uint8_t* base = storage_.get();
|
||||||
|
for (DlIndex index : op_indices) {
|
||||||
|
DispatchOneOp(receiver, base + offsets_[index]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const DlRTree* rtree = this->rtree().get();
|
|
||||||
FML_DCHECK(rtree != nullptr);
|
|
||||||
const uint8_t* ptr = storage_.get();
|
|
||||||
std::vector<int> rect_indices;
|
|
||||||
rtree->search(cull_rect, &rect_indices);
|
|
||||||
VectorCuller culler(rtree, rect_indices);
|
|
||||||
Dispatch(receiver, ptr, ptr + byte_count_, culler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayList::Dispatch(DlOpReceiver& receiver,
|
void DisplayList::DispatchOneOp(DlOpReceiver& receiver,
|
||||||
const uint8_t* ptr,
|
const uint8_t* ptr) const {
|
||||||
const uint8_t* end,
|
auto op = reinterpret_cast<const DLOp*>(ptr);
|
||||||
Culler& culler) const {
|
switch (op->type) {
|
||||||
DispatchContext context = {
|
#define DL_OP_DISPATCH(name) \
|
||||||
.receiver = receiver,
|
case DisplayListOpType::k##name: \
|
||||||
.cur_index = 0,
|
static_cast<const name##Op*>(op)->dispatch(receiver); \
|
||||||
// next_render_index will be initialized by culler.init()
|
|
||||||
.next_restore_index = std::numeric_limits<int>::max(),
|
|
||||||
};
|
|
||||||
if (!culler.init(context)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (ptr < end) {
|
|
||||||
auto op = reinterpret_cast<const DLOp*>(ptr);
|
|
||||||
ptr += op->size;
|
|
||||||
FML_DCHECK(ptr <= end);
|
|
||||||
switch (op->type) {
|
|
||||||
#define DL_OP_DISPATCH(name) \
|
|
||||||
case DisplayListOpType::k##name: \
|
|
||||||
static_cast<const name##Op*>(op)->dispatch(context); \
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPATCH)
|
FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPATCH)
|
||||||
|
|
||||||
#ifdef IMPELLER_ENABLE_3D
|
#ifdef IMPELLER_ENABLE_3D
|
||||||
DL_OP_DISPATCH(SetSceneColorSource)
|
DL_OP_DISPATCH(SetSceneColorSource)
|
||||||
#endif // IMPELLER_ENABLE_3D
|
#endif // IMPELLER_ENABLE_3D
|
||||||
|
|
||||||
#undef DL_OP_DISPATCH
|
#undef DL_OP_DISPATCH
|
||||||
|
|
||||||
default:
|
case DisplayListOpType::kInvalidOp:
|
||||||
FML_DCHECK(false);
|
default:
|
||||||
return;
|
FML_DCHECK(false) << "Unrecognized op type: "
|
||||||
}
|
<< static_cast<int>(op->type);
|
||||||
culler.update(context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +258,7 @@ void DisplayList::DisposeOps(const uint8_t* ptr, const uint8_t* end) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPOSE)
|
FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPOSE)
|
||||||
|
|
||||||
#ifdef IMPELLER_ENABLE_3D
|
#ifdef IMPELLER_ENABLE_3D
|
||||||
DL_OP_DISPOSE(SetSceneColorSource)
|
DL_OP_DISPOSE(SetSceneColorSource)
|
||||||
#endif // IMPELLER_ENABLE_3D
|
#endif // IMPELLER_ENABLE_3D
|
||||||
@ -242,6 +271,154 @@ void DisplayList::DisposeOps(const uint8_t* ptr, const uint8_t* end) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisplayListOpCategory DisplayList::GetOpCategory(DlIndex index) const {
|
||||||
|
return GetOpCategory(GetOpType(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayListOpCategory DisplayList::GetOpCategory(DisplayListOpType type) {
|
||||||
|
switch (type) {
|
||||||
|
case DisplayListOpType::kSetAntiAlias:
|
||||||
|
case DisplayListOpType::kSetInvertColors:
|
||||||
|
case DisplayListOpType::kSetStrokeCap:
|
||||||
|
case DisplayListOpType::kSetStrokeJoin:
|
||||||
|
case DisplayListOpType::kSetStyle:
|
||||||
|
case DisplayListOpType::kSetStrokeWidth:
|
||||||
|
case DisplayListOpType::kSetStrokeMiter:
|
||||||
|
case DisplayListOpType::kSetColor:
|
||||||
|
case DisplayListOpType::kSetBlendMode:
|
||||||
|
case DisplayListOpType::kClearColorFilter:
|
||||||
|
case DisplayListOpType::kSetPodColorFilter:
|
||||||
|
case DisplayListOpType::kClearColorSource:
|
||||||
|
case DisplayListOpType::kSetPodColorSource:
|
||||||
|
case DisplayListOpType::kSetImageColorSource:
|
||||||
|
case DisplayListOpType::kSetRuntimeEffectColorSource:
|
||||||
|
case DisplayListOpType::kClearImageFilter:
|
||||||
|
case DisplayListOpType::kSetPodImageFilter:
|
||||||
|
case DisplayListOpType::kSetSharedImageFilter:
|
||||||
|
case DisplayListOpType::kClearMaskFilter:
|
||||||
|
case DisplayListOpType::kSetPodMaskFilter:
|
||||||
|
#ifdef IMPELLER_ENABLE_3D
|
||||||
|
case DisplayListOpType::kSetSceneColorSource:
|
||||||
|
#endif // IMPELLER_ENABLE_3D
|
||||||
|
return DisplayListOpCategory::kAttribute;
|
||||||
|
|
||||||
|
case DisplayListOpType::kSave:
|
||||||
|
return DisplayListOpCategory::kSave;
|
||||||
|
case DisplayListOpType::kSaveLayer:
|
||||||
|
case DisplayListOpType::kSaveLayerBackdrop:
|
||||||
|
return DisplayListOpCategory::kSaveLayer;
|
||||||
|
case DisplayListOpType::kRestore:
|
||||||
|
return DisplayListOpCategory::kRestore;
|
||||||
|
|
||||||
|
case DisplayListOpType::kTranslate:
|
||||||
|
case DisplayListOpType::kScale:
|
||||||
|
case DisplayListOpType::kRotate:
|
||||||
|
case DisplayListOpType::kSkew:
|
||||||
|
case DisplayListOpType::kTransform2DAffine:
|
||||||
|
case DisplayListOpType::kTransformFullPerspective:
|
||||||
|
case DisplayListOpType::kTransformReset:
|
||||||
|
return DisplayListOpCategory::kTransform;
|
||||||
|
|
||||||
|
case DisplayListOpType::kClipIntersectRect:
|
||||||
|
case DisplayListOpType::kClipIntersectOval:
|
||||||
|
case DisplayListOpType::kClipIntersectRRect:
|
||||||
|
case DisplayListOpType::kClipIntersectPath:
|
||||||
|
case DisplayListOpType::kClipDifferenceRect:
|
||||||
|
case DisplayListOpType::kClipDifferenceOval:
|
||||||
|
case DisplayListOpType::kClipDifferenceRRect:
|
||||||
|
case DisplayListOpType::kClipDifferencePath:
|
||||||
|
return DisplayListOpCategory::kClip;
|
||||||
|
|
||||||
|
case DisplayListOpType::kDrawPaint:
|
||||||
|
case DisplayListOpType::kDrawColor:
|
||||||
|
case DisplayListOpType::kDrawLine:
|
||||||
|
case DisplayListOpType::kDrawDashedLine:
|
||||||
|
case DisplayListOpType::kDrawRect:
|
||||||
|
case DisplayListOpType::kDrawOval:
|
||||||
|
case DisplayListOpType::kDrawCircle:
|
||||||
|
case DisplayListOpType::kDrawRRect:
|
||||||
|
case DisplayListOpType::kDrawDRRect:
|
||||||
|
case DisplayListOpType::kDrawArc:
|
||||||
|
case DisplayListOpType::kDrawPath:
|
||||||
|
case DisplayListOpType::kDrawPoints:
|
||||||
|
case DisplayListOpType::kDrawLines:
|
||||||
|
case DisplayListOpType::kDrawPolygon:
|
||||||
|
case DisplayListOpType::kDrawVertices:
|
||||||
|
case DisplayListOpType::kDrawImage:
|
||||||
|
case DisplayListOpType::kDrawImageWithAttr:
|
||||||
|
case DisplayListOpType::kDrawImageRect:
|
||||||
|
case DisplayListOpType::kDrawImageNine:
|
||||||
|
case DisplayListOpType::kDrawImageNineWithAttr:
|
||||||
|
case DisplayListOpType::kDrawAtlas:
|
||||||
|
case DisplayListOpType::kDrawAtlasCulled:
|
||||||
|
case DisplayListOpType::kDrawTextBlob:
|
||||||
|
case DisplayListOpType::kDrawTextFrame:
|
||||||
|
case DisplayListOpType::kDrawShadow:
|
||||||
|
case DisplayListOpType::kDrawShadowTransparentOccluder:
|
||||||
|
return DisplayListOpCategory::kRendering;
|
||||||
|
|
||||||
|
case DisplayListOpType::kDrawDisplayList:
|
||||||
|
return DisplayListOpCategory::kSubDisplayList;
|
||||||
|
|
||||||
|
case DisplayListOpType::kInvalidOp:
|
||||||
|
return DisplayListOpCategory::kInvalidCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayListOpType DisplayList::GetOpType(DlIndex index) const {
|
||||||
|
// Assert unsigned type so we can eliminate >= 0 comparison
|
||||||
|
static_assert(std::is_unsigned_v<DlIndex>);
|
||||||
|
if (index >= offsets_.size()) {
|
||||||
|
return DisplayListOpType::kInvalidOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset = offsets_[index];
|
||||||
|
FML_DCHECK(offset < byte_count_);
|
||||||
|
auto ptr = storage_.get() + offset;
|
||||||
|
auto op = reinterpret_cast<const DLOp*>(ptr);
|
||||||
|
FML_DCHECK(ptr + op->size <= storage_.get() + byte_count_);
|
||||||
|
return op->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FillAllIndices(std::vector<DlIndex>& indices, DlIndex size) {
|
||||||
|
indices.reserve(size);
|
||||||
|
for (DlIndex i = 0u; i < size; i++) {
|
||||||
|
indices.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DlIndex> DisplayList::GetCulledIndices(
|
||||||
|
const SkRect& cull_rect) const {
|
||||||
|
std::vector<DlIndex> indices;
|
||||||
|
if (!cull_rect.isEmpty()) {
|
||||||
|
if (rtree_) {
|
||||||
|
std::vector<int> rect_indices;
|
||||||
|
rtree_->search(cull_rect, &rect_indices);
|
||||||
|
RTreeResultsToIndexVector(indices, rect_indices);
|
||||||
|
} else {
|
||||||
|
FillAllIndices(indices, offsets_.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DisplayList::Dispatch(DlOpReceiver& receiver, DlIndex index) const {
|
||||||
|
// Assert unsigned type so we can eliminate >= 0 comparison
|
||||||
|
static_assert(std::is_unsigned_v<DlIndex>);
|
||||||
|
if (index >= offsets_.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset = offsets_[index];
|
||||||
|
FML_DCHECK(offset < byte_count_);
|
||||||
|
auto ptr = storage_.get() + offset;
|
||||||
|
FML_DCHECK(offset + reinterpret_cast<const DLOp*>(ptr)->size <= byte_count_);
|
||||||
|
|
||||||
|
DispatchOneOp(receiver, ptr);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool CompareOps(const uint8_t* ptrA,
|
static bool CompareOps(const uint8_t* ptrA,
|
||||||
const uint8_t* endA,
|
const uint8_t* endA,
|
||||||
const uint8_t* ptrB,
|
const uint8_t* ptrB,
|
||||||
@ -270,6 +447,7 @@ static bool CompareOps(const uint8_t* ptrA,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
FOR_EACH_DISPLAY_LIST_OP(DL_OP_EQUALS)
|
FOR_EACH_DISPLAY_LIST_OP(DL_OP_EQUALS)
|
||||||
|
|
||||||
#ifdef IMPELLER_ENABLE_3D
|
#ifdef IMPELLER_ENABLE_3D
|
||||||
DL_OP_EQUALS(SetSceneColorSource)
|
DL_OP_EQUALS(SetSceneColorSource)
|
||||||
#endif // IMPELLER_ENABLE_3D
|
#endif // IMPELLER_ENABLE_3D
|
||||||
|
@ -142,12 +142,30 @@ namespace flutter {
|
|||||||
#define DL_OP_TO_ENUM_VALUE(name) k##name,
|
#define DL_OP_TO_ENUM_VALUE(name) k##name,
|
||||||
enum class DisplayListOpType {
|
enum class DisplayListOpType {
|
||||||
FOR_EACH_DISPLAY_LIST_OP(DL_OP_TO_ENUM_VALUE)
|
FOR_EACH_DISPLAY_LIST_OP(DL_OP_TO_ENUM_VALUE)
|
||||||
|
|
||||||
#ifdef IMPELLER_ENABLE_3D
|
#ifdef IMPELLER_ENABLE_3D
|
||||||
DL_OP_TO_ENUM_VALUE(SetSceneColorSource)
|
DL_OP_TO_ENUM_VALUE(SetSceneColorSource)
|
||||||
#endif // IMPELLER_ENABLE_3D
|
#endif // IMPELLER_ENABLE_3D
|
||||||
|
|
||||||
|
// empty comment to make formatter happy
|
||||||
|
kInvalidOp,
|
||||||
|
kMaxOp = kInvalidOp,
|
||||||
};
|
};
|
||||||
#undef DL_OP_TO_ENUM_VALUE
|
#undef DL_OP_TO_ENUM_VALUE
|
||||||
|
|
||||||
|
enum class DisplayListOpCategory {
|
||||||
|
kAttribute,
|
||||||
|
kTransform,
|
||||||
|
kClip,
|
||||||
|
kSave,
|
||||||
|
kSaveLayer,
|
||||||
|
kRestore,
|
||||||
|
kRendering,
|
||||||
|
kSubDisplayList,
|
||||||
|
kInvalidCategory,
|
||||||
|
kMaxCategory = kInvalidCategory,
|
||||||
|
};
|
||||||
|
|
||||||
class DlOpReceiver;
|
class DlOpReceiver;
|
||||||
class DisplayListBuilder;
|
class DisplayListBuilder;
|
||||||
|
|
||||||
@ -270,7 +288,7 @@ class DisplayListStorage {
|
|||||||
std::unique_ptr<uint8_t, FreeDeleter> ptr_;
|
std::unique_ptr<uint8_t, FreeDeleter> ptr_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Culler;
|
using DlIndex = uint32_t;
|
||||||
|
|
||||||
// The base class that contains a sequence of rendering operations
|
// The base class that contains a sequence of rendering operations
|
||||||
// for dispatch to a DlOpReceiver. These objects must be instantiated
|
// for dispatch to a DlOpReceiver. These objects must be instantiated
|
||||||
@ -360,6 +378,158 @@ class DisplayList : public SkRefCnt {
|
|||||||
/// be required for the indicated blend mode to do its work.
|
/// be required for the indicated blend mode to do its work.
|
||||||
DlBlendMode max_root_blend_mode() const { return max_root_blend_mode_; }
|
DlBlendMode max_root_blend_mode() const { return max_root_blend_mode_; }
|
||||||
|
|
||||||
|
/// @brief Iterator utility class used for the |DisplayList::begin|
|
||||||
|
/// and |DisplayList::end| methods. It implements just the
|
||||||
|
/// basic methods to enable iteration-style for loops.
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
DlIndex operator*() const { return value_; }
|
||||||
|
bool operator!=(const Iterator& other) { return value_ != other.value_; }
|
||||||
|
Iterator& operator++() {
|
||||||
|
value_++;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Iterator(DlIndex value) : value_(value) {}
|
||||||
|
|
||||||
|
DlIndex value_;
|
||||||
|
|
||||||
|
friend class DisplayList;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Return the number of stored records in the DisplayList.
|
||||||
|
///
|
||||||
|
/// Each stored record represents a dispatchable operation that will be
|
||||||
|
/// sent to a |DlOpReceiver| by the |Dispatch| method. You can directly
|
||||||
|
/// simulate the |Dispatch| method using a simple for loop on the indices:
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// for (DlIndex i = 0u; i < display_list->GetRecordCount(); i++) {
|
||||||
|
/// display_list->Dispatch(my_receiver, i);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// @see |Dispatch(receiver, index)|
|
||||||
|
/// @see |begin|
|
||||||
|
/// @see |end|
|
||||||
|
/// @see |GetCulledIndices|
|
||||||
|
DlIndex GetRecordCount() const { return offsets_.size(); }
|
||||||
|
|
||||||
|
/// @brief Return an iterator to the start of the stored records,
|
||||||
|
/// enabling the iteration form of a for loop.
|
||||||
|
///
|
||||||
|
/// Each stored record represents a dispatchable operation that will be
|
||||||
|
/// sent to a |DlOpReceiver| by the |Dispatch| method. You can directly
|
||||||
|
/// simulate the |Dispatch| method using a simple for loop on the indices:
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// for (DlIndex i : *display_list) {
|
||||||
|
/// display_list->Dispatch(my_receiver, i);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// @see |end|
|
||||||
|
/// @see |GetCulledIndices|
|
||||||
|
Iterator begin() const { return Iterator(0u); }
|
||||||
|
|
||||||
|
/// @brief Return an iterator to the end of the stored records,
|
||||||
|
/// enabling the iteration form of a for loop.
|
||||||
|
///
|
||||||
|
/// Each stored record represents a dispatchable operation that will be
|
||||||
|
/// sent to a |DlOpReceiver| by the |Dispatch| method. You can directly
|
||||||
|
/// simulate the |Dispatch| method using a simple for loop on the indices:
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// for (DlIndex i : *display_list) {
|
||||||
|
/// display_list->Dispatch(my_receiver, i);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// @see |begin|
|
||||||
|
/// @see |GetCulledIndices|
|
||||||
|
Iterator end() const { return Iterator(offsets_.size()); }
|
||||||
|
|
||||||
|
/// @brief Dispatch a single stored operation by its index.
|
||||||
|
///
|
||||||
|
/// Each stored record represents a dispatchable operation that will be
|
||||||
|
/// sent to a |DlOpReceiver| by the |Dispatch| method. You can use this
|
||||||
|
/// method to dispatch a single operation to your receiver with an index
|
||||||
|
/// between |0u| (inclusive) and |GetRecordCount()| (exclusive), as in:
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// for (DlIndex i = 0u; i < display_list->GetRecordCount(); i++) {
|
||||||
|
/// display_list->Dispatch(my_receiver, i);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// If the index is out of the range of the stored records, this method
|
||||||
|
/// will not call any methods on the receiver and return false. You can
|
||||||
|
/// check the return value for true if you want to make sure you are
|
||||||
|
/// using valid indices.
|
||||||
|
///
|
||||||
|
/// @see |GetRecordCount|
|
||||||
|
/// @see |begin|
|
||||||
|
/// @see |end|
|
||||||
|
/// @see |GetCulledIndices|
|
||||||
|
bool Dispatch(DlOpReceiver& receiver, DlIndex index) const;
|
||||||
|
|
||||||
|
/// @brief Return an enum describing the specific op type stored at
|
||||||
|
/// the indicated index.
|
||||||
|
///
|
||||||
|
/// The specific types of the records are subject to change without notice
|
||||||
|
/// as the DisplayList code is developed and optimized. These values are
|
||||||
|
/// useful mostly for debugging purposes and should not be used in
|
||||||
|
/// production code.
|
||||||
|
///
|
||||||
|
/// @see |GetOpCategory| for a more stable description of the records
|
||||||
|
DisplayListOpType GetOpType(DlIndex index) const;
|
||||||
|
|
||||||
|
/// @brief Return an enum describing the general category of the
|
||||||
|
/// operation record stored at the indicated index.
|
||||||
|
///
|
||||||
|
/// The categories are general and stable and can be used fairly safely
|
||||||
|
/// in production code to plan how to dispatch or reorder ops during
|
||||||
|
/// final rendering.
|
||||||
|
///
|
||||||
|
/// @see |GetOpType| for a more detailed description of the records
|
||||||
|
/// primarily for debugging use
|
||||||
|
DisplayListOpCategory GetOpCategory(DlIndex index) const;
|
||||||
|
|
||||||
|
/// @brief Return an enum describing the general category of the
|
||||||
|
/// operation record with the given type.
|
||||||
|
///
|
||||||
|
/// @see |GetOpType| for a more detailed description of the records
|
||||||
|
/// primarily for debugging use
|
||||||
|
static DisplayListOpCategory GetOpCategory(DisplayListOpType type);
|
||||||
|
|
||||||
|
/// @brief Return a vector of valid indices for records stored in
|
||||||
|
/// the DisplayList that must be dispatched if you are
|
||||||
|
/// restricted to the indicated cull_rect.
|
||||||
|
///
|
||||||
|
/// This method can be used along with indexed dispatching to implement
|
||||||
|
/// RTree culling while still maintaining control over planning of
|
||||||
|
/// operations to be rendered, as in:
|
||||||
|
///
|
||||||
|
/// {
|
||||||
|
/// std::vector<DlIndex> indices =
|
||||||
|
/// display_list->GetCulledIndices(cull-rect);
|
||||||
|
/// for (DlIndex i : indices) {
|
||||||
|
/// display_list->Dispatch(my_receiver, i);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// The indices returned in the vector will automatically deal with
|
||||||
|
/// including or culling related operations such as attributes, clips
|
||||||
|
/// and transforms that will provide state for any rendering operations
|
||||||
|
/// selected by the culling checks.
|
||||||
|
///
|
||||||
|
/// @see |GetOpType| for a more detailed description of the records
|
||||||
|
/// primarily for debugging use
|
||||||
|
///
|
||||||
|
/// @see |Dispatch(receiver, index)|
|
||||||
|
std::vector<DlIndex> GetCulledIndices(const SkRect& cull_rect) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DisplayList(DisplayListStorage&& ptr,
|
DisplayList(DisplayListStorage&& ptr,
|
||||||
size_t byte_count,
|
size_t byte_count,
|
||||||
@ -381,6 +551,7 @@ class DisplayList : public SkRefCnt {
|
|||||||
static void DisposeOps(const uint8_t* ptr, const uint8_t* end);
|
static void DisposeOps(const uint8_t* ptr, const uint8_t* end);
|
||||||
|
|
||||||
const DisplayListStorage storage_;
|
const DisplayListStorage storage_;
|
||||||
|
const std::vector<size_t> offsets_;
|
||||||
const size_t byte_count_;
|
const size_t byte_count_;
|
||||||
const uint32_t op_count_;
|
const uint32_t op_count_;
|
||||||
|
|
||||||
@ -401,10 +572,10 @@ class DisplayList : public SkRefCnt {
|
|||||||
|
|
||||||
const sk_sp<const DlRTree> rtree_;
|
const sk_sp<const DlRTree> rtree_;
|
||||||
|
|
||||||
void Dispatch(DlOpReceiver& ctx,
|
void DispatchOneOp(DlOpReceiver& receiver, const uint8_t* ptr) const;
|
||||||
const uint8_t* ptr,
|
|
||||||
const uint8_t* end,
|
void RTreeResultsToIndexVector(std::vector<DlIndex>& indices,
|
||||||
Culler& culler) const;
|
const std::vector<int>& rtree_results) const;
|
||||||
|
|
||||||
friend class DisplayListBuilder;
|
friend class DisplayListBuilder;
|
||||||
};
|
};
|
||||||
|
@ -253,6 +253,70 @@ TEST_F(DisplayListTest, EmptyRebuild) {
|
|||||||
ASSERT_TRUE(dl2->Equals(dl3));
|
ASSERT_TRUE(dl2->Equals(dl3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DisplayListTest, GeneralReceiverInitialValues) {
|
||||||
|
DisplayListGeneralReceiver receiver;
|
||||||
|
|
||||||
|
EXPECT_EQ(receiver.GetOpsReceived(), 0u);
|
||||||
|
|
||||||
|
auto max_type = static_cast<int>(DisplayListOpType::kMaxOp);
|
||||||
|
for (int i = 0; i <= max_type; i++) {
|
||||||
|
DisplayListOpType type = static_cast<DisplayListOpType>(i);
|
||||||
|
EXPECT_EQ(receiver.GetOpsReceived(type), 0u) << type;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto max_category = static_cast<int>(DisplayListOpCategory::kMaxCategory);
|
||||||
|
for (int i = 0; i <= max_category; i++) {
|
||||||
|
DisplayListOpCategory category = static_cast<DisplayListOpCategory>(i);
|
||||||
|
EXPECT_EQ(receiver.GetOpsReceived(category), 0u) << category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DisplayListTest, Iteration) {
|
||||||
|
DisplayListBuilder builder;
|
||||||
|
builder.DrawRect({10, 10, 20, 20}, DlPaint());
|
||||||
|
auto dl = builder.Build();
|
||||||
|
for (DlIndex i : *dl) {
|
||||||
|
EXPECT_EQ(dl->GetOpType(i), DisplayListOpType::kDrawRect) //
|
||||||
|
<< "at " << i;
|
||||||
|
EXPECT_EQ(dl->GetOpCategory(i), DisplayListOpCategory::kRendering)
|
||||||
|
<< "at " << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DisplayListTest, InvalidIndices) {
|
||||||
|
DisplayListBuilder builder;
|
||||||
|
builder.DrawRect(kTestBounds, DlPaint());
|
||||||
|
auto dl = builder.Build();
|
||||||
|
DisplayListGeneralReceiver receiver;
|
||||||
|
|
||||||
|
EXPECT_FALSE(dl->Dispatch(receiver, -1));
|
||||||
|
EXPECT_FALSE(dl->Dispatch(receiver, dl->GetRecordCount()));
|
||||||
|
EXPECT_EQ(dl->GetOpType(-1), DisplayListOpType::kInvalidOp);
|
||||||
|
EXPECT_EQ(dl->GetOpType(dl->GetRecordCount()), DisplayListOpType::kInvalidOp);
|
||||||
|
EXPECT_EQ(dl->GetOpCategory(-1), DisplayListOpCategory::kInvalidCategory);
|
||||||
|
EXPECT_EQ(dl->GetOpCategory(dl->GetRecordCount()),
|
||||||
|
DisplayListOpCategory::kInvalidCategory);
|
||||||
|
EXPECT_EQ(dl->GetOpCategory(-1), DisplayListOpCategory::kInvalidCategory);
|
||||||
|
EXPECT_EQ(dl->GetOpCategory(DisplayListOpType::kInvalidOp),
|
||||||
|
DisplayListOpCategory::kInvalidCategory);
|
||||||
|
EXPECT_EQ(dl->GetOpCategory(DisplayListOpType::kMaxOp),
|
||||||
|
DisplayListOpCategory::kInvalidCategory);
|
||||||
|
EXPECT_EQ(receiver.GetOpsReceived(), 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DisplayListTest, ValidIndices) {
|
||||||
|
DisplayListBuilder builder;
|
||||||
|
builder.DrawRect(kTestBounds, DlPaint());
|
||||||
|
auto dl = builder.Build();
|
||||||
|
DisplayListGeneralReceiver receiver;
|
||||||
|
|
||||||
|
EXPECT_EQ(dl->GetRecordCount(), 1u);
|
||||||
|
EXPECT_TRUE(dl->Dispatch(receiver, 0u));
|
||||||
|
EXPECT_EQ(dl->GetOpType(0u), DisplayListOpType::kDrawRect);
|
||||||
|
EXPECT_EQ(dl->GetOpCategory(0u), DisplayListOpCategory::kRendering);
|
||||||
|
EXPECT_EQ(receiver.GetOpsReceived(), 1u);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(DisplayListTest, BuilderCanBeReused) {
|
TEST_F(DisplayListTest, BuilderCanBeReused) {
|
||||||
DisplayListBuilder builder(kTestBounds);
|
DisplayListBuilder builder(kTestBounds);
|
||||||
builder.DrawRect(kTestBounds, DlPaint());
|
builder.DrawRect(kTestBounds, DlPaint());
|
||||||
@ -678,6 +742,38 @@ TEST_F(DisplayListTest, SingleOpDisplayListsRecapturedAreEqual) {
|
|||||||
group.op_name + "(variant " + std::to_string(i + 1) + " == copy)";
|
group.op_name + "(variant " + std::to_string(i + 1) + " == copy)";
|
||||||
ASSERT_TRUE(DisplayListsEQ_Verbose(dl, copy));
|
ASSERT_TRUE(DisplayListsEQ_Verbose(dl, copy));
|
||||||
ASSERT_EQ(copy->op_count(false), dl->op_count(false)) << desc;
|
ASSERT_EQ(copy->op_count(false), dl->op_count(false)) << desc;
|
||||||
|
ASSERT_EQ(copy->GetRecordCount(), dl->GetRecordCount());
|
||||||
|
ASSERT_EQ(copy->bytes(false), dl->bytes(false)) << desc;
|
||||||
|
ASSERT_EQ(copy->op_count(true), dl->op_count(true)) << desc;
|
||||||
|
ASSERT_EQ(copy->bytes(true), dl->bytes(true)) << desc;
|
||||||
|
ASSERT_EQ(copy->total_depth(), dl->total_depth()) << desc;
|
||||||
|
ASSERT_EQ(copy->bounds(), dl->bounds()) << desc;
|
||||||
|
ASSERT_TRUE(copy->Equals(*dl)) << desc;
|
||||||
|
ASSERT_TRUE(dl->Equals(*copy)) << desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DisplayListTest, SingleOpDisplayListsRecapturedByIndexAreEqual) {
|
||||||
|
for (auto& group : allGroups) {
|
||||||
|
for (size_t i = 0; i < group.variants.size(); i++) {
|
||||||
|
sk_sp<DisplayList> dl = Build(group.variants[i]);
|
||||||
|
// Verify recapturing the replay of the display list is Equals()
|
||||||
|
// when dispatching directly from the DL to another builder
|
||||||
|
DisplayListBuilder copy_builder;
|
||||||
|
DlOpReceiver& r = ToReceiver(copy_builder);
|
||||||
|
for (DlIndex i = 0; i < dl->GetRecordCount(); i++) {
|
||||||
|
EXPECT_NE(dl->GetOpType(i), DisplayListOpType::kInvalidOp);
|
||||||
|
EXPECT_NE(dl->GetOpCategory(i),
|
||||||
|
DisplayListOpCategory::kInvalidCategory);
|
||||||
|
EXPECT_TRUE(dl->Dispatch(r, i));
|
||||||
|
}
|
||||||
|
sk_sp<DisplayList> copy = copy_builder.Build();
|
||||||
|
auto desc =
|
||||||
|
group.op_name + "(variant " + std::to_string(i + 1) + " == copy)";
|
||||||
|
ASSERT_TRUE(DisplayListsEQ_Verbose(dl, copy));
|
||||||
|
ASSERT_EQ(copy->op_count(false), dl->op_count(false)) << desc;
|
||||||
|
ASSERT_EQ(copy->GetRecordCount(), dl->GetRecordCount());
|
||||||
ASSERT_EQ(copy->bytes(false), dl->bytes(false)) << desc;
|
ASSERT_EQ(copy->bytes(false), dl->bytes(false)) << desc;
|
||||||
ASSERT_EQ(copy->op_count(true), dl->op_count(true)) << desc;
|
ASSERT_EQ(copy->op_count(true), dl->op_count(true)) << desc;
|
||||||
ASSERT_EQ(copy->bytes(true), dl->bytes(true)) << desc;
|
ASSERT_EQ(copy->bytes(true), dl->bytes(true)) << desc;
|
||||||
@ -3140,27 +3236,55 @@ TEST_F(DisplayListTest, RTreeOfClippedSaveLayerFilterScene) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DisplayListTest, RTreeRenderCulling) {
|
TEST_F(DisplayListTest, RTreeRenderCulling) {
|
||||||
|
SkRect rect1 = SkRect::MakeLTRB(0, 0, 10, 10);
|
||||||
|
SkRect rect2 = SkRect::MakeLTRB(20, 0, 30, 10);
|
||||||
|
SkRect rect3 = SkRect::MakeLTRB(0, 20, 10, 30);
|
||||||
|
SkRect rect4 = SkRect::MakeLTRB(20, 20, 30, 30);
|
||||||
|
DlPaint paint1 = DlPaint().setColor(DlColor::kRed());
|
||||||
|
DlPaint paint2 = DlPaint().setColor(DlColor::kGreen());
|
||||||
|
DlPaint paint3 = DlPaint().setColor(DlColor::kBlue());
|
||||||
|
DlPaint paint4 = DlPaint().setColor(DlColor::kMagenta());
|
||||||
|
|
||||||
DisplayListBuilder main_builder(true);
|
DisplayListBuilder main_builder(true);
|
||||||
DlOpReceiver& main_receiver = ToReceiver(main_builder);
|
main_builder.DrawRect(rect1, paint1);
|
||||||
main_receiver.drawRect({0, 0, 10, 10});
|
main_builder.DrawRect(rect2, paint2);
|
||||||
main_receiver.drawRect({20, 0, 30, 10});
|
main_builder.DrawRect(rect3, paint3);
|
||||||
main_receiver.drawRect({0, 20, 10, 30});
|
main_builder.DrawRect(rect4, paint4);
|
||||||
main_receiver.drawRect({20, 20, 30, 30});
|
|
||||||
auto main = main_builder.Build();
|
auto main = main_builder.Build();
|
||||||
|
|
||||||
auto test = [main](SkIRect cull_rect, const sk_sp<DisplayList>& expected) {
|
auto test = [main](SkIRect cull_rect, const sk_sp<DisplayList>& expected,
|
||||||
|
const std::string& label) {
|
||||||
|
SkRect cull_rectf = SkRect::Make(cull_rect);
|
||||||
|
|
||||||
{ // Test SkIRect culling
|
{ // Test SkIRect culling
|
||||||
DisplayListBuilder culling_builder;
|
DisplayListBuilder culling_builder;
|
||||||
main->Dispatch(ToReceiver(culling_builder), cull_rect);
|
main->Dispatch(ToReceiver(culling_builder), cull_rect);
|
||||||
|
|
||||||
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
|
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected))
|
||||||
|
<< "using cull rect " << cull_rect //
|
||||||
|
<< " where " << label;
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Test SkRect culling
|
{ // Test SkRect culling
|
||||||
DisplayListBuilder culling_builder;
|
DisplayListBuilder culling_builder;
|
||||||
main->Dispatch(ToReceiver(culling_builder), SkRect::Make(cull_rect));
|
main->Dispatch(ToReceiver(culling_builder), cull_rectf);
|
||||||
|
|
||||||
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
|
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected))
|
||||||
|
<< "using cull rect " << cull_rectf //
|
||||||
|
<< " where " << label;
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Test using vector of culled indices
|
||||||
|
DisplayListBuilder culling_builder;
|
||||||
|
DlOpReceiver& receiver = ToReceiver(culling_builder);
|
||||||
|
auto indices = main->GetCulledIndices(cull_rectf);
|
||||||
|
for (DlIndex i : indices) {
|
||||||
|
EXPECT_TRUE(main->Dispatch(receiver, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected))
|
||||||
|
<< "using culled indices on cull rect " << cull_rectf //
|
||||||
|
<< " where " << label;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -3170,57 +3294,62 @@ TEST_F(DisplayListTest, RTreeRenderCulling) {
|
|||||||
DisplayListBuilder expected_builder;
|
DisplayListBuilder expected_builder;
|
||||||
auto expected = expected_builder.Build();
|
auto expected = expected_builder.Build();
|
||||||
|
|
||||||
test(cull_rect, expected);
|
test(cull_rect, expected, "no rects intersect");
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Rect 1
|
{ // Rect 1
|
||||||
SkIRect cull_rect = {9, 9, 19, 19};
|
SkIRect cull_rect = {9, 9, 19, 19};
|
||||||
|
|
||||||
DisplayListBuilder expected_builder;
|
DisplayListBuilder expected_builder;
|
||||||
DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
|
expected_builder.DrawRect(rect1, paint1);
|
||||||
expected_receiver.drawRect({0, 0, 10, 10});
|
|
||||||
auto expected = expected_builder.Build();
|
auto expected = expected_builder.Build();
|
||||||
|
|
||||||
test(cull_rect, expected);
|
test(cull_rect, expected, "rect 1 intersects");
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Rect 2
|
{ // Rect 2
|
||||||
SkIRect cull_rect = {11, 9, 21, 19};
|
SkIRect cull_rect = {11, 9, 21, 19};
|
||||||
|
|
||||||
DisplayListBuilder expected_builder;
|
DisplayListBuilder expected_builder;
|
||||||
DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
|
// Unfortunately we don't cull attribute records (yet?) until the last op
|
||||||
expected_receiver.drawRect({20, 0, 30, 10});
|
ToReceiver(expected_builder).setColor(paint1.getColor());
|
||||||
|
expected_builder.DrawRect(rect2, paint2);
|
||||||
auto expected = expected_builder.Build();
|
auto expected = expected_builder.Build();
|
||||||
|
|
||||||
test(cull_rect, expected);
|
test(cull_rect, expected, "rect 2 intersects");
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Rect 3
|
{ // Rect 3
|
||||||
SkIRect cull_rect = {9, 11, 19, 21};
|
SkIRect cull_rect = {9, 11, 19, 21};
|
||||||
|
|
||||||
DisplayListBuilder expected_builder;
|
DisplayListBuilder expected_builder;
|
||||||
DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
|
// Unfortunately we don't cull attribute records (yet?) until the last op
|
||||||
expected_receiver.drawRect({0, 20, 10, 30});
|
ToReceiver(expected_builder).setColor(paint1.getColor());
|
||||||
|
ToReceiver(expected_builder).setColor(paint2.getColor());
|
||||||
|
expected_builder.DrawRect(rect3, paint3);
|
||||||
auto expected = expected_builder.Build();
|
auto expected = expected_builder.Build();
|
||||||
|
|
||||||
test(cull_rect, expected);
|
test(cull_rect, expected, "rect 3 intersects");
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Rect 4
|
{ // Rect 4
|
||||||
SkIRect cull_rect = {11, 11, 21, 21};
|
SkIRect cull_rect = {11, 11, 21, 21};
|
||||||
|
|
||||||
DisplayListBuilder expected_builder;
|
DisplayListBuilder expected_builder;
|
||||||
DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
|
// Unfortunately we don't cull attribute records (yet?) until the last op
|
||||||
expected_receiver.drawRect({20, 20, 30, 30});
|
ToReceiver(expected_builder).setColor(paint1.getColor());
|
||||||
|
ToReceiver(expected_builder).setColor(paint2.getColor());
|
||||||
|
ToReceiver(expected_builder).setColor(paint3.getColor());
|
||||||
|
expected_builder.DrawRect(rect4, paint4);
|
||||||
auto expected = expected_builder.Build();
|
auto expected = expected_builder.Build();
|
||||||
|
|
||||||
test(cull_rect, expected);
|
test(cull_rect, expected, "rect 4 intersects");
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // All 4 rects
|
{ // All 4 rects
|
||||||
SkIRect cull_rect = {9, 9, 21, 21};
|
SkIRect cull_rect = {9, 9, 21, 21};
|
||||||
|
|
||||||
test(cull_rect, main);
|
test(cull_rect, main, "all rects intersect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5713,5 +5842,82 @@ TEST_F(DisplayListTest, UnboundedRenderOpsAreReportedUnlessClipped) {
|
|||||||
1);
|
1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DisplayListTest, BackdropFilterCulledAlongsideClipAndTransform) {
|
||||||
|
SkRect frame_bounds = SkRect::MakeWH(100.0f, 100.0f);
|
||||||
|
SkRect frame_clip = frame_bounds.makeInset(0.5f, 0.5f);
|
||||||
|
|
||||||
|
SkRect clip_rect = SkRect::MakeLTRB(40.0f, 40.0f, 60.0f, 60.0f);
|
||||||
|
SkRect draw_rect1 = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
|
||||||
|
SkRect draw_rect2 = SkRect::MakeLTRB(45.0f, 20.0f, 55.0f, 55.0f);
|
||||||
|
SkRect cull_rect = SkRect::MakeLTRB(1.0f, 1.0f, 99.0f, 30.0f);
|
||||||
|
auto bdf_filter = DlBlurImageFilter::Make(5.0f, 5.0f, DlTileMode::kClamp);
|
||||||
|
|
||||||
|
ASSERT_TRUE(frame_bounds.contains(clip_rect));
|
||||||
|
ASSERT_TRUE(frame_bounds.contains(draw_rect1));
|
||||||
|
ASSERT_TRUE(frame_bounds.contains(draw_rect2));
|
||||||
|
ASSERT_TRUE(frame_bounds.contains(cull_rect));
|
||||||
|
|
||||||
|
ASSERT_TRUE(frame_clip.contains(clip_rect));
|
||||||
|
ASSERT_TRUE(frame_clip.contains(draw_rect1));
|
||||||
|
ASSERT_TRUE(frame_clip.contains(draw_rect2));
|
||||||
|
ASSERT_TRUE(frame_clip.contains(cull_rect));
|
||||||
|
|
||||||
|
ASSERT_FALSE(clip_rect.intersects(draw_rect1));
|
||||||
|
ASSERT_TRUE(clip_rect.intersects(draw_rect2));
|
||||||
|
|
||||||
|
ASSERT_FALSE(cull_rect.intersects(clip_rect));
|
||||||
|
ASSERT_TRUE(cull_rect.intersects(draw_rect1));
|
||||||
|
ASSERT_TRUE(cull_rect.intersects(draw_rect2));
|
||||||
|
|
||||||
|
DisplayListBuilder builder(frame_bounds, true);
|
||||||
|
builder.Save();
|
||||||
|
{
|
||||||
|
builder.Translate(0.1f, 0.1f);
|
||||||
|
builder.ClipRect(frame_clip);
|
||||||
|
builder.DrawRect(draw_rect1, DlPaint());
|
||||||
|
// Should all be culled below
|
||||||
|
builder.ClipRect(clip_rect);
|
||||||
|
builder.Translate(0.1f, 0.1f);
|
||||||
|
builder.SaveLayer(nullptr, nullptr, bdf_filter.get());
|
||||||
|
{ //
|
||||||
|
builder.DrawRect(clip_rect, DlPaint());
|
||||||
|
}
|
||||||
|
builder.Restore();
|
||||||
|
// End of culling
|
||||||
|
}
|
||||||
|
builder.Restore();
|
||||||
|
builder.DrawRect(draw_rect2, DlPaint());
|
||||||
|
auto display_list = builder.Build();
|
||||||
|
|
||||||
|
{
|
||||||
|
DisplayListBuilder unculled(frame_bounds);
|
||||||
|
display_list->Dispatch(ToReceiver(unculled), frame_bounds);
|
||||||
|
auto unculled_dl = unculled.Build();
|
||||||
|
|
||||||
|
EXPECT_TRUE(DisplayListsEQ_Verbose(display_list, unculled_dl));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
DisplayListBuilder culled(frame_bounds);
|
||||||
|
display_list->Dispatch(ToReceiver(culled), cull_rect);
|
||||||
|
auto culled_dl = culled.Build();
|
||||||
|
|
||||||
|
EXPECT_TRUE(DisplayListsNE_Verbose(display_list, culled_dl));
|
||||||
|
|
||||||
|
DisplayListBuilder expected(frame_bounds);
|
||||||
|
expected.Save();
|
||||||
|
{
|
||||||
|
expected.Translate(0.1f, 0.1f);
|
||||||
|
expected.ClipRect(frame_clip);
|
||||||
|
expected.DrawRect(draw_rect1, DlPaint());
|
||||||
|
}
|
||||||
|
expected.Restore();
|
||||||
|
expected.DrawRect(draw_rect2, DlPaint());
|
||||||
|
auto expected_dl = expected.Build();
|
||||||
|
|
||||||
|
EXPECT_TRUE(DisplayListsEQ_Verbose(culled_dl, expected_dl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
@ -510,7 +510,7 @@ class DisplayListBuilder final : public virtual DlCanvas,
|
|||||||
// Most rendering ops will use 1 depth value, but some attributes may
|
// Most rendering ops will use 1 depth value, but some attributes may
|
||||||
// require an additional depth value (due to implicit saveLayers)
|
// require an additional depth value (due to implicit saveLayers)
|
||||||
uint32_t render_op_depth_cost_ = 1u;
|
uint32_t render_op_depth_cost_ = 1u;
|
||||||
int op_index_ = 0;
|
DlIndex op_index_ = 0;
|
||||||
|
|
||||||
// bytes and ops from |drawPicture| and |drawDisplayList|
|
// bytes and ops from |drawPicture| and |drawDisplayList|
|
||||||
size_t nested_bytes_ = 0;
|
size_t nested_bytes_ = 0;
|
||||||
|
@ -18,47 +18,6 @@
|
|||||||
|
|
||||||
namespace flutter {
|
namespace flutter {
|
||||||
|
|
||||||
// Structure holding the information necessary to dispatch and
|
|
||||||
// potentially cull the DLOps during playback.
|
|
||||||
//
|
|
||||||
// Generally drawing ops will execute as long as |cur_index|
|
|
||||||
// is at or after |next_render_index|, so setting the latter
|
|
||||||
// to 0 will render all primitives and setting it to MAX_INT
|
|
||||||
// will skip all remaining rendering primitives.
|
|
||||||
//
|
|
||||||
// Save and saveLayer ops will execute as long as the next
|
|
||||||
// rendering index is before their closing restore index.
|
|
||||||
// They will also store their own restore index into the
|
|
||||||
// |next_restore_index| field for use by clip and transform ops.
|
|
||||||
//
|
|
||||||
// Clip and transform ops will only execute if the next
|
|
||||||
// render index is before the next restore index. Otherwise
|
|
||||||
// their modified state will not be used before it gets
|
|
||||||
// restored.
|
|
||||||
//
|
|
||||||
// Attribute ops always execute as they are too numerous and
|
|
||||||
// cheap to deal with a complicated "lifetime" tracking to
|
|
||||||
// determine if they will be used.
|
|
||||||
struct DispatchContext {
|
|
||||||
DlOpReceiver& receiver;
|
|
||||||
|
|
||||||
int cur_index;
|
|
||||||
int next_render_index;
|
|
||||||
|
|
||||||
int next_restore_index;
|
|
||||||
|
|
||||||
struct SaveInfo {
|
|
||||||
SaveInfo(int previous_restore_index, bool save_was_needed)
|
|
||||||
: previous_restore_index(previous_restore_index),
|
|
||||||
save_was_needed(save_was_needed) {}
|
|
||||||
|
|
||||||
int previous_restore_index;
|
|
||||||
bool save_was_needed;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<SaveInfo> save_infos;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Most Ops can be bulk compared using memcmp because they contain
|
// Most Ops can be bulk compared using memcmp because they contain
|
||||||
// only numeric values or constructs that are constructed from numeric
|
// only numeric values or constructs that are constructed from numeric
|
||||||
// values.
|
// values.
|
||||||
@ -116,8 +75,8 @@ struct DLOp {
|
|||||||
\
|
\
|
||||||
const bool value; \
|
const bool value; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
ctx.receiver.set##name(value); \
|
receiver.set##name(value); \
|
||||||
} \
|
} \
|
||||||
};
|
};
|
||||||
DEFINE_SET_BOOL_OP(AntiAlias)
|
DEFINE_SET_BOOL_OP(AntiAlias)
|
||||||
@ -133,8 +92,8 @@ DEFINE_SET_BOOL_OP(InvertColors)
|
|||||||
\
|
\
|
||||||
const DlStroke##name value; \
|
const DlStroke##name value; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
ctx.receiver.setStroke##name(value); \
|
receiver.setStroke##name(value); \
|
||||||
} \
|
} \
|
||||||
};
|
};
|
||||||
DEFINE_SET_ENUM_OP(Cap)
|
DEFINE_SET_ENUM_OP(Cap)
|
||||||
@ -149,8 +108,8 @@ struct SetStyleOp final : DLOp {
|
|||||||
|
|
||||||
const DlDrawStyle style;
|
const DlDrawStyle style;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
ctx.receiver.setDrawStyle(style);
|
receiver.setDrawStyle(style);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 4 byte header + 4 byte payload packs into minimum 8 bytes
|
// 4 byte header + 4 byte payload packs into minimum 8 bytes
|
||||||
@ -161,8 +120,8 @@ struct SetStrokeWidthOp final : DLOp {
|
|||||||
|
|
||||||
const float width;
|
const float width;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
ctx.receiver.setStrokeWidth(width);
|
receiver.setStrokeWidth(width);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 4 byte header + 4 byte payload packs into minimum 8 bytes
|
// 4 byte header + 4 byte payload packs into minimum 8 bytes
|
||||||
@ -173,8 +132,8 @@ struct SetStrokeMiterOp final : DLOp {
|
|||||||
|
|
||||||
const float limit;
|
const float limit;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
ctx.receiver.setStrokeMiter(limit);
|
receiver.setStrokeMiter(limit);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -186,7 +145,7 @@ struct SetColorOp final : DLOp {
|
|||||||
|
|
||||||
const DlColor color;
|
const DlColor color;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const { ctx.receiver.setColor(color); }
|
void dispatch(DlOpReceiver& receiver) const { receiver.setColor(color); }
|
||||||
};
|
};
|
||||||
// 4 byte header + 4 byte payload packs into minimum 8 bytes
|
// 4 byte header + 4 byte payload packs into minimum 8 bytes
|
||||||
struct SetBlendModeOp final : DLOp {
|
struct SetBlendModeOp final : DLOp {
|
||||||
@ -196,8 +155,8 @@ struct SetBlendModeOp final : DLOp {
|
|||||||
|
|
||||||
const DlBlendMode mode;
|
const DlBlendMode mode;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const { //
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
ctx.receiver.setBlendMode(mode);
|
receiver.setBlendMode(mode);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -213,8 +172,8 @@ struct SetBlendModeOp final : DLOp {
|
|||||||
\
|
\
|
||||||
Clear##name##Op() {} \
|
Clear##name##Op() {} \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
ctx.receiver.set##name(nullptr); \
|
receiver.set##name(nullptr); \
|
||||||
} \
|
} \
|
||||||
}; \
|
}; \
|
||||||
struct SetPod##name##Op final : DLOp { \
|
struct SetPod##name##Op final : DLOp { \
|
||||||
@ -222,9 +181,9 @@ struct SetBlendModeOp final : DLOp {
|
|||||||
\
|
\
|
||||||
SetPod##name##Op() {} \
|
SetPod##name##Op() {} \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
const Dl##name* filter = reinterpret_cast<const Dl##name*>(this + 1); \
|
const Dl##name* filter = reinterpret_cast<const Dl##name*>(this + 1); \
|
||||||
ctx.receiver.set##name(filter); \
|
receiver.set##name(filter); \
|
||||||
} \
|
} \
|
||||||
};
|
};
|
||||||
DEFINE_SET_CLEAR_DLATTR_OP(ColorFilter, ColorFilter, filter)
|
DEFINE_SET_CLEAR_DLATTR_OP(ColorFilter, ColorFilter, filter)
|
||||||
@ -247,8 +206,8 @@ struct SetImageColorSourceOp : DLOp {
|
|||||||
|
|
||||||
const DlImageColorSource source;
|
const DlImageColorSource source;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
ctx.receiver.setColorSource(&source);
|
receiver.setColorSource(&source);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -265,8 +224,8 @@ struct SetRuntimeEffectColorSourceOp : DLOp {
|
|||||||
|
|
||||||
const DlRuntimeEffectColorSource source;
|
const DlRuntimeEffectColorSource source;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
ctx.receiver.setColorSource(&source);
|
receiver.setColorSource(&source);
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayListCompare equals(const SetRuntimeEffectColorSourceOp* other) const {
|
DisplayListCompare equals(const SetRuntimeEffectColorSourceOp* other) const {
|
||||||
@ -284,8 +243,8 @@ struct SetSceneColorSourceOp : DLOp {
|
|||||||
|
|
||||||
const DlSceneColorSource source;
|
const DlSceneColorSource source;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
ctx.receiver.setColorSource(&source);
|
receiver.setColorSource(&source);
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayListCompare equals(const SetSceneColorSourceOp* other) const {
|
DisplayListCompare equals(const SetSceneColorSourceOp* other) const {
|
||||||
@ -304,8 +263,8 @@ struct SetSharedImageFilterOp : DLOp {
|
|||||||
|
|
||||||
const std::shared_ptr<DlImageFilter> filter;
|
const std::shared_ptr<DlImageFilter> filter;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
ctx.receiver.setImageFilter(filter.get());
|
receiver.setImageFilter(filter.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayListCompare equals(const SetSharedImageFilterOp* other) const {
|
DisplayListCompare equals(const SetSharedImageFilterOp* other) const {
|
||||||
@ -330,15 +289,8 @@ struct SaveOpBase : DLOp {
|
|||||||
// of the data here, it can be stored for free and defaulted to 0 for
|
// of the data here, it can be stored for free and defaulted to 0 for
|
||||||
// save operations.
|
// save operations.
|
||||||
SaveLayerOptions options;
|
SaveLayerOptions options;
|
||||||
int restore_index;
|
DlIndex restore_index;
|
||||||
uint32_t total_content_depth;
|
uint32_t total_content_depth;
|
||||||
|
|
||||||
inline bool save_needed(DispatchContext& ctx) const {
|
|
||||||
bool needed = ctx.next_render_index <= restore_index;
|
|
||||||
ctx.save_infos.emplace_back(ctx.next_restore_index, needed);
|
|
||||||
ctx.next_restore_index = restore_index;
|
|
||||||
return needed;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// 16 byte SaveOpBase with no additional data (options is unsed here)
|
// 16 byte SaveOpBase with no additional data (options is unsed here)
|
||||||
struct SaveOp final : SaveOpBase {
|
struct SaveOp final : SaveOpBase {
|
||||||
@ -346,10 +298,8 @@ struct SaveOp final : SaveOpBase {
|
|||||||
|
|
||||||
SaveOp() : SaveOpBase() {}
|
SaveOp() : SaveOpBase() {}
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (save_needed(ctx)) {
|
receiver.save(total_content_depth);
|
||||||
ctx.receiver.save(total_content_depth);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// The base struct for all saveLayer() ops
|
// The base struct for all saveLayer() ops
|
||||||
@ -369,11 +319,8 @@ struct SaveLayerOp final : SaveLayerOpBase {
|
|||||||
SaveLayerOp(const SaveLayerOptions& options, const SkRect& rect)
|
SaveLayerOp(const SaveLayerOptions& options, const SkRect& rect)
|
||||||
: SaveLayerOpBase(options, rect) {}
|
: SaveLayerOpBase(options, rect) {}
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (save_needed(ctx)) {
|
receiver.saveLayer(rect, options, total_content_depth, max_blend_mode);
|
||||||
ctx.receiver.saveLayer(rect, options, total_content_depth,
|
|
||||||
max_blend_mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 36 byte SaveLayerOpBase + 4 bytes for alignment + 16 byte payload packs
|
// 36 byte SaveLayerOpBase + 4 bytes for alignment + 16 byte payload packs
|
||||||
@ -388,11 +335,9 @@ struct SaveLayerBackdropOp final : SaveLayerOpBase {
|
|||||||
|
|
||||||
const std::shared_ptr<DlImageFilter> backdrop;
|
const std::shared_ptr<DlImageFilter> backdrop;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (save_needed(ctx)) {
|
receiver.saveLayer(rect, options, total_content_depth, max_blend_mode,
|
||||||
ctx.receiver.saveLayer(rect, options, total_content_depth, max_blend_mode,
|
backdrop.get());
|
||||||
backdrop.get());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayListCompare equals(const SaveLayerBackdropOp* other) const {
|
DisplayListCompare equals(const SaveLayerBackdropOp* other) const {
|
||||||
@ -410,23 +355,14 @@ struct RestoreOp final : DLOp {
|
|||||||
|
|
||||||
RestoreOp() {}
|
RestoreOp() {}
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
DispatchContext::SaveInfo& info = ctx.save_infos.back();
|
receiver.restore();
|
||||||
if (info.save_was_needed) {
|
|
||||||
ctx.receiver.restore();
|
|
||||||
}
|
|
||||||
ctx.next_restore_index = info.previous_restore_index;
|
|
||||||
ctx.save_infos.pop_back();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TransformClipOpBase : DLOp {
|
struct TransformClipOpBase : DLOp {
|
||||||
static constexpr uint32_t kDepthInc = 0;
|
static constexpr uint32_t kDepthInc = 0;
|
||||||
static constexpr uint32_t kRenderOpInc = 1;
|
static constexpr uint32_t kRenderOpInc = 1;
|
||||||
|
|
||||||
inline bool op_needed(const DispatchContext& context) const {
|
|
||||||
return context.next_render_index <= context.next_restore_index;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
|
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
|
||||||
// (4 bytes unused)
|
// (4 bytes unused)
|
||||||
@ -438,10 +374,8 @@ struct TranslateOp final : TransformClipOpBase {
|
|||||||
const SkScalar tx;
|
const SkScalar tx;
|
||||||
const SkScalar ty;
|
const SkScalar ty;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
if (op_needed(ctx)) {
|
receiver.translate(tx, ty);
|
||||||
ctx.receiver.translate(tx, ty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
|
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
|
||||||
@ -454,10 +388,8 @@ struct ScaleOp final : TransformClipOpBase {
|
|||||||
const SkScalar sx;
|
const SkScalar sx;
|
||||||
const SkScalar sy;
|
const SkScalar sy;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
if (op_needed(ctx)) {
|
receiver.scale(sx, sy);
|
||||||
ctx.receiver.scale(sx, sy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 4 byte header + 4 byte payload packs into minimum 8 bytes
|
// 4 byte header + 4 byte payload packs into minimum 8 bytes
|
||||||
@ -468,10 +400,8 @@ struct RotateOp final : TransformClipOpBase {
|
|||||||
|
|
||||||
const SkScalar degrees;
|
const SkScalar degrees;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
if (op_needed(ctx)) {
|
receiver.rotate(degrees);
|
||||||
ctx.receiver.rotate(degrees);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
|
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
|
||||||
@ -484,10 +414,8 @@ struct SkewOp final : TransformClipOpBase {
|
|||||||
const SkScalar sx;
|
const SkScalar sx;
|
||||||
const SkScalar sy;
|
const SkScalar sy;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
if (op_needed(ctx)) {
|
receiver.skew(sx, sy);
|
||||||
ctx.receiver.skew(sx, sy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes
|
// 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes
|
||||||
@ -504,11 +432,9 @@ struct Transform2DAffineOp final : TransformClipOpBase {
|
|||||||
const SkScalar mxx, mxy, mxt;
|
const SkScalar mxx, mxy, mxt;
|
||||||
const SkScalar myx, myy, myt;
|
const SkScalar myx, myy, myt;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.transform2DAffine(mxx, mxy, mxt, //
|
||||||
ctx.receiver.transform2DAffine(mxx, mxy, mxt, //
|
myx, myy, myt);
|
||||||
myx, myy, myt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 4 byte header + 64 byte payload uses 68 bytes which is rounded up to 72 bytes
|
// 4 byte header + 64 byte payload uses 68 bytes which is rounded up to 72 bytes
|
||||||
@ -533,13 +459,11 @@ struct TransformFullPerspectiveOp final : TransformClipOpBase {
|
|||||||
const SkScalar mzx, mzy, mzz, mzt;
|
const SkScalar mzx, mzy, mzz, mzt;
|
||||||
const SkScalar mwx, mwy, mwz, mwt;
|
const SkScalar mwx, mwy, mwz, mwt;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.transformFullPerspective(mxx, mxy, mxz, mxt, //
|
||||||
ctx.receiver.transformFullPerspective(mxx, mxy, mxz, mxt, //
|
myx, myy, myz, myt, //
|
||||||
myx, myy, myz, myt, //
|
mzx, mzy, mzz, mzt, //
|
||||||
mzx, mzy, mzz, mzt, //
|
mwx, mwy, mwz, mwt);
|
||||||
mwx, mwy, mwz, mwt);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -549,10 +473,8 @@ struct TransformResetOp final : TransformClipOpBase {
|
|||||||
|
|
||||||
TransformResetOp() = default;
|
TransformResetOp() = default;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
if (op_needed(ctx)) {
|
receiver.transformReset();
|
||||||
ctx.receiver.transformReset();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -576,11 +498,8 @@ struct TransformResetOp final : TransformClipOpBase {
|
|||||||
const bool is_aa; \
|
const bool is_aa; \
|
||||||
const Sk##shapetype shape; \
|
const Sk##shapetype shape; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
if (op_needed(ctx)) { \
|
receiver.clip##shapename(shape, DlCanvas::ClipOp::k##clipop, is_aa); \
|
||||||
ctx.receiver.clip##shapename(shape, DlCanvas::ClipOp::k##clipop, \
|
|
||||||
is_aa); \
|
|
||||||
} \
|
|
||||||
} \
|
} \
|
||||||
};
|
};
|
||||||
DEFINE_CLIP_SHAPE_OP(Rect, Rect, Intersect)
|
DEFINE_CLIP_SHAPE_OP(Rect, Rect, Intersect)
|
||||||
@ -591,33 +510,30 @@ DEFINE_CLIP_SHAPE_OP(Oval, Rect, Difference)
|
|||||||
DEFINE_CLIP_SHAPE_OP(RRect, RRect, Difference)
|
DEFINE_CLIP_SHAPE_OP(RRect, RRect, Difference)
|
||||||
#undef DEFINE_CLIP_SHAPE_OP
|
#undef DEFINE_CLIP_SHAPE_OP
|
||||||
|
|
||||||
#define DEFINE_CLIP_PATH_OP(clipop) \
|
#define DEFINE_CLIP_PATH_OP(clipop) \
|
||||||
struct Clip##clipop##PathOp final : TransformClipOpBase { \
|
struct Clip##clipop##PathOp final : TransformClipOpBase { \
|
||||||
static constexpr auto kType = DisplayListOpType::kClip##clipop##Path; \
|
static constexpr auto kType = DisplayListOpType::kClip##clipop##Path; \
|
||||||
\
|
\
|
||||||
Clip##clipop##PathOp(const SkPath& path, bool is_aa) \
|
Clip##clipop##PathOp(const SkPath& path, bool is_aa) \
|
||||||
: is_aa(is_aa), cached_path(path) {} \
|
: is_aa(is_aa), cached_path(path) {} \
|
||||||
\
|
\
|
||||||
const bool is_aa; \
|
const bool is_aa; \
|
||||||
const DlOpReceiver::CacheablePath cached_path; \
|
const DlOpReceiver::CacheablePath cached_path; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
if (op_needed(ctx)) { \
|
if (receiver.PrefersImpellerPaths()) { \
|
||||||
if (ctx.receiver.PrefersImpellerPaths()) { \
|
receiver.clipPath(cached_path, DlCanvas::ClipOp::k##clipop, is_aa); \
|
||||||
ctx.receiver.clipPath(cached_path, DlCanvas::ClipOp::k##clipop, \
|
} else { \
|
||||||
is_aa); \
|
receiver.clipPath(cached_path.sk_path, DlCanvas::ClipOp::k##clipop, \
|
||||||
} else { \
|
is_aa); \
|
||||||
ctx.receiver.clipPath(cached_path.sk_path, \
|
} \
|
||||||
DlCanvas::ClipOp::k##clipop, is_aa); \
|
} \
|
||||||
} \
|
\
|
||||||
} \
|
DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \
|
||||||
} \
|
return is_aa == other->is_aa && cached_path == other->cached_path \
|
||||||
\
|
? DisplayListCompare::kEqual \
|
||||||
DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \
|
: DisplayListCompare::kNotEqual; \
|
||||||
return is_aa == other->is_aa && cached_path == other->cached_path \
|
} \
|
||||||
? DisplayListCompare::kEqual \
|
|
||||||
: DisplayListCompare::kNotEqual; \
|
|
||||||
} \
|
|
||||||
};
|
};
|
||||||
DEFINE_CLIP_PATH_OP(Intersect)
|
DEFINE_CLIP_PATH_OP(Intersect)
|
||||||
DEFINE_CLIP_PATH_OP(Difference)
|
DEFINE_CLIP_PATH_OP(Difference)
|
||||||
@ -626,10 +542,6 @@ DEFINE_CLIP_PATH_OP(Difference)
|
|||||||
struct DrawOpBase : DLOp {
|
struct DrawOpBase : DLOp {
|
||||||
static constexpr uint32_t kDepthInc = 1;
|
static constexpr uint32_t kDepthInc = 1;
|
||||||
static constexpr uint32_t kRenderOpInc = 1;
|
static constexpr uint32_t kRenderOpInc = 1;
|
||||||
|
|
||||||
inline bool op_needed(const DispatchContext& ctx) const {
|
|
||||||
return ctx.cur_index >= ctx.next_render_index;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused)
|
// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused)
|
||||||
@ -638,10 +550,8 @@ struct DrawPaintOp final : DrawOpBase {
|
|||||||
|
|
||||||
DrawPaintOp() {}
|
DrawPaintOp() {}
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const { //
|
||||||
if (op_needed(ctx)) {
|
receiver.drawPaint();
|
||||||
ctx.receiver.drawPaint();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
|
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
|
||||||
@ -654,10 +564,8 @@ struct DrawColorOp final : DrawOpBase {
|
|||||||
const DlColor color;
|
const DlColor color;
|
||||||
const DlBlendMode mode;
|
const DlBlendMode mode;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.drawColor(color, mode);
|
||||||
ctx.receiver.drawColor(color, mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -674,10 +582,8 @@ struct DrawColorOp final : DrawOpBase {
|
|||||||
\
|
\
|
||||||
const arg_type arg_name; \
|
const arg_type arg_name; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
if (op_needed(ctx)) { \
|
receiver.draw##op_name(arg_name); \
|
||||||
ctx.receiver.draw##op_name(arg_name); \
|
|
||||||
} \
|
|
||||||
} \
|
} \
|
||||||
};
|
};
|
||||||
DEFINE_DRAW_1ARG_OP(Rect, SkRect, rect)
|
DEFINE_DRAW_1ARG_OP(Rect, SkRect, rect)
|
||||||
@ -694,13 +600,11 @@ struct DrawPathOp final : DrawOpBase {
|
|||||||
|
|
||||||
const DlOpReceiver::CacheablePath cached_path;
|
const DlOpReceiver::CacheablePath cached_path;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
if (receiver.PrefersImpellerPaths()) {
|
||||||
if (ctx.receiver.PrefersImpellerPaths()) {
|
receiver.drawPath(cached_path);
|
||||||
ctx.receiver.drawPath(cached_path);
|
} else {
|
||||||
} else {
|
receiver.drawPath(cached_path.sk_path);
|
||||||
ctx.receiver.drawPath(cached_path.sk_path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -726,10 +630,8 @@ struct DrawPathOp final : DrawOpBase {
|
|||||||
const type1 name1; \
|
const type1 name1; \
|
||||||
const type2 name2; \
|
const type2 name2; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
if (op_needed(ctx)) { \
|
receiver.draw##op_name(name1, name2); \
|
||||||
ctx.receiver.draw##op_name(name1, name2); \
|
|
||||||
} \
|
|
||||||
} \
|
} \
|
||||||
};
|
};
|
||||||
DEFINE_DRAW_2ARG_OP(Line, SkPoint, p0, SkPoint, p1)
|
DEFINE_DRAW_2ARG_OP(Line, SkPoint, p0, SkPoint, p1)
|
||||||
@ -752,10 +654,8 @@ struct DrawDashedLineOp final : DrawOpBase {
|
|||||||
const SkScalar on_length;
|
const SkScalar on_length;
|
||||||
const SkScalar off_length;
|
const SkScalar off_length;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.drawDashedLine(p0, p1, on_length, off_length);
|
||||||
ctx.receiver.drawDashedLine(p0, p1, on_length, off_length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -771,10 +671,8 @@ struct DrawArcOp final : DrawOpBase {
|
|||||||
const SkScalar sweep;
|
const SkScalar sweep;
|
||||||
const bool center;
|
const bool center;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.drawArc(bounds, start, sweep, center);
|
||||||
ctx.receiver.drawArc(bounds, start, sweep, center);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -784,20 +682,18 @@ struct DrawArcOp final : DrawOpBase {
|
|||||||
// so this op will always pack efficiently
|
// so this op will always pack efficiently
|
||||||
// The point type is packed into 3 different OpTypes to avoid expanding
|
// The point type is packed into 3 different OpTypes to avoid expanding
|
||||||
// the fixed payload beyond the 8 bytes
|
// the fixed payload beyond the 8 bytes
|
||||||
#define DEFINE_DRAW_POINTS_OP(name, mode) \
|
#define DEFINE_DRAW_POINTS_OP(name, mode) \
|
||||||
struct Draw##name##Op final : DrawOpBase { \
|
struct Draw##name##Op final : DrawOpBase { \
|
||||||
static constexpr auto kType = DisplayListOpType::kDraw##name; \
|
static constexpr auto kType = DisplayListOpType::kDraw##name; \
|
||||||
\
|
\
|
||||||
explicit Draw##name##Op(uint32_t count) : count(count) {} \
|
explicit Draw##name##Op(uint32_t count) : count(count) {} \
|
||||||
\
|
\
|
||||||
const uint32_t count; \
|
const uint32_t count; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
if (op_needed(ctx)) { \
|
const SkPoint* pts = reinterpret_cast<const SkPoint*>(this + 1); \
|
||||||
const SkPoint* pts = reinterpret_cast<const SkPoint*>(this + 1); \
|
receiver.drawPoints(DlCanvas::PointMode::mode, count, pts); \
|
||||||
ctx.receiver.drawPoints(DlCanvas::PointMode::mode, count, pts); \
|
} \
|
||||||
} \
|
|
||||||
} \
|
|
||||||
};
|
};
|
||||||
DEFINE_DRAW_POINTS_OP(Points, kPoints);
|
DEFINE_DRAW_POINTS_OP(Points, kPoints);
|
||||||
DEFINE_DRAW_POINTS_OP(Lines, kLines);
|
DEFINE_DRAW_POINTS_OP(Lines, kLines);
|
||||||
@ -815,40 +711,36 @@ struct DrawVerticesOp final : DrawOpBase {
|
|||||||
const DlBlendMode mode;
|
const DlBlendMode mode;
|
||||||
const std::shared_ptr<DlVertices> vertices;
|
const std::shared_ptr<DlVertices> vertices;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.drawVertices(vertices, mode);
|
||||||
ctx.receiver.drawVertices(vertices, mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4 byte header + 40 byte payload uses 44 bytes but is rounded up to 48 bytes
|
// 4 byte header + 40 byte payload uses 44 bytes but is rounded up to 48 bytes
|
||||||
// (4 bytes unused)
|
// (4 bytes unused)
|
||||||
#define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \
|
#define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \
|
||||||
struct name##Op final : DrawOpBase { \
|
struct name##Op final : DrawOpBase { \
|
||||||
static constexpr auto kType = DisplayListOpType::k##name; \
|
static constexpr auto kType = DisplayListOpType::k##name; \
|
||||||
\
|
\
|
||||||
name##Op(const sk_sp<DlImage>& image, \
|
name##Op(const sk_sp<DlImage>& image, \
|
||||||
const SkPoint& point, \
|
const SkPoint& point, \
|
||||||
DlImageSampling sampling) \
|
DlImageSampling sampling) \
|
||||||
: point(point), sampling(sampling), image(std::move(image)) {} \
|
: point(point), sampling(sampling), image(std::move(image)) {} \
|
||||||
\
|
\
|
||||||
const SkPoint point; \
|
const SkPoint point; \
|
||||||
const DlImageSampling sampling; \
|
const DlImageSampling sampling; \
|
||||||
const sk_sp<DlImage> image; \
|
const sk_sp<DlImage> image; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
if (op_needed(ctx)) { \
|
receiver.drawImage(image, point, sampling, with_attributes); \
|
||||||
ctx.receiver.drawImage(image, point, sampling, with_attributes); \
|
} \
|
||||||
} \
|
\
|
||||||
} \
|
DisplayListCompare equals(const name##Op* other) const { \
|
||||||
\
|
return (point == other->point && sampling == other->sampling && \
|
||||||
DisplayListCompare equals(const name##Op* other) const { \
|
image->Equals(other->image)) \
|
||||||
return (point == other->point && sampling == other->sampling && \
|
? DisplayListCompare::kEqual \
|
||||||
image->Equals(other->image)) \
|
: DisplayListCompare::kNotEqual; \
|
||||||
? DisplayListCompare::kEqual \
|
} \
|
||||||
: DisplayListCompare::kNotEqual; \
|
|
||||||
} \
|
|
||||||
};
|
};
|
||||||
DEFINE_DRAW_IMAGE_OP(DrawImage, false)
|
DEFINE_DRAW_IMAGE_OP(DrawImage, false)
|
||||||
DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true)
|
DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true)
|
||||||
@ -879,11 +771,9 @@ struct DrawImageRectOp final : DrawOpBase {
|
|||||||
const DlCanvas::SrcRectConstraint constraint;
|
const DlCanvas::SrcRectConstraint constraint;
|
||||||
const sk_sp<DlImage> image;
|
const sk_sp<DlImage> image;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.drawImageRect(image, src, dst, sampling, render_with_attributes,
|
||||||
ctx.receiver.drawImageRect(image, src, dst, sampling,
|
constraint);
|
||||||
render_with_attributes, constraint);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayListCompare equals(const DrawImageRectOp* other) const {
|
DisplayListCompare equals(const DrawImageRectOp* other) const {
|
||||||
@ -912,11 +802,9 @@ struct DrawImageRectOp final : DrawOpBase {
|
|||||||
const DlFilterMode mode; \
|
const DlFilterMode mode; \
|
||||||
const sk_sp<DlImage> image; \
|
const sk_sp<DlImage> image; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
if (op_needed(ctx)) { \
|
receiver.drawImageNine(image, center, dst, mode, \
|
||||||
ctx.receiver.drawImageNine(image, center, dst, mode, \
|
render_with_attributes); \
|
||||||
render_with_attributes); \
|
|
||||||
} \
|
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
DisplayListCompare equals(const name##Op* other) const { \
|
DisplayListCompare equals(const name##Op* other) const { \
|
||||||
@ -994,16 +882,14 @@ struct DrawAtlasOp final : DrawAtlasBaseOp {
|
|||||||
has_colors,
|
has_colors,
|
||||||
render_with_attributes) {}
|
render_with_attributes) {}
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1);
|
||||||
const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1);
|
const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count);
|
||||||
const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count);
|
const DlColor* colors =
|
||||||
const DlColor* colors =
|
has_colors ? reinterpret_cast<const DlColor*>(tex + count) : nullptr;
|
||||||
has_colors ? reinterpret_cast<const DlColor*>(tex + count) : nullptr;
|
const DlBlendMode mode = static_cast<DlBlendMode>(mode_index);
|
||||||
const DlBlendMode mode = static_cast<DlBlendMode>(mode_index);
|
receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
|
||||||
ctx.receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
|
nullptr, render_with_attributes);
|
||||||
nullptr, render_with_attributes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayListCompare equals(const DrawAtlasOp* other) const {
|
DisplayListCompare equals(const DrawAtlasOp* other) const {
|
||||||
@ -1039,16 +925,14 @@ struct DrawAtlasCulledOp final : DrawAtlasBaseOp {
|
|||||||
|
|
||||||
const SkRect cull_rect;
|
const SkRect cull_rect;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1);
|
||||||
const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1);
|
const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count);
|
||||||
const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count);
|
const DlColor* colors =
|
||||||
const DlColor* colors =
|
has_colors ? reinterpret_cast<const DlColor*>(tex + count) : nullptr;
|
||||||
has_colors ? reinterpret_cast<const DlColor*>(tex + count) : nullptr;
|
const DlBlendMode mode = static_cast<DlBlendMode>(mode_index);
|
||||||
const DlBlendMode mode = static_cast<DlBlendMode>(mode_index);
|
receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
|
||||||
ctx.receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
|
&cull_rect, render_with_attributes);
|
||||||
&cull_rect, render_with_attributes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayListCompare equals(const DrawAtlasCulledOp* other) const {
|
DisplayListCompare equals(const DrawAtlasCulledOp* other) const {
|
||||||
@ -1073,10 +957,8 @@ struct DrawDisplayListOp final : DrawOpBase {
|
|||||||
SkScalar opacity;
|
SkScalar opacity;
|
||||||
const sk_sp<DisplayList> display_list;
|
const sk_sp<DisplayList> display_list;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.drawDisplayList(display_list, opacity);
|
||||||
ctx.receiver.drawDisplayList(display_list, opacity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayListCompare equals(const DrawDisplayListOp* other) const {
|
DisplayListCompare equals(const DrawDisplayListOp* other) const {
|
||||||
@ -1099,10 +981,8 @@ struct DrawTextBlobOp final : DrawOpBase {
|
|||||||
const SkScalar y;
|
const SkScalar y;
|
||||||
const sk_sp<SkTextBlob> blob;
|
const sk_sp<SkTextBlob> blob;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.drawTextBlob(blob, x, y);
|
||||||
ctx.receiver.drawTextBlob(blob, x, y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1118,10 +998,8 @@ struct DrawTextFrameOp final : DrawOpBase {
|
|||||||
const SkScalar y;
|
const SkScalar y;
|
||||||
const std::shared_ptr<impeller::TextFrame> text_frame;
|
const std::shared_ptr<impeller::TextFrame> text_frame;
|
||||||
|
|
||||||
void dispatch(DispatchContext& ctx) const {
|
void dispatch(DlOpReceiver& receiver) const {
|
||||||
if (op_needed(ctx)) {
|
receiver.drawTextFrame(text_frame, x, y);
|
||||||
ctx.receiver.drawTextFrame(text_frame, x, y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1141,15 +1019,13 @@ struct DrawTextFrameOp final : DrawOpBase {
|
|||||||
const SkScalar dpr; \
|
const SkScalar dpr; \
|
||||||
const DlOpReceiver::CacheablePath cached_path; \
|
const DlOpReceiver::CacheablePath cached_path; \
|
||||||
\
|
\
|
||||||
void dispatch(DispatchContext& ctx) const { \
|
void dispatch(DlOpReceiver& receiver) const { \
|
||||||
if (op_needed(ctx)) { \
|
if (receiver.PrefersImpellerPaths()) { \
|
||||||
if (ctx.receiver.PrefersImpellerPaths()) { \
|
receiver.drawShadow(cached_path, color, elevation, \
|
||||||
ctx.receiver.drawShadow(cached_path, color, elevation, \
|
transparent_occluder, dpr); \
|
||||||
transparent_occluder, dpr); \
|
} else { \
|
||||||
} else { \
|
receiver.drawShadow(cached_path.sk_path, color, elevation, \
|
||||||
ctx.receiver.drawShadow(cached_path.sk_path, color, elevation, \
|
transparent_occluder, dpr); \
|
||||||
transparent_occluder, dpr); \
|
|
||||||
} \
|
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
|
@ -55,6 +55,8 @@ using DlVertexMode = flutter::DlVertexMode;
|
|||||||
using DlTileMode = flutter::DlTileMode;
|
using DlTileMode = flutter::DlTileMode;
|
||||||
using DlImageSampling = flutter::DlImageSampling;
|
using DlImageSampling = flutter::DlImageSampling;
|
||||||
using SaveLayerOptions = flutter::SaveLayerOptions;
|
using SaveLayerOptions = flutter::SaveLayerOptions;
|
||||||
|
using DisplayListOpType = flutter::DisplayListOpType;
|
||||||
|
using DisplayListOpCategory = flutter::DisplayListOpCategory;
|
||||||
|
|
||||||
using DisplayListStreamDispatcher = flutter::testing::DisplayListStreamDispatcher;
|
using DisplayListStreamDispatcher = flutter::testing::DisplayListStreamDispatcher;
|
||||||
|
|
||||||
@ -99,44 +101,84 @@ std::ostream& operator<<(std::ostream& os, const DlPaint& paint) {
|
|||||||
return os << ")";
|
return os << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DLT_OSTREAM_CASE(enum_name, value_name) \
|
||||||
|
case enum_name::k##value_name: return os << #enum_name "::k" #value_name
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const DlBlendMode& mode) {
|
std::ostream& operator<<(std::ostream& os, const DlBlendMode& mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case DlBlendMode::kClear: return os << "BlendMode::kClear";
|
DLT_OSTREAM_CASE(DlBlendMode, Clear);
|
||||||
case DlBlendMode::kSrc: return os << "BlendMode::kSrc";
|
DLT_OSTREAM_CASE(DlBlendMode, Src);
|
||||||
case DlBlendMode::kDst: return os << "BlendMode::kDst";
|
DLT_OSTREAM_CASE(DlBlendMode, Dst);
|
||||||
case DlBlendMode::kSrcOver: return os << "BlendMode::kSrcOver";
|
DLT_OSTREAM_CASE(DlBlendMode, SrcOver);
|
||||||
case DlBlendMode::kDstOver: return os << "BlendMode::kDstOver";
|
DLT_OSTREAM_CASE(DlBlendMode, DstOver);
|
||||||
case DlBlendMode::kSrcIn: return os << "BlendMode::kSrcIn";
|
DLT_OSTREAM_CASE(DlBlendMode, SrcIn);
|
||||||
case DlBlendMode::kDstIn: return os << "BlendMode::kDstIn";
|
DLT_OSTREAM_CASE(DlBlendMode, DstIn);
|
||||||
case DlBlendMode::kSrcOut: return os << "BlendMode::kSrcOut";
|
DLT_OSTREAM_CASE(DlBlendMode, SrcOut);
|
||||||
case DlBlendMode::kDstOut: return os << "BlendMode::kDstOut";
|
DLT_OSTREAM_CASE(DlBlendMode, DstOut);
|
||||||
case DlBlendMode::kSrcATop: return os << "BlendMode::kSrcATop";
|
DLT_OSTREAM_CASE(DlBlendMode, SrcATop);
|
||||||
case DlBlendMode::kDstATop: return os << "BlendMode::kDstATop";
|
DLT_OSTREAM_CASE(DlBlendMode, DstATop);
|
||||||
case DlBlendMode::kXor: return os << "BlendMode::kXor";
|
DLT_OSTREAM_CASE(DlBlendMode, Xor);
|
||||||
case DlBlendMode::kPlus: return os << "BlendMode::kPlus";
|
DLT_OSTREAM_CASE(DlBlendMode, Plus);
|
||||||
case DlBlendMode::kModulate: return os << "BlendMode::kModulate";
|
DLT_OSTREAM_CASE(DlBlendMode, Modulate);
|
||||||
case DlBlendMode::kScreen: return os << "BlendMode::kScreen";
|
DLT_OSTREAM_CASE(DlBlendMode, Screen);
|
||||||
|
|
||||||
case DlBlendMode::kOverlay: return os << "BlendMode::kOverlay";
|
DLT_OSTREAM_CASE(DlBlendMode, Overlay);
|
||||||
case DlBlendMode::kDarken: return os << "BlendMode::kDarken";
|
DLT_OSTREAM_CASE(DlBlendMode, Darken);
|
||||||
case DlBlendMode::kLighten: return os << "BlendMode::kLighten";
|
DLT_OSTREAM_CASE(DlBlendMode, Lighten);
|
||||||
case DlBlendMode::kColorDodge: return os << "BlendMode::kColorDodge";
|
DLT_OSTREAM_CASE(DlBlendMode, ColorDodge);
|
||||||
case DlBlendMode::kColorBurn: return os << "BlendMode::kColorBurn";
|
DLT_OSTREAM_CASE(DlBlendMode, ColorBurn);
|
||||||
case DlBlendMode::kHardLight: return os << "BlendMode::kHardLight";
|
DLT_OSTREAM_CASE(DlBlendMode, HardLight);
|
||||||
case DlBlendMode::kSoftLight: return os << "BlendMode::kSoftLight";
|
DLT_OSTREAM_CASE(DlBlendMode, SoftLight);
|
||||||
case DlBlendMode::kDifference: return os << "BlendMode::kDifference";
|
DLT_OSTREAM_CASE(DlBlendMode, Difference);
|
||||||
case DlBlendMode::kExclusion: return os << "BlendMode::kExclusion";
|
DLT_OSTREAM_CASE(DlBlendMode, Exclusion);
|
||||||
case DlBlendMode::kMultiply: return os << "BlendMode::kMultiply";
|
DLT_OSTREAM_CASE(DlBlendMode, Multiply);
|
||||||
|
|
||||||
case DlBlendMode::kHue: return os << "BlendMode::kHue";
|
DLT_OSTREAM_CASE(DlBlendMode, Hue);
|
||||||
case DlBlendMode::kSaturation: return os << "BlendMode::kSaturation";
|
DLT_OSTREAM_CASE(DlBlendMode, Saturation);
|
||||||
case DlBlendMode::kColor: return os << "BlendMode::kColor";
|
DLT_OSTREAM_CASE(DlBlendMode, Color);
|
||||||
case DlBlendMode::kLuminosity: return os << "BlendMode::kLuminosity";
|
DLT_OSTREAM_CASE(DlBlendMode, Luminosity);
|
||||||
|
|
||||||
default: return os << "BlendMode::????";
|
|
||||||
}
|
}
|
||||||
|
// Not a valid enum, should never happen, but in case we encounter bad data.
|
||||||
|
return os << "DlBlendMode::????";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern std::ostream& operator<<(std::ostream& os,
|
||||||
|
const flutter::DisplayListOpType& type) {
|
||||||
|
switch (type) {
|
||||||
|
#define DLT_OP_TYPE_CASE(V) DLT_OSTREAM_CASE(DisplayListOpType, V);
|
||||||
|
FOR_EACH_DISPLAY_LIST_OP(DLT_OP_TYPE_CASE)
|
||||||
|
DLT_OP_TYPE_CASE(InvalidOp)
|
||||||
|
|
||||||
|
#ifdef IMPELLER_ENABLE_3D
|
||||||
|
DLT_OP_TYPE_CASE(SetSceneColorSource)
|
||||||
|
#endif // IMPELLER_ENABLE_3D
|
||||||
|
|
||||||
|
|
||||||
|
#undef DLT_OP_TYPE_CASE
|
||||||
|
}
|
||||||
|
// Not a valid enum, should never happen, but in case we encounter bad data.
|
||||||
|
return os << "DisplayListOpType::???";
|
||||||
|
}
|
||||||
|
|
||||||
|
extern std::ostream& operator<<(
|
||||||
|
std::ostream& os, const flutter::DisplayListOpCategory& category) {
|
||||||
|
switch (category) {
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, Attribute);
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, Transform);
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, Clip);
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, Save);
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, SaveLayer);
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, Restore);
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, Rendering);
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, SubDisplayList);
|
||||||
|
DLT_OSTREAM_CASE(DisplayListOpCategory, InvalidCategory);
|
||||||
|
}
|
||||||
|
// Not a valid enum, should never happen, but in case we encounter bad data.
|
||||||
|
return os << "DisplayListOpCategory::???";
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef DLT_OSTREAM_CASE
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const SaveLayerOptions& options) {
|
std::ostream& operator<<(std::ostream& os, const SaveLayerOptions& options) {
|
||||||
return os << "SaveLayerOptions("
|
return os << "SaveLayerOptions("
|
||||||
<< "renders_with_attributes: " << options.renders_with_attributes()
|
<< "renders_with_attributes: " << options.renders_with_attributes()
|
||||||
@ -497,6 +539,12 @@ void DisplayListStreamDispatcher::setColorSource(const DlColorSource* source) {
|
|||||||
<< sweep_src->tile_mode() << ", " << sweep_src->matrix_ptr() << ")";
|
<< sweep_src->tile_mode() << ", " << sweep_src->matrix_ptr() << ")";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#ifdef IMPELLER_ENABLE_3D
|
||||||
|
case DlColorSourceType::kScene: {
|
||||||
|
os_ << "DlSceneColorSource()";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif // IMPELLER_ENABLE_3D
|
||||||
default:
|
default:
|
||||||
os_ << "?DlUnknownColorSource?()";
|
os_ << "?DlUnknownColorSource?()";
|
||||||
break;
|
break;
|
||||||
|
@ -75,6 +75,10 @@ extern std::ostream& operator<<(std::ostream& os,
|
|||||||
const flutter::DlImage* image);
|
const flutter::DlImage* image);
|
||||||
extern std::ostream& operator<<(std::ostream& os,
|
extern std::ostream& operator<<(std::ostream& os,
|
||||||
const flutter::SaveLayerOptions& image);
|
const flutter::SaveLayerOptions& image);
|
||||||
|
extern std::ostream& operator<<(std::ostream& os,
|
||||||
|
const flutter::DisplayListOpType& type);
|
||||||
|
extern std::ostream& operator<<(std::ostream& os,
|
||||||
|
const flutter::DisplayListOpCategory& category);
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|
||||||
@ -209,6 +213,398 @@ class DisplayListStreamDispatcher final : public DlOpReceiver {
|
|||||||
void out(const DlImageFilter* filter);
|
void out(const DlImageFilter* filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DisplayListGeneralReceiver : public DlOpReceiver {
|
||||||
|
public:
|
||||||
|
DisplayListGeneralReceiver() {
|
||||||
|
type_counts_.fill(0u);
|
||||||
|
category_counts_.fill(0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAntiAlias(bool aa) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetAntiAlias);
|
||||||
|
}
|
||||||
|
void setInvertColors(bool invert) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetInvertColors);
|
||||||
|
}
|
||||||
|
void setStrokeCap(DlStrokeCap cap) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetStrokeCap);
|
||||||
|
}
|
||||||
|
void setStrokeJoin(DlStrokeJoin join) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetStrokeJoin);
|
||||||
|
}
|
||||||
|
void setDrawStyle(DlDrawStyle style) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetStyle);
|
||||||
|
}
|
||||||
|
void setStrokeWidth(float width) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetStrokeWidth);
|
||||||
|
}
|
||||||
|
void setStrokeMiter(float limit) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetStrokeMiter);
|
||||||
|
}
|
||||||
|
void setColor(DlColor color) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetColor);
|
||||||
|
}
|
||||||
|
void setBlendMode(DlBlendMode mode) override {
|
||||||
|
RecordByType(DisplayListOpType::kSetBlendMode);
|
||||||
|
}
|
||||||
|
void setColorSource(const DlColorSource* source) override {
|
||||||
|
if (source) {
|
||||||
|
switch (source->type()) {
|
||||||
|
case DlColorSourceType::kImage:
|
||||||
|
RecordByType(DisplayListOpType::kSetImageColorSource);
|
||||||
|
break;
|
||||||
|
case DlColorSourceType::kRuntimeEffect:
|
||||||
|
RecordByType(DisplayListOpType::kSetRuntimeEffectColorSource);
|
||||||
|
break;
|
||||||
|
case DlColorSourceType::kColor:
|
||||||
|
case DlColorSourceType::kLinearGradient:
|
||||||
|
case DlColorSourceType::kRadialGradient:
|
||||||
|
case DlColorSourceType::kConicalGradient:
|
||||||
|
case DlColorSourceType::kSweepGradient:
|
||||||
|
RecordByType(DisplayListOpType::kSetPodColorSource);
|
||||||
|
break;
|
||||||
|
#ifdef IMPELLER_ENABLE_3D
|
||||||
|
case DlColorSourceType::kScene:
|
||||||
|
RecordByType(DisplayListOpType::kSetSceneColorSource);
|
||||||
|
break;
|
||||||
|
#endif // IMPELLER_ENABLE_3D
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kClearColorSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void setImageFilter(const DlImageFilter* filter) override {
|
||||||
|
if (filter) {
|
||||||
|
switch (filter->type()) {
|
||||||
|
case DlImageFilterType::kBlur:
|
||||||
|
case DlImageFilterType::kDilate:
|
||||||
|
case DlImageFilterType::kErode:
|
||||||
|
case DlImageFilterType::kMatrix:
|
||||||
|
RecordByType(DisplayListOpType::kSetPodImageFilter);
|
||||||
|
break;
|
||||||
|
case DlImageFilterType::kCompose:
|
||||||
|
case DlImageFilterType::kLocalMatrix:
|
||||||
|
case DlImageFilterType::kColorFilter:
|
||||||
|
RecordByType(DisplayListOpType::kSetSharedImageFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kClearImageFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void setColorFilter(const DlColorFilter* filter) override {
|
||||||
|
if (filter) {
|
||||||
|
switch (filter->type()) {
|
||||||
|
case DlColorFilterType::kBlend:
|
||||||
|
case DlColorFilterType::kMatrix:
|
||||||
|
case DlColorFilterType::kLinearToSrgbGamma:
|
||||||
|
case DlColorFilterType::kSrgbToLinearGamma:
|
||||||
|
RecordByType(DisplayListOpType::kSetPodColorFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kClearColorFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void setMaskFilter(const DlMaskFilter* filter) override {
|
||||||
|
if (filter) {
|
||||||
|
switch (filter->type()) {
|
||||||
|
case DlMaskFilterType::kBlur:
|
||||||
|
RecordByType(DisplayListOpType::kSetPodMaskFilter);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kClearMaskFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void translate(SkScalar tx, SkScalar ty) override {
|
||||||
|
RecordByType(DisplayListOpType::kTranslate);
|
||||||
|
}
|
||||||
|
void scale(SkScalar sx, SkScalar sy) override {
|
||||||
|
RecordByType(DisplayListOpType::kScale);
|
||||||
|
}
|
||||||
|
void rotate(SkScalar degrees) override {
|
||||||
|
RecordByType(DisplayListOpType::kRotate);
|
||||||
|
}
|
||||||
|
void skew(SkScalar sx, SkScalar sy) override {
|
||||||
|
RecordByType(DisplayListOpType::kSkew);
|
||||||
|
}
|
||||||
|
// clang-format off
|
||||||
|
// 2x3 2D affine subset of a 4x4 transform in row major order
|
||||||
|
void transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt,
|
||||||
|
SkScalar myx, SkScalar myy, SkScalar myt) override {
|
||||||
|
RecordByType(DisplayListOpType::kTransform2DAffine);
|
||||||
|
}
|
||||||
|
// full 4x4 transform in row major order
|
||||||
|
void transformFullPerspective(
|
||||||
|
SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
|
||||||
|
SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
|
||||||
|
SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
|
||||||
|
SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override {
|
||||||
|
RecordByType(DisplayListOpType::kTransformFullPerspective);
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
void transformReset() override {
|
||||||
|
RecordByType(DisplayListOpType::kTransformReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clipRect(const SkRect& rect,
|
||||||
|
DlCanvas::ClipOp clip_op,
|
||||||
|
bool is_aa) override {
|
||||||
|
switch (clip_op) {
|
||||||
|
case DlCanvas::ClipOp::kIntersect:
|
||||||
|
RecordByType(DisplayListOpType::kClipIntersectRect);
|
||||||
|
break;
|
||||||
|
case DlCanvas::ClipOp::kDifference:
|
||||||
|
RecordByType(DisplayListOpType::kClipDifferenceRect);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void clipOval(const SkRect& bounds,
|
||||||
|
DlCanvas::ClipOp clip_op,
|
||||||
|
bool is_aa) override {
|
||||||
|
switch (clip_op) {
|
||||||
|
case DlCanvas::ClipOp::kIntersect:
|
||||||
|
RecordByType(DisplayListOpType::kClipIntersectOval);
|
||||||
|
break;
|
||||||
|
case DlCanvas::ClipOp::kDifference:
|
||||||
|
RecordByType(DisplayListOpType::kClipDifferenceOval);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void clipRRect(const SkRRect& rrect,
|
||||||
|
DlCanvas::ClipOp clip_op,
|
||||||
|
bool is_aa) override {
|
||||||
|
switch (clip_op) {
|
||||||
|
case DlCanvas::ClipOp::kIntersect:
|
||||||
|
RecordByType(DisplayListOpType::kClipIntersectRRect);
|
||||||
|
break;
|
||||||
|
case DlCanvas::ClipOp::kDifference:
|
||||||
|
RecordByType(DisplayListOpType::kClipDifferenceRRect);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void clipPath(const SkPath& path,
|
||||||
|
DlCanvas::ClipOp clip_op,
|
||||||
|
bool is_aa) override {
|
||||||
|
switch (clip_op) {
|
||||||
|
case DlCanvas::ClipOp::kIntersect:
|
||||||
|
RecordByType(DisplayListOpType::kClipIntersectPath);
|
||||||
|
break;
|
||||||
|
case DlCanvas::ClipOp::kDifference:
|
||||||
|
RecordByType(DisplayListOpType::kClipDifferencePath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void save() override { RecordByType(DisplayListOpType::kSave); }
|
||||||
|
void saveLayer(const SkRect& bounds,
|
||||||
|
const SaveLayerOptions options,
|
||||||
|
const DlImageFilter* backdrop) override {
|
||||||
|
if (backdrop) {
|
||||||
|
RecordByType(DisplayListOpType::kSaveLayerBackdrop);
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kSaveLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void restore() override { RecordByType(DisplayListOpType::kRestore); }
|
||||||
|
|
||||||
|
void drawColor(DlColor color, DlBlendMode mode) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawColor);
|
||||||
|
}
|
||||||
|
void drawPaint() override { RecordByType(DisplayListOpType::kDrawPaint); }
|
||||||
|
void drawLine(const SkPoint& p0, const SkPoint& p1) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawLine);
|
||||||
|
}
|
||||||
|
void drawDashedLine(const DlPoint& p0,
|
||||||
|
const DlPoint& p1,
|
||||||
|
DlScalar on_length,
|
||||||
|
DlScalar off_length) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawDashedLine);
|
||||||
|
}
|
||||||
|
void drawRect(const SkRect& rect) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawRect);
|
||||||
|
}
|
||||||
|
void drawOval(const SkRect& bounds) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawOval);
|
||||||
|
}
|
||||||
|
void drawCircle(const SkPoint& center, SkScalar radius) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawCircle);
|
||||||
|
}
|
||||||
|
void drawRRect(const SkRRect& rrect) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawRRect);
|
||||||
|
}
|
||||||
|
void drawDRRect(const SkRRect& outer, const SkRRect& inner) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawDRRect);
|
||||||
|
}
|
||||||
|
void drawPath(const SkPath& path) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawPath);
|
||||||
|
}
|
||||||
|
void drawArc(const SkRect& oval_bounds,
|
||||||
|
SkScalar start_degrees,
|
||||||
|
SkScalar sweep_degrees,
|
||||||
|
bool use_center) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawArc);
|
||||||
|
}
|
||||||
|
void drawPoints(DlCanvas::PointMode mode,
|
||||||
|
uint32_t count,
|
||||||
|
const SkPoint points[]) override {
|
||||||
|
switch (mode) {
|
||||||
|
case DlCanvas::PointMode::kPoints:
|
||||||
|
RecordByType(DisplayListOpType::kDrawPoints);
|
||||||
|
break;
|
||||||
|
case DlCanvas::PointMode::kLines:
|
||||||
|
RecordByType(DisplayListOpType::kDrawLines);
|
||||||
|
break;
|
||||||
|
case DlCanvas::PointMode::kPolygon:
|
||||||
|
RecordByType(DisplayListOpType::kDrawPolygon);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void drawVertices(const std::shared_ptr<DlVertices>& vertices,
|
||||||
|
DlBlendMode mode) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawVertices);
|
||||||
|
}
|
||||||
|
void drawImage(const sk_sp<DlImage> image,
|
||||||
|
const SkPoint point,
|
||||||
|
DlImageSampling sampling,
|
||||||
|
bool render_with_attributes) override {
|
||||||
|
if (render_with_attributes) {
|
||||||
|
RecordByType(DisplayListOpType::kDrawImageWithAttr);
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kDrawImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void drawImageRect(const sk_sp<DlImage> image,
|
||||||
|
const SkRect& src,
|
||||||
|
const SkRect& dst,
|
||||||
|
DlImageSampling sampling,
|
||||||
|
bool render_with_attributes,
|
||||||
|
SrcRectConstraint constraint) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawImageRect);
|
||||||
|
}
|
||||||
|
void drawImageNine(const sk_sp<DlImage> image,
|
||||||
|
const SkIRect& center,
|
||||||
|
const SkRect& dst,
|
||||||
|
DlFilterMode filter,
|
||||||
|
bool render_with_attributes) override {
|
||||||
|
if (render_with_attributes) {
|
||||||
|
RecordByType(DisplayListOpType::kDrawImageNineWithAttr);
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kDrawImageNine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void drawAtlas(const sk_sp<DlImage> atlas,
|
||||||
|
const SkRSXform xform[],
|
||||||
|
const SkRect tex[],
|
||||||
|
const DlColor colors[],
|
||||||
|
int count,
|
||||||
|
DlBlendMode mode,
|
||||||
|
DlImageSampling sampling,
|
||||||
|
const SkRect* cull_rect,
|
||||||
|
bool render_with_attributes) override {
|
||||||
|
if (cull_rect) {
|
||||||
|
RecordByType(DisplayListOpType::kDrawAtlasCulled);
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kDrawAtlas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void drawDisplayList(const sk_sp<DisplayList> display_list,
|
||||||
|
SkScalar opacity) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawDisplayList);
|
||||||
|
}
|
||||||
|
void drawTextBlob(const sk_sp<SkTextBlob> blob,
|
||||||
|
SkScalar x,
|
||||||
|
SkScalar y) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawTextBlob);
|
||||||
|
}
|
||||||
|
void drawTextFrame(const std::shared_ptr<impeller::TextFrame>& text_frame,
|
||||||
|
SkScalar x,
|
||||||
|
SkScalar y) override {
|
||||||
|
RecordByType(DisplayListOpType::kDrawTextFrame);
|
||||||
|
}
|
||||||
|
void drawShadow(const SkPath& path,
|
||||||
|
const DlColor color,
|
||||||
|
const SkScalar elevation,
|
||||||
|
bool transparent_occluder,
|
||||||
|
SkScalar dpr) override {
|
||||||
|
if (transparent_occluder) {
|
||||||
|
RecordByType(DisplayListOpType::kDrawShadowTransparentOccluder);
|
||||||
|
} else {
|
||||||
|
RecordByType(DisplayListOpType::kDrawShadow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetOpsReceived() { return op_count_; }
|
||||||
|
uint32_t GetOpsReceived(DisplayListOpCategory category) {
|
||||||
|
return category_counts_[static_cast<int>(category)];
|
||||||
|
}
|
||||||
|
uint32_t GetOpsReceived(DisplayListOpType type) {
|
||||||
|
return type_counts_[static_cast<int>(type)];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void RecordByType(DisplayListOpType type) {
|
||||||
|
type_counts_[static_cast<int>(type)]++;
|
||||||
|
RecordByCategory(DisplayList::GetOpCategory(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void RecordByCategory(DisplayListOpCategory category) {
|
||||||
|
category_counts_[static_cast<int>(category)]++;
|
||||||
|
switch (category) {
|
||||||
|
case DisplayListOpCategory::kAttribute:
|
||||||
|
RecordAttribute();
|
||||||
|
break;
|
||||||
|
case DisplayListOpCategory::kTransform:
|
||||||
|
RecordTransform();
|
||||||
|
break;
|
||||||
|
case DisplayListOpCategory::kClip:
|
||||||
|
RecordClip();
|
||||||
|
break;
|
||||||
|
case DisplayListOpCategory::kSave:
|
||||||
|
RecordSave();
|
||||||
|
break;
|
||||||
|
case DisplayListOpCategory::kSaveLayer:
|
||||||
|
RecordSaveLayer();
|
||||||
|
break;
|
||||||
|
case DisplayListOpCategory::kRestore:
|
||||||
|
RecordRestore();
|
||||||
|
break;
|
||||||
|
case DisplayListOpCategory::kRendering:
|
||||||
|
RecordRendering();
|
||||||
|
break;
|
||||||
|
case DisplayListOpCategory::kSubDisplayList:
|
||||||
|
RecordSubDisplayList();
|
||||||
|
break;
|
||||||
|
case DisplayListOpCategory::kInvalidCategory:
|
||||||
|
RecordInvalid();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void RecordAttribute() { RecordOp(); }
|
||||||
|
virtual void RecordTransform() { RecordOp(); }
|
||||||
|
virtual void RecordClip() { RecordOp(); }
|
||||||
|
virtual void RecordSave() { RecordOp(); }
|
||||||
|
virtual void RecordSaveLayer() { RecordOp(); }
|
||||||
|
virtual void RecordRestore() { RecordOp(); }
|
||||||
|
virtual void RecordRendering() { RecordOp(); }
|
||||||
|
virtual void RecordSubDisplayList() { RecordOp(); }
|
||||||
|
virtual void RecordInvalid() { RecordOp(); }
|
||||||
|
|
||||||
|
virtual void RecordOp() { op_count_++; }
|
||||||
|
|
||||||
|
static constexpr size_t kTypeCount =
|
||||||
|
static_cast<size_t>(DisplayListOpType::kMaxOp) + 1;
|
||||||
|
static constexpr size_t kCategoryCount =
|
||||||
|
static_cast<size_t>(DisplayListOpCategory::kMaxCategory) + 1;
|
||||||
|
|
||||||
|
std::array<uint32_t, kTypeCount> type_counts_;
|
||||||
|
std::array<uint32_t, kCategoryCount> category_counts_;
|
||||||
|
uint32_t op_count_ = 0u;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user