Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: API to track damage rectangles #21126

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions flow/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ source_set("flow") {
sources = [
"compositor_context.cc",
"compositor_context.h",
"damage_context.cc",
"damage_context.h",
"embedded_views.cc",
"embedded_views.h",
"gl_context_switch.cc",
Expand Down
47 changes: 43 additions & 4 deletions flow/compositor_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ std::unique_ptr<CompositorContext::ScopedFrame> CompositorContext::AcquireFrame(
bool surface_supports_readback,
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
return std::make_unique<ScopedFrame>(
*this, gr_context, canvas, view_embedder, root_surface_transformation,
instrumentation_enabled, surface_supports_readback, raster_thread_merger);
*this, gr_context, canvas, view_embedder, &damage_context_,
root_surface_transformation, instrumentation_enabled,
surface_supports_readback, raster_thread_merger);
}

CompositorContext::ScopedFrame::ScopedFrame(
CompositorContext& context,
GrDirectContext* gr_context,
SkCanvas* canvas,
ExternalViewEmbedder* view_embedder,
DamageContext* damage_context,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled,
bool surface_supports_readback,
Expand All @@ -56,6 +58,7 @@ CompositorContext::ScopedFrame::ScopedFrame(
gr_context_(gr_context),
canvas_(canvas),
view_embedder_(view_embedder),
damage_context_(damage_context),
root_surface_transformation_(root_surface_transformation),
instrumentation_enabled_(instrumentation_enabled),
surface_supports_readback_(surface_supports_readback),
Expand All @@ -69,9 +72,31 @@ CompositorContext::ScopedFrame::~ScopedFrame() {

RasterStatus CompositorContext::ScopedFrame::Raster(
flutter::LayerTree& layer_tree,
bool ignore_raster_cache) {
bool ignore_raster_cache,
FrameDamage* frame_damage) {
TRACE_EVENT0("flutter", "CompositorContext::ScopedFrame::Raster");
bool root_needs_readback = layer_tree.Preroll(*this, ignore_raster_cache);

std::optional<DamageArea> damage_area;

if (frame_damage) {
damage_context()->InitFrame(layer_tree.frame_size(),
frame_damage->previous_frame_description);
layer_tree.Preroll(*this, true);
auto damage_res = damage_context()->FinishFrame();
frame_damage->frame_description = std::move(damage_res.frame_description);

// only bother clipping when less than 80% of screen
if (damage_res.area.bounds().width() * damage_res.area.bounds().height() <
0.8 * layer_tree.frame_size().width() *
layer_tree.frame_size().height()) {
damage_area = std::move(damage_res.area);
}
}

auto cull_rect =
damage_area ? SkRect::Make(damage_area->bounds()) : kGiantRect;
bool root_needs_readback =
layer_tree.Preroll(*this, ignore_raster_cache, cull_rect);
bool needs_save_layer = root_needs_readback && !surface_supports_readback();
PostPrerollResult post_preroll_result = PostPrerollResult::kSuccess;
if (view_embedder_ && raster_thread_merger_) {
Expand All @@ -82,6 +107,12 @@ RasterStatus CompositorContext::ScopedFrame::Raster(
if (post_preroll_result == PostPrerollResult::kResubmitFrame) {
return RasterStatus::kResubmit;
}

if (canvas() && damage_area) {
canvas()->save();
canvas()->clipRect(SkRect::Make(damage_area->bounds()));
}

// Clearing canvas after preroll reduces one render target switch when preroll
// paints some raster cache.
if (canvas()) {
Expand All @@ -98,6 +129,14 @@ RasterStatus CompositorContext::ScopedFrame::Raster(
if (canvas() && needs_save_layer) {
canvas()->restore();
}
if (canvas() && damage_area) {
canvas()->restore();
}

if (frame_damage) {
frame_damage->damage_area = std::move(damage_area);
}

return RasterStatus::kSuccess;
}

Expand Down
21 changes: 20 additions & 1 deletion flow/compositor_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <memory>
#include <string>

#include "flutter/flow/damage_context.h"
#include "flutter/flow/embedded_views.h"
#include "flutter/flow/instrumentation.h"
#include "flutter/flow/raster_cache.h"
Expand Down Expand Up @@ -35,6 +36,18 @@ enum class RasterStatus {
kFailed
};

struct FrameDamage {
// in: description for frame previously rasterized in target framebuffer
const DamageContext::FrameDescription* previous_frame_description;

// out: description for frame being rasterized
std::unique_ptr<DamageContext::FrameDescription> frame_description;

// out: area in framebuffer that has changed between frames;
// if optional is empty, whole frame was repainted
std::optional<DamageArea> damage_area;
};

class CompositorContext {
public:
class ScopedFrame {
Expand All @@ -43,6 +56,7 @@ class CompositorContext {
GrDirectContext* gr_context,
SkCanvas* canvas,
ExternalViewEmbedder* view_embedder,
DamageContext* damage_context,
const SkMatrix& root_surface_transformation,
bool instrumentation_enabled,
bool surface_supports_readback,
Expand All @@ -54,6 +68,8 @@ class CompositorContext {

ExternalViewEmbedder* view_embedder() { return view_embedder_; }

DamageContext* damage_context() { return damage_context_; }

CompositorContext& context() const { return context_; }

const SkMatrix& root_surface_transformation() const {
Expand All @@ -65,13 +81,15 @@ class CompositorContext {
GrDirectContext* gr_context() const { return gr_context_; }

virtual RasterStatus Raster(LayerTree& layer_tree,
bool ignore_raster_cache);
bool ignore_raster_cache,
FrameDamage* frame_damage);

private:
CompositorContext& context_;
GrDirectContext* gr_context_;
SkCanvas* canvas_;
ExternalViewEmbedder* view_embedder_;
DamageContext* damage_context_;
const SkMatrix& root_surface_transformation_;
const bool instrumentation_enabled_;
const bool surface_supports_readback_;
Expand Down Expand Up @@ -110,6 +128,7 @@ class CompositorContext {
private:
RasterCache raster_cache_;
TextureRegistry texture_registry_;
DamageContext damage_context_;
Counter frame_count_;
Stopwatch raster_time_;
Stopwatch ui_time_;
Expand Down
200 changes: 200 additions & 0 deletions flow/damage_context.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#include "flutter/flow/damage_context.h"
#include "flutter/flow/layers/layer.h"
#include "third_party/skia/include/core/SkImageFilter.h"

namespace flutter {

std::size_t DamageContext::LayerContribution::Hash::operator()(
const LayerContribution& e) const noexcept {
size_t res = e.paint_bounds.left();
res = 37 * res + e.paint_bounds.top();
res = 37 * res + e.paint_bounds.width();
res = 37 * res + e.paint_bounds.height();
res = 37 * res + size_t(reinterpret_cast<std::uintptr_t>(e.comparator));
return res;
}

bool DamageContext::LayerContribution::operator==(
const LayerContribution& e) const {
return comparator == e.comparator && paint_bounds == e.paint_bounds &&
std::equal(
mutators.begin(), mutators.end(), e.mutators.begin(),
e.mutators.end(),
[](const std::shared_ptr<Mutator>& m1,
const std::shared_ptr<Mutator>& m2) { return *m1 == *m2; }) &&
(layer.get() == e.layer.get() ||
comparator(layer.get(), e.layer.get()));
}

void DamageContext::InitFrame(const SkISize& tree_size,
const FrameDescription* previous_frame) {
current_layer_tree_size_ = tree_size;
previous_frame_ = previous_frame;
}

void DamageContext::PushLayerContribution(const Layer* layer,
LayerComparator comparator,
const SkMatrix& matrix,
const PrerollContext& preroll_context,
size_t index) {
if (current_layer_tree_size_.isEmpty() || layer->paint_bounds().isEmpty()) {
return;
}

LayerContribution e;
e.layer = layer->shared_from_this();
e.comparator = comparator;

SkRect bounds = layer->paint_bounds();
bounds.intersect(preroll_context.cull_rect);
e.paint_bounds = matrix.mapRect(bounds);

for (auto i = preroll_context.mutators_stack.Begin();
i != preroll_context.mutators_stack.End(); ++i) {
auto type = (*i)->GetType();
// transforms are irrelevant because we compare paint_bounds in
// screen coordinates
if (type != MutatorType::transform) {
e.mutators.push_back(*i);
}
}
if (index == size_t(-1)) {
layer_entries_.push_back(std::move(e));
} else {
layer_entries_.insert(layer_entries_.begin() + index, std::move(e));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it isn't likely to be a problem in practice due to the tree-traversal nature of when this operation is called, an insertion here can invalidate tracking places by remembering a "count". It would be more stable to have layers that need to "go back and insert a contribution" to insert the contribution before recursing to children, or if they need to do it after the fact then to insert a dummy contribution to claim the slot that gets replaced after recursing to the children?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for this is that pushing layer contribution requires paint bounds. For some layers (i.e. image filter) those are known only after prerolling the children. So we first preroll the children to determine paint bounds and then contribute the layer at order below children.

}
}

bool DamageContext::ApplyImageFilter(size_t from,
size_t count,
const SkImageFilter* filter,
const SkMatrix& matrix,
const SkRect& bounds) {
SkMatrix inverted;
if (!matrix.invert(&inverted)) {
return false;
}

for (size_t i = from; i < count; ++i) {
auto& entry = layer_entries_[i];
SkRect layer_bounds(entry.paint_bounds);
inverted.mapRect(&layer_bounds);
if (layer_bounds.intersects(bounds)) {
// layer paint bounds after filter can not get bigger than union of
// original paint bounds and filter bounds
SkRect max(layer_bounds);
max.join(bounds);

layer_bounds = filter->computeFastBounds(layer_bounds);
layer_bounds.intersect(max);

matrix.mapRect(&layer_bounds);
entry.paint_bounds = layer_bounds;
}
}

return true;
}

void DamageArea::AddRect(const SkRect& rect) {
SkIRect irect;
rect.roundOut(&irect);
bounds_.join(irect);
}

void DamageArea::AddRect(const SkIRect& rect) {
bounds_.join(rect);
}

std::vector<SkIRect> DamageArea::GetRects() const {
std::vector<SkIRect> res;
res.push_back(bounds_);
return res;
}

DamageContext::DamageResult DamageContext::FinishFrame() {
DamageResult res;
res.frame_description.reset(new FrameDescription());
res.frame_description->layer_tree_size = current_layer_tree_size_;
auto& entries = res.frame_description->entries;

for (size_t i = 0; i < layer_entries_.size(); ++i) {
auto& entry = layer_entries_[i];
entry.paint_order = i;
entries.insert(std::move(entry));
}

if (!previous_frame_ ||
previous_frame_->layer_tree_size != current_layer_tree_size_) {
res.area.AddRect(SkRect::MakeIWH(current_layer_tree_size_.width(),
current_layer_tree_size_.height()));
} else {
// layer entries that are only found in one set (only this frame or only
// previous frame) are for layers that were either added, removed, or
// modified in any way (fail the equality check in LayerContribution) and
// thus contribute to damage area

// matching layer entries from previous frame and this frame; we still need
// to check for paint order to detect reordered layers
std::vector<const LayerContribution*> matching_previous;
std::vector<const LayerContribution*> matching_current;

for (const auto& l : entries) {
auto prev = previous_frame_->entries.find(l);
if (prev == previous_frame_->entries.end()) {
res.area.AddRect(l.paint_bounds);
} else {
matching_current.push_back(&l);
matching_previous.push_back(&*prev);
}
}
for (const auto& l : previous_frame_->entries) {
if (entries.find(l) == entries.end()) {
res.area.AddRect(l.paint_bounds);
}
}

// determine which layers are reordered
auto comparator = [](const LayerContribution* l1,
const LayerContribution* l2) {
return l1->paint_order < l2->paint_order;
};
std::sort(matching_previous.begin(), matching_previous.end(), comparator);
std::sort(matching_current.begin(), matching_current.end(), comparator);

// We have two sets of matching layer entries that possibly differ in paint
// order and we sorted them by paint order, i.e.
// B C D E
// ^-- prev
// C D B E
// ^-- cur
// 1. move cur until match with prev is found (B)
// all layers before cur that intersect with prev are reordered and will
// contribute to damage, as does prev
// 2. remove prev and cur (now both pointing at B)
// 3. repeat until empty
// (except we actually do it in reverse to not erase from beginning)
while (!matching_previous.empty()) {
auto prev = matching_previous.end() - 1;
auto cur = matching_current.end() - 1;

while (*(*prev) != *(*cur)) {
if ((*prev)->paint_bounds.intersects((*cur)->paint_bounds)) {
res.area.AddRect((*prev)->paint_bounds);
res.area.AddRect((*cur)->paint_bounds);
}
--cur;
}
matching_previous.erase(prev);
matching_current.erase(cur);
}
}

previous_frame_ = nullptr;
layer_entries_.clear();
current_layer_tree_size_ = SkISize::MakeEmpty();

return res;
}

} // namespace flutter
Loading