From 93f5730af51af283713ad19dd5e08c3b3ea34eaa Mon Sep 17 00:00:00 2001 From: danakj Date: Sat, 25 Apr 2015 10:53:17 -0700 Subject: [PATCH] ui: Cache the output of View::OnPaint when the View isn't invalid. Store the output of View::OnPaint as a PaintCache object, and when the view has not been invalidated, use that cache output instead of doing work to build a recording. Performance data as follows when the loading spinner is going: Before impl-side (TOT): 0.13ms / paint With impl-side (no slimming paint): 0.22ms / paint With impl-side and slimming paint (this patch): 0.17ms / paint So this gets us some of the way there. I need to investigate why it's not doing more. R=piman@chromium.org, sky BUG=466426 Committed: https://crrev.com/7f686cdcff81d6779b962e98e529a7360be2809c Cr-Commit-Position: refs/heads/master@{#326592} Review URL: https://codereview.chromium.org/1101783002 Cr-Commit-Position: refs/heads/master@{#326976} --- cc/resources/drawing_display_item.cc | 4 ++++ cc/resources/drawing_display_item.h | 2 ++ ui/compositor/BUILD.gn | 2 ++ ui/compositor/compositor.gyp | 2 ++ ui/compositor/layer.cc | 9 ++++---- ui/compositor/paint_cache.cc | 31 +++++++++++++++++++++++++ ui/compositor/paint_cache.h | 45 ++++++++++++++++++++++++++++++++++++ ui/compositor/paint_context.h | 16 +++++++++---- ui/compositor/paint_recorder.cc | 21 ++++++++++++----- ui/compositor/paint_recorder.h | 5 ++++ ui/views/view.cc | 13 +++++++---- ui/views/view.h | 4 ++++ 12 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 ui/compositor/paint_cache.cc create mode 100644 ui/compositor/paint_cache.h diff --git a/cc/resources/drawing_display_item.cc b/cc/resources/drawing_display_item.cc index 91ab3fb0d381..a37729f7bd45 100644 --- a/cc/resources/drawing_display_item.cc +++ b/cc/resources/drawing_display_item.cc @@ -62,4 +62,8 @@ void DrawingDisplayItem::AsValueInto( array->EndDictionary(); } +scoped_ptr DrawingDisplayItem::Clone() { + return Create(picture_); +} + } // namespace cc diff --git a/cc/resources/drawing_display_item.h b/cc/resources/drawing_display_item.h index a3eef7711a0a..da302d87d9c8 100644 --- a/cc/resources/drawing_display_item.h +++ b/cc/resources/drawing_display_item.h @@ -33,6 +33,8 @@ class CC_EXPORT DrawingDisplayItem : public DisplayItem { size_t PictureMemoryUsage() const override; void AsValueInto(base::trace_event::TracedValue* array) const override; + scoped_ptr Clone(); + protected: explicit DrawingDisplayItem(skia::RefPtr picture); diff --git a/ui/compositor/BUILD.gn b/ui/compositor/BUILD.gn index 590cb7326e76..a0944fe78494 100644 --- a/ui/compositor/BUILD.gn +++ b/ui/compositor/BUILD.gn @@ -47,6 +47,8 @@ component("compositor") { "layer_tree_owner.cc", "layer_tree_owner.h", "layer_type.h", + "paint_cache.cc", + "paint_cache.h", "paint_context.cc", "paint_context.h", "paint_recorder.cc", diff --git a/ui/compositor/compositor.gyp b/ui/compositor/compositor.gyp index 11d6723e492a..92f5ef4e5111 100644 --- a/ui/compositor/compositor.gyp +++ b/ui/compositor/compositor.gyp @@ -65,6 +65,8 @@ 'layer_tree_owner.cc', 'layer_tree_owner.h', 'layer_type.h', + 'paint_cache.cc', + 'paint_cache.h', 'paint_context.cc', 'paint_context.h', 'paint_recorder.cc', diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc index 914e000feedf..2b6f9219c574 100644 --- a/ui/compositor/layer.cc +++ b/ui/compositor/layer.cc @@ -750,14 +750,13 @@ void Layer::PaintContentsToDisplayList( const gfx::Rect& clip, ContentLayerClient::PaintingControlSetting painting_control) { TRACE_EVENT1("ui", "Layer::PaintContentsToDisplayList", "name", name_); + gfx::Rect local_bounds(bounds().size()); + gfx::Rect invalidation( + gfx::IntersectRects(damaged_region_.bounds(), local_bounds)); + DCHECK(clip.Contains(invalidation)); ClearDamagedRects(); if (!delegate_) return; - // TODO(danakj): Save the invalidation on the layer and pass that down - // instead of the |clip| here. That will break everything until View - // early-outs emit cached display items instead of nothing. - gfx::Rect invalidation = clip; - DCHECK(clip.Contains(invalidation)); delegate_->OnPaintLayer( PaintContext(display_list, device_scale_factor_, clip, invalidation)); } diff --git a/ui/compositor/paint_cache.cc b/ui/compositor/paint_cache.cc new file mode 100644 index 000000000000..ef0e5a72e874 --- /dev/null +++ b/ui/compositor/paint_cache.cc @@ -0,0 +1,31 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/compositor/paint_cache.h" + +#include "cc/resources/display_item_list.h" +#include "cc/resources/drawing_display_item.h" +#include "ui/compositor/paint_context.h" + +namespace ui { + +PaintCache::PaintCache() { +} + +PaintCache::~PaintCache() { +} + +bool PaintCache::UseCache(const PaintContext& context) { + if (!display_item_) + return false; + DCHECK(context.list_); + context.list_->AppendItem(display_item_->Clone()); + return true; +} + +void PaintCache::SetCache(scoped_ptr item) { + display_item_ = item.Pass(); +} + +} // namespace ui diff --git a/ui/compositor/paint_cache.h b/ui/compositor/paint_cache.h new file mode 100644 index 000000000000..599a8ac35534 --- /dev/null +++ b/ui/compositor/paint_cache.h @@ -0,0 +1,45 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_COMPOSITOR_PAINT_CACHE_H_ +#define UI_COMPOSITOR_PAINT_CACHE_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/compositor/compositor_export.h" + +namespace cc { +class DrawingDisplayItem; +} + +namespace ui { +class PaintContext; +class PaintRecorder; + +// A class that holds the output of a PaintRecorder to be reused when the +// object that created the PaintRecorder has not been changed/invalidated. +class COMPOSITOR_EXPORT PaintCache { + public: + PaintCache(); + ~PaintCache(); + + // Returns true if the PaintCache was able to insert a previously-saved + // painting output into the PaintContext. If it returns false, the caller + // needs to do the work of painting, which can be stored into the PaintCache + // to be used next time. + bool UseCache(const PaintContext& context); + + private: + // Only PaintRecorder can modify these. + friend PaintRecorder; + + void SetCache(scoped_ptr item); + + scoped_ptr display_item_; + + DISALLOW_COPY_AND_ASSIGN(PaintCache); +}; + +} // namespace ui + +#endif // UI_COMPOSITOR_PAINT_CACHE_H_ diff --git a/ui/compositor/paint_context.h b/ui/compositor/paint_context.h index d63f11f7852b..a754ee469858 100644 --- a/ui/compositor/paint_context.h +++ b/ui/compositor/paint_context.h @@ -56,15 +56,20 @@ class COMPOSITOR_EXPORT PaintContext { return PaintContext(canvas_); } - // When true, IsRectInvalidated() can be called, otherwise its result would be + // When true, IsRectInvalid() can be called, otherwise its result would be // invalid. - bool CanCheckInvalidated() const { return !invalidation_.IsEmpty(); } + bool CanCheckInvalid() const { return !invalidation_.IsEmpty(); } + + // When true, if a thing is not invalidated it does not need to paint itself. + // When false, everything should provide an output when painting regardless of + // being invalidated in order to remain visible. + bool ShouldEarlyOutOfPaintingWhenValid() const { return !!canvas_; } // When true, the |bounds| touches an invalidated area, so should be // re-painted. When false, re-painting can be skipped. Bounds should be in // the local space with offsets up to the painting root in the PaintContext. - bool IsRectInvalidated(const gfx::Rect& bounds) const { - DCHECK(CanCheckInvalidated()); + bool IsRectInvalid(const gfx::Rect& bounds) const { + DCHECK(CanCheckInvalid()); return invalidation_.Intersects(bounds + offset_); } @@ -86,6 +91,9 @@ class COMPOSITOR_EXPORT PaintContext { friend class ClipTransformRecorder; friend class CompositingRecorder; friend class PaintRecorder; + // The Cache class also needs to access the DisplayItemList to append its + // cache contents. + friend class PaintCache; PaintContext& operator=(const PaintContext& other) = delete; diff --git a/ui/compositor/paint_recorder.cc b/ui/compositor/paint_recorder.cc index 15aa0fb8edff..80fb616a35fa 100644 --- a/ui/compositor/paint_recorder.cc +++ b/ui/compositor/paint_recorder.cc @@ -7,14 +7,15 @@ #include "cc/resources/display_item_list.h" #include "cc/resources/drawing_display_item.h" #include "third_party/skia/include/core/SkPictureRecorder.h" +#include "ui/compositor/paint_cache.h" #include "ui/compositor/paint_context.h" #include "ui/gfx/canvas.h" #include "ui/gfx/skia_util.h" namespace ui { -PaintRecorder::PaintRecorder(const PaintContext& context) - : context_(context), canvas_(context.canvas_) { +PaintRecorder::PaintRecorder(const PaintContext& context, PaintCache* cache) + : context_(context), canvas_(context.canvas_), cache_(cache) { #if DCHECK_IS_ON() DCHECK(!context.inside_paint_recorder_); context.inside_paint_recorder_ = true; @@ -34,15 +35,23 @@ PaintRecorder::PaintRecorder(const PaintContext& context) } } +PaintRecorder::PaintRecorder(const PaintContext& context) + : PaintRecorder(context, nullptr) { +} + PaintRecorder::~PaintRecorder() { #if DCHECK_IS_ON() context_.inside_paint_recorder_ = false; #endif - if (context_.list_) { - context_.list_->AppendItem(cc::DrawingDisplayItem::Create( - skia::AdoptRef(context_.recorder_->endRecordingAsPicture()))); - } + if (!context_.list_) + return; + + scoped_ptr item = cc::DrawingDisplayItem::Create( + skia::AdoptRef(context_.recorder_->endRecordingAsPicture())); + if (cache_) + cache_->SetCache(item->Clone()); + context_.list_->AppendItem(item.Pass()); } } // namespace ui diff --git a/ui/compositor/paint_recorder.h b/ui/compositor/paint_recorder.h index 7c3df82b9056..9cac7a86f443 100644 --- a/ui/compositor/paint_recorder.h +++ b/ui/compositor/paint_recorder.h @@ -22,6 +22,7 @@ class SkCanvas; class SkPictureRecorder; namespace ui { +class PaintCache; class PaintContext; // A class to hide the complexity behind setting up a recording into a @@ -30,6 +31,9 @@ class PaintContext; // recording is complete and can be cached. class COMPOSITOR_EXPORT PaintRecorder { public: + // The |cache| is owned by the caller and must be kept alive while + // PaintRecorder is in use. + PaintRecorder(const PaintContext& context, PaintCache* cache); explicit PaintRecorder(const PaintContext& context); ~PaintRecorder(); @@ -40,6 +44,7 @@ class COMPOSITOR_EXPORT PaintRecorder { const PaintContext& context_; gfx::Canvas* canvas_; scoped_ptr owned_canvas_; + PaintCache* cache_; DISALLOW_COPY_AND_ASSIGN(PaintRecorder); }; diff --git a/ui/views/view.cc b/ui/views/view.cc index 78659caf418c..c79cc6c85de1 100644 --- a/ui/views/view.cc +++ b/ui/views/view.cc @@ -748,7 +748,8 @@ void View::Paint(const ui::PaintContext& parent_context) { ui::PaintContext context = parent_context.CloneWithPaintOffset(offset_to_parent); - if (context.CanCheckInvalidated()) { + bool is_invalidated = true; + if (context.CanCheckInvalid()) { #if DCHECK_IS_ON() gfx::Vector2d offset; context.Visited(this); @@ -770,10 +771,12 @@ void View::Paint(const ui::PaintContext& parent_context) { // If the View wasn't invalidated, don't waste time painting it, the output // would be culled. - if (!context.IsRectInvalidated(GetLocalBounds())) - return; + is_invalidated = context.IsRectInvalid(GetLocalBounds()); } + if (!is_invalidated && context.ShouldEarlyOutOfPaintingWhenValid()) + return; + TRACE_EVENT1("views", "View::Paint", "class", GetClassName()); // If the view is backed by a layer, it should paint with itself as the origin @@ -803,8 +806,8 @@ void View::Paint(const ui::PaintContext& parent_context) { clip_transform_recorder->Transform(transform_from_parent); } - { - ui::PaintRecorder recorder(context); + if (is_invalidated || !paint_cache_.UseCache(context)) { + ui::PaintRecorder recorder(context, &paint_cache_); gfx::Canvas* canvas = recorder.canvas(); gfx::ScopedCanvas scoped_canvas(canvas); diff --git a/ui/views/view.h b/ui/views/view.h index 00813e06a700..1a56db579bfc 100644 --- a/ui/views/view.h +++ b/ui/views/view.h @@ -24,6 +24,7 @@ #include "ui/base/ui_base_types.h" #include "ui/compositor/layer_delegate.h" #include "ui/compositor/layer_owner.h" +#include "ui/compositor/paint_cache.h" #include "ui/events/event.h" #include "ui/events/event_target.h" #include "ui/gfx/geometry/insets.h" @@ -1509,6 +1510,9 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, // Border. scoped_ptr border_; + // Cached output of painting to be reused in future frames until invalidated. + ui::PaintCache paint_cache_; + // RTL painting -------------------------------------------------------------- // Indicates whether or not the gfx::Canvas object passed to View::Paint() -- 2.11.4.GIT