diff options
Diffstat (limited to 'cc/layers')
91 files changed, 19572 insertions, 0 deletions
diff --git a/cc/layers/append_quads_data.h b/cc/layers/append_quads_data.h new file mode 100644 index 0000000..18580a9 --- /dev/null +++ b/cc/layers/append_quads_data.h @@ -0,0 +1,41 @@ +// Copyright 2012 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 CC_LAYERS_APPEND_QUADS_DATA_H_ +#define CC_LAYERS_APPEND_QUADS_DATA_H_ + +#include "base/basictypes.h" +#include "cc/quads/render_pass.h" + +namespace cc { + +struct AppendQuadsData { + AppendQuadsData() + : hadOcclusionFromOutsideTargetSurface(false) + , hadIncompleteTile(false) + , numMissingTiles(0) + , renderPassId(0, 0) + { + } + + explicit AppendQuadsData(RenderPass::Id renderPassId) + : hadOcclusionFromOutsideTargetSurface(false) + , hadIncompleteTile(false) + , numMissingTiles(0) + , renderPassId(renderPassId) + { + } + + // Set by the QuadCuller. + bool hadOcclusionFromOutsideTargetSurface; + // Set by the layer appending quads. + bool hadIncompleteTile; + // Set by the layer appending quads. + int64 numMissingTiles; + // Given to the layer appending quads. + const RenderPass::Id renderPassId; +}; + +} +#endif // CC_LAYERS_APPEND_QUADS_DATA_H_ diff --git a/cc/layers/content_layer.cc b/cc/layers/content_layer.cc new file mode 100644 index 0000000..b6c8d7b --- /dev/null +++ b/cc/layers/content_layer.cc @@ -0,0 +1,114 @@ +// Copyright 2010 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 "cc/layers/content_layer.h" + +#include "base/auto_reset.h" +#include "base/metrics/histogram.h" +#include "base/time.h" +#include "cc/layers/content_layer_client.h" +#include "cc/resources/bitmap_content_layer_updater.h" +#include "cc/resources/bitmap_skpicture_content_layer_updater.h" +#include "cc/resources/layer_painter.h" +#include "cc/trees/layer_tree_host.h" + +namespace cc { + +ContentLayerPainter::ContentLayerPainter(ContentLayerClient* client) + : client_(client) {} + +scoped_ptr<ContentLayerPainter> ContentLayerPainter::Create( + ContentLayerClient* client) { + return make_scoped_ptr(new ContentLayerPainter(client)); +} + +void ContentLayerPainter::Paint(SkCanvas* canvas, + gfx::Rect content_rect, + gfx::RectF* opaque) { + base::TimeTicks paint_start = base::TimeTicks::HighResNow(); + client_->PaintContents(canvas, content_rect, opaque); + base::TimeTicks paint_end = base::TimeTicks::HighResNow(); + double pixels_per_sec = (content_rect.width() * content_rect.height()) / + (paint_end - paint_start).InSecondsF(); + UMA_HISTOGRAM_CUSTOM_COUNTS("Renderer4.AccelContentPaintDurationMS", + (paint_end - paint_start).InMilliseconds(), + 0, + 120, + 30); + UMA_HISTOGRAM_CUSTOM_COUNTS("Renderer4.AccelContentPaintMegapixPerSecond", + pixels_per_sec / 1000000, + 10, + 210, + 30); +} + +scoped_refptr<ContentLayer> ContentLayer::Create(ContentLayerClient* client) { + return make_scoped_refptr(new ContentLayer(client)); +} + +ContentLayer::ContentLayer(ContentLayerClient* client) + : TiledLayer(), + client_(client) {} + +ContentLayer::~ContentLayer() {} + +bool ContentLayer::DrawsContent() const { + return TiledLayer::DrawsContent() && client_; +} + +void ContentLayer::SetTexturePriorities( + const PriorityCalculator& priority_calc) { + // Update the tile data before creating all the layer's tiles. + UpdateTileSizeAndTilingOption(); + + TiledLayer::SetTexturePriorities(priority_calc); +} + +void ContentLayer::Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) { + { + base::AutoReset<bool> ignore_set_needs_commit(&ignore_set_needs_commit_, + true); + + CreateUpdaterIfNeeded(); + } + + TiledLayer::Update(queue, occlusion, stats); + needs_display_ = false; +} + +bool ContentLayer::NeedMoreUpdates() { + return NeedsIdlePaint(); +} + +LayerUpdater* ContentLayer::Updater() const { + return updater_.get(); +} + +void ContentLayer::CreateUpdaterIfNeeded() { + if (updater_) + return; + scoped_ptr<LayerPainter> painter = + ContentLayerPainter::Create(client_).PassAs<LayerPainter>(); + if (layer_tree_host()->settings().acceleratePainting) + updater_ = SkPictureContentLayerUpdater::Create(painter.Pass()); + else if (layer_tree_host()->settings().perTilePaintingEnabled) + updater_ = BitmapSkPictureContentLayerUpdater::Create(painter.Pass()); + else + updater_ = BitmapContentLayerUpdater::Create(painter.Pass()); + updater_->SetOpaque(contents_opaque()); + + unsigned texture_format = + layer_tree_host()->GetRendererCapabilities().best_texture_format; + SetTextureFormat(texture_format); +} + +void ContentLayer::SetContentsOpaque(bool opaque) { + Layer::SetContentsOpaque(opaque); + if (updater_) + updater_->SetOpaque(opaque); +} + +} // namespace cc diff --git a/cc/layers/content_layer.h b/cc/layers/content_layer.h new file mode 100644 index 0000000..94db0bf --- /dev/null +++ b/cc/layers/content_layer.h @@ -0,0 +1,69 @@ +// Copyright 2010 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 CC_LAYERS_CONTENT_LAYER_H_ +#define CC_LAYERS_CONTENT_LAYER_H_ + +#include "base/basictypes.h" +#include "cc/base/cc_export.h" +#include "cc/layers/tiled_layer.h" +#include "cc/resources/layer_painter.h" + +class SkCanvas; + +namespace cc { + +class ContentLayerClient; +class LayerUpdater; + +class CC_EXPORT ContentLayerPainter : public LayerPainter { + public: + static scoped_ptr<ContentLayerPainter> Create(ContentLayerClient* client); + + virtual void Paint(SkCanvas* canvas, + gfx::Rect content_rect, + gfx::RectF* opaque) OVERRIDE; + + private: + explicit ContentLayerPainter(ContentLayerClient* client); + + ContentLayerClient* client_; + + DISALLOW_COPY_AND_ASSIGN(ContentLayerPainter); +}; + +// A layer that renders its contents into an SkCanvas. +class CC_EXPORT ContentLayer : public TiledLayer { + public: + static scoped_refptr<ContentLayer> Create(ContentLayerClient* client); + + void ClearClient() { client_ = NULL; } + + virtual bool DrawsContent() const OVERRIDE; + virtual void SetTexturePriorities(const PriorityCalculator& priority_calc) + OVERRIDE; + virtual void Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) OVERRIDE; + virtual bool NeedMoreUpdates() OVERRIDE; + + virtual void SetContentsOpaque(bool contents_opaque) OVERRIDE; + + protected: + explicit ContentLayer(ContentLayerClient* client); + virtual ~ContentLayer(); + + private: + // TiledLayer implementation. + virtual LayerUpdater* Updater() const OVERRIDE; + virtual void CreateUpdaterIfNeeded() OVERRIDE; + + ContentLayerClient* client_; + scoped_refptr<LayerUpdater> updater_; + + DISALLOW_COPY_AND_ASSIGN(ContentLayer); +}; + +} +#endif // CC_LAYERS_CONTENT_LAYER_H_ diff --git a/cc/layers/content_layer_client.h b/cc/layers/content_layer_client.h new file mode 100644 index 0000000..7d51fd9 --- /dev/null +++ b/cc/layers/content_layer_client.h @@ -0,0 +1,31 @@ +// Copyright 2012 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 CC_LAYERS_CONTENT_LAYER_CLIENT_H_ +#define CC_LAYERS_CONTENT_LAYER_CLIENT_H_ + +#include "cc/base/cc_export.h" + +class SkCanvas; + +namespace gfx { +class Rect; +class RectF; +} + +namespace cc { + +class CC_EXPORT ContentLayerClient { + public: + virtual void PaintContents(SkCanvas* canvas, + gfx::Rect clip, + gfx::RectF* opaque) = 0; + + protected: + virtual ~ContentLayerClient() {} +}; + +} + +#endif // CC_LAYERS_CONTENT_LAYER_CLIENT_H_ diff --git a/cc/layers/content_layer_unittest.cc b/cc/layers/content_layer_unittest.cc new file mode 100644 index 0000000..259e44f --- /dev/null +++ b/cc/layers/content_layer_unittest.cc @@ -0,0 +1,58 @@ +// Copyright 2012 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 "cc/layers/content_layer.h" + +#include "cc/layers/content_layer_client.h" +#include "cc/resources/bitmap_content_layer_updater.h" +#include "cc/test/geometry_test_utils.h" +#include "skia/ext/platform_canvas.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect_conversions.h" + +using namespace WebKit; + +namespace cc { +namespace { + +class MockContentLayerClient : public ContentLayerClient { + public: + explicit MockContentLayerClient(gfx::Rect opaque_layer_rect) + : opaque_layer_rect_(opaque_layer_rect) {} + + virtual void PaintContents(SkCanvas* canvas, + gfx::Rect clip, + gfx::RectF* opaque) OVERRIDE { + *opaque = gfx::RectF(opaque_layer_rect_); + } + + private: + gfx::Rect opaque_layer_rect_; +}; + +TEST(ContentLayerTest, ContentLayerPainterWithDeviceScale) { + float contents_scale = 2.f; + gfx::Rect content_rect(10, 10, 100, 100); + gfx::Rect opaque_rect_in_layer_space(5, 5, 20, 20); + gfx::RectF opaque_rect_in_content_space = gfx::ScaleRect( + opaque_rect_in_layer_space, contents_scale, contents_scale); + MockContentLayerClient client(opaque_rect_in_layer_space); + scoped_refptr<BitmapContentLayerUpdater> updater = + BitmapContentLayerUpdater::Create(ContentLayerPainter::Create(&client). + PassAs<LayerPainter>()); + + gfx::Rect resulting_opaque_rect; + updater->PrepareToUpdate(content_rect, + gfx::Size(256, 256), + contents_scale, + contents_scale, + &resulting_opaque_rect, + NULL); + + EXPECT_RECT_EQ(gfx::ToEnclosingRect(opaque_rect_in_content_space), + resulting_opaque_rect); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/contents_scaling_layer.cc b/cc/layers/contents_scaling_layer.cc new file mode 100644 index 0000000..d602e88 --- /dev/null +++ b/cc/layers/contents_scaling_layer.cc @@ -0,0 +1,50 @@ +// Copyright 2012 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 "cc/layers/contents_scaling_layer.h" +#include "ui/gfx/size_conversions.h" + +namespace cc { + +gfx::Size ContentsScalingLayer::ComputeContentBoundsForScale( + float scale_x, + float scale_y) const { + return gfx::ToCeiledSize(gfx::ScaleSize(bounds(), scale_x, scale_y)); +} + +ContentsScalingLayer::ContentsScalingLayer() + : last_update_contents_scale_x_(0.f), + last_update_contents_scale_y_(0.f) {} + +ContentsScalingLayer::~ContentsScalingLayer() { +} + +void ContentsScalingLayer::CalculateContentsScale( + float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* content_bounds) { + *contents_scale_x = ideal_contents_scale; + *contents_scale_y = ideal_contents_scale; + *content_bounds = ComputeContentBoundsForScale( + ideal_contents_scale, + ideal_contents_scale); +} + +void ContentsScalingLayer::Update( + ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) { + if (draw_properties().contents_scale_x == last_update_contents_scale_x_ && + draw_properties().contents_scale_y == last_update_contents_scale_y_) + return; + + last_update_contents_scale_x_ = draw_properties().contents_scale_x; + last_update_contents_scale_y_ = draw_properties().contents_scale_y; + // Invalidate the whole layer if scale changed. + SetNeedsDisplayRect(gfx::Rect(bounds())); +} + +} // namespace cc diff --git a/cc/layers/contents_scaling_layer.h b/cc/layers/contents_scaling_layer.h new file mode 100644 index 0000000..a550c26 --- /dev/null +++ b/cc/layers/contents_scaling_layer.h @@ -0,0 +1,42 @@ +// Copyright 2012 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 CC_LAYERS_CONTENTS_SCALING_LAYER_H_ +#define CC_LAYERS_CONTENTS_SCALING_LAYER_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/layer.h" + +namespace cc { + +// Base class for layers that need contents scale. +// The content bounds are determined by bounds and scale of the contents. +class CC_EXPORT ContentsScalingLayer : public Layer { + public: + virtual void CalculateContentsScale( + float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* content_bounds) OVERRIDE; + + virtual void Update( + ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) OVERRIDE; + + protected: + ContentsScalingLayer(); + virtual ~ContentsScalingLayer(); + + gfx::Size ComputeContentBoundsForScale(float scale_x, float scale_y) const; + + private: + float last_update_contents_scale_x_; + float last_update_contents_scale_y_; +}; + +} // namespace cc + +#endif // CC_LAYERS_CONTENTS_SCALING_LAYER_H__ diff --git a/cc/layers/contents_scaling_layer_unittest.cc b/cc/layers/contents_scaling_layer_unittest.cc new file mode 100644 index 0000000..69710d8 --- /dev/null +++ b/cc/layers/contents_scaling_layer_unittest.cc @@ -0,0 +1,88 @@ +// Copyright 2012 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 "cc/layers/contents_scaling_layer.h" + +#include "cc/test/geometry_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cc { +namespace { + +class MockContentsScalingLayer : public ContentsScalingLayer { + public: + MockContentsScalingLayer() + : ContentsScalingLayer() {} + + virtual void SetNeedsDisplayRect(const gfx::RectF& dirty_rect) OVERRIDE { + last_needs_display_rect_ = dirty_rect; + ContentsScalingLayer::SetNeedsDisplayRect(dirty_rect); + } + + void ResetNeedsDisplay() { + needs_display_ = false; + } + + const gfx::RectF& LastNeedsDisplayRect() const { + return last_needs_display_rect_; + } + + void UpdateContentsScale(float contents_scale) { + // Simulate CalcDrawProperties. + CalculateContentsScale( + contents_scale, + false, // animating_transform_to_screen + &draw_properties().contents_scale_x, + &draw_properties().contents_scale_y, + &draw_properties().content_bounds); + } + + private: + virtual ~MockContentsScalingLayer() {} + + gfx::RectF last_needs_display_rect_; +}; + +void CalcDrawProps(Layer* root, float device_scale) { + std::vector<scoped_refptr<Layer> > render_surface_layer_list; + LayerTreeHostCommon::calculateDrawProperties( + root, + gfx::Size(500, 500), + device_scale, + 1.f, + 1024, + false, + render_surface_layer_list); +} + +TEST(ContentsScalingLayerTest, CheckContentsBounds) { + scoped_refptr<MockContentsScalingLayer> test_layer = + make_scoped_refptr(new MockContentsScalingLayer()); + + scoped_refptr<Layer> root = Layer::Create(); + root->AddChild(test_layer); + + test_layer->SetBounds(gfx::Size(320, 240)); + CalcDrawProps(root, 1.f); + EXPECT_FLOAT_EQ(1.f, test_layer->contents_scale_x()); + EXPECT_FLOAT_EQ(1.f, test_layer->contents_scale_y()); + EXPECT_EQ(320, test_layer->content_bounds().width()); + EXPECT_EQ(240, test_layer->content_bounds().height()); + + CalcDrawProps(root, 2.f); + EXPECT_EQ(640, test_layer->content_bounds().width()); + EXPECT_EQ(480, test_layer->content_bounds().height()); + + test_layer->SetBounds(gfx::Size(10, 20)); + CalcDrawProps(root, 2.f); + EXPECT_EQ(20, test_layer->content_bounds().width()); + EXPECT_EQ(40, test_layer->content_bounds().height()); + + CalcDrawProps(root, 1.33f); + EXPECT_EQ(14, test_layer->content_bounds().width()); + EXPECT_EQ(27, test_layer->content_bounds().height()); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/delegated_renderer_layer.cc b/cc/layers/delegated_renderer_layer.cc new file mode 100644 index 0000000..37da92a --- /dev/null +++ b/cc/layers/delegated_renderer_layer.cc @@ -0,0 +1,103 @@ +// Copyright 2012 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 "cc/layers/delegated_renderer_layer.h" + +#include "cc/layers/delegated_renderer_layer_impl.h" +#include "cc/output/delegated_frame_data.h" + +namespace cc { + +scoped_refptr<DelegatedRendererLayer> DelegatedRendererLayer::Create() { + return scoped_refptr<DelegatedRendererLayer>(new DelegatedRendererLayer()); +} + +DelegatedRendererLayer::DelegatedRendererLayer() + : Layer() { +} + +DelegatedRendererLayer::~DelegatedRendererLayer() {} + +scoped_ptr<LayerImpl> DelegatedRendererLayer::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return DelegatedRendererLayerImpl::Create( + tree_impl, layer_id_).PassAs<LayerImpl>(); +} + +bool DelegatedRendererLayer::DrawsContent() const { + return Layer::DrawsContent() && !frame_size_.IsEmpty(); +} + +void DelegatedRendererLayer::PushPropertiesTo(LayerImpl* impl) { + Layer::PushPropertiesTo(impl); + + DelegatedRendererLayerImpl* delegated_impl = + static_cast<DelegatedRendererLayerImpl*>(impl); + + delegated_impl->SetDisplaySize(display_size_); + + if (!frame_data_) { + delegated_impl->SetFrameData(scoped_ptr<DelegatedFrameData>(), + gfx::Rect(), + &unused_resources_for_child_compositor_); + } else if (frame_size_.IsEmpty()) { + scoped_ptr<DelegatedFrameData> empty_frame(new DelegatedFrameData); + delegated_impl->SetFrameData(empty_frame.Pass(), + gfx::Rect(), + &unused_resources_for_child_compositor_); + } else { + delegated_impl->SetFrameData(frame_data_.Pass(), + damage_in_frame_, + &unused_resources_for_child_compositor_); + } + frame_data_.reset(); + damage_in_frame_ = gfx::RectF(); +} + +void DelegatedRendererLayer::SetDisplaySize(gfx::Size size) { + if (display_size_ == size) + return; + display_size_ = size; + SetNeedsCommit(); +} + +void DelegatedRendererLayer::SetFrameData( + scoped_ptr<DelegatedFrameData> new_frame_data) { + if (frame_data_) { + // Copy the resources from the last provided frame into the new frame, as + // it may use resources that were transferred in the last frame. + new_frame_data->resource_list.insert(new_frame_data->resource_list.end(), + frame_data_->resource_list.begin(), + frame_data_->resource_list.end()); + } + frame_data_ = new_frame_data.Pass(); + if (!frame_data_->render_pass_list.empty()) { + RenderPass* root_pass = frame_data_->render_pass_list.back(); + damage_in_frame_.Union(root_pass->damage_rect); + frame_size_ = root_pass->output_rect.size(); + + // TODO(danakj): This could be optimized to only add resources to the + // frame_data_ if they are actually used in the frame. For now, it will + // cause the parent (this layer) to hold onto some resources it doesn't + // need to for an extra frame. + for (size_t i = 0; i < unused_resources_for_child_compositor_.size(); ++i) { + frame_data_->resource_list.push_back( + unused_resources_for_child_compositor_[i]); + } + unused_resources_for_child_compositor_.clear(); + } else { + frame_size_ = gfx::Size(); + } + SetNeedsCommit(); +} + +void DelegatedRendererLayer::TakeUnusedResourcesForChildCompositor( + TransferableResourceArray* array) { + DCHECK(array->empty()); + array->clear(); + + array->swap(unused_resources_for_child_compositor_); +} + +} // namespace cc diff --git a/cc/layers/delegated_renderer_layer.h b/cc/layers/delegated_renderer_layer.h new file mode 100644 index 0000000..a789317 --- /dev/null +++ b/cc/layers/delegated_renderer_layer.h @@ -0,0 +1,49 @@ +// Copyright 2012 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 CC_LAYERS_DELEGATED_RENDERER_LAYER_H_ +#define CC_LAYERS_DELEGATED_RENDERER_LAYER_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/layer.h" +#include "cc/resources/transferable_resource.h" + +namespace cc { +class DelegatedFrameData; + +class CC_EXPORT DelegatedRendererLayer : public Layer { + public: + static scoped_refptr<DelegatedRendererLayer> Create(); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* impl) OVERRIDE; + virtual bool DrawsContent() const OVERRIDE; + + // Set the size at which the frame should be displayed, with the origin at the + // layer's origin. This must always contain at least the layer's bounds. A + // value of (0, 0) implies that the frame should be displayed to fit exactly + // in the layer's bounds. + void SetDisplaySize(gfx::Size size); + + void SetFrameData(scoped_ptr<DelegatedFrameData> frame_data); + + // Passes ownership of any unused resources that had been given by the child + // compositor to the given array, so they can be given back to the child. + void TakeUnusedResourcesForChildCompositor(TransferableResourceArray* array); + + protected: + DelegatedRendererLayer(); + virtual ~DelegatedRendererLayer(); + + private: + scoped_ptr<DelegatedFrameData> frame_data_; + gfx::RectF damage_in_frame_; + gfx::Size frame_size_; + gfx::Size display_size_; + TransferableResourceArray unused_resources_for_child_compositor_; +}; + +} +#endif // CC_LAYERS_DELEGATED_RENDERER_LAYER_H_ diff --git a/cc/layers/delegated_renderer_layer_impl.cc b/cc/layers/delegated_renderer_layer_impl.cc new file mode 100644 index 0000000..cfd4897 --- /dev/null +++ b/cc/layers/delegated_renderer_layer_impl.cc @@ -0,0 +1,350 @@ +// Copyright 2012 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 "cc/layers/delegated_renderer_layer_impl.h" + +#include "base/bind.h" +#include "cc/base/math_util.h" +#include "cc/layers/append_quads_data.h" +#include "cc/layers/quad_sink.h" +#include "cc/layers/render_pass_sink.h" +#include "cc/output/delegated_frame_data.h" +#include "cc/quads/render_pass_draw_quad.h" +#include "cc/trees/layer_tree_impl.h" + +namespace cc { + +DelegatedRendererLayerImpl::DelegatedRendererLayerImpl( + LayerTreeImpl* tree_impl, int id) + : LayerImpl(tree_impl, id), + child_id_(0) { +} + +DelegatedRendererLayerImpl::~DelegatedRendererLayerImpl() { + ClearRenderPasses(); + ClearChildId(); +} + +bool DelegatedRendererLayerImpl::HasDelegatedContent() const { + return !render_passes_in_draw_order_.empty(); +} + +bool DelegatedRendererLayerImpl::HasContributingDelegatedRenderPasses() const { + // The root RenderPass for the layer is merged with its target + // RenderPass in each frame. So we only have extra RenderPasses + // to merge when we have a non-root RenderPass present. + return render_passes_in_draw_order_.size() > 1; +} + +static ResourceProvider::ResourceId ResourceRemapHelper( + bool* invalid_frame, + const ResourceProvider::ResourceIdMap& child_to_parent_map, + ResourceProvider::ResourceIdSet *remapped_resources, + ResourceProvider::ResourceId id) { + + ResourceProvider::ResourceIdMap::const_iterator it = + child_to_parent_map.find(id); + if (it == child_to_parent_map.end()) { + *invalid_frame = true; + return 0; + } + + DCHECK(it->first == id); + ResourceProvider::ResourceId remapped_id = it->second; + remapped_resources->insert(remapped_id); + return remapped_id; +} + +void DelegatedRendererLayerImpl::SetFrameData( + scoped_ptr<DelegatedFrameData> frame_data, + gfx::RectF damage_in_frame, + TransferableResourceArray* resources_for_ack) { + CreateChildIdIfNeeded(); + DCHECK(child_id_); + + ResourceProvider* resource_provider = layer_tree_impl()->resource_provider(); + const ResourceProvider::ResourceIdMap& resource_map = + resource_provider->GetChildToParentMap(child_id_); + + if (frame_data) { + // A frame with an empty root render pass is invalid. + DCHECK(frame_data->render_pass_list.empty() || + !frame_data->render_pass_list.back()->output_rect.IsEmpty()); + + // Display size is already set so we can compute what the damage rect + // will be in layer space. + if (!frame_data->render_pass_list.empty()) { + RenderPass* new_root_pass = frame_data->render_pass_list.back(); + gfx::RectF damage_in_layer = MathUtil::MapClippedRect( + DelegatedFrameToLayerSpaceTransform( + new_root_pass->output_rect.size()), + damage_in_frame); + set_update_rect(gfx::UnionRects(update_rect(), damage_in_layer)); + } + + resource_provider->ReceiveFromChild(child_id_, frame_data->resource_list); + + bool invalid_frame = false; + ResourceProvider::ResourceIdSet used_resources; + DrawQuad::ResourceIteratorCallback remap_resources_to_parent_callback = + base::Bind(&ResourceRemapHelper, + &invalid_frame, + resource_map, + &used_resources); + for (size_t i = 0; i < frame_data->render_pass_list.size(); ++i) { + RenderPass* pass = frame_data->render_pass_list[i]; + for (size_t j = 0; j < pass->quad_list.size(); ++j) { + DrawQuad* quad = pass->quad_list[j]; + quad->IterateResources(remap_resources_to_parent_callback); + } + } + + if (!invalid_frame) { + // Save the remapped quads on the layer. This steals the quads and render + // passes from the frame_data. + SetRenderPasses(&frame_data->render_pass_list); + resources_.swap(used_resources); + } + } + + ResourceProvider::ResourceIdArray unused_resources; + for (ResourceProvider::ResourceIdMap::const_iterator it = + resource_map.begin(); + it != resource_map.end(); + ++it) { + bool resource_is_in_current_frame = resources_.count(it->second); + bool resource_is_in_use = resource_provider->InUseByConsumer(it->second); + if (!resource_is_in_current_frame && !resource_is_in_use) + unused_resources.push_back(it->second); + } + resource_provider->PrepareSendToChild( + child_id_, unused_resources, resources_for_ack); +} + +void DelegatedRendererLayerImpl::SetDisplaySize(gfx::Size size) { + if (display_size_ == size) + return; + display_size_ = size; + NoteLayerPropertyChanged(); +} + +void DelegatedRendererLayerImpl::SetRenderPasses( + ScopedPtrVector<RenderPass>* render_passes_in_draw_order) { + gfx::RectF old_root_damage; + if (!render_passes_in_draw_order_.empty()) + old_root_damage = render_passes_in_draw_order_.back()->damage_rect; + + ClearRenderPasses(); + + for (size_t i = 0; i < render_passes_in_draw_order->size(); ++i) { + ScopedPtrVector<RenderPass>::iterator to_take = + render_passes_in_draw_order->begin() + i; + render_passes_index_by_id_.insert( + std::pair<RenderPass::Id, int>((*to_take)->id, i)); + scoped_ptr<RenderPass> taken_render_pass = + render_passes_in_draw_order->take(to_take); + render_passes_in_draw_order_.push_back(taken_render_pass.Pass()); + } + + if (!render_passes_in_draw_order_.empty()) + render_passes_in_draw_order_.back()->damage_rect.Union(old_root_damage); +} + +void DelegatedRendererLayerImpl::ClearRenderPasses() { + // FIXME: Release the resources back to the nested compositor. + render_passes_index_by_id_.clear(); + render_passes_in_draw_order_.clear(); +} + +scoped_ptr<LayerImpl> DelegatedRendererLayerImpl::CreateLayerImpl( + LayerTreeImpl* treeImpl) { + return DelegatedRendererLayerImpl::Create(treeImpl, id()).PassAs<LayerImpl>(); +} + +void DelegatedRendererLayerImpl::DidLoseOutputSurface() { + ClearRenderPasses(); + ClearChildId(); +} + +gfx::Transform DelegatedRendererLayerImpl::DelegatedFrameToLayerSpaceTransform( + gfx::Size frame_size) const { + gfx::Size display_size = display_size_.IsEmpty() ? bounds() : display_size_; + + gfx::Transform delegated_frame_to_layer_space_transform; + delegated_frame_to_layer_space_transform.Scale( + static_cast<double>(display_size.width()) / frame_size.width(), + static_cast<double>(display_size.height()) / frame_size.height()); + return delegated_frame_to_layer_space_transform; +} + +static inline int IndexToId(int index) { return index + 1; } +static inline int IdToIndex(int id) { return id - 1; } + +RenderPass::Id DelegatedRendererLayerImpl::FirstContributingRenderPassId() + const { + return RenderPass::Id(id(), IndexToId(0)); +} + +RenderPass::Id DelegatedRendererLayerImpl::NextContributingRenderPassId( + RenderPass::Id previous) const { + return RenderPass::Id(previous.layer_id, previous.index + 1); +} + +RenderPass::Id DelegatedRendererLayerImpl::ConvertDelegatedRenderPassId( + RenderPass::Id delegated_render_pass_id) const { + base::hash_map<RenderPass::Id, int>::const_iterator found = + render_passes_index_by_id_.find(delegated_render_pass_id); + DCHECK(found != render_passes_index_by_id_.end()); + unsigned delegatedRenderPassIndex = found->second; + return RenderPass::Id(id(), IndexToId(delegatedRenderPassIndex)); +} + +void DelegatedRendererLayerImpl::AppendContributingRenderPasses( + RenderPassSink* render_pass_sink) { + DCHECK(HasContributingDelegatedRenderPasses()); + + for (size_t i = 0; i < render_passes_in_draw_order_.size() - 1; ++i) { + RenderPass::Id output_render_pass_id = + ConvertDelegatedRenderPassId(render_passes_in_draw_order_[i]->id); + + // Don't clash with the RenderPass we generate if we own a RenderSurface. + DCHECK(output_render_pass_id.index > 0); + + render_pass_sink->AppendRenderPass( + render_passes_in_draw_order_[i]->Copy(output_render_pass_id)); + } +} + +void DelegatedRendererLayerImpl::AppendQuads( + QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + if (render_passes_in_draw_order_.empty()) + return; + + RenderPass::Id target_render_pass_id = append_quads_data->renderPassId; + + const RenderPass* root_delegated_render_pass = + render_passes_in_draw_order_.back(); + + DCHECK(root_delegated_render_pass->output_rect.origin().IsOrigin()); + gfx::Size frame_size = root_delegated_render_pass->output_rect.size(); + + // If the index of the renderPassId is 0, then it is a renderPass generated + // for a layer in this compositor, not the delegated renderer. Then we want to + // merge our root renderPass with the target renderPass. Otherwise, it is some + // renderPass which we added from the delegated renderer. + bool should_merge_root_render_pass_with_target = !target_render_pass_id.index; + if (should_merge_root_render_pass_with_target) { + // Verify that the renderPass we are appending to is created our + // renderTarget. + DCHECK(target_render_pass_id.layer_id == render_target()->id()); + + AppendRenderPassQuads( + quad_sink, append_quads_data, root_delegated_render_pass, frame_size); + } else { + // Verify that the renderPass we are appending to was created by us. + DCHECK(target_render_pass_id.layer_id == id()); + + int render_pass_index = IdToIndex(target_render_pass_id.index); + const RenderPass* delegated_render_pass = + render_passes_in_draw_order_[render_pass_index]; + AppendRenderPassQuads( + quad_sink, append_quads_data, delegated_render_pass, frame_size); + } +} + +void DelegatedRendererLayerImpl::AppendRenderPassQuads( + QuadSink* quad_sink, + AppendQuadsData* append_quads_data, + const RenderPass* delegated_render_pass, + gfx::Size frame_size) const { + + const SharedQuadState* delegated_shared_quad_state = NULL; + SharedQuadState* output_shared_quad_state = NULL; + + for (size_t i = 0; i < delegated_render_pass->quad_list.size(); ++i) { + const DrawQuad* delegated_quad = delegated_render_pass->quad_list[i]; + + if (delegated_quad->shared_quad_state != delegated_shared_quad_state) { + delegated_shared_quad_state = delegated_quad->shared_quad_state; + output_shared_quad_state = quad_sink->UseSharedQuadState( + delegated_shared_quad_state->Copy()); + + bool is_root_delegated_render_pass = + delegated_render_pass == render_passes_in_draw_order_.back(); + if (is_root_delegated_render_pass) { + // Don't allow areas inside the bounds that are empty. + DCHECK(display_size_.IsEmpty() || + gfx::Rect(display_size_).Contains(gfx::Rect(bounds()))); + gfx::Transform delegated_frame_to_target_transform = + draw_transform() * DelegatedFrameToLayerSpaceTransform(frame_size); + + output_shared_quad_state->content_to_target_transform.ConcatTransform( + delegated_frame_to_target_transform); + + if (render_target() == this) { + DCHECK(!is_clipped()); + DCHECK(render_surface()); + output_shared_quad_state->clip_rect = MathUtil::MapClippedRect( + delegated_frame_to_target_transform, + output_shared_quad_state->clip_rect); + } else { + gfx::Rect clip_rect = drawable_content_rect(); + if (output_shared_quad_state->is_clipped) { + clip_rect.Intersect(MathUtil::MapClippedRect( + delegated_frame_to_target_transform, + output_shared_quad_state->clip_rect)); + } + output_shared_quad_state->clip_rect = clip_rect; + output_shared_quad_state->is_clipped = true; + } + + output_shared_quad_state->opacity *= draw_opacity(); + } + } + DCHECK(output_shared_quad_state); + + scoped_ptr<DrawQuad> output_quad; + if (delegated_quad->material != DrawQuad::RENDER_PASS) { + output_quad = delegated_quad->Copy(output_shared_quad_state); + } else { + RenderPass::Id delegated_contributing_render_pass_id = + RenderPassDrawQuad::MaterialCast(delegated_quad)->render_pass_id; + RenderPass::Id output_contributing_render_pass_id = + ConvertDelegatedRenderPassId(delegated_contributing_render_pass_id); + DCHECK(output_contributing_render_pass_id != + append_quads_data->renderPassId); + + output_quad = RenderPassDrawQuad::MaterialCast(delegated_quad)->Copy( + output_shared_quad_state, + output_contributing_render_pass_id).PassAs<DrawQuad>(); + } + DCHECK(output_quad.get()); + + quad_sink->Append(output_quad.Pass(), append_quads_data); + } +} + +const char* DelegatedRendererLayerImpl::LayerTypeAsString() const { + return "DelegatedRendererLayer"; +} + +void DelegatedRendererLayerImpl::CreateChildIdIfNeeded() { + if (child_id_) + return; + + ResourceProvider* resource_provider = layer_tree_impl()->resource_provider(); + child_id_ = resource_provider->CreateChild(); +} + +void DelegatedRendererLayerImpl::ClearChildId() { + if (!child_id_) + return; + + ResourceProvider* resource_provider = layer_tree_impl()->resource_provider(); + resource_provider->DestroyChild(child_id_); + child_id_ = 0; +} + +} // namespace cc diff --git a/cc/layers/delegated_renderer_layer_impl.h b/cc/layers/delegated_renderer_layer_impl.h new file mode 100644 index 0000000..1f6e214 --- /dev/null +++ b/cc/layers/delegated_renderer_layer_impl.h @@ -0,0 +1,89 @@ +// Copyright 2012 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 CC_LAYERS_DELEGATED_RENDERER_LAYER_IMPL_H_ +#define CC_LAYERS_DELEGATED_RENDERER_LAYER_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/base/scoped_ptr_vector.h" +#include "cc/layers/layer_impl.h" + +namespace cc { +class DelegatedFrameData; + +class CC_EXPORT DelegatedRendererLayerImpl : public LayerImpl { + public: + static scoped_ptr<DelegatedRendererLayerImpl> Create( + LayerTreeImpl* tree_impl, int id) { + return make_scoped_ptr(new DelegatedRendererLayerImpl(tree_impl, id)); + } + virtual ~DelegatedRendererLayerImpl(); + + // LayerImpl overrides. + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl*) OVERRIDE; + virtual bool HasDelegatedContent() const OVERRIDE; + virtual bool HasContributingDelegatedRenderPasses() const OVERRIDE; + virtual RenderPass::Id FirstContributingRenderPassId() const OVERRIDE; + virtual RenderPass::Id NextContributingRenderPassId( + RenderPass::Id previous) const OVERRIDE; + virtual void DidLoseOutputSurface() OVERRIDE; + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + + void AppendContributingRenderPasses(RenderPassSink* render_pass_sink); + + void SetFrameData(scoped_ptr<DelegatedFrameData> frame_data, + gfx::RectF damage_in_frame, + TransferableResourceArray* resources_for_ack); + + void SetDisplaySize(gfx::Size size); + + protected: + DelegatedRendererLayerImpl(LayerTreeImpl* tree_impl, int id); + + int ChildIdForTesting() const { return child_id_; } + const ScopedPtrVector<RenderPass>& RenderPassesInDrawOrderForTesting() const { + return render_passes_in_draw_order_; + } + const ResourceProvider::ResourceIdSet& ResourcesForTesting() const { + return resources_; + } + + private: + // Creates an ID with the resource provider for the child renderer + // that will be sending quads to the layer. + void CreateChildIdIfNeeded(); + void ClearChildId(); + + void SetRenderPasses( + ScopedPtrVector<RenderPass>* render_passes_in_draw_order); + void ClearRenderPasses(); + + RenderPass::Id ConvertDelegatedRenderPassId( + RenderPass::Id delegated_render_pass_id) const; + + gfx::Transform DelegatedFrameToLayerSpaceTransform(gfx::Size frame_size) + const; + + void AppendRenderPassQuads( + QuadSink* quad_sink, + AppendQuadsData* append_quads_data, + const RenderPass* delegated_render_pass, + gfx::Size frame_size) const; + + // LayerImpl overrides. + virtual const char* LayerTypeAsString() const OVERRIDE; + + ScopedPtrVector<RenderPass> render_passes_in_draw_order_; + base::hash_map<RenderPass::Id, int> render_passes_index_by_id_; + ResourceProvider::ResourceIdSet resources_; + + gfx::Size display_size_; + int child_id_; +}; + +} + +#endif // CC_LAYERS_DELEGATED_RENDERER_LAYER_IMPL_H_ diff --git a/cc/layers/delegated_renderer_layer_impl_unittest.cc b/cc/layers/delegated_renderer_layer_impl_unittest.cc new file mode 100644 index 0000000..ca3f833 --- /dev/null +++ b/cc/layers/delegated_renderer_layer_impl_unittest.cc @@ -0,0 +1,1255 @@ +// Copyright 2012 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 "cc/layers/delegated_renderer_layer_impl.h" + +#include "cc/base/scoped_ptr_vector.h" +#include "cc/layers/append_quads_data.h" +#include "cc/layers/quad_sink.h" +#include "cc/layers/solid_color_layer_impl.h" +#include "cc/quads/render_pass_draw_quad.h" +#include "cc/quads/solid_color_draw_quad.h" +#include "cc/test/fake_delegated_renderer_layer_impl.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/fake_layer_tree_host_impl_client.h" +#include "cc/test/fake_output_surface.h" +#include "cc/test/fake_proxy.h" +#include "cc/test/geometry_test_utils.h" +#include "cc/test/mock_quad_culler.h" +#include "cc/test/render_pass_test_common.h" +#include "cc/test/render_pass_test_utils.h" +#include "cc/test/test_web_graphics_context_3d.h" +#include "cc/trees/layer_tree_host_impl.h" +#include "cc/trees/layer_tree_impl.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/transform.h" + +namespace cc { +namespace { + +class DelegatedRendererLayerImplTest : public testing::Test { + public: + DelegatedRendererLayerImplTest() + : proxy_(scoped_ptr<Thread>(NULL)) + , always_impl_thread_and_main_thread_blocked_(&proxy_) { + LayerTreeSettings settings; + settings.minimumOcclusionTrackingSize = gfx::Size(); + + host_impl_ = LayerTreeHostImpl::Create(settings, &client_, &proxy_); + host_impl_->InitializeRenderer(createFakeOutputSurface()); + host_impl_->SetViewportSize(gfx::Size(10, 10), gfx::Size(10, 10)); + } + + protected: + FakeProxy proxy_; + FakeLayerTreeHostImplClient client_; + DebugScopedSetImplThreadAndMainThreadBlocked + always_impl_thread_and_main_thread_blocked_; + scoped_ptr<LayerTreeHostImpl> host_impl_; +}; + +class DelegatedRendererLayerImplTestSimple + : public DelegatedRendererLayerImplTest { + public: + DelegatedRendererLayerImplTestSimple() + : DelegatedRendererLayerImplTest() { + scoped_ptr<LayerImpl> root_layer = SolidColorLayerImpl::Create( + host_impl_->active_tree(), 1).PassAs<LayerImpl>(); + scoped_ptr<LayerImpl> layer_before = SolidColorLayerImpl::Create( + host_impl_->active_tree(), 2).PassAs<LayerImpl>(); + scoped_ptr<LayerImpl> layer_after = SolidColorLayerImpl::Create( + host_impl_->active_tree(), 3).PassAs<LayerImpl>(); + scoped_ptr<FakeDelegatedRendererLayerImpl> delegated_renderer_layer = + FakeDelegatedRendererLayerImpl::Create(host_impl_->active_tree(), 4); + + host_impl_->SetViewportSize(gfx::Size(100, 100), gfx::Size(100, 100)); + root_layer->SetBounds(gfx::Size(100, 100)); + + layer_before->SetPosition(gfx::Point(20, 20)); + layer_before->SetBounds(gfx::Size(14, 14)); + layer_before->SetContentBounds(gfx::Size(14, 14)); + layer_before->SetDrawsContent(true); + layer_before->SetForceRenderSurface(true); + + layer_after->SetPosition(gfx::Point(5, 5)); + layer_after->SetBounds(gfx::Size(15, 15)); + layer_after->SetContentBounds(gfx::Size(15, 15)); + layer_after->SetDrawsContent(true); + layer_after->SetForceRenderSurface(true); + + delegated_renderer_layer->SetPosition(gfx::Point(3, 3)); + delegated_renderer_layer->SetBounds(gfx::Size(10, 10)); + delegated_renderer_layer->SetContentBounds(gfx::Size(10, 10)); + delegated_renderer_layer->SetDrawsContent(true); + gfx::Transform transform; + transform.Translate(1.0, 1.0); + delegated_renderer_layer->SetTransform(transform); + + ScopedPtrVector<RenderPass> delegated_render_passes; + TestRenderPass* pass1 = addRenderPass( + delegated_render_passes, + RenderPass::Id(9, 6), + gfx::Rect(6, 6, 6, 6), + gfx::Transform()); + addQuad(pass1, gfx::Rect(0, 0, 6, 6), 33u); + TestRenderPass* pass2 = addRenderPass( + delegated_render_passes, + RenderPass::Id(9, 7), + gfx::Rect(7, 7, 7, 7), + gfx::Transform()); + addQuad(pass2, gfx::Rect(0, 0, 7, 7), 22u); + addRenderPassQuad(pass2, pass1); + TestRenderPass* pass3 = addRenderPass( + delegated_render_passes, + RenderPass::Id(9, 8), + gfx::Rect(0, 0, 8, 8), + gfx::Transform()); + addRenderPassQuad(pass3, pass2); + delegated_renderer_layer->SetFrameDataForRenderPasses( + &delegated_render_passes); + + // The RenderPasses should be taken by the layer. + EXPECT_EQ(0u, delegated_render_passes.size()); + + root_layer_ = root_layer.get(); + layer_before_ = layer_before.get(); + layer_after_ = layer_after.get(); + delegated_renderer_layer_ = delegated_renderer_layer.get(); + + // Force the delegated RenderPasses to come before the RenderPass from + // layer_after. + layer_after->AddChild(delegated_renderer_layer.PassAs<LayerImpl>()); + root_layer->AddChild(layer_after.Pass()); + + // Get the RenderPass generated by layer_before to come before the delegated + // RenderPasses. + root_layer->AddChild(layer_before.Pass()); + host_impl_->active_tree()->SetRootLayer(root_layer.Pass()); + } + + protected: + LayerImpl* root_layer_; + LayerImpl* layer_before_; + LayerImpl* layer_after_; + DelegatedRendererLayerImpl* delegated_renderer_layer_; +}; + +TEST_F(DelegatedRendererLayerImplTestSimple, AddsContributingRenderPasses) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // Each non-DelegatedRendererLayer added one RenderPass. The + // DelegatedRendererLayer added two contributing passes. + ASSERT_EQ(5u, frame.render_passes.size()); + + // The DelegatedRendererLayer should have added its contributing RenderPasses + // to the frame. + EXPECT_EQ(4, frame.render_passes[1]->id.layer_id); + EXPECT_EQ(1, frame.render_passes[1]->id.index); + EXPECT_EQ(4, frame.render_passes[2]->id.layer_id); + EXPECT_EQ(2, frame.render_passes[2]->id.index); + // And all other RenderPasses should be non-delegated. + EXPECT_NE(4, frame.render_passes[0]->id.layer_id); + EXPECT_EQ(0, frame.render_passes[0]->id.index); + EXPECT_NE(4, frame.render_passes[3]->id.layer_id); + EXPECT_EQ(0, frame.render_passes[3]->id.index); + EXPECT_NE(4, frame.render_passes[4]->id.layer_id); + EXPECT_EQ(0, frame.render_passes[4]->id.index); + + // The DelegatedRendererLayer should have added its RenderPasses to the frame + // in order. + EXPECT_EQ(gfx::Rect(6, 6, 6, 6).ToString(), + frame.render_passes[1]->output_rect.ToString()); + EXPECT_EQ(gfx::Rect(7, 7, 7, 7).ToString(), + frame.render_passes[2]->output_rect.ToString()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestSimple, + AddsQuadsToContributingRenderPasses) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // Each non-DelegatedRendererLayer added one RenderPass. The + // DelegatedRendererLayer added two contributing passes. + ASSERT_EQ(5u, frame.render_passes.size()); + + // The DelegatedRendererLayer should have added its contributing RenderPasses + // to the frame. + EXPECT_EQ(4, frame.render_passes[1]->id.layer_id); + EXPECT_EQ(1, frame.render_passes[1]->id.index); + EXPECT_EQ(4, frame.render_passes[2]->id.layer_id); + EXPECT_EQ(2, frame.render_passes[2]->id.index); + + // The DelegatedRendererLayer should have added copies of its quads to + // contributing RenderPasses. + ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size()); + EXPECT_EQ(gfx::Rect(0, 0, 6, 6).ToString(), + frame.render_passes[1]->quad_list[0]->rect.ToString()); + + // Verify it added the right quads. + ASSERT_EQ(2u, frame.render_passes[2]->quad_list.size()); + EXPECT_EQ(gfx::Rect(0, 0, 7, 7).ToString(), + frame.render_passes[2]->quad_list[0]->rect.ToString()); + EXPECT_EQ(gfx::Rect(6, 6, 6, 6).ToString(), + frame.render_passes[2]->quad_list[1]->rect.ToString()); + ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size()); + EXPECT_EQ(gfx::Rect(0, 0, 6, 6).ToString(), + frame.render_passes[1]->quad_list[0]->rect.ToString()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestSimple, AddsQuadsToTargetRenderPass) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // Each non-DelegatedRendererLayer added one RenderPass. The + // DelegatedRendererLayer added two contributing passes. + ASSERT_EQ(5u, frame.render_passes.size()); + + // The layer's target is the RenderPass from m_layer_after. + EXPECT_EQ(RenderPass::Id(3, 0), frame.render_passes[3]->id); + + // The DelegatedRendererLayer should have added copies of quads in its root + // RenderPass to its target RenderPass. The m_layer_after also adds one quad. + ASSERT_EQ(2u, frame.render_passes[3]->quad_list.size()); + + // Verify it added the right quads. + EXPECT_EQ(gfx::Rect(7, 7, 7, 7).ToString(), + frame.render_passes[3]->quad_list[0]->rect.ToString()); + + // Its target layer should have a quad as well. + EXPECT_EQ(gfx::Rect(0, 0, 15, 15).ToString(), + frame.render_passes[3]->quad_list[1]->rect.ToString()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestSimple, + QuadsFromRootRenderPassAreModifiedForTheTarget) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // Each non-DelegatedRendererLayer added one RenderPass. The + // DelegatedRendererLayer added two contributing passes. + ASSERT_EQ(5u, frame.render_passes.size()); + + // The DelegatedRendererLayer is at position 3,3 compared to its target, and + // has a translation transform of 1,1. So its root RenderPass' quads should + // all be transformed by that combined amount. + // The DelegatedRendererLayer has a size of 10x10, but the root delegated + // RenderPass has a size of 8x8, so any quads should be scaled by 10/8. + gfx::Transform transform; + transform.Translate(4.0, 4.0); + transform.Scale(10.0 / 8.0, 10.0 / 8.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + transform, frame.render_passes[3]->quad_list[0]->quadTransform()); + + // Quads from non-root RenderPasses should not be shifted though. + ASSERT_EQ(2u, frame.render_passes[2]->quad_list.size()); + EXPECT_TRANSFORMATION_MATRIX_EQ( + gfx::Transform(), frame.render_passes[2]->quad_list[0]->quadTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ( + gfx::Transform(), frame.render_passes[2]->quad_list[1]->quadTransform()); + ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size()); + EXPECT_TRANSFORMATION_MATRIX_EQ( + gfx::Transform(), frame.render_passes[1]->quad_list[0]->quadTransform()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestSimple, DoesNotOwnARenderSurface) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // If the DelegatedRendererLayer is axis aligned and has opacity 1, then it + // has no need to be a renderSurface for the quads it carries. + EXPECT_FALSE(delegated_renderer_layer_->render_surface()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestSimple, DoesOwnARenderSurfaceForOpacity) { + delegated_renderer_layer_->SetOpacity(0.5f); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // This test case has quads from multiple layers in the delegated renderer, so + // if the DelegatedRendererLayer has opacity < 1, it should end up with a + // render surface. + EXPECT_TRUE(delegated_renderer_layer_->render_surface()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestSimple, + DoesOwnARenderSurfaceForTransform) { + gfx::Transform rotation; + rotation.RotateAboutZAxis(30.0); + delegated_renderer_layer_->SetTransform(rotation); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // This test case has quads from multiple layers in the delegated renderer, so + // if the DelegatedRendererLayer has opacity < 1, it should end up with a + // render surface. + EXPECT_TRUE(delegated_renderer_layer_->render_surface()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +class DelegatedRendererLayerImplTestOwnSurface + : public DelegatedRendererLayerImplTestSimple { + public: + DelegatedRendererLayerImplTestOwnSurface() + : DelegatedRendererLayerImplTestSimple() { + delegated_renderer_layer_->SetForceRenderSurface(true); + } +}; + +TEST_F(DelegatedRendererLayerImplTestOwnSurface, AddsRenderPasses) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // Each non-DelegatedRendererLayer added one RenderPass. The + // DelegatedRendererLayer added two contributing passes and its owned surface + // added one pass. + ASSERT_EQ(6u, frame.render_passes.size()); + + // The DelegatedRendererLayer should have added its contributing RenderPasses + // to the frame. + EXPECT_EQ(4, frame.render_passes[1]->id.layer_id); + EXPECT_EQ(1, frame.render_passes[1]->id.index); + EXPECT_EQ(4, frame.render_passes[2]->id.layer_id); + EXPECT_EQ(2, frame.render_passes[2]->id.index); + // The DelegatedRendererLayer should have added a RenderPass for its surface + // to the frame. + EXPECT_EQ(4, frame.render_passes[1]->id.layer_id); + EXPECT_EQ(0, frame.render_passes[3]->id.index); + // And all other RenderPasses should be non-delegated. + EXPECT_NE(4, frame.render_passes[0]->id.layer_id); + EXPECT_EQ(0, frame.render_passes[0]->id.index); + EXPECT_NE(4, frame.render_passes[4]->id.layer_id); + EXPECT_EQ(0, frame.render_passes[4]->id.index); + EXPECT_NE(4, frame.render_passes[5]->id.layer_id); + EXPECT_EQ(0, frame.render_passes[5]->id.index); + + // The DelegatedRendererLayer should have added its RenderPasses to the frame + // in order. + EXPECT_EQ(gfx::Rect(6, 6, 6, 6).ToString(), + frame.render_passes[1]->output_rect.ToString()); + EXPECT_EQ(gfx::Rect(7, 7, 7, 7).ToString(), + frame.render_passes[2]->output_rect.ToString()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestOwnSurface, + AddsQuadsToContributingRenderPasses) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // Each non-DelegatedRendererLayer added one RenderPass. The + // DelegatedRendererLayer added two contributing passes and its owned surface + // added one pass. + ASSERT_EQ(6u, frame.render_passes.size()); + + // The DelegatedRendererLayer should have added its contributing RenderPasses + // to the frame. + EXPECT_EQ(4, frame.render_passes[1]->id.layer_id); + EXPECT_EQ(1, frame.render_passes[1]->id.index); + EXPECT_EQ(4, frame.render_passes[2]->id.layer_id); + EXPECT_EQ(2, frame.render_passes[2]->id.index); + + // The DelegatedRendererLayer should have added copies of its quads to + // contributing RenderPasses. + ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size()); + EXPECT_EQ(gfx::Rect(0, 0, 6, 6).ToString(), + frame.render_passes[1]->quad_list[0]->rect.ToString()); + + // Verify it added the right quads. + ASSERT_EQ(2u, frame.render_passes[2]->quad_list.size()); + EXPECT_EQ(gfx::Rect(0, 0, 7, 7).ToString(), + frame.render_passes[2]->quad_list[0]->rect.ToString()); + EXPECT_EQ(gfx::Rect(6, 6, 6, 6).ToString(), + frame.render_passes[2]->quad_list[1]->rect.ToString()); + ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size()); + EXPECT_EQ(gfx::Rect(0, 0, 6, 6).ToString(), + frame.render_passes[1]->quad_list[0]->rect.ToString()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestOwnSurface, AddsQuadsToTargetRenderPass) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // Each non-DelegatedRendererLayer added one RenderPass. The + // DelegatedRendererLayer added two contributing passes and its owned surface + // added one pass. + ASSERT_EQ(6u, frame.render_passes.size()); + + // The layer's target is the RenderPass owned by itself. + EXPECT_EQ(RenderPass::Id(4, 0), frame.render_passes[3]->id); + + // The DelegatedRendererLayer should have added copies of quads in its root + // RenderPass to its target RenderPass. + // The m_layer_after also adds one quad. + ASSERT_EQ(1u, frame.render_passes[3]->quad_list.size()); + + // Verify it added the right quads. + EXPECT_EQ(gfx::Rect(7, 7, 7, 7).ToString(), + frame.render_passes[3]->quad_list[0]->rect.ToString()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestOwnSurface, + QuadsFromRootRenderPassAreNotModifiedForTheTarget) { + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + // Each non-DelegatedRendererLayer added one RenderPass. The + // DelegatedRendererLayer added two contributing passes and its owned surface + // added one pass. + ASSERT_EQ(6u, frame.render_passes.size()); + + // Because the DelegatedRendererLayer owns a RenderSurfaceImpl, its root + // RenderPass' quads do not need to be translated at all. However, they are + // scaled from the frame's size (8x8) to the layer's bounds (10x10). + gfx::Transform transform; + transform.Scale(10.0 / 8.0, 10.0 / 8.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + transform, frame.render_passes[3]->quad_list[0]->quadTransform()); + + // Quads from non-root RenderPasses should not be shifted either. + ASSERT_EQ(2u, frame.render_passes[2]->quad_list.size()); + EXPECT_TRANSFORMATION_MATRIX_EQ( + gfx::Transform(), frame.render_passes[2]->quad_list[0]->quadTransform()); + EXPECT_TRANSFORMATION_MATRIX_EQ( + gfx::Transform(), frame.render_passes[2]->quad_list[1]->quadTransform()); + ASSERT_EQ(1u, frame.render_passes[1]->quad_list.size()); + EXPECT_TRANSFORMATION_MATRIX_EQ( + gfx::Transform(), frame.render_passes[1]->quad_list[0]->quadTransform()); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +class DelegatedRendererLayerImplTestTransform + : public DelegatedRendererLayerImplTest { + public: + void SetUpTest() { + scoped_ptr<LayerImpl> root_layer = LayerImpl::Create( + host_impl_->active_tree(), 1); + scoped_ptr<FakeDelegatedRendererLayerImpl> delegated_renderer_layer = + FakeDelegatedRendererLayerImpl::Create(host_impl_->active_tree(), 2); + + host_impl_->SetViewportSize(gfx::Size(100, 100), gfx::Size(100, 100)); + root_layer->SetBounds(gfx::Size(100, 100)); + + delegated_renderer_layer->SetPosition(gfx::Point(20, 20)); + delegated_renderer_layer->SetBounds(gfx::Size(30, 30)); + delegated_renderer_layer->SetContentBounds(gfx::Size(30, 30)); + delegated_renderer_layer->SetDrawsContent(true); + gfx::Transform transform; + transform.Scale(2.0, 2.0); + transform.Translate(8.0, 8.0); + delegated_renderer_layer->SetTransform(transform); + + ScopedPtrVector<RenderPass> delegated_render_passes; + + gfx::Size child_pass_content_bounds(7, 7); + gfx::Rect child_pass_rect(20, 20, 7, 7); + gfx::Transform child_pass_transform; + child_pass_transform.Scale(0.8, 0.8); + child_pass_transform.Translate(9.0, 9.0); + gfx::Rect child_pass_clip_rect(21, 21, 3, 3); + bool child_pass_clipped = false; + + { + TestRenderPass* pass = addRenderPass( + delegated_render_passes, + RenderPass::Id(10, 7), + child_pass_rect, + gfx::Transform()); + MockQuadCuller quad_sink(pass->quad_list, pass->shared_quad_state_list); + AppendQuadsData data(pass->id); + SharedQuadState* shared_quad_state = quad_sink.UseSharedQuadState( + SharedQuadState::Create()); + shared_quad_state->SetAll( + child_pass_transform, + child_pass_content_bounds, + child_pass_rect, + child_pass_clip_rect, + child_pass_clipped, + 1.f); + + scoped_ptr<SolidColorDrawQuad> color_quad; + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(20, 20, 3, 7), 1u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(23, 20, 4, 7), 1u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + } + + gfx::Size root_pass_content_bounds(50, 50); + gfx::Rect root_pass_rect(0, 0, 50, 50); + gfx::Transform root_pass_transform; + root_pass_transform.Scale(1.5, 1.5); + root_pass_transform.Translate(7.0, 7.0); + gfx::Rect root_pass_clip_rect(10, 10, 35, 35); + bool root_pass_clipped = root_delegated_render_pass_is_clipped_; + + TestRenderPass* pass = addRenderPass( + delegated_render_passes, + RenderPass::Id(9, 6), + root_pass_rect, + gfx::Transform()); + MockQuadCuller quad_sink(pass->quad_list, pass->shared_quad_state_list); + AppendQuadsData data(pass->id); + SharedQuadState* shared_quad_state = + quad_sink.UseSharedQuadState(SharedQuadState::Create()); + shared_quad_state->SetAll( + root_pass_transform, + root_pass_content_bounds, + root_pass_rect, + root_pass_clip_rect, + root_pass_clipped, + 1.f); + + scoped_ptr<RenderPassDrawQuad> render_pass_quad = + RenderPassDrawQuad::Create(); + render_pass_quad->SetNew( + shared_quad_state, + gfx::Rect(5, 5, 7, 7), // rect + RenderPass::Id(10, 7), // render_pass_id + false, // is_replica + 0, // mask_resource_id + child_pass_rect, // contents_changed_since_last_frame + gfx::RectF(), // mask_uv_rect + WebKit::WebFilterOperations(), // filters + skia::RefPtr<SkImageFilter>(), // filter + WebKit::WebFilterOperations()); // background_filters + quad_sink.Append(render_pass_quad.PassAs<DrawQuad>(), &data); + + scoped_ptr<SolidColorDrawQuad> color_quad; + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(0, 0, 10, 10), 1u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(0, 10, 10, 10), 2u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(10, 0, 10, 10), 3u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(10, 10, 10, 10), 4u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + delegated_renderer_layer->SetFrameDataForRenderPasses( + &delegated_render_passes); + + // The RenderPasses should be taken by the layer. + EXPECT_EQ(0u, delegated_render_passes.size()); + + root_layer_ = root_layer.get(); + delegated_renderer_layer_ = delegated_renderer_layer.get(); + + root_layer->AddChild(delegated_renderer_layer.PassAs<LayerImpl>()); + host_impl_->active_tree()->SetRootLayer(root_layer.Pass()); + } + + void VerifyRenderPasses( + const LayerTreeHostImpl::FrameData& frame, + size_t num_render_passes, + const SharedQuadState** root_delegated_shared_quad_state, + const SharedQuadState** contrib_delegated_shared_quad_state) { + ASSERT_EQ(num_render_passes, frame.render_passes.size()); + // The contributing render pass in the DelegatedRendererLayer. + EXPECT_EQ(2, frame.render_passes[0]->id.layer_id); + EXPECT_EQ(1, frame.render_passes[0]->id.index); + // The root render pass. + EXPECT_EQ(1, frame.render_passes.back()->id.layer_id); + EXPECT_EQ(0, frame.render_passes.back()->id.index); + + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + + const QuadList& root_delegated_quad_list = + frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + + // All quads in a render pass should share the same state. + *contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + EXPECT_EQ(*contrib_delegated_shared_quad_state, + contrib_delegated_quad_list[1]->shared_quad_state); + + *root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + EXPECT_EQ(*root_delegated_shared_quad_state, + root_delegated_quad_list[1]->shared_quad_state); + EXPECT_EQ(*root_delegated_shared_quad_state, + root_delegated_quad_list[2]->shared_quad_state); + EXPECT_EQ(*root_delegated_shared_quad_state, + root_delegated_quad_list[3]->shared_quad_state); + EXPECT_EQ(*root_delegated_shared_quad_state, + root_delegated_quad_list[4]->shared_quad_state); + + EXPECT_NE(*contrib_delegated_shared_quad_state, + *root_delegated_shared_quad_state); + } + + protected: + LayerImpl* root_layer_; + DelegatedRendererLayerImpl* delegated_renderer_layer_; + bool root_delegated_render_pass_is_clipped_; +}; + +TEST_F(DelegatedRendererLayerImplTestTransform, QuadsUnclipped_NoSurface) { + root_delegated_render_pass_is_clipped_ = false; + SetUpTest(); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + const SharedQuadState* root_delegated_shared_quad_state = NULL; + const SharedQuadState* contrib_delegated_shared_quad_state = NULL; + VerifyRenderPasses( + frame, + 2, + &root_delegated_shared_quad_state, + &contrib_delegated_shared_quad_state); + + // When the quads don't have a clip of their own, the clip rect is set to + // the drawableContentRect of the delegated renderer layer. + EXPECT_EQ(gfx::Rect(21, 21, 60, 60).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + + // Even though the quads in the root pass have no clip of their own, they + // inherit the clip rect from the delegated renderer layer if it does not + // own a surface. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + gfx::Transform expected; + // This is the transform from the layer's space to its target. + // The position (20) - the width / scale (30 / 2) = 20 - 15 = 5 + expected.Translate(5.0, 5.0); + expected.Scale(2.0, 2.0); + expected.Translate(8.0, 8.0); + // The frame has size 50x50 but the layer's bounds are 30x30. + expected.Scale(30.0 / 50.0, 30.0 / 50.0); + // This is the transform within the source frame. + expected.Scale(1.5, 1.5); + expected.Translate(7.0, 7.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + expected, root_delegated_shared_quad_state->content_to_target_transform); + + // The contributing render pass should not be transformed from its input. + EXPECT_EQ(gfx::Rect(21, 21, 3, 3).ToString(), + contrib_delegated_shared_quad_state->clip_rect.ToString()); + EXPECT_FALSE(contrib_delegated_shared_quad_state->is_clipped); + expected.MakeIdentity(); + expected.Scale(0.8, 0.8); + expected.Translate(9.0, 9.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + expected, + contrib_delegated_shared_quad_state->content_to_target_transform); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestTransform, QuadsClipped_NoSurface) { + root_delegated_render_pass_is_clipped_ = true; + SetUpTest(); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + const SharedQuadState* root_delegated_shared_quad_state = NULL; + const SharedQuadState* contrib_delegated_shared_quad_state = NULL; + VerifyRenderPasses( + frame, + 2, + &root_delegated_shared_quad_state, + &contrib_delegated_shared_quad_state); + + // Since the quads have a clip_rect it should be modified by delegated + // renderer layer's drawTransform. + // The position of the resulting clip_rect is: + // (clip rect position (10) * scale to layer (30/50) + translate (8)) * + // layer scale (2) + layer position (20) = 48 + // But the layer is centered, so: 48 - (width / 2) = 48 - 30 / 2 = 33 + // + // The size is 35x35 scaled to fit inside the layer's bounds at 30x30 from + // a frame at 50x50: 35 * 2 (layer's scale) * 30 / 50 = 42. + EXPECT_EQ(gfx::Rect(33, 33, 42, 42).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + + // The quads had a clip and it should be preserved. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + gfx::Transform expected; + // This is the transform from the layer's space to its target. + // The position (20) - the width / scale (30 / 2) = 20 - 15 = 5 + expected.Translate(5.0, 5.0); + expected.Scale(2.0, 2.0); + expected.Translate(8.0, 8.0); + // The frame has size 50x50 but the layer's bounds are 30x30. + expected.Scale(30.0 / 50.0, 30.0 / 50.0); + // This is the transform within the source frame. + expected.Scale(1.5, 1.5); + expected.Translate(7.0, 7.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + expected, root_delegated_shared_quad_state->content_to_target_transform); + + // The contributing render pass should not be transformed from its input. + EXPECT_EQ(gfx::Rect(21, 21, 3, 3).ToString(), + contrib_delegated_shared_quad_state->clip_rect.ToString()); + EXPECT_FALSE(contrib_delegated_shared_quad_state->is_clipped); + expected.MakeIdentity(); + expected.Scale(0.8, 0.8); + expected.Translate(9.0, 9.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + expected, + contrib_delegated_shared_quad_state->content_to_target_transform); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestTransform, QuadsUnclipped_Surface) { + root_delegated_render_pass_is_clipped_ = false; + SetUpTest(); + + delegated_renderer_layer_->SetForceRenderSurface(true); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + const SharedQuadState* root_delegated_shared_quad_state = NULL; + const SharedQuadState* contrib_delegated_shared_quad_state = NULL; + VerifyRenderPasses( + frame, + 3, + &root_delegated_shared_quad_state, + &contrib_delegated_shared_quad_state); + + // When the layer owns a surface, then its position and translation are not + // a part of its draw transform. + // The position of the resulting clip_rect is: + // (clip rect position (10) * scale to layer (30/50)) * layer scale (2) = 12 + // The size is 35x35 scaled to fit inside the layer's bounds at 30x30 from + // a frame at 50x50: 35 * 2 (layer's scale) * 30 / 50 = 42. + EXPECT_EQ(gfx::Rect(12, 12, 42, 42).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + + // Since the layer owns a surface it doesn't need to clip its quads, so + // unclipped quads remain unclipped. + EXPECT_FALSE(root_delegated_shared_quad_state->is_clipped); + + gfx::Transform expected; + expected.Scale(2.0, 2.0); + // The frame has size 50x50 but the layer's bounds are 30x30. + expected.Scale(30.0 / 50.0, 30.0 / 50.0); + // This is the transform within the source frame. + expected.Scale(1.5, 1.5); + expected.Translate(7.0, 7.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + expected, root_delegated_shared_quad_state->content_to_target_transform); + + // The contributing render pass should not be transformed from its input. + EXPECT_EQ(gfx::Rect(21, 21, 3, 3).ToString(), + contrib_delegated_shared_quad_state->clip_rect.ToString()); + EXPECT_FALSE(contrib_delegated_shared_quad_state->is_clipped); + expected.MakeIdentity(); + expected.Scale(0.8, 0.8); + expected.Translate(9.0, 9.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + expected, + contrib_delegated_shared_quad_state->content_to_target_transform); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestTransform, QuadsClipped_Surface) { + root_delegated_render_pass_is_clipped_ = true; + SetUpTest(); + + delegated_renderer_layer_->SetForceRenderSurface(true); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + const SharedQuadState* root_delegated_shared_quad_state = NULL; + const SharedQuadState* contrib_delegated_shared_quad_state = NULL; + VerifyRenderPasses( + frame, + 3, + &root_delegated_shared_quad_state, + &contrib_delegated_shared_quad_state); + + // When the layer owns a surface, then its position and translation are not + // a part of its draw transform. + // The position of the resulting clip_rect is: + // (clip rect position (10) * scale to layer (30/50)) * layer scale (2) = 12 + // The size is 35x35 scaled to fit inside the layer's bounds at 30x30 from + // a frame at 50x50: 35 * 2 (layer's scale) * 30 / 50 = 42. + EXPECT_EQ(gfx::Rect(12, 12, 42, 42).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + + // The quads had a clip and it should be preserved. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + gfx::Transform expected; + expected.Scale(2.0, 2.0); + // The frame has size 50x50 but the layer's bounds are 30x30. + expected.Scale(30.0 / 50.0, 30.0 / 50.0); + // This is the transform within the source frame. + expected.Scale(1.5, 1.5); + expected.Translate(7.0, 7.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + expected, root_delegated_shared_quad_state->content_to_target_transform); + + // The contributing render pass should not be transformed from its input. + EXPECT_EQ(gfx::Rect(21, 21, 3, 3).ToString(), + contrib_delegated_shared_quad_state->clip_rect.ToString()); + EXPECT_FALSE(contrib_delegated_shared_quad_state->is_clipped); + expected.MakeIdentity(); + expected.Scale(0.8, 0.8); + expected.Translate(9.0, 9.0); + EXPECT_TRANSFORMATION_MATRIX_EQ( + expected, + contrib_delegated_shared_quad_state->content_to_target_transform); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +class DelegatedRendererLayerImplTestClip + : public DelegatedRendererLayerImplTest { + public: + void SetUpTest() { + scoped_ptr<LayerImpl> root_layer = + LayerImpl::Create(host_impl_->active_tree(), 1); + scoped_ptr<FakeDelegatedRendererLayerImpl> delegated_renderer_layer = + FakeDelegatedRendererLayerImpl::Create(host_impl_->active_tree(), 2); + scoped_ptr<LayerImpl> clip_layer = + LayerImpl::Create(host_impl_->active_tree(), 3); + scoped_ptr<LayerImpl> origin_layer = + LayerImpl::Create(host_impl_->active_tree(), 4); + + host_impl_->SetViewportSize(gfx::Size(100, 100), gfx::Size(100, 100)); + root_layer->SetBounds(gfx::Size(100, 100)); + + delegated_renderer_layer->SetPosition(gfx::Point(20, 20)); + delegated_renderer_layer->SetBounds(gfx::Size(50, 50)); + delegated_renderer_layer->SetContentBounds(gfx::Size(50, 50)); + delegated_renderer_layer->SetDrawsContent(true); + + ScopedPtrVector<RenderPass> delegated_render_passes; + + gfx::Size child_pass_content_bounds(7, 7); + gfx::Rect child_pass_rect(20, 20, 7, 7); + gfx::Transform child_pass_transform; + gfx::Rect child_pass_clip_rect(21, 21, 3, 3); + bool child_pass_clipped = false; + + { + TestRenderPass* pass = addRenderPass( + delegated_render_passes, + RenderPass::Id(10, 7), + child_pass_rect, + gfx::Transform()); + MockQuadCuller quad_sink(pass->quad_list, pass->shared_quad_state_list); + AppendQuadsData data(pass->id); + SharedQuadState* shared_quad_state = + quad_sink.UseSharedQuadState(SharedQuadState::Create()); + shared_quad_state->SetAll( + child_pass_transform, + child_pass_content_bounds, + child_pass_rect, + child_pass_clip_rect, + child_pass_clipped, + 1.f); + + scoped_ptr<SolidColorDrawQuad> color_quad; + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(20, 20, 3, 7), 1u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(23, 20, 4, 7), 1u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + } + + gfx::Size root_pass_content_bounds(50, 50); + gfx::Rect root_pass_rect(0, 0, 50, 50); + gfx::Transform root_pass_transform; + gfx::Rect root_pass_clip_rect(5, 5, 40, 40); + bool root_pass_clipped = root_delegated_render_pass_is_clipped_; + + TestRenderPass* pass = addRenderPass( + delegated_render_passes, + RenderPass::Id(9, 6), + root_pass_rect, + gfx::Transform()); + MockQuadCuller quad_sink(pass->quad_list, pass->shared_quad_state_list); + AppendQuadsData data(pass->id); + SharedQuadState* shared_quad_state = + quad_sink.UseSharedQuadState(SharedQuadState::Create()); + shared_quad_state->SetAll(root_pass_transform, + root_pass_content_bounds, + root_pass_rect, + root_pass_clip_rect, + root_pass_clipped, + 1.f); + + scoped_ptr<RenderPassDrawQuad> render_pass_quad = + RenderPassDrawQuad::Create(); + render_pass_quad->SetNew( + shared_quad_state, + gfx::Rect(5, 5, 7, 7), // rect + RenderPass::Id(10, 7), // render_pass_id + false, // is_replica + 0, // mask_resource_id + child_pass_rect, // contents_changed_since_last_frame + gfx::RectF(), // mask_uv_rect + WebKit::WebFilterOperations(), // filters + skia::RefPtr<SkImageFilter>(), // filter + WebKit::WebFilterOperations()); // background_filters + quad_sink.Append(render_pass_quad.PassAs<DrawQuad>(), &data); + + scoped_ptr<SolidColorDrawQuad> color_quad; + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(0, 0, 10, 10), 1u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(0, 10, 10, 10), 2u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(10, 0, 10, 10), 3u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + color_quad = SolidColorDrawQuad::Create(); + color_quad->SetNew(shared_quad_state, gfx::Rect(10, 10, 10, 10), 4u); + quad_sink.Append(color_quad.PassAs<DrawQuad>(), &data); + + delegated_renderer_layer->SetFrameDataForRenderPasses( + &delegated_render_passes); + + // The RenderPasses should be taken by the layer. + EXPECT_EQ(0u, delegated_render_passes.size()); + + root_layer_ = root_layer.get(); + delegated_renderer_layer_ = delegated_renderer_layer.get(); + + if (clip_delegated_renderer_layer_) { + gfx::Rect clip_rect(21, 27, 23, 21); + + clip_layer->SetPosition(clip_rect.origin()); + clip_layer->SetBounds(clip_rect.size()); + clip_layer->SetContentBounds(clip_rect.size()); + clip_layer->SetMasksToBounds(true); + + origin_layer->SetPosition( + gfx::PointAtOffsetFromOrigin(-clip_rect.OffsetFromOrigin())); + + origin_layer->AddChild(delegated_renderer_layer.PassAs<LayerImpl>()); + clip_layer->AddChild(origin_layer.Pass()); + root_layer->AddChild(clip_layer.Pass()); + } else { + root_layer->AddChild(delegated_renderer_layer.PassAs<LayerImpl>()); + } + + host_impl_->active_tree()->SetRootLayer(root_layer.Pass()); + } + + protected: + LayerImpl* root_layer_; + DelegatedRendererLayerImpl* delegated_renderer_layer_; + bool root_delegated_render_pass_is_clipped_; + bool clip_delegated_renderer_layer_; +}; + +TEST_F(DelegatedRendererLayerImplTestClip, + QuadsUnclipped_LayerUnclipped_NoSurface) { + root_delegated_render_pass_is_clipped_ = false; + clip_delegated_renderer_layer_ = false; + SetUpTest(); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + ASSERT_EQ(2u, frame.render_passes.size()); + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + const SharedQuadState* root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + const SharedQuadState* contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + + // When the quads don't have a clip of their own, the clip rect is set to + // the drawableContentRect of the delegated renderer layer. + EXPECT_EQ(gfx::Rect(20, 20, 50, 50).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + // Quads are clipped to the delegated renderer layer. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestClip, + QuadsClipped_LayerUnclipped_NoSurface) { + root_delegated_render_pass_is_clipped_ = true; + clip_delegated_renderer_layer_ = false; + SetUpTest(); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + ASSERT_EQ(2u, frame.render_passes.size()); + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + const QuadList& root_delegated_quad_list = + frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + const SharedQuadState* root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + const SharedQuadState* contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + + // When the quads have a clip of their own, it is used. + EXPECT_EQ(gfx::Rect(25, 25, 40, 40).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + // Quads came with a clip rect. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestClip, + QuadsUnclipped_LayerClipped_NoSurface) { + root_delegated_render_pass_is_clipped_ = false; + clip_delegated_renderer_layer_ = true; + SetUpTest(); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + ASSERT_EQ(2u, frame.render_passes.size()); + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + const SharedQuadState* root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + const SharedQuadState* contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + + // When the quads don't have a clip of their own, the clip rect is set to + // the drawableContentRect of the delegated renderer layer. When the layer + // is clipped, that should be seen in the quads' clip_rect. + EXPECT_EQ(gfx::Rect(21, 27, 23, 21).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + // Quads are clipped to the delegated renderer layer. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestClip, + QuadsClipped_LayerClipped_NoSurface) { + root_delegated_render_pass_is_clipped_ = true; + clip_delegated_renderer_layer_ = true; + SetUpTest(); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + ASSERT_EQ(2u, frame.render_passes.size()); + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + const SharedQuadState* root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + const SharedQuadState* contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + + // When the quads have a clip of their own, it is used, but it is + // combined with the clip rect of the delegated renderer layer. + EXPECT_EQ(gfx::Rect(25, 27, 19, 21).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + // Quads came with a clip rect. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestClip, + QuadsUnclipped_LayerUnclipped_Surface) { + root_delegated_render_pass_is_clipped_ = false; + clip_delegated_renderer_layer_ = false; + SetUpTest(); + + delegated_renderer_layer_->SetForceRenderSurface(true); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + ASSERT_EQ(3u, frame.render_passes.size()); + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + const SharedQuadState* root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + const SharedQuadState* contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + + // When the layer owns a surface, the quads don't need to be clipped + // further than they already specify. If they aren't clipped, then their + // clip rect is ignored, and they are not set as clipped. + EXPECT_FALSE(root_delegated_shared_quad_state->is_clipped); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestClip, + QuadsClipped_LayerUnclipped_Surface) { + root_delegated_render_pass_is_clipped_ = true; + clip_delegated_renderer_layer_ = false; + SetUpTest(); + + delegated_renderer_layer_->SetForceRenderSurface(true); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + ASSERT_EQ(3u, frame.render_passes.size()); + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + const SharedQuadState* root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + const SharedQuadState* contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + + // When the quads have a clip of their own, it is used. + EXPECT_EQ(gfx::Rect(5, 5, 40, 40).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + // Quads came with a clip rect. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestClip, + QuadsUnclipped_LayerClipped_Surface) { + root_delegated_render_pass_is_clipped_ = false; + clip_delegated_renderer_layer_ = true; + SetUpTest(); + + delegated_renderer_layer_->SetForceRenderSurface(true); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + ASSERT_EQ(3u, frame.render_passes.size()); + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + const SharedQuadState* root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + const SharedQuadState* contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + + // When the layer owns a surface, the quads don't need to be clipped + // further than they already specify. If they aren't clipped, then their + // clip rect is ignored, and they are not set as clipped. + EXPECT_FALSE(root_delegated_shared_quad_state->is_clipped); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +TEST_F(DelegatedRendererLayerImplTestClip, QuadsClipped_LayerClipped_Surface) { + root_delegated_render_pass_is_clipped_ = true; + clip_delegated_renderer_layer_ = true; + SetUpTest(); + + delegated_renderer_layer_->SetForceRenderSurface(true); + + LayerTreeHostImpl::FrameData frame; + EXPECT_TRUE(host_impl_->PrepareToDraw(&frame)); + + ASSERT_EQ(3u, frame.render_passes.size()); + const QuadList& contrib_delegated_quad_list = + frame.render_passes[0]->quad_list; + ASSERT_EQ(2u, contrib_delegated_quad_list.size()); + const QuadList& root_delegated_quad_list = frame.render_passes[1]->quad_list; + ASSERT_EQ(5u, root_delegated_quad_list.size()); + const SharedQuadState* root_delegated_shared_quad_state = + root_delegated_quad_list[0]->shared_quad_state; + const SharedQuadState* contrib_delegated_shared_quad_state = + contrib_delegated_quad_list[0]->shared_quad_state; + + // When the quads have a clip of their own, it is used, but it is + // combined with the clip rect of the delegated renderer layer. If the + // layer owns a surface, then it does not have a clip rect of its own. + EXPECT_EQ(gfx::Rect(5, 5, 40, 40).ToString(), + root_delegated_shared_quad_state->clip_rect.ToString()); + // Quads came with a clip rect. + EXPECT_TRUE(root_delegated_shared_quad_state->is_clipped); + + host_impl_->DrawLayers(&frame, base::TimeTicks::Now()); + host_impl_->DidDrawAllLayers(frame); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/draw_properties.h b/cc/layers/draw_properties.h new file mode 100644 index 0000000..23db23c --- /dev/null +++ b/cc/layers/draw_properties.h @@ -0,0 +1,99 @@ +// Copyright 2012 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 CC_LAYERS_DRAW_PROPERTIES_H_ +#define CC_LAYERS_DRAW_PROPERTIES_H_ + +#include "base/memory/scoped_ptr.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/transform.h" + +namespace cc { + +// Container for properties that layers need to compute before they can be +// drawn. +template<typename LayerType, typename RenderSurfaceType> +struct CC_EXPORT DrawProperties { + DrawProperties() + : opacity(0) + , opacity_is_animating(false) + , screen_space_opacity_is_animating(false) + , target_space_transform_is_animating(false) + , screen_space_transform_is_animating(false) + , can_use_lcd_text(false) + , is_clipped(false) + , render_target(0) + , contents_scale_x(1) + , contents_scale_y(1) + , num_descendants_that_draw_content(0) + , descendants_can_clip_selves(false) + { + } + + // Transforms objects from content space to target surface space, where + // this layer would be drawn. + gfx::Transform target_space_transform; + + // Transforms objects from content space to screen space (viewport space). + gfx::Transform screen_space_transform; + + // DrawProperties::opacity may be different than LayerType::opacity, + // particularly in the case when a renderSurface re-parents the layer's + // opacity, or when opacity is compounded by the hierarchy. + float opacity; + + // XXXIsAnimating flags are used to indicate whether the drawProperties + // are actually meaningful on the main thread. When the properties are + // animating, the main thread may not have the same values that are used + // to draw. + bool opacity_is_animating; + bool screen_space_opacity_is_animating; + bool target_space_transform_is_animating; + bool screen_space_transform_is_animating; + + // True if the layer can use LCD text. + bool can_use_lcd_text; + + // True if the layer needs to be clipped by clipRect. + bool is_clipped; + + // The layer whose coordinate space this layer draws into. This can be + // either the same layer (m_drawProperties.render_target == this) or an + // ancestor of this layer. + LayerType* render_target; + + // The surface that this layer and its subtree would contribute to. + scoped_ptr<RenderSurfaceType> render_surface; + + // This rect is in the layer's content space. + gfx::Rect visible_content_rect; + + // In target surface space, the rect that encloses the clipped, drawable + // content of the layer. + gfx::Rect drawable_content_rect; + + // In target surface space, the original rect that clipped this + // layer. This value is used to avoid unnecessarily changing GL scissor + // state. + gfx::Rect clip_rect; + + // The scale used to move between layer space and content space, and bounds + // of the space. One is always a function of the other, but which one + // depends on the layer type. For picture layers, this is an ideal scale, + // and not always the one used. + float contents_scale_x; + float contents_scale_y; + gfx::Size content_bounds; + + // Does not include this layer itself, only its children and descendants. + int num_descendants_that_draw_content; + + // If true, every descendant in the sub-tree can clip itself without the + // need to use hardware sissoring or a new render target. + bool descendants_can_clip_selves; +}; + +} // namespace cc + +#endif // CC_LAYERS_DRAW_PROPERTIES_H_ diff --git a/cc/layers/heads_up_display_layer.cc b/cc/layers/heads_up_display_layer.cc new file mode 100644 index 0000000..56d7c96 --- /dev/null +++ b/cc/layers/heads_up_display_layer.cc @@ -0,0 +1,64 @@ +// Copyright 2012 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 "cc/layers/heads_up_display_layer.h" + +#include "base/debug/trace_event.h" +#include "cc/layers/heads_up_display_layer_impl.h" +#include "cc/trees/layer_tree_host.h" + +namespace cc { + +scoped_refptr<HeadsUpDisplayLayer> HeadsUpDisplayLayer::Create() { + return make_scoped_refptr(new HeadsUpDisplayLayer()); +} + +HeadsUpDisplayLayer::HeadsUpDisplayLayer() : Layer() { + SetBounds(gfx::Size(256, 256)); +} + +HeadsUpDisplayLayer::~HeadsUpDisplayLayer() {} + +void HeadsUpDisplayLayer::Update(ResourceUpdateQueue*, + const OcclusionTracker*, + RenderingStats*) { + const LayerTreeDebugState& debug_state = layer_tree_host()->debug_state(); + int max_texture_size = + layer_tree_host()->GetRendererCapabilities().max_texture_size; + + int device_viewport_in_layout_pixels_width = + layer_tree_host()->device_viewport_size().width() / + layer_tree_host()->device_scale_factor(); + int device_viewport_in_layout_pixels_height = + layer_tree_host()->device_viewport_size().height() / + layer_tree_host()->device_scale_factor(); + + gfx::Size bounds; + gfx::Transform matrix; + matrix.MakeIdentity(); + + if (debug_state.showPlatformLayerTree || debug_state.showHudRects()) { + int width = + std::min(max_texture_size, device_viewport_in_layout_pixels_width); + int height = + std::min(max_texture_size, device_viewport_in_layout_pixels_height); + bounds = gfx::Size(width, height); + } else { + bounds = gfx::Size(256, 256); + matrix.Translate(device_viewport_in_layout_pixels_width - 256.0, 0.0); + } + + SetBounds(bounds); + SetTransform(matrix); +} + +bool HeadsUpDisplayLayer::DrawsContent() const { return true; } + +scoped_ptr<LayerImpl> HeadsUpDisplayLayer::CreateLayerImpl( + LayerTreeImpl* treeImpl) { + return HeadsUpDisplayLayerImpl::Create(treeImpl, layer_id_). + PassAs<LayerImpl>(); +} + +} // namespace cc diff --git a/cc/layers/heads_up_display_layer.h b/cc/layers/heads_up_display_layer.h new file mode 100644 index 0000000..d7656c5 --- /dev/null +++ b/cc/layers/heads_up_display_layer.h @@ -0,0 +1,35 @@ +// Copyright 2012 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 CC_LAYERS_HEADS_UP_DISPLAY_LAYER_H_ +#define CC_LAYERS_HEADS_UP_DISPLAY_LAYER_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/layers/layer.h" + +namespace cc { + +class CC_EXPORT HeadsUpDisplayLayer : public Layer { + public: + static scoped_refptr<HeadsUpDisplayLayer> Create(); + + virtual void Update(ResourceUpdateQueue* queue, + const OcclusionTracker* tracker, + RenderingStats* stats) OVERRIDE; + virtual bool DrawsContent() const OVERRIDE; + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + + protected: + HeadsUpDisplayLayer(); + + private: + virtual ~HeadsUpDisplayLayer(); +}; + +} // namespace cc + +#endif // CC_LAYERS_HEADS_UP_DISPLAY_LAYER_H_ diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc new file mode 100644 index 0000000..93c51e1 --- /dev/null +++ b/cc/layers/heads_up_display_layer_impl.cc @@ -0,0 +1,697 @@ +// Copyright 2012 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 "cc/layers/heads_up_display_layer_impl.h" + +#include "base/stringprintf.h" +#include "base/strings/string_split.h" +#include "cc/debug/debug_colors.h" +#include "cc/debug/debug_rect_history.h" +#include "cc/debug/frame_rate_counter.h" +#include "cc/debug/paint_time_counter.h" +#include "cc/layers/quad_sink.h" +#include "cc/output/renderer.h" +#include "cc/quads/texture_draw_quad.h" +#include "cc/resources/memory_history.h" +#include "cc/resources/tile_manager.h" +#include "cc/trees/layer_tree_impl.h" +#include "skia/ext/platform_canvas.h" +#include "skia/ext/platform_canvas.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "third_party/khronos/GLES2/gl2ext.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkTypeface.h" +#include "third_party/skia/include/effects/SkColorMatrixFilter.h" +#include "ui/gfx/point.h" +#include "ui/gfx/size.h" + +namespace cc { + +static inline SkPaint CreatePaint() { + SkPaint paint; +#if (SK_R32_SHIFT || SK_B32_SHIFT != 16) + // The SkCanvas is in RGBA but the shader is expecting BGRA, so we need to + // swizzle our colors when drawing to the SkCanvas. + SkColorMatrix swizzle_matrix; + for (int i = 0; i < 20; ++i) + swizzle_matrix.fMat[i] = 0; + swizzle_matrix.fMat[0 + 5 * 2] = 1; + swizzle_matrix.fMat[1 + 5 * 1] = 1; + swizzle_matrix.fMat[2 + 5 * 0] = 1; + swizzle_matrix.fMat[3 + 5 * 3] = 1; + + skia::RefPtr<SkColorMatrixFilter> filter = + skia::AdoptRef(new SkColorMatrixFilter(swizzle_matrix)); + paint.setColorFilter(filter.get()); +#endif + return paint; +} + +HeadsUpDisplayLayerImpl::Graph::Graph(double indicator_value, + double start_upper_bound) + : value(0.0), + min(0.0), + max(0.0), + current_upper_bound(start_upper_bound), + default_upper_bound(start_upper_bound), + indicator(indicator_value) {} + +double HeadsUpDisplayLayerImpl::Graph::UpdateUpperBound() { + double target_upper_bound = std::max(max, default_upper_bound); + current_upper_bound += (target_upper_bound - current_upper_bound) * 0.5; + return current_upper_bound; +} + +HeadsUpDisplayLayerImpl::HeadsUpDisplayLayerImpl(LayerTreeImpl* tree_impl, + int id) + : LayerImpl(tree_impl, id), + fps_graph_(60.0, 80.0), + paint_time_graph_(16.0, 48.0), + typeface_(skia::AdoptRef( + SkTypeface::CreateFromName("monospace", SkTypeface::kBold))) {} + +HeadsUpDisplayLayerImpl::~HeadsUpDisplayLayerImpl() {} + +scoped_ptr<LayerImpl> HeadsUpDisplayLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return HeadsUpDisplayLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void HeadsUpDisplayLayerImpl::WillDraw(ResourceProvider* resource_provider) { + LayerImpl::WillDraw(resource_provider); + + if (!hud_texture_) + hud_texture_ = ScopedResource::create(resource_provider); + + // TODO(danakj): Scale the HUD by deviceScale to make it more friendly under + // high DPI. + + // TODO(danakj): The HUD could swap between two textures instead of creating a + // texture every frame in ubercompositor. + if (hud_texture_->size() != bounds() || + resource_provider->InUseByConsumer(hud_texture_->id())) + hud_texture_->Free(); + + if (!hud_texture_->id()) { + hud_texture_->Allocate( + bounds(), GL_RGBA, ResourceProvider::TextureUsageAny); + // TODO(epenner): This texture was being used before SetPixels was called, + // which is now not allowed (it's an uninitialized read). This should be + // fixed and this allocateForTesting() removed. + // http://crbug.com/166784 + resource_provider->AllocateForTesting(hud_texture_->id()); + } +} + +void HeadsUpDisplayLayerImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + if (!hud_texture_->id()) + return; + + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(CreateSharedQuadState()); + + gfx::Rect quad_rect(gfx::Point(), bounds()); + gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect()); + bool premultiplied_alpha = true; + gfx::PointF uv_top_left(0.f, 0.f); + gfx::PointF uv_bottom_right(1.f, 1.f); + const float vertex_opacity[] = { 1.f, 1.f, 1.f, 1.f }; + bool flipped = false; + scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + hud_texture_->id(), + premultiplied_alpha, + uv_top_left, + uv_bottom_right, + vertex_opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); +} + +void HeadsUpDisplayLayerImpl::UpdateHudTexture( + ResourceProvider* resource_provider) { + if (!hud_texture_->id()) + return; + + SkISize canvas_size; + if (hud_canvas_) + canvas_size = hud_canvas_->getDeviceSize(); + else + canvas_size.set(0, 0); + + if (canvas_size.fWidth != bounds().width() || + canvas_size.fHeight != bounds().height() || !hud_canvas_) { + bool opaque = false; + hud_canvas_ = make_scoped_ptr( + skia::CreateBitmapCanvas(bounds().width(), bounds().height(), opaque)); + } + + UpdateHudContents(); + + hud_canvas_->clear(SkColorSetARGB(0, 0, 0, 0)); + DrawHudContents(hud_canvas_.get()); + + const SkBitmap* bitmap = &hud_canvas_->getDevice()->accessBitmap(false); + SkAutoLockPixels locker(*bitmap); + + gfx::Rect layer_rect(bounds()); + DCHECK(bitmap->config() == SkBitmap::kARGB_8888_Config); + resource_provider->SetPixels(hud_texture_->id(), + static_cast<const uint8_t*>(bitmap->getPixels()), + layer_rect, + layer_rect, + gfx::Vector2d()); +} + +void HeadsUpDisplayLayerImpl::DidDraw(ResourceProvider* resource_provider) { + LayerImpl::DidDraw(resource_provider); + + if (!hud_texture_->id()) + return; + + // FIXME: the following assert will not be true when sending resources to a + // parent compositor. We will probably need to hold on to hud_texture_ for + // longer, and have several HUD textures in the pipeline. + DCHECK(!resource_provider->InUseByConsumer(hud_texture_->id())); +} + +void HeadsUpDisplayLayerImpl::DidLoseOutputSurface() { hud_texture_.reset(); } + +bool HeadsUpDisplayLayerImpl::LayerIsAlwaysDamaged() const { return true; } + +void HeadsUpDisplayLayerImpl::UpdateHudContents() { + const LayerTreeDebugState& debug_state = layer_tree_impl()->debug_state(); + + // Don't update numbers every frame so text is readable. + base::TimeTicks now = layer_tree_impl()->CurrentFrameTime(); + if (base::TimeDelta(now - time_of_last_graph_update_).InSecondsF() > 0.25f) { + time_of_last_graph_update_ = now; + + if (debug_state.showFPSCounter) { + FrameRateCounter* fps_counter = layer_tree_impl()->frame_rate_counter(); + fps_graph_.value = fps_counter->GetAverageFPS(); + fps_counter->GetMinAndMaxFPS(&fps_graph_.min, &fps_graph_.max); + } + + if (debug_state.continuousPainting) { + PaintTimeCounter* paint_time_counter = + layer_tree_impl()->paint_time_counter(); + base::TimeDelta latest, min, max; + + if (paint_time_counter->End()) + latest = paint_time_counter->End()->total_time(); + paint_time_counter->GetMinAndMaxPaintTime(&min, &max); + + paint_time_graph_.value = latest.InMillisecondsF(); + paint_time_graph_.min = min.InMillisecondsF(); + paint_time_graph_.max = max.InMillisecondsF(); + } + + if (debug_state.showMemoryStats()) { + MemoryHistory* memory_history = layer_tree_impl()->memory_history(); + if (memory_history->End()) + memory_entry_ = **memory_history->End(); + else + memory_entry_ = MemoryHistory::Entry(); + } + } + + fps_graph_.UpdateUpperBound(); + paint_time_graph_.UpdateUpperBound(); +} + +void HeadsUpDisplayLayerImpl::DrawHudContents(SkCanvas* canvas) const { + const LayerTreeDebugState& debug_state = layer_tree_impl()->debug_state(); + + if (debug_state.showHudRects()) + DrawDebugRects(canvas, layer_tree_impl()->debug_rect_history()); + + if (debug_state.showPlatformLayerTree) + DrawPlatformLayerTree(canvas); + + SkRect area = SkRect::MakeEmpty(); + if (debug_state.continuousPainting) { + // Don't show the FPS display when continuous painting is enabled, because + // it would show misleading numbers. + area = DrawPaintTimeDisplay( + canvas, layer_tree_impl()->paint_time_counter(), 0, 0); + } else if (debug_state.showFPSCounter) { + area = + DrawFPSDisplay(canvas, layer_tree_impl()->frame_rate_counter(), 0, 0); + } + + if (debug_state.showMemoryStats()) + DrawMemoryDisplay(canvas, 0, area.bottom(), SkMaxScalar(area.width(), 150)); +} + +void HeadsUpDisplayLayerImpl::DrawText(SkCanvas* canvas, + SkPaint* paint, + const std::string& text, + SkPaint::Align align, + int size, + int x, + int y) const { + const bool anti_alias = paint->isAntiAlias(); + paint->setAntiAlias(true); + + paint->setTextSize(size); + paint->setTextAlign(align); + paint->setTypeface(typeface_.get()); + canvas->drawText(text.c_str(), text.length(), x, y, *paint); + + paint->setAntiAlias(anti_alias); +} + +void HeadsUpDisplayLayerImpl::DrawText(SkCanvas* canvas, + SkPaint* paint, + const std::string& text, + SkPaint::Align align, + int size, + const SkPoint& pos) const { + DrawText(canvas, paint, text, align, size, pos.x(), pos.y()); +} + +void HeadsUpDisplayLayerImpl::DrawGraphBackground(SkCanvas* canvas, + SkPaint* paint, + const SkRect& bounds) const { + paint->setColor(DebugColors::HUDBackgroundColor()); + canvas->drawRect(bounds, *paint); +} + +void HeadsUpDisplayLayerImpl::DrawGraphLines(SkCanvas* canvas, + SkPaint* paint, + const SkRect& bounds, + const Graph& graph) const { + // Draw top and bottom line. + paint->setColor(DebugColors::HUDSeparatorLineColor()); + canvas->drawLine(bounds.left(), + bounds.top() - 1, + bounds.right(), + bounds.top() - 1, + *paint); + canvas->drawLine( + bounds.left(), bounds.bottom(), bounds.right(), bounds.bottom(), *paint); + + // Draw indicator line (additive blend mode to increase contrast when drawn on + // top of graph). + paint->setColor(DebugColors::HUDIndicatorLineColor()); + paint->setXfermodeMode(SkXfermode::kPlus_Mode); + const double indicator_top = + bounds.height() * (1.0 - graph.indicator / graph.current_upper_bound) - + 1.0; + canvas->drawLine(bounds.left(), + bounds.top() + indicator_top, + bounds.right(), + bounds.top() + indicator_top, + *paint); + paint->setXfermode(NULL); +} + +void HeadsUpDisplayLayerImpl::DrawPlatformLayerTree(SkCanvas* canvas) const { + const int kFontHeight = 14; + SkPaint paint = CreatePaint(); + DrawGraphBackground( + canvas, + &paint, + SkRect::MakeXYWH(0, 0, bounds().width(), bounds().height())); + + std::string layer_tree = layer_tree_impl()->layer_tree_as_text(); + std::vector<std::string> lines; + base::SplitString(layer_tree, '\n', &lines); + + paint.setColor(DebugColors::PlatformLayerTreeTextColor()); + for (size_t i = 0; + i < lines.size() && + static_cast<int>(2 + i * kFontHeight) < bounds().height(); + ++i) { + DrawText(canvas, + &paint, + lines[i], + SkPaint::kLeft_Align, + kFontHeight, + 2, + 2 + (i + 1) * kFontHeight); + } +} + +SkRect HeadsUpDisplayLayerImpl::DrawFPSDisplay( + SkCanvas* canvas, + const FrameRateCounter* fps_counter, + int right, + int top) const { + const int kPadding = 4; + const int kGap = 6; + + const int kFontHeight = 15; + + const int kGraphWidth = fps_counter->time_stamp_history_size() - 2; + const int kGraphHeight = 40; + + const int kHistogramWidth = 37; + + int width = kGraphWidth + kHistogramWidth + 4 * kPadding; + int height = kFontHeight + kGraphHeight + 4 * kPadding + 2; + int left = bounds().width() - width - right; + SkRect area = SkRect::MakeXYWH(left, top, width, height); + + SkPaint paint = CreatePaint(); + DrawGraphBackground(canvas, &paint, area); + + SkRect text_bounds = + SkRect::MakeXYWH(left + kPadding, + top + kPadding, + kGraphWidth + kHistogramWidth + kGap + 2, + kFontHeight); + SkRect graph_bounds = SkRect::MakeXYWH(left + kPadding, + text_bounds.bottom() + 2 * kPadding, + kGraphWidth, + kGraphHeight); + SkRect histogram_bounds = SkRect::MakeXYWH(graph_bounds.right() + kGap, + graph_bounds.top(), + kHistogramWidth, + kGraphHeight); + + const std::string value_text = + base::StringPrintf("FPS:%5.1f", fps_graph_.value); + const std::string min_max_text = + base::StringPrintf("%.0f-%.0f", fps_graph_.min, fps_graph_.max); + + paint.setColor(DebugColors::FPSDisplayTextAndGraphColor()); + DrawText(canvas, + &paint, + value_text, + SkPaint::kLeft_Align, + kFontHeight, + text_bounds.left(), + text_bounds.bottom()); + DrawText(canvas, + &paint, + min_max_text, + SkPaint::kRight_Align, + kFontHeight, + text_bounds.right(), + text_bounds.bottom()); + + DrawGraphLines(canvas, &paint, graph_bounds, fps_graph_); + + // Collect graph and histogram data. + SkPath path; + + const int kHistogramSize = 20; + double histogram[kHistogramSize] = { 1.0 }; + double max_bucket_value = 1.0; + + for (FrameRateCounter::RingBufferType::Iterator it = --fps_counter->end(); it; + --it) { + base::TimeDelta delta = fps_counter->RecentFrameInterval(it.index() + 1); + + // Skip this particular instantaneous frame rate if it is not likely to have + // been valid. + if (!fps_counter->IsBadFrameInterval(delta)) { + double fps = 1.0 / delta.InSecondsF(); + + // Clamp the FPS to the range we want to plot visually. + double p = fps / fps_graph_.current_upper_bound; + if (p > 1.0) + p = 1.0; + + // Plot this data point. + SkPoint cur = + SkPoint::Make(graph_bounds.left() + it.index(), + graph_bounds.bottom() - p * graph_bounds.height()); + if (path.isEmpty()) + path.moveTo(cur); + else + path.lineTo(cur); + + // Use the fps value to find the right bucket in the histogram. + int bucket_index = floor(p * (kHistogramSize - 1)); + + // Add the delta time to take the time spent at that fps rate into + // account. + histogram[bucket_index] += delta.InSecondsF(); + max_bucket_value = std::max(histogram[bucket_index], max_bucket_value); + } + } + + // Draw FPS histogram. + paint.setColor(DebugColors::HUDSeparatorLineColor()); + canvas->drawLine(histogram_bounds.left() - 1, + histogram_bounds.top() - 1, + histogram_bounds.left() - 1, + histogram_bounds.bottom() + 1, + paint); + canvas->drawLine(histogram_bounds.right() + 1, + histogram_bounds.top() - 1, + histogram_bounds.right() + 1, + histogram_bounds.bottom() + 1, + paint); + + paint.setColor(DebugColors::FPSDisplayTextAndGraphColor()); + const double bar_height = histogram_bounds.height() / kHistogramSize; + + for (int i = kHistogramSize - 1; i >= 0; --i) { + if (histogram[i] > 0) { + double bar_width = + histogram[i] / max_bucket_value * histogram_bounds.width(); + canvas->drawRect( + SkRect::MakeXYWH(histogram_bounds.left(), + histogram_bounds.bottom() - (i + 1) * bar_height, + bar_width, + 1), + paint); + } + } + + // Draw FPS graph. + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(1); + canvas->drawPath(path, paint); + + return area; +} + +SkRect HeadsUpDisplayLayerImpl::DrawMemoryDisplay(SkCanvas* canvas, + int right, + int top, + int width) const { + if (!memory_entry_.bytes_total()) + return SkRect::MakeEmpty(); + + const int kPadding = 4; + const int kFontHeight = 13; + + const int height = 3 * kFontHeight + 4 * kPadding; + const int left = bounds().width() - width - right; + const SkRect area = SkRect::MakeXYWH(left, top, width, height); + + const double megabyte = 1024.0 * 1024.0; + + SkPaint paint = CreatePaint(); + DrawGraphBackground(canvas, &paint, area); + + SkPoint title_pos = SkPoint::Make(left + kPadding, top + kFontHeight); + SkPoint stat1_pos = SkPoint::Make(left + width - kPadding - 1, + top + kPadding + 2 * kFontHeight); + SkPoint stat2_pos = SkPoint::Make(left + width - kPadding - 1, + top + 2 * kPadding + 3 * kFontHeight); + + paint.setColor(DebugColors::MemoryDisplayTextColor()); + DrawText(canvas, + &paint, + "GPU memory", + SkPaint::kLeft_Align, + kFontHeight, + title_pos); + + std::string text = + base::StringPrintf("%6.1f MB used", + (memory_entry_.bytes_unreleasable + + memory_entry_.bytes_allocated) / megabyte); + DrawText(canvas, &paint, text, SkPaint::kRight_Align, kFontHeight, stat1_pos); + + if (memory_entry_.bytes_over) { + paint.setColor(SK_ColorRED); + text = base::StringPrintf("%6.1f MB over", + memory_entry_.bytes_over / megabyte); + } else { + text = base::StringPrintf("%6.1f MB max ", + memory_entry_.total_budget_in_bytes / megabyte); + } + DrawText(canvas, &paint, text, SkPaint::kRight_Align, kFontHeight, stat2_pos); + + return area; +} + +SkRect HeadsUpDisplayLayerImpl::DrawPaintTimeDisplay( + SkCanvas* canvas, + const PaintTimeCounter* paint_time_counter, + int right, + int top) const { + const int kPadding = 4; + const int kFontHeight = 15; + + const int kGraphWidth = paint_time_counter->HistorySize(); + const int kGraphHeight = 40; + + const int width = kGraphWidth + 2 * kPadding; + const int height = + kFontHeight + kGraphHeight + 4 * kPadding + 2 + kFontHeight + kPadding; + const int left = bounds().width() - width - right; + + const SkRect area = SkRect::MakeXYWH(left, top, width, height); + + SkPaint paint = CreatePaint(); + DrawGraphBackground(canvas, &paint, area); + + SkRect text_bounds = SkRect::MakeXYWH( + left + kPadding, top + kPadding, kGraphWidth, kFontHeight); + SkRect text_bounds2 = SkRect::MakeXYWH(left + kPadding, + text_bounds.bottom() + kPadding, + kGraphWidth, + kFontHeight); + SkRect graph_bounds = SkRect::MakeXYWH(left + kPadding, + text_bounds2.bottom() + 2 * kPadding, + kGraphWidth, + kGraphHeight); + + const std::string value_text = + base::StringPrintf("%.1f", paint_time_graph_.value); + const std::string min_max_text = base::StringPrintf( + "%.1f-%.1f", paint_time_graph_.min, paint_time_graph_.max); + + paint.setColor(DebugColors::PaintTimeDisplayTextAndGraphColor()); + DrawText(canvas, + &paint, + "Page paint time (ms)", + SkPaint::kLeft_Align, + kFontHeight, + text_bounds.left(), + text_bounds.bottom()); + DrawText(canvas, + &paint, + value_text, + SkPaint::kLeft_Align, + kFontHeight, + text_bounds2.left(), + text_bounds2.bottom()); + DrawText(canvas, + &paint, + min_max_text, + SkPaint::kRight_Align, + kFontHeight, + text_bounds2.right(), + text_bounds2.bottom()); + + paint.setColor(DebugColors::PaintTimeDisplayTextAndGraphColor()); + for (PaintTimeCounter::RingBufferType::Iterator it = + paint_time_counter->End(); + it; + --it) { + double pt = it->total_time().InMillisecondsF(); + + if (pt == 0.0) + continue; + + double p = pt / paint_time_graph_.current_upper_bound; + if (p > 1.0) + p = 1.0; + + canvas->drawRect( + SkRect::MakeXYWH(graph_bounds.left() + it.index(), + graph_bounds.bottom() - p * graph_bounds.height(), + 1, + p * graph_bounds.height()), + paint); + } + + DrawGraphLines(canvas, &paint, graph_bounds, paint_time_graph_); + + return area; +} + +void HeadsUpDisplayLayerImpl::DrawDebugRects( + SkCanvas* canvas, + DebugRectHistory* debug_rect_history) const { + const std::vector<DebugRect>& debug_rects = debug_rect_history->debug_rects(); + float rect_scale = 1.f / layer_tree_impl()->device_scale_factor(); + SkPaint paint = CreatePaint(); + + canvas->save(); + canvas->scale(rect_scale, rect_scale); + + for (size_t i = 0; i < debug_rects.size(); ++i) { + SkColor stroke_color = 0; + SkColor fill_color = 0; + float stroke_width = 0.f; + + switch (debug_rects[i].type) { + case PAINT_RECT_TYPE: + stroke_color = DebugColors::PaintRectBorderColor(); + fill_color = DebugColors::PaintRectFillColor(); + stroke_width = DebugColors::PaintRectBorderWidth(layer_tree_impl()); + break; + case PROPERTY_CHANGED_RECT_TYPE: + stroke_color = DebugColors::PropertyChangedRectBorderColor(); + fill_color = DebugColors::PropertyChangedRectFillColor(); + stroke_width = + DebugColors::PropertyChangedRectBorderWidth(layer_tree_impl()); + break; + case SURFACE_DAMAGE_RECT_TYPE: + stroke_color = DebugColors::SurfaceDamageRectBorderColor(); + fill_color = DebugColors::SurfaceDamageRectFillColor(); + stroke_width = + DebugColors::SurfaceDamageRectBorderWidth(layer_tree_impl()); + break; + case REPLICA_SCREEN_SPACE_RECT_TYPE: + stroke_color = DebugColors::ScreenSpaceSurfaceReplicaRectBorderColor(); + fill_color = DebugColors::ScreenSpaceSurfaceReplicaRectFillColor(); + stroke_width = DebugColors::ScreenSpaceSurfaceReplicaRectBorderWidth( + layer_tree_impl()); + break; + case SCREEN_SPACE_RECT_TYPE: + stroke_color = DebugColors::ScreenSpaceLayerRectBorderColor(); + fill_color = DebugColors::ScreenSpaceLayerRectFillColor(); + stroke_width = + DebugColors::ScreenSpaceLayerRectBorderWidth(layer_tree_impl()); + break; + case OCCLUDING_RECT_TYPE: + stroke_color = DebugColors::OccludingRectBorderColor(); + fill_color = DebugColors::OccludingRectFillColor(); + stroke_width = DebugColors::OccludingRectBorderWidth(layer_tree_impl()); + break; + case NONOCCLUDING_RECT_TYPE: + stroke_color = DebugColors::NonOccludingRectBorderColor(); + fill_color = DebugColors::NonOccludingRectFillColor(); + stroke_width = + DebugColors::NonOccludingRectBorderWidth(layer_tree_impl()); + break; + } + + const gfx::RectF& rect = debug_rects[i].rect; + SkRect sk_rect = + SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()); + paint.setColor(fill_color); + paint.setStyle(SkPaint::kFill_Style); + canvas->drawRect(sk_rect, paint); + + paint.setColor(stroke_color); + paint.setStyle(SkPaint::kStroke_Style); + paint.setStrokeWidth(SkFloatToScalar(stroke_width)); + canvas->drawRect(sk_rect, paint); + } + + canvas->restore(); +} + +const char* HeadsUpDisplayLayerImpl::LayerTypeAsString() const { + return "HeadsUpDisplayLayer"; +} + +} // namespace cc diff --git a/cc/layers/heads_up_display_layer_impl.h b/cc/layers/heads_up_display_layer_impl.h new file mode 100644 index 0000000..3bc3cf2 --- /dev/null +++ b/cc/layers/heads_up_display_layer_impl.h @@ -0,0 +1,126 @@ +// Copyright 2012 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 CC_LAYERS_HEADS_UP_DISPLAY_LAYER_IMPL_H_ +#define CC_LAYERS_HEADS_UP_DISPLAY_LAYER_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/time.h" +#include "cc/base/cc_export.h" +#include "cc/layers/layer_impl.h" +#include "cc/resources/memory_history.h" +#include "cc/resources/scoped_resource.h" + +class SkCanvas; +class SkPaint; +class SkTypeface; +struct SkRect; + +namespace cc { + +class DebugRectHistory; +class FrameRateCounter; +class PaintTimeCounter; + +class CC_EXPORT HeadsUpDisplayLayerImpl : public LayerImpl { + public: + static scoped_ptr<HeadsUpDisplayLayerImpl> Create(LayerTreeImpl* tree_impl, + int id) { + return make_scoped_ptr(new HeadsUpDisplayLayerImpl(tree_impl, id)); + } + virtual ~HeadsUpDisplayLayerImpl(); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + + virtual void WillDraw(ResourceProvider* resource_provider) OVERRIDE; + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + void UpdateHudTexture(ResourceProvider* resource_provider); + virtual void DidDraw(ResourceProvider* resource_provider) OVERRIDE; + + virtual void DidLoseOutputSurface() OVERRIDE; + + virtual bool LayerIsAlwaysDamaged() const OVERRIDE; + + private: + class Graph { + public: + Graph(double indicator_value, double start_upper_bound); + + // Eases the upper bound, which limits what is currently visible in the + // graph, so that the graph always scales to either it's max or + // default_upper_bound. + double UpdateUpperBound(); + + double value; + double min; + double max; + + double current_upper_bound; + const double default_upper_bound; + const double indicator; + }; + + HeadsUpDisplayLayerImpl(LayerTreeImpl* tree_impl, int id); + + virtual const char* LayerTypeAsString() const OVERRIDE; + + void UpdateHudContents(); + void DrawHudContents(SkCanvas* canvas) const; + + void DrawText(SkCanvas* canvas, + SkPaint* paint, + const std::string& text, + SkPaint::Align align, + int size, + int x, + int y) const; + void DrawText(SkCanvas* canvas, + SkPaint* paint, + const std::string& text, + SkPaint::Align align, + int size, + const SkPoint& pos) const; + void DrawGraphBackground(SkCanvas* canvas, + SkPaint* paint, + const SkRect& bounds) const; + void DrawGraphLines(SkCanvas* canvas, + SkPaint* paint, + const SkRect& bounds, + const Graph& graph) const; + + void DrawPlatformLayerTree(SkCanvas* canvas) const; + SkRect DrawFPSDisplay(SkCanvas* canvas, + const FrameRateCounter* fps_counter, + int right, + int top) const; + SkRect DrawMemoryDisplay(SkCanvas* canvas, + int top, + int right, + int width) const; + SkRect DrawPaintTimeDisplay(SkCanvas* canvas, + const PaintTimeCounter* paint_time_counter, + int top, + int right) const; + void DrawDebugRects(SkCanvas* canvas, + DebugRectHistory* debug_rect_history) const; + + scoped_ptr<ScopedResource> hud_texture_; + scoped_ptr<SkCanvas> hud_canvas_; + + skia::RefPtr<SkTypeface> typeface_; + + Graph fps_graph_; + Graph paint_time_graph_; + MemoryHistory::Entry memory_entry_; + + base::TimeTicks time_of_last_graph_update_; + + DISALLOW_COPY_AND_ASSIGN(HeadsUpDisplayLayerImpl); +}; + +} // namespace cc + +#endif // CC_LAYERS_HEADS_UP_DISPLAY_LAYER_IMPL_H_ diff --git a/cc/layers/heads_up_display_unittest.cc b/cc/layers/heads_up_display_unittest.cc new file mode 100644 index 0000000..1a4213e --- /dev/null +++ b/cc/layers/heads_up_display_unittest.cc @@ -0,0 +1,110 @@ +// Copyright 2012 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 "cc/layers/heads_up_display_layer.h" +#include "cc/layers/layer.h" +#include "cc/test/layer_tree_test_common.h" +#include "cc/trees/layer_tree_host.h" + +namespace cc { +namespace { + +class HeadsUpDisplayTest : public ThreadedTest { +protected: + virtual void initializeSettings(LayerTreeSettings& settings) OVERRIDE + { + // Enable the HUD without requiring text. + settings.initialDebugState.showPropertyChangedRects = true; + } +}; + +class DrawsContentLayer : public Layer { +public: + static scoped_refptr<DrawsContentLayer> Create() { return make_scoped_refptr(new DrawsContentLayer()); } + virtual bool DrawsContent() const OVERRIDE { return true; } + +private: + DrawsContentLayer() : Layer() { } + virtual ~DrawsContentLayer() + { + } +}; + +class HudWithRootLayerChange : public HeadsUpDisplayTest { +public: + HudWithRootLayerChange() + : m_rootLayer1(DrawsContentLayer::Create()) + , m_rootLayer2(DrawsContentLayer::Create()) + , m_numCommits(0) + { + } + + virtual void beginTest() OVERRIDE + { + m_rootLayer1->SetBounds(gfx::Size(30, 30)); + m_rootLayer2->SetBounds(gfx::Size(30, 30)); + + postSetNeedsCommitToMainThread(); + } + + virtual void didCommit() OVERRIDE + { + ++m_numCommits; + + ASSERT_TRUE(m_layerTreeHost->hud_layer()); + + switch (m_numCommits) { + case 1: + // Change directly to a new root layer. + m_layerTreeHost->SetRootLayer(m_rootLayer1); + break; + case 2: + EXPECT_EQ(m_rootLayer1.get(), m_layerTreeHost->hud_layer()->parent()); + // Unset the root layer. + m_layerTreeHost->SetRootLayer(NULL); + break; + case 3: + EXPECT_EQ(0, m_layerTreeHost->hud_layer()->parent()); + // Change back to the previous root layer. + m_layerTreeHost->SetRootLayer(m_rootLayer1); + break; + case 4: + EXPECT_EQ(m_rootLayer1.get(), m_layerTreeHost->hud_layer()->parent()); + // Unset the root layer. + m_layerTreeHost->SetRootLayer(NULL); + break; + case 5: + EXPECT_EQ(0, m_layerTreeHost->hud_layer()->parent()); + // Change to a new root layer from a null root. + m_layerTreeHost->SetRootLayer(m_rootLayer2); + break; + case 6: + EXPECT_EQ(m_rootLayer2.get(), m_layerTreeHost->hud_layer()->parent()); + // Change directly back to the last root layer/ + m_layerTreeHost->SetRootLayer(m_rootLayer1); + break; + case 7: + EXPECT_EQ(m_rootLayer1.get(), m_layerTreeHost->hud_layer()->parent()); + endTest(); + break; + } + } + + virtual void afterTest() OVERRIDE + { + } + +private: + scoped_refptr<DrawsContentLayer> m_rootLayer1; + scoped_refptr<DrawsContentLayer> m_rootLayer2; + int m_numCommits; +}; + +TEST_F(HudWithRootLayerChange, runMultiThread) +{ + runTest(true); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/image_layer.cc b/cc/layers/image_layer.cc new file mode 100644 index 0000000..4f5e105 --- /dev/null +++ b/cc/layers/image_layer.cc @@ -0,0 +1,96 @@ +// Copyright 2010 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 "cc/layers/image_layer.h" + +#include "base/compiler_specific.h" +#include "cc/resources/image_layer_updater.h" +#include "cc/resources/layer_updater.h" +#include "cc/resources/prioritized_resource.h" +#include "cc/resources/resource_update_queue.h" +#include "cc/trees/layer_tree_host.h" + +namespace cc { + +scoped_refptr<ImageLayer> ImageLayer::Create() { + return make_scoped_refptr(new ImageLayer()); +} + +ImageLayer::ImageLayer() : TiledLayer() {} + +ImageLayer::~ImageLayer() {} + +void ImageLayer::SetBitmap(const SkBitmap& bitmap) { + // SetBitmap() currently gets called whenever there is any + // style change that affects the layer even if that change doesn't + // affect the actual contents of the image (e.g. a CSS animation). + // With this check in place we avoid unecessary texture uploads. + if (bitmap.pixelRef() && bitmap.pixelRef() == bitmap_.pixelRef()) + return; + + bitmap_ = bitmap; + SetNeedsDisplay(); +} + +void ImageLayer::SetTexturePriorities(const PriorityCalculator& priority_calc) { + // Update the tile data before creating all the layer's tiles. + UpdateTileSizeAndTilingOption(); + + TiledLayer::SetTexturePriorities(priority_calc); +} + +void ImageLayer::Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) { + CreateUpdaterIfNeeded(); + if (needs_display_) { + updater_->set_bitmap(bitmap_); + UpdateTileSizeAndTilingOption(); + InvalidateContentRect(gfx::Rect(content_bounds())); + needs_display_ = false; + } + TiledLayer::Update(queue, occlusion, stats); +} + +void ImageLayer::CreateUpdaterIfNeeded() { + if (updater_) + return; + + updater_ = ImageLayerUpdater::Create(); + GLenum texture_format = + layer_tree_host()->GetRendererCapabilities().best_texture_format; + SetTextureFormat(texture_format); +} + +LayerUpdater* ImageLayer::Updater() const { + return updater_.get(); +} + +void ImageLayer::CalculateContentsScale(float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* content_bounds) { + *contents_scale_x = ImageContentsScaleX(); + *contents_scale_y = ImageContentsScaleY(); + *content_bounds = gfx::Size(bitmap_.width(), bitmap_.height()); +} + +bool ImageLayer::DrawsContent() const { + return !bitmap_.isNull() && TiledLayer::DrawsContent(); +} + +float ImageLayer::ImageContentsScaleX() const { + if (bounds().IsEmpty() || bitmap_.width() == 0) + return 1; + return static_cast<float>(bitmap_.width()) / bounds().width(); +} + +float ImageLayer::ImageContentsScaleY() const { + if (bounds().IsEmpty() || bitmap_.height() == 0) + return 1; + return static_cast<float>(bitmap_.height()) / bounds().height(); +} + +} // namespace cc diff --git a/cc/layers/image_layer.h b/cc/layers/image_layer.h new file mode 100644 index 0000000..8ba5b78 --- /dev/null +++ b/cc/layers/image_layer.h @@ -0,0 +1,56 @@ +// Copyright 2010 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 CC_LAYERS_IMAGE_LAYER_H_ +#define CC_LAYERS_IMAGE_LAYER_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/content_layer.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace cc { + +class ImageLayerUpdater; + +// A Layer that contains only an Image element. +class CC_EXPORT ImageLayer : public TiledLayer { + public: + static scoped_refptr<ImageLayer> Create(); + + // Layer implementation. + virtual bool DrawsContent() const OVERRIDE; + virtual void SetTexturePriorities(const PriorityCalculator& priority_calc) + OVERRIDE; + virtual void Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) OVERRIDE; + virtual void CalculateContentsScale(float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* content_bounds) OVERRIDE; + + void SetBitmap(const SkBitmap& image); + + private: + ImageLayer(); + virtual ~ImageLayer(); + + // TiledLayer Implementation. + virtual LayerUpdater* Updater() const OVERRIDE; + virtual void CreateUpdaterIfNeeded() OVERRIDE; + + float ImageContentsScaleX() const; + float ImageContentsScaleY() const; + + SkBitmap bitmap_; + + scoped_refptr<ImageLayerUpdater> updater_; + + DISALLOW_COPY_AND_ASSIGN(ImageLayer); +}; + +} // namespace cc + +#endif // CC_LAYERS_IMAGE_LAYER_H_ diff --git a/cc/layers/io_surface_layer.cc b/cc/layers/io_surface_layer.cc new file mode 100644 index 0000000..3ccbdd9 --- /dev/null +++ b/cc/layers/io_surface_layer.cc @@ -0,0 +1,43 @@ +// Copyright 2012 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 "cc/layers/io_surface_layer.h" + +#include "cc/layers/io_surface_layer_impl.h" + +namespace cc { + +scoped_refptr<IOSurfaceLayer> IOSurfaceLayer::Create() { + return make_scoped_refptr(new IOSurfaceLayer()); +} + +IOSurfaceLayer::IOSurfaceLayer() : Layer(), io_surface_id_(0) {} + +IOSurfaceLayer::~IOSurfaceLayer() {} + +void IOSurfaceLayer::SetIOSurfaceProperties(uint32_t io_surface_id, + gfx::Size size) { + io_surface_id_ = io_surface_id; + io_surface_size_ = size; + SetNeedsCommit(); +} + +scoped_ptr<LayerImpl> IOSurfaceLayer::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return IOSurfaceLayerImpl::Create(tree_impl, layer_id_).PassAs<LayerImpl>(); +} + +bool IOSurfaceLayer::DrawsContent() const { + return io_surface_id_ && Layer::DrawsContent(); +} + +void IOSurfaceLayer::PushPropertiesTo(LayerImpl* layer) { + Layer::PushPropertiesTo(layer); + + IOSurfaceLayerImpl* io_surface_layer = + static_cast<IOSurfaceLayerImpl*>(layer); + io_surface_layer->SetIOSurfaceProperties(io_surface_id_, io_surface_size_); +} + +} // namespace cc diff --git a/cc/layers/io_surface_layer.h b/cc/layers/io_surface_layer.h new file mode 100644 index 0000000..4073a0d --- /dev/null +++ b/cc/layers/io_surface_layer.h @@ -0,0 +1,35 @@ +// Copyright 2012 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 CC_LAYERS_IO_SURFACE_LAYER_H_ +#define CC_LAYERS_IO_SURFACE_LAYER_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/layer.h" + +namespace cc { + +class CC_EXPORT IOSurfaceLayer : public Layer { + public: + static scoped_refptr<IOSurfaceLayer> Create(); + + void SetIOSurfaceProperties(uint32_t io_surface_id, gfx::Size size); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual bool DrawsContent() const OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + + protected: + IOSurfaceLayer(); + + private: + virtual ~IOSurfaceLayer(); + + uint32_t io_surface_id_; + gfx::Size io_surface_size_; +}; + +} +#endif // CC_LAYERS_IO_SURFACE_LAYER_H_ diff --git a/cc/layers/io_surface_layer_impl.cc b/cc/layers/io_surface_layer_impl.cc new file mode 100644 index 0000000..3bfc1de --- /dev/null +++ b/cc/layers/io_surface_layer_impl.cc @@ -0,0 +1,145 @@ +// Copyright 2012 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 "cc/layers/io_surface_layer_impl.h" + +#include "base/stringprintf.h" +#include "cc/layers/quad_sink.h" +#include "cc/output/gl_renderer.h" // For the GLC() macro. +#include "cc/output/output_surface.h" +#include "cc/quads/io_surface_draw_quad.h" +#include "cc/trees/layer_tree_impl.h" +#include "gpu/GLES2/gl2extchromium.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebGraphicsContext3D.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "third_party/khronos/GLES2/gl2ext.h" + +namespace cc { + +IOSurfaceLayerImpl::IOSurfaceLayerImpl(LayerTreeImpl* tree_impl, int id) + : LayerImpl(tree_impl, id), + io_surface_id_(0), + io_surface_changed_(false), + io_surface_texture_id_(0) {} + +IOSurfaceLayerImpl::~IOSurfaceLayerImpl() { + if (!io_surface_texture_id_) + return; + + OutputSurface* output_surface = layer_tree_impl()->output_surface(); + // FIXME: Implement this path for software compositing. + WebKit::WebGraphicsContext3D* context3d = output_surface->context3d(); + if (context3d) + context3d->deleteTexture(io_surface_texture_id_); +} + +scoped_ptr<LayerImpl> IOSurfaceLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return IOSurfaceLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void IOSurfaceLayerImpl::PushPropertiesTo(LayerImpl* layer) { + LayerImpl::PushPropertiesTo(layer); + + IOSurfaceLayerImpl* io_surface_layer = + static_cast<IOSurfaceLayerImpl*>(layer); + io_surface_layer->SetIOSurfaceProperties(io_surface_id_, io_surface_size_); +} + +void IOSurfaceLayerImpl::WillDraw(ResourceProvider* resource_provider) { + LayerImpl::WillDraw(resource_provider); + + if (io_surface_changed_) { + WebKit::WebGraphicsContext3D* context3d = + resource_provider->GraphicsContext3D(); + if (!context3d) { + // FIXME: Implement this path for software compositing. + return; + } + + // FIXME: Do this in a way that we can track memory usage. + if (!io_surface_texture_id_) + io_surface_texture_id_ = context3d->createTexture(); + + GLC(context3d, context3d->activeTexture(GL_TEXTURE0)); + GLC(context3d, + context3d->bindTexture(GL_TEXTURE_RECTANGLE_ARB, + io_surface_texture_id_)); + GLC(context3d, + context3d->texParameteri( + GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GLC(context3d, + context3d->texParameteri( + GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); + GLC(context3d, + context3d->texParameteri( + GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); + GLC(context3d, + context3d->texParameteri( + GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); + context3d->texImageIOSurface2DCHROMIUM(GL_TEXTURE_RECTANGLE_ARB, + io_surface_size_.width(), + io_surface_size_.height(), + io_surface_id_, + 0); + // Do not check for error conditions. texImageIOSurface2DCHROMIUM is + // supposed to hold on to the last good IOSurface if the new one is already + // closed. This is only a possibility during live resizing of plugins. + // However, it seems that this is not sufficient to completely guard against + // garbage being drawn. If this is found to be a significant issue, it may + // be necessary to explicitly tell the embedder when to free the surfaces it + // has allocated. + io_surface_changed_ = false; + } +} + +void IOSurfaceLayerImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(CreateSharedQuadState()); + AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data); + + gfx::Rect quad_rect(gfx::Point(), content_bounds()); + gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect()); + scoped_ptr<IOSurfaceDrawQuad> quad = IOSurfaceDrawQuad::Create(); + quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + io_surface_size_, + io_surface_texture_id_, + IOSurfaceDrawQuad::FLIPPED); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); +} + +void IOSurfaceLayerImpl::DumpLayerProperties(std::string* str, + int indent) const { + str->append(IndentString(indent)); + base::StringAppendF(str, + "iosurface id: %u texture id: %u\n", + io_surface_id_, + io_surface_texture_id_); + LayerImpl::DumpLayerProperties(str, indent); +} + +void IOSurfaceLayerImpl::DidLoseOutputSurface() { + // We don't have a valid texture ID in the new context; however, + // the IOSurface is still valid. + io_surface_texture_id_ = 0; + io_surface_changed_ = true; +} + +void IOSurfaceLayerImpl::SetIOSurfaceProperties(unsigned io_surface_id, + gfx::Size size) { + if (io_surface_id_ != io_surface_id) + io_surface_changed_ = true; + + io_surface_id_ = io_surface_id; + io_surface_size_ = size; +} + +const char* IOSurfaceLayerImpl::LayerTypeAsString() const { + return "IOSurfaceLayer"; +} + +} // namespace cc diff --git a/cc/layers/io_surface_layer_impl.h b/cc/layers/io_surface_layer_impl.h new file mode 100644 index 0000000..2c04ddd --- /dev/null +++ b/cc/layers/io_surface_layer_impl.h @@ -0,0 +1,51 @@ +// Copyright 2012 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 CC_LAYERS_IO_SURFACE_LAYER_IMPL_H_ +#define CC_LAYERS_IO_SURFACE_LAYER_IMPL_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/layer_impl.h" +#include "ui/gfx/size.h" + +namespace cc { + +class CC_EXPORT IOSurfaceLayerImpl : public LayerImpl { + public: + static scoped_ptr<IOSurfaceLayerImpl> Create(LayerTreeImpl* tree_impl, + int id) { + return make_scoped_ptr(new IOSurfaceLayerImpl(tree_impl, id)); + } + virtual ~IOSurfaceLayerImpl(); + + void SetIOSurfaceProperties(unsigned io_surface_id, gfx::Size size); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer_tree_impl) OVERRIDE; + + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + + virtual void WillDraw(ResourceProvider* resource_provider) OVERRIDE; + virtual void DidLoseOutputSurface() OVERRIDE; + + virtual void DumpLayerProperties(std::string* str, int indent) const OVERRIDE; + + private: + IOSurfaceLayerImpl(LayerTreeImpl* tree_impl, int id); + + virtual const char* LayerTypeAsString() const OVERRIDE; + + unsigned io_surface_id_; + gfx::Size io_surface_size_; + bool io_surface_changed_; + unsigned io_surface_texture_id_; + + DISALLOW_COPY_AND_ASSIGN(IOSurfaceLayerImpl); +}; + +} // namespace cc + +#endif // CC_LAYERS_IO_SURFACE_LAYER_IMPL_H_ diff --git a/cc/layers/layer.cc b/cc/layers/layer.cc new file mode 100644 index 0000000..53a3390 --- /dev/null +++ b/cc/layers/layer.cc @@ -0,0 +1,817 @@ +// Copyright 2010 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 "cc/layers/layer.h" + +#include "cc/animation/animation.h" +#include "cc/animation/animation_events.h" +#include "cc/animation/layer_animation_controller.h" +#include "cc/layers/layer_impl.h" +#include "cc/trees/layer_tree_host.h" +#include "cc/trees/layer_tree_impl.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebAnimationDelegate.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebLayerScrollClient.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebSize.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "ui/gfx/rect_conversions.h" + +namespace cc { + +static int s_next_layer_id = 1; + +scoped_refptr<Layer> Layer::Create() { + return make_scoped_refptr(new Layer()); +} + +Layer::Layer() + : needs_display_(false), + stacking_order_changed_(false), + layer_id_(s_next_layer_id++), + ignore_set_needs_commit_(false), + parent_(NULL), + layer_tree_host_(NULL), + scrollable_(false), + should_scroll_on_main_thread_(false), + have_wheel_event_handlers_(false), + anchor_point_(0.5f, 0.5f), + background_color_(0), + opacity_(1.f), + anchor_point_z_(0.f), + is_container_for_fixed_position_layers_(false), + fixed_to_container_layer_(false), + is_drawable_(false), + masks_to_bounds_(false), + contents_opaque_(false), + double_sided_(true), + preserves_3d_(false), + use_parent_backface_visibility_(false), + draw_checkerboard_for_missing_tiles_(false), + force_render_surface_(false), + replica_layer_(NULL), + raster_scale_(1.f), + automatically_compute_raster_scale_(false), + bounds_contain_page_scale_(false), + layer_animation_delegate_(NULL), + layer_scroll_client_(NULL) { + if (layer_id_ < 0) { + s_next_layer_id = 1; + layer_id_ = s_next_layer_id++; + } + + layer_animation_controller_ = LayerAnimationController::Create(layer_id_); + layer_animation_controller_->AddObserver(this); + AddLayerAnimationEventObserver(layer_animation_controller_.get()); +} + +Layer::~Layer() { + // Our parent should be holding a reference to us so there should be no + // way for us to be destroyed while we still have a parent. + DCHECK(!parent()); + + layer_animation_controller_->RemoveObserver(this); + + // Remove the parent reference from all children and dependents. + RemoveAllChildren(); + if (mask_layer_) { + DCHECK_EQ(this, mask_layer_->parent()); + mask_layer_->RemoveFromParent(); + } + if (replica_layer_) { + DCHECK_EQ(this, replica_layer_->parent()); + replica_layer_->RemoveFromParent(); + } +} + +void Layer::SetLayerTreeHost(LayerTreeHost* host) { + if (layer_tree_host_ == host) + return; + + layer_tree_host_ = host; + + for (size_t i = 0; i < children_.size(); ++i) + children_[i]->SetLayerTreeHost(host); + + if (mask_layer_) + mask_layer_->SetLayerTreeHost(host); + if (replica_layer_) + replica_layer_->SetLayerTreeHost(host); + + layer_animation_controller_->SetAnimationRegistrar( + host ? host->animation_registrar() : NULL); + + if (host && layer_animation_controller_->has_any_animation()) + host->SetNeedsCommit(); + if (host && + (!filters_.isEmpty() || !background_filters_.isEmpty() || filter_)) + layer_tree_host_->set_needs_filter_context(); + +} + +void Layer::SetNeedsCommit() { + if (ignore_set_needs_commit_) + return; + if (layer_tree_host_) + layer_tree_host_->SetNeedsCommit(); +} + +void Layer::SetNeedsFullTreeSync() { + if (layer_tree_host_) + layer_tree_host_->SetNeedsFullTreeSync(); +} + +gfx::Rect Layer::LayerRectToContentRect(const gfx::RectF& layer_rect) const { + gfx::RectF content_rect = + gfx::ScaleRect(layer_rect, contents_scale_x(), contents_scale_y()); + // Intersect with content rect to avoid the extra pixel because for some + // values x and y, ceil((x / y) * y) may be x + 1. + content_rect.Intersect(gfx::Rect(gfx::Point(), content_bounds())); + return gfx::ToEnclosingRect(content_rect); +} + +bool Layer::BlocksPendingCommit() const { + return false; +} + +bool Layer::CanClipSelf() const { + return false; +} + +bool Layer::BlocksPendingCommitRecursive() const { + if (BlocksPendingCommit()) + return true; + if (mask_layer() && mask_layer()->BlocksPendingCommitRecursive()) + return true; + if (replica_layer() && replica_layer()->BlocksPendingCommitRecursive()) + return true; + for (size_t i = 0; i < children_.size(); ++i) { + if (children_[i]->BlocksPendingCommitRecursive()) + return true; + } + return false; +} + +void Layer::SetParent(Layer* layer) { + DCHECK(!layer || !layer->HasAncestor(this)); + parent_ = layer; + SetLayerTreeHost(parent_ ? parent_->layer_tree_host() : NULL); + + ForceAutomaticRasterScaleToBeRecomputed(); + if (mask_layer_) + mask_layer_->ForceAutomaticRasterScaleToBeRecomputed(); + if (replica_layer_ && replica_layer_->mask_layer_) + replica_layer_->mask_layer_->ForceAutomaticRasterScaleToBeRecomputed(); +} + +bool Layer::HasAncestor(Layer* ancestor) const { + for (const Layer* layer = parent(); layer; layer = layer->parent()) { + if (layer == ancestor) + return true; + } + return false; +} + +void Layer::AddChild(scoped_refptr<Layer> child) { + InsertChild(child, children_.size()); +} + +void Layer::InsertChild(scoped_refptr<Layer> child, size_t index) { + child->RemoveFromParent(); + child->SetParent(this); + child->stacking_order_changed_ = true; + + index = std::min(index, children_.size()); + children_.insert(children_.begin() + index, child); + SetNeedsFullTreeSync(); +} + +void Layer::RemoveFromParent() { + if (parent_) + parent_->RemoveChildOrDependent(this); +} + +void Layer::RemoveChildOrDependent(Layer* child) { + if (mask_layer_ == child) { + mask_layer_->SetParent(NULL); + mask_layer_ = NULL; + SetNeedsFullTreeSync(); + return; + } + if (replica_layer_ == child) { + replica_layer_->SetParent(NULL); + replica_layer_ = NULL; + SetNeedsFullTreeSync(); + return; + } + + for (LayerList::iterator iter = children_.begin(); + iter != children_.end(); + ++iter) { + if (*iter != child) + continue; + + child->SetParent(NULL); + children_.erase(iter); + SetNeedsFullTreeSync(); + return; + } +} + +void Layer::ReplaceChild(Layer* reference, scoped_refptr<Layer> new_layer) { + DCHECK(reference); + DCHECK_EQ(reference->parent(), this); + + if (reference == new_layer) + return; + + int reference_index = IndexOfChild(reference); + if (reference_index == -1) { + NOTREACHED(); + return; + } + + reference->RemoveFromParent(); + + if (new_layer) { + new_layer->RemoveFromParent(); + InsertChild(new_layer, reference_index); + } +} + +int Layer::IndexOfChild(const Layer* reference) { + for (size_t i = 0; i < children_.size(); ++i) { + if (children_[i] == reference) + return i; + } + return -1; +} + +void Layer::SetBounds(gfx::Size size) { + if (bounds() == size) + return; + + bool first_resize = bounds().IsEmpty() && !size.IsEmpty(); + + bounds_ = size; + + if (first_resize) + SetNeedsDisplay(); + else + SetNeedsCommit(); +} + +Layer* Layer::RootLayer() { + Layer* layer = this; + while (layer->parent()) + layer = layer->parent(); + return layer; +} + +void Layer::RemoveAllChildren() { + while (children_.size()) { + Layer* layer = children_[0].get(); + DCHECK_EQ(this, layer->parent()); + layer->RemoveFromParent(); + } +} + +void Layer::SetChildren(const LayerList& children) { + if (children == children_) + return; + + RemoveAllChildren(); + for (size_t i = 0; i < children.size(); ++i) + AddChild(children[i]); +} + +void Layer::SetAnchorPoint(gfx::PointF anchor_point) { + if (anchor_point_ == anchor_point) + return; + anchor_point_ = anchor_point; + SetNeedsCommit(); +} + +void Layer::SetAnchorPointZ(float anchor_point_z) { + if (anchor_point_z_ == anchor_point_z) + return; + anchor_point_z_ = anchor_point_z; + SetNeedsCommit(); +} + +void Layer::SetBackgroundColor(SkColor background_color) { + if (background_color_ == background_color) + return; + background_color_ = background_color; + SetNeedsCommit(); +} + +void Layer::CalculateContentsScale( + float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* contentBounds) { + *contents_scale_x = 1; + *contents_scale_y = 1; + *contentBounds = bounds(); +} + +void Layer::SetMasksToBounds(bool masks_to_bounds) { + if (masks_to_bounds_ == masks_to_bounds) + return; + masks_to_bounds_ = masks_to_bounds; + SetNeedsCommit(); +} + +void Layer::SetMaskLayer(Layer* mask_layer) { + if (mask_layer_ == mask_layer) + return; + if (mask_layer_) { + DCHECK_EQ(this, mask_layer_->parent()); + mask_layer_->RemoveFromParent(); + } + mask_layer_ = mask_layer; + if (mask_layer_) { + DCHECK(!mask_layer_->parent()); + mask_layer_->RemoveFromParent(); + mask_layer_->SetParent(this); + mask_layer_->SetIsMask(true); + } + SetNeedsFullTreeSync(); +} + +void Layer::SetReplicaLayer(Layer* layer) { + if (replica_layer_ == layer) + return; + if (replica_layer_) { + DCHECK_EQ(this, replica_layer_->parent()); + replica_layer_->RemoveFromParent(); + } + replica_layer_ = layer; + if (replica_layer_) { + DCHECK(!replica_layer_->parent()); + replica_layer_->RemoveFromParent(); + replica_layer_->SetParent(this); + } + SetNeedsFullTreeSync(); +} + +void Layer::SetFilters(const WebKit::WebFilterOperations& filters) { + if (filters_ == filters) + return; + DCHECK(!filter_); + filters_ = filters; + SetNeedsCommit(); + if (!filters.isEmpty() && layer_tree_host_) + layer_tree_host_->set_needs_filter_context(); +} + +void Layer::SetFilter(const skia::RefPtr<SkImageFilter>& filter) { + if (filter_.get() == filter.get()) + return; + DCHECK(filters_.isEmpty()); + filter_ = filter; + SetNeedsCommit(); + if (filter && layer_tree_host_) + layer_tree_host_->set_needs_filter_context(); +} + +void Layer::SetBackgroundFilters(const WebKit::WebFilterOperations& filters) { + if (background_filters_ == filters) + return; + background_filters_ = filters; + SetNeedsCommit(); + if (!filters.isEmpty() && layer_tree_host_) + layer_tree_host_->set_needs_filter_context(); +} + +void Layer::SetOpacity(float opacity) { + if (opacity_ == opacity) + return; + opacity_ = opacity; + SetNeedsCommit(); +} + +bool Layer::OpacityIsAnimating() const { + return layer_animation_controller_->IsAnimatingProperty(Animation::Opacity); +} + +bool Layer::OpacityCanAnimateOnImplThread() const { + return false; +} + +void Layer::SetContentsOpaque(bool opaque) { + if (contents_opaque_ == opaque) + return; + contents_opaque_ = opaque; + SetNeedsCommit(); +} + +void Layer::SetPosition(gfx::PointF position) { + if (position_ == position) + return; + position_ = position; + SetNeedsCommit(); +} + +void Layer::SetSublayerTransform(const gfx::Transform& sublayer_transform) { + if (sublayer_transform_ == sublayer_transform) + return; + sublayer_transform_ = sublayer_transform; + SetNeedsCommit(); +} + +void Layer::SetTransform(const gfx::Transform& transform) { + if (transform_ == transform) + return; + transform_ = transform; + SetNeedsCommit(); +} + +bool Layer::TransformIsAnimating() const { + return layer_animation_controller_->IsAnimatingProperty(Animation::Transform); +} + +void Layer::SetScrollOffset(gfx::Vector2d scroll_offset) { + if (scroll_offset_ == scroll_offset) + return; + scroll_offset_ = scroll_offset; + if (layer_scroll_client_) + layer_scroll_client_->didScroll(); + SetNeedsCommit(); +} + +void Layer::SetMaxScrollOffset(gfx::Vector2d max_scroll_offset) { + if (max_scroll_offset_ == max_scroll_offset) + return; + max_scroll_offset_ = max_scroll_offset; + SetNeedsCommit(); +} + +void Layer::SetScrollable(bool scrollable) { + if (scrollable_ == scrollable) + return; + scrollable_ = scrollable; + SetNeedsCommit(); +} + +void Layer::SetShouldScrollOnMainThread(bool should_scroll_on_main_thread) { + if (should_scroll_on_main_thread_ == should_scroll_on_main_thread) + return; + should_scroll_on_main_thread_ = should_scroll_on_main_thread; + SetNeedsCommit(); +} + +void Layer::SetHaveWheelEventHandlers(bool have_wheel_event_handlers) { + if (have_wheel_event_handlers_ == have_wheel_event_handlers) + return; + have_wheel_event_handlers_ = have_wheel_event_handlers; + SetNeedsCommit(); +} + +void Layer::SetNonFastScrollableRegion(const Region& region) { + if (non_fast_scrollable_region_ == region) + return; + non_fast_scrollable_region_ = region; + SetNeedsCommit(); +} + +void Layer::SetTouchEventHandlerRegion(const Region& region) { + if (touch_event_handler_region_ == region) + return; + touch_event_handler_region_ = region; +} + +void Layer::SetDrawCheckerboardForMissingTiles(bool checkerboard) { + if (draw_checkerboard_for_missing_tiles_ == checkerboard) + return; + draw_checkerboard_for_missing_tiles_ = checkerboard; + SetNeedsCommit(); +} + +void Layer::SetForceRenderSurface(bool force) { + if (force_render_surface_ == force) + return; + force_render_surface_ = force; + SetNeedsCommit(); +} + +void Layer::SetImplTransform(const gfx::Transform& transform) { + if (impl_transform_ == transform) + return; + impl_transform_ = transform; + SetNeedsCommit(); +} + +void Layer::SetDoubleSided(bool double_sided) { + if (double_sided_ == double_sided) + return; + double_sided_ = double_sided; + SetNeedsCommit(); +} + +void Layer::SetIsDrawable(bool is_drawable) { + if (is_drawable_ == is_drawable) + return; + + is_drawable_ = is_drawable; + SetNeedsCommit(); +} + +void Layer::SetNeedsDisplayRect(const gfx::RectF& dirty_rect) { + update_rect_.Union(dirty_rect); + needs_display_ = true; + + // Simply mark the contents as dirty. For non-root layers, the call to + // SetNeedsCommit will schedule a fresh compositing pass. + // For the root layer, SetNeedsCommit has no effect. + if (DrawsContent() && !update_rect_.IsEmpty()) + SetNeedsCommit(); +} + +bool Layer::DescendantIsFixedToContainerLayer() const { + for (size_t i = 0; i < children_.size(); ++i) { + if (children_[i]->fixed_to_container_layer() || + children_[i]->DescendantIsFixedToContainerLayer()) + return true; + } + return false; +} + +void Layer::SetIsContainerForFixedPositionLayers(bool container) { + if (is_container_for_fixed_position_layers_ == container) + return; + is_container_for_fixed_position_layers_ = container; + + if (layer_tree_host_ && layer_tree_host_->CommitRequested()) + return; + + // Only request a commit if we have a fixed positioned descendant. + if (DescendantIsFixedToContainerLayer()) + SetNeedsCommit(); +} + +void Layer::SetFixedToContainerLayer(bool fixed_to_container_layer) { + if (fixed_to_container_layer_ == fixed_to_container_layer) + return; + fixed_to_container_layer_ = fixed_to_container_layer; + SetNeedsCommit(); +} + +void Layer::PushPropertiesTo(LayerImpl* layer) { + layer->SetAnchorPoint(anchor_point_); + layer->SetAnchorPointZ(anchor_point_z_); + layer->SetBackgroundColor(background_color_); + layer->SetBounds(bounds_); + layer->SetContentBounds(content_bounds()); + layer->SetContentsScale(contents_scale_x(), contents_scale_y()); + layer->SetDebugName(debug_name_); + layer->SetDoubleSided(double_sided_); + layer->SetDrawCheckerboardForMissingTiles( + draw_checkerboard_for_missing_tiles_); + layer->SetForceRenderSurface(force_render_surface_); + layer->SetDrawsContent(DrawsContent()); + layer->SetFilters(filters()); + layer->SetFilter(filter()); + layer->SetBackgroundFilters(background_filters()); + layer->SetMasksToBounds(masks_to_bounds_); + layer->SetShouldScrollOnMainThread(should_scroll_on_main_thread_); + layer->SetHaveWheelEventHandlers(have_wheel_event_handlers_); + layer->SetNonFastScrollableRegion(non_fast_scrollable_region_); + layer->SetTouchEventHandlerRegion(touch_event_handler_region_); + layer->SetContentsOpaque(contents_opaque_); + if (!layer->OpacityIsAnimatingOnImplOnly()) + layer->SetOpacity(opacity_); + DCHECK(!(OpacityIsAnimating() && layer->OpacityIsAnimatingOnImplOnly())); + layer->SetPosition(position_); + layer->SetIsContainerForFixedPositionLayers( + is_container_for_fixed_position_layers_); + layer->SetFixedToContainerLayer(fixed_to_container_layer_); + layer->SetPreserves3d(preserves_3d()); + layer->SetUseParentBackfaceVisibility(use_parent_backface_visibility_); + layer->SetSublayerTransform(sublayer_transform_); + if (!layer->TransformIsAnimatingOnImplOnly()) + layer->SetTransform(transform_); + DCHECK(!(TransformIsAnimating() && layer->TransformIsAnimatingOnImplOnly())); + + layer->SetScrollable(scrollable_); + layer->SetScrollOffset(scroll_offset_); + layer->SetMaxScrollOffset(max_scroll_offset_); + + // If the main thread commits multiple times before the impl thread actually + // draws, then damage tracking will become incorrect if we simply clobber the + // updateRect here. The LayerImpl's updateRect needs to accumulate (i.e. + // union) any update changes that have occurred on the main thread. + update_rect_.Union(layer->update_rect()); + layer->set_update_rect(update_rect_); + + if (layer->layer_tree_impl()->settings().implSidePainting) { + DCHECK(layer->layer_tree_impl()->IsPendingTree()); + LayerImpl* active_twin = + layer->layer_tree_impl()->FindActiveTreeLayerById(id()); + // Update the scroll delta from the active layer, which may have + // adjusted its scroll delta prior to this pending layer being created. + // This code is identical to that in LayerImpl::setScrollDelta. + if (active_twin) { + DCHECK(layer->sent_scroll_delta().IsZero()); + layer->SetScrollDelta(active_twin->scroll_delta() - + active_twin->sent_scroll_delta()); + } + } else { + layer->SetScrollDelta(layer->scroll_delta() - layer->sent_scroll_delta()); + layer->SetSentScrollDelta(gfx::Vector2d()); + } + + layer->SetStackingOrderChanged(stacking_order_changed_); + + layer_animation_controller_->PushAnimationUpdatesTo( + layer->layer_animation_controller()); + + // Reset any state that should be cleared for the next update. + stacking_order_changed_ = false; + update_rect_ = gfx::RectF(); +} + +scoped_ptr<LayerImpl> Layer::CreateLayerImpl(LayerTreeImpl* tree_impl) { + return LayerImpl::Create(tree_impl, layer_id_); +} + +bool Layer::DrawsContent() const { + return is_drawable_; +} + +bool Layer::NeedMoreUpdates() { + return false; +} + +void Layer::SetDebugName(const std::string& debug_name) { + debug_name_ = debug_name; + SetNeedsCommit(); +} + +void Layer::SetRasterScale(float scale) { + if (raster_scale_ == scale) + return; + raster_scale_ = scale; + + // When automatically computed, this acts like a draw property. + if (automatically_compute_raster_scale_) + return; + SetNeedsDisplay(); +} + +void Layer::SetAutomaticallyComputeRasterScale(bool automatic) { + if (automatically_compute_raster_scale_ == automatic) + return; + automatically_compute_raster_scale_ = automatic; + + if (automatically_compute_raster_scale_) + ForceAutomaticRasterScaleToBeRecomputed(); + else + SetRasterScale(1); +} + +void Layer::ForceAutomaticRasterScaleToBeRecomputed() { + if (!automatically_compute_raster_scale_) + return; + if (!raster_scale_) + return; + raster_scale_ = 0.f; + SetNeedsCommit(); +} + +void Layer::SetBoundsContainPageScale(bool bounds_contain_page_scale) { + for (size_t i = 0; i < children_.size(); ++i) + children_[i]->SetBoundsContainPageScale(bounds_contain_page_scale); + + if (bounds_contain_page_scale == bounds_contain_page_scale_) + return; + + bounds_contain_page_scale_ = bounds_contain_page_scale; + SetNeedsDisplay(); +} + +void Layer::CreateRenderSurface() { + DCHECK(!draw_properties_.render_surface); + draw_properties_.render_surface = make_scoped_ptr(new RenderSurface(this)); + draw_properties_.render_target = this; +} + +void Layer::OnOpacityAnimated(float opacity) { + // This is called due to an ongoing accelerated animation. Since this + // animation is also being run on the impl thread, there is no need to request + // a commit to push this value over, so set the value directly rather than + // calling setOpacity. + opacity_ = opacity; +} + +void Layer::OnTransformAnimated(const gfx::Transform& transform) { + // This is called due to an ongoing accelerated animation. Since this + // animation is also being run on the impl thread, there is no need to request + // a commit to push this value over, so set this value directly rather than + // calling setTransform. + transform_ = transform; +} + +bool Layer::IsActive() const { + return true; +} + +bool Layer::AddAnimation(scoped_ptr <Animation> animation) { + if (!layer_animation_controller_->animation_registrar()) + return false; + + layer_animation_controller_->AddAnimation(animation.Pass()); + SetNeedsCommit(); + return true; +} + +void Layer::PauseAnimation(int animation_id, double time_offset) { + layer_animation_controller_->PauseAnimation(animation_id, time_offset); + SetNeedsCommit(); +} + +void Layer::RemoveAnimation(int animation_id) { + layer_animation_controller_->RemoveAnimation(animation_id); + SetNeedsCommit(); +} + +void Layer::SuspendAnimations(double monotonic_time) { + layer_animation_controller_->SuspendAnimations(monotonic_time); + SetNeedsCommit(); +} + +void Layer::ResumeAnimations(double monotonic_time) { + layer_animation_controller_->ResumeAnimations(monotonic_time); + SetNeedsCommit(); +} + +void Layer::SetLayerAnimationController( + scoped_refptr<LayerAnimationController> controller) { + RemoveLayerAnimationEventObserver(layer_animation_controller_.get()); + layer_animation_controller_->RemoveObserver(this); + layer_animation_controller_ = controller; + layer_animation_controller_->set_force_sync(); + layer_animation_controller_->AddObserver(this); + AddLayerAnimationEventObserver(layer_animation_controller_.get()); + SetNeedsCommit(); +} + +scoped_refptr<LayerAnimationController> +Layer::ReleaseLayerAnimationController() { + layer_animation_controller_->RemoveObserver(this); + scoped_refptr<LayerAnimationController> to_return = + layer_animation_controller_; + layer_animation_controller_ = LayerAnimationController::Create(id()); + layer_animation_controller_->AddObserver(this); + layer_animation_controller_->SetAnimationRegistrar( + to_return->animation_registrar()); + return to_return; +} + +bool Layer::HasActiveAnimation() const { + return layer_animation_controller_->HasActiveAnimation(); +} + +void Layer::NotifyAnimationStarted(const AnimationEvent& event, + double wall_clock_time) { + FOR_EACH_OBSERVER(LayerAnimationEventObserver, layer_animation_observers_, + OnAnimationStarted(event)); + if (layer_animation_delegate_) + layer_animation_delegate_->notifyAnimationStarted(wall_clock_time); +} + +void Layer::NotifyAnimationFinished(double wall_clock_time) { + if (layer_animation_delegate_) + layer_animation_delegate_->notifyAnimationFinished(wall_clock_time); +} + +void Layer::NotifyAnimationPropertyUpdate(const AnimationEvent& event) { + if (event.target_property == Animation::Opacity) + SetOpacity(event.opacity); + else + SetTransform(event.transform); +} + +void Layer::AddLayerAnimationEventObserver( + LayerAnimationEventObserver* animation_observer) { + if (!layer_animation_observers_.HasObserver(animation_observer)) + layer_animation_observers_.AddObserver(animation_observer); +} + +void Layer::RemoveLayerAnimationEventObserver( + LayerAnimationEventObserver* animation_observer) { + layer_animation_observers_.RemoveObserver(animation_observer); +} + +Region Layer::VisibleContentOpaqueRegion() const { + if (contents_opaque()) + return visible_content_rect(); + return Region(); +} + +ScrollbarLayer* Layer::ToScrollbarLayer() { + return NULL; +} + +} // namespace cc diff --git a/cc/layers/layer.h b/cc/layers/layer.h new file mode 100644 index 0000000..c0c34cc --- /dev/null +++ b/cc/layers/layer.h @@ -0,0 +1,488 @@ +// Copyright 2010 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 CC_LAYERS_LAYER_H_ +#define CC_LAYERS_LAYER_H_ + +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "cc/animation/layer_animation_controller.h" +#include "cc/animation/layer_animation_event_observer.h" +#include "cc/animation/layer_animation_value_observer.h" +#include "cc/base/cc_export.h" +#include "cc/base/region.h" +#include "cc/layers/draw_properties.h" +#include "cc/layers/render_surface.h" +#include "cc/trees/occlusion_tracker.h" +#include "skia/ext/refptr.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebFilterOperations.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/rect_f.h" +#include "ui/gfx/transform.h" + +namespace WebKit { +class WebAnimationDelegate; +class WebLayerScrollClient; +} + +namespace cc { + +class Animation; +struct AnimationEvent; +class LayerAnimationDelegate; +class LayerImpl; +class LayerTreeHost; +class LayerTreeImpl; +class PriorityCalculator; +class ResourceUpdateQueue; +class ScrollbarLayer; +struct AnimationEvent; +struct RenderingStats; + +// Base class for composited layers. Special layer types are derived from +// this class. +class CC_EXPORT Layer : public base::RefCounted<Layer>, + public LayerAnimationValueObserver { + public: + typedef std::vector<scoped_refptr<Layer> > LayerList; + enum LayerIdLabels { + PINCH_ZOOM_ROOT_SCROLL_LAYER_ID = -2, + INVALID_ID = -1, + }; + + static scoped_refptr<Layer> Create(); + + int id() const { return layer_id_; } + + Layer* RootLayer(); + Layer* parent() { return parent_; } + const Layer* parent() const { return parent_; } + void AddChild(scoped_refptr<Layer> child); + void InsertChild(scoped_refptr<Layer> child, size_t index); + void ReplaceChild(Layer* reference, scoped_refptr<Layer> new_layer); + void RemoveFromParent(); + void RemoveAllChildren(); + void SetChildren(const LayerList& children); + + const LayerList& children() const { return children_; } + Layer* child_at(size_t index) { return children_[index].get(); } + + void SetAnchorPoint(gfx::PointF anchor_point); + gfx::PointF anchor_point() const { return anchor_point_; } + + void SetAnchorPointZ(float anchor_point_z); + float anchor_point_z() const { return anchor_point_z_; } + + virtual void SetBackgroundColor(SkColor background_color); + SkColor background_color() const { return background_color_; } + + // A layer's bounds are in logical, non-page-scaled pixels (however, the + // root layer's bounds are in physical pixels). + void SetBounds(gfx::Size bounds); + gfx::Size bounds() const { return bounds_; } + + void SetMasksToBounds(bool masks_to_bounds); + bool masks_to_bounds() const { return masks_to_bounds_; } + + void SetMaskLayer(Layer* mask_layer); + Layer* mask_layer() { return mask_layer_.get(); } + const Layer* mask_layer() const { return mask_layer_.get(); } + + virtual void SetNeedsDisplayRect(const gfx::RectF& dirty_rect); + void SetNeedsDisplay() { SetNeedsDisplayRect(gfx::RectF(bounds())); } + + void SetOpacity(float opacity); + float opacity() const { return opacity_; } + bool OpacityIsAnimating() const; + virtual bool OpacityCanAnimateOnImplThread() const; + + void SetFilters(const WebKit::WebFilterOperations& filters); + const WebKit::WebFilterOperations& filters() const { return filters_; } + + void SetFilter(const skia::RefPtr<SkImageFilter>& filter); + skia::RefPtr<SkImageFilter> filter() const { return filter_; } + + // Background filters are filters applied to what is behind this layer, when + // they are viewed through non-opaque regions in this layer. They are used + // through the WebLayer interface, and are not exposed to HTML. + void SetBackgroundFilters(const WebKit::WebFilterOperations& filters); + const WebKit::WebFilterOperations& background_filters() const { + return background_filters_; + } + + virtual void SetContentsOpaque(bool opaque); + bool contents_opaque() const { return contents_opaque_; } + + void SetPosition(gfx::PointF position); + gfx::PointF position() const { return position_; } + + void SetIsContainerForFixedPositionLayers(bool container); + bool is_container_for_fixed_position_layers() const { + return is_container_for_fixed_position_layers_; + } + + void SetFixedToContainerLayer(bool fixed_to_container_layer); + bool fixed_to_container_layer() const { return fixed_to_container_layer_; } + + void SetSublayerTransform(const gfx::Transform& sublayer_transform); + const gfx::Transform& sublayer_transform() const { + return sublayer_transform_; + } + + void SetTransform(const gfx::Transform& transform); + const gfx::Transform& transform() const { return transform_; } + bool TransformIsAnimating() const; + + DrawProperties<Layer, RenderSurface>& draw_properties() { + return draw_properties_; + } + const DrawProperties<Layer, RenderSurface>& draw_properties() const { + return draw_properties_; + } + + // The following are shortcut accessors to get various information from + // draw_properties_ + const gfx::Transform& draw_transform() const { + return draw_properties_.target_space_transform; + } + const gfx::Transform& screen_space_transform() const { + return draw_properties_.screen_space_transform; + } + float draw_opacity() const { return draw_properties_.opacity; } + bool draw_opacity_is_animating() const { + return draw_properties_.opacity_is_animating; + } + bool draw_transform_is_animating() const { + return draw_properties_.target_space_transform_is_animating; + } + bool screen_space_transform_is_animating() const { + return draw_properties_.screen_space_transform_is_animating; + } + bool screen_space_opacity_is_animating() const { + return draw_properties_.screen_space_opacity_is_animating; + } + bool can_use_lcd_text() const { return draw_properties_.can_use_lcd_text; } + bool is_clipped() const { return draw_properties_.is_clipped; } + gfx::Rect clip_rect() const { return draw_properties_.clip_rect; } + gfx::Rect drawable_content_rect() const { + return draw_properties_.drawable_content_rect; + } + gfx::Rect visible_content_rect() const { + return draw_properties_.visible_content_rect; + } + Layer* render_target() { + DCHECK(!draw_properties_.render_target || + draw_properties_.render_target->render_surface()); + return draw_properties_.render_target; + } + const Layer* render_target() const { + DCHECK(!draw_properties_.render_target || + draw_properties_.render_target->render_surface()); + return draw_properties_.render_target; + } + RenderSurface* render_surface() const { + return draw_properties_.render_surface.get(); + } + + void SetScrollOffset(gfx::Vector2d scroll_offset); + gfx::Vector2d scroll_offset() const { return scroll_offset_; } + + void SetMaxScrollOffset(gfx::Vector2d max_scroll_offset); + gfx::Vector2d max_scroll_offset() const { return max_scroll_offset_; } + + void SetScrollable(bool scrollable); + bool scrollable() const { return scrollable_; } + + void SetShouldScrollOnMainThread(bool should_scroll_on_main_thread); + bool should_scroll_on_main_thread() const { + return should_scroll_on_main_thread_; + } + + void SetHaveWheelEventHandlers(bool have_wheel_event_handlers); + bool have_wheel_event_handlers() const { return have_wheel_event_handlers_; } + + void SetNonFastScrollableRegion(const Region& non_fast_scrollable_region); + const Region& non_fast_scrollable_region() const { + return non_fast_scrollable_region_; + } + + void SetTouchEventHandlerRegion(const Region& touch_event_handler_region); + const Region& touch_event_handler_region() const { + return touch_event_handler_region_; + } + + void set_layer_scroll_client(WebKit::WebLayerScrollClient* client) { + layer_scroll_client_ = client; + } + + void SetDrawCheckerboardForMissingTiles(bool checkerboard); + bool DrawCheckerboardForMissingTiles() const { + return draw_checkerboard_for_missing_tiles_; + } + + void SetForceRenderSurface(bool force_render_surface); + bool force_render_surface() const { return force_render_surface_; } + + gfx::Vector2d scroll_delta() const { return gfx::Vector2d(); } + + void SetImplTransform(const gfx::Transform& transform); + const gfx::Transform& impl_transform() const { return impl_transform_; } + + void SetDoubleSided(bool double_sided); + bool double_sided() const { return double_sided_; } + + void SetPreserves3d(bool preserves_3d) { preserves_3d_ = preserves_3d; } + bool preserves_3d() const { return preserves_3d_; } + + void set_use_parent_backface_visibility(bool use) { + use_parent_backface_visibility_ = use; + } + bool use_parent_backface_visibility() const { + return use_parent_backface_visibility_; + } + + virtual void SetLayerTreeHost(LayerTreeHost* host); + + bool HasDelegatedContent() const { return false; } + bool HasContributingDelegatedRenderPasses() const { return false; } + + void SetIsDrawable(bool is_drawable); + + void SetReplicaLayer(Layer* layer); + Layer* replica_layer() { return replica_layer_.get(); } + const Layer* replica_layer() const { return replica_layer_.get(); } + + bool has_mask() const { return !!mask_layer_; } + bool has_replica() const { return !!replica_layer_; } + bool replica_has_mask() const { + return replica_layer_ && (mask_layer_ || replica_layer_->mask_layer_); + } + + // These methods typically need to be overwritten by derived classes. + virtual bool DrawsContent() const; + virtual void Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) {} + virtual bool NeedMoreUpdates(); + virtual void SetIsMask(bool is_mask) {} + + void SetDebugName(const std::string& debug_name); + + virtual void PushPropertiesTo(LayerImpl* layer); + + void ClearRenderSurface() { draw_properties_.render_surface.reset(); } + void CreateRenderSurface(); + + // The contentsScale converts from logical, non-page-scaled pixels to target + // pixels. The contentsScale is 1 for the root layer as it is already in + // physical pixels. By default contentsScale is forced to be 1 except for + // subclasses of ContentsScalingLayer. + float contents_scale_x() const { return draw_properties_.contents_scale_x; } + float contents_scale_y() const { return draw_properties_.contents_scale_y; } + gfx::Size content_bounds() const { return draw_properties_.content_bounds; } + + virtual void CalculateContentsScale(float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* content_bounds); + + // The scale at which contents should be rastered, to match the scale at + // which they will drawn to the screen. This scale is a component of the + // contentsScale() but does not include page/device scale factors. + void SetRasterScale(float scale); + float raster_scale() const { return raster_scale_; } + + // When true, the RasterScale() will be set by the compositor. If false, it + // will use whatever value is given to it by the embedder. + bool automatically_compute_raster_scale() { + return automatically_compute_raster_scale_; + } + void SetAutomaticallyComputeRasterScale(bool automatic); + + void ForceAutomaticRasterScaleToBeRecomputed(); + + // When true, the layer's contents are not scaled by the current page scale + // factor. SetBoundsContainPageScale() recursively sets the value on all + // child layers. + void SetBoundsContainPageScale(bool bounds_contain_page_scale); + bool bounds_contain_page_scale() const { return bounds_contain_page_scale_; } + + LayerTreeHost* layer_tree_host() const { return layer_tree_host_; } + + // Set the priority of all desired textures in this layer. + virtual void SetTexturePriorities(const PriorityCalculator& priority_calc) {} + + bool AddAnimation(scoped_ptr<Animation> animation); + void PauseAnimation(int animation_id, double time_offset); + void RemoveAnimation(int animation_id); + + void SuspendAnimations(double monotonic_time); + void ResumeAnimations(double monotonic_time); + + LayerAnimationController* layer_animation_controller() { + return layer_animation_controller_.get(); + } + void SetLayerAnimationController( + scoped_refptr<LayerAnimationController> controller); + scoped_refptr<LayerAnimationController> ReleaseLayerAnimationController(); + + void set_layer_animation_delegate(WebKit::WebAnimationDelegate* delegate) { + layer_animation_delegate_ = delegate; + } + + bool HasActiveAnimation() const; + + virtual void NotifyAnimationStarted(const AnimationEvent& event, + double wall_clock_time); + virtual void NotifyAnimationFinished(double wall_clock_time); + virtual void NotifyAnimationPropertyUpdate(const AnimationEvent& event); + + void AddLayerAnimationEventObserver( + LayerAnimationEventObserver* animation_observer); + void RemoveLayerAnimationEventObserver( + LayerAnimationEventObserver* animation_observer); + + virtual Region VisibleContentOpaqueRegion() const; + + virtual ScrollbarLayer* ToScrollbarLayer(); + + gfx::Rect LayerRectToContentRect(const gfx::RectF& layer_rect) const; + + // In impl-side painting, this returns true if this layer type is not + // compatible with the main thread running freely, such as a double-buffered + // canvas that doesn't want to be triple-buffered across all three trees. + virtual bool BlocksPendingCommit() const; + // Returns true if anything in this tree blocksPendingCommit. + bool BlocksPendingCommitRecursive() const; + + virtual bool CanClipSelf() const; + + // Constructs a LayerImpl of the correct runtime type for this Layer type. + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl); + + bool NeedsDisplayForTesting() const { return needs_display_; } + void ResetNeedsDisplayForTesting() { needs_display_ = false; } + + protected: + friend class LayerImpl; + friend class TreeSynchronizer; + virtual ~Layer(); + + Layer(); + + void SetNeedsCommit(); + void SetNeedsFullTreeSync(); + + // This flag is set when layer need repainting/updating. + bool needs_display_; + + // Tracks whether this layer may have changed stacking order with its + // siblings. + bool stacking_order_changed_; + + // The update rect is the region of the compositor resource that was + // actually updated by the compositor. For layers that may do updating + // outside the compositor's control (i.e. plugin layers), this information + // is not available and the update rect will remain empty. + // Note this rect is in layer space (not content space). + gfx::RectF update_rect_; + + scoped_refptr<Layer> mask_layer_; + + int layer_id_; + + // When true, the layer is about to perform an update. Any commit requests + // will be handled implcitly after the update completes. + bool ignore_set_needs_commit_; + + private: + friend class base::RefCounted<Layer>; + + void SetParent(Layer* layer); + bool HasAncestor(Layer* ancestor) const; + bool DescendantIsFixedToContainerLayer() const; + + // Returns the index of the child or -1 if not found. + int IndexOfChild(const Layer* reference); + + // This should only be called from RemoveFromParent(). + void RemoveChildOrDependent(Layer* child); + + // LayerAnimationValueObserver implementation. + virtual void OnOpacityAnimated(float opacity) OVERRIDE; + virtual void OnTransformAnimated(const gfx::Transform& transform) OVERRIDE; + virtual bool IsActive() const OVERRIDE; + + LayerList children_; + Layer* parent_; + + // Layer instances have a weak pointer to their LayerTreeHost. + // This pointer value is nil when a Layer is not in a tree and is + // updated via SetLayerTreeHost() if a layer moves between trees. + LayerTreeHost* layer_tree_host_; + + ObserverList<LayerAnimationEventObserver> layer_animation_observers_; + + scoped_refptr<LayerAnimationController> layer_animation_controller_; + + // Layer properties. + gfx::Size bounds_; + + gfx::Vector2d scroll_offset_; + gfx::Vector2d max_scroll_offset_; + bool scrollable_; + bool should_scroll_on_main_thread_; + bool have_wheel_event_handlers_; + Region non_fast_scrollable_region_; + Region touch_event_handler_region_; + gfx::PointF position_; + gfx::PointF anchor_point_; + SkColor background_color_; + std::string debug_name_; + float opacity_; + skia::RefPtr<SkImageFilter> filter_; + WebKit::WebFilterOperations filters_; + WebKit::WebFilterOperations background_filters_; + float anchor_point_z_; + bool is_container_for_fixed_position_layers_; + bool fixed_to_container_layer_; + bool is_drawable_; + bool masks_to_bounds_; + bool contents_opaque_; + bool double_sided_; + bool preserves_3d_; + bool use_parent_backface_visibility_; + bool draw_checkerboard_for_missing_tiles_; + bool force_render_surface_; + + gfx::Transform transform_; + gfx::Transform sublayer_transform_; + + // Replica layer used for reflections. + scoped_refptr<Layer> replica_layer_; + + // Transient properties. + float raster_scale_; + bool automatically_compute_raster_scale_; + bool bounds_contain_page_scale_; + + gfx::Transform impl_transform_; + + WebKit::WebAnimationDelegate* layer_animation_delegate_; + WebKit::WebLayerScrollClient* layer_scroll_client_; + + DrawProperties<Layer, RenderSurface> draw_properties_; + + DISALLOW_COPY_AND_ASSIGN(Layer); +}; + +} // namespace cc + +#endif // CC_LAYERS_LAYER_H_ diff --git a/cc/layers/layer_impl.cc b/cc/layers/layer_impl.cc new file mode 100644 index 0000000..60fbbf35 --- /dev/null +++ b/cc/layers/layer_impl.cc @@ -0,0 +1,966 @@ +// Copyright 2011 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 "cc/layers/layer_impl.h" + +#include "base/debug/trace_event.h" +#include "base/stringprintf.h" +#include "base/values.h" +#include "cc/animation/animation_registrar.h" +#include "cc/animation/scrollbar_animation_controller.h" +#include "cc/animation/scrollbar_animation_controller_linear_fade.h" +#include "cc/base/math_util.h" +#include "cc/debug/debug_colors.h" +#include "cc/debug/layer_tree_debug_state.h" +#include "cc/layers/quad_sink.h" +#include "cc/layers/scrollbar_layer_impl.h" +#include "cc/quads/debug_border_draw_quad.h" +#include "cc/trees/layer_tree_impl.h" +#include "cc/trees/layer_tree_settings.h" +#include "cc/trees/proxy.h" +#include "ui/gfx/point_conversions.h" +#include "ui/gfx/quad_f.h" +#include "ui/gfx/rect_conversions.h" + +namespace cc { + +LayerImpl::LayerImpl(LayerTreeImpl* tree_impl, int id) + : parent_(NULL), + mask_layer_id_(-1), + replica_layer_id_(-1), + layer_id_(id), + layer_tree_impl_(tree_impl), + anchor_point_(0.5f, 0.5f), + anchor_point_z_(0.f), + scrollable_(false), + should_scroll_on_main_thread_(false), + have_wheel_event_handlers_(false), + background_color_(0), + stacking_order_changed_(false), + double_sided_(true), + layer_property_changed_(false), + layer_surface_property_changed_(false), + masks_to_bounds_(false), + contents_opaque_(false), + opacity_(1.0), + preserves_3d_(false), + use_parent_backface_visibility_(false), + draw_checkerboard_for_missing_tiles_(false), + draws_content_(false), + force_render_surface_(false), + is_container_for_fixed_position_layers_(false), + fixed_to_container_layer_(false), + draw_depth_(0.f), +#ifndef NDEBUG + between_will_draw_and_did_draw_(false), +#endif + horizontal_scrollbar_layer_(NULL), + vertical_scrollbar_layer_(NULL) { + DCHECK(layer_id_ > 0); + DCHECK(layer_tree_impl_); + layer_tree_impl_->RegisterLayer(this); + AnimationRegistrar* registrar = layer_tree_impl_->animationRegistrar(); + layer_animation_controller_ = + registrar->GetAnimationControllerForId(layer_id_); + layer_animation_controller_->AddObserver(this); +} + +LayerImpl::~LayerImpl() { +#ifndef NDEBUG + DCHECK(!between_will_draw_and_did_draw_); +#endif + layer_tree_impl_->UnregisterLayer(this); + layer_animation_controller_->RemoveObserver(this); +} + +void LayerImpl::AddChild(scoped_ptr<LayerImpl> child) { + child->set_parent(this); + DCHECK_EQ(layer_tree_impl(), child->layer_tree_impl()); + children_.push_back(child.Pass()); + layer_tree_impl()->set_needs_update_draw_properties(); +} + +scoped_ptr<LayerImpl> LayerImpl::RemoveChild(LayerImpl* child) { + for (ScopedPtrVector<LayerImpl>::iterator it = children_.begin(); + it != children_.end(); + ++it) { + if (*it == child) { + scoped_ptr<LayerImpl> ret = children_.take(it); + children_.erase(it); + layer_tree_impl()->set_needs_update_draw_properties(); + return ret.Pass(); + } + } + return scoped_ptr<LayerImpl>(); +} + +void LayerImpl::ClearChildList() { + if (children_.empty()) + return; + + children_.clear(); + layer_tree_impl()->set_needs_update_draw_properties(); +} + +void LayerImpl::CreateRenderSurface() { + DCHECK(!draw_properties_.render_surface); + draw_properties_.render_surface = + make_scoped_ptr(new RenderSurfaceImpl(this)); + draw_properties_.render_target = this; +} + +scoped_ptr<SharedQuadState> LayerImpl::CreateSharedQuadState() const { + scoped_ptr<SharedQuadState> state = SharedQuadState::Create(); + state->SetAll(draw_properties_.target_space_transform, + draw_properties_.content_bounds, + draw_properties_.visible_content_rect, + draw_properties_.clip_rect, + draw_properties_.is_clipped, + draw_properties_.opacity); + return state.Pass(); +} + +void LayerImpl::WillDraw(ResourceProvider* resource_provider) { +#ifndef NDEBUG + // willDraw/didDraw must be matched. + DCHECK(!between_will_draw_and_did_draw_); + between_will_draw_and_did_draw_ = true; +#endif +} + +void LayerImpl::DidDraw(ResourceProvider* resource_provider) { +#ifndef NDEBUG + DCHECK(between_will_draw_and_did_draw_); + between_will_draw_and_did_draw_ = false; +#endif +} + +bool LayerImpl::ShowDebugBorders() const { + return layer_tree_impl()->debug_state().showDebugBorders; +} + +void LayerImpl::GetDebugBorderProperties(SkColor* color, float* width) const { + if (draws_content_) { + *color = DebugColors::ContentLayerBorderColor(); + *width = DebugColors::ContentLayerBorderWidth(layer_tree_impl()); + return; + } + + if (masks_to_bounds_) { + *color = DebugColors::MaskingLayerBorderColor(); + *width = DebugColors::MaskingLayerBorderWidth(layer_tree_impl()); + return; + } + + *color = DebugColors::ContainerLayerBorderColor(); + *width = DebugColors::ContainerLayerBorderWidth(layer_tree_impl()); +} + +void LayerImpl::AppendDebugBorderQuad( + QuadSink* quad_sink, + const SharedQuadState* shared_quad_state, + AppendQuadsData* append_quads_data) const { + if (!ShowDebugBorders()) + return; + + SkColor color; + float width; + GetDebugBorderProperties(&color, &width); + + gfx::Rect content_rect(content_bounds()); + scoped_ptr<DebugBorderDrawQuad> debugBorderQuad = + DebugBorderDrawQuad::Create(); + debugBorderQuad->SetNew(shared_quad_state, content_rect, color, width); + quad_sink->Append(debugBorderQuad.PassAs<DrawQuad>(), append_quads_data); +} + +bool LayerImpl::HasDelegatedContent() const { + return false; +} + +bool LayerImpl::HasContributingDelegatedRenderPasses() const { + return false; +} + +RenderPass::Id LayerImpl::FirstContributingRenderPassId() const { + return RenderPass::Id(0, 0); +} + +RenderPass::Id LayerImpl::NextContributingRenderPassId(RenderPass::Id id) + const { + return RenderPass::Id(0, 0); +} + +ResourceProvider::ResourceId LayerImpl::ContentsResourceId() const { + NOTREACHED(); + return 0; +} + +void LayerImpl::SetSentScrollDelta(gfx::Vector2d sent_scroll_delta) { + // Pending tree never has sent scroll deltas + DCHECK(layer_tree_impl()->IsActiveTree()); + + if (sent_scroll_delta_ == sent_scroll_delta) + return; + + sent_scroll_delta_ = sent_scroll_delta; +} + +gfx::Vector2dF LayerImpl::ScrollBy(gfx::Vector2dF scroll) { + gfx::Vector2dF min_delta = -scroll_offset_; + gfx::Vector2dF max_delta = max_scroll_offset_ - scroll_offset_; + // Clamp new_delta so that position + delta stays within scroll bounds. + gfx::Vector2dF new_delta = (scroll_delta_ + scroll); + new_delta.ClampToMin(min_delta); + new_delta.ClampToMax(max_delta); + gfx::Vector2dF unscrolled = scroll_delta_ + scroll - new_delta; + + SetScrollDelta(new_delta); + return unscrolled; +} + +InputHandlerClient::ScrollStatus LayerImpl::TryScroll( + gfx::PointF screen_space_point, + InputHandlerClient::ScrollInputType type) const { + if (should_scroll_on_main_thread()) { + TRACE_EVENT0("cc", "LayerImpl::tryScroll: Failed shouldScrollOnMainThread"); + return InputHandlerClient::ScrollOnMainThread; + } + + if (!screen_space_transform().IsInvertible()) { + TRACE_EVENT0("cc", "LayerImpl::tryScroll: Ignored nonInvertibleTransform"); + return InputHandlerClient::ScrollIgnored; + } + + if (!non_fast_scrollable_region().IsEmpty()) { + bool clipped = false; + gfx::Transform inverse_screen_space_transform( + gfx::Transform::kSkipInitialization); + if (!screen_space_transform().GetInverse(&inverse_screen_space_transform)) { + // TODO(shawnsingh): We shouldn't be applying a projection if screen space + // transform is uninvertible here. Perhaps we should be returning + // ScrollOnMainThread in this case? + } + + gfx::PointF hit_test_point_in_content_space = + MathUtil::ProjectPoint(inverse_screen_space_transform, + screen_space_point, + &clipped); + gfx::PointF hit_test_point_in_layer_space = + gfx::ScalePoint(hit_test_point_in_content_space, + 1.f / contents_scale_x(), + 1.f / contents_scale_y()); + if (!clipped && + non_fast_scrollable_region().Contains( + gfx::ToRoundedPoint(hit_test_point_in_layer_space))) { + TRACE_EVENT0("cc", + "LayerImpl::tryScroll: Failed nonFastScrollableRegion"); + return InputHandlerClient::ScrollOnMainThread; + } + } + + if (type == InputHandlerClient::Wheel && have_wheel_event_handlers()) { + TRACE_EVENT0("cc", "LayerImpl::tryScroll: Failed wheelEventHandlers"); + return InputHandlerClient::ScrollOnMainThread; + } + + if (!scrollable()) { + TRACE_EVENT0("cc", "LayerImpl::tryScroll: Ignored not scrollable"); + return InputHandlerClient::ScrollIgnored; + } + + if (max_scroll_offset_.x() <= 0 && max_scroll_offset_.y() <= 0) { + TRACE_EVENT0("cc", + "LayerImpl::tryScroll: Ignored. Technically scrollable," + " but has no affordance in either direction."); + return InputHandlerClient::ScrollIgnored; + } + + return InputHandlerClient::ScrollStarted; +} + +bool LayerImpl::DrawCheckerboardForMissingTiles() const { + return draw_checkerboard_for_missing_tiles_ && + !layer_tree_impl()->settings().backgroundColorInsteadOfCheckerboard; +} + +gfx::Rect LayerImpl::LayerRectToContentRect( + const gfx::RectF& layer_rect) const { + gfx::RectF content_rect = + gfx::ScaleRect(layer_rect, contents_scale_x(), contents_scale_y()); + // Intersect with content rect to avoid the extra pixel because for some + // values x and y, ceil((x / y) * y) may be x + 1. + content_rect.Intersect(gfx::Rect(content_bounds())); + return gfx::ToEnclosingRect(content_rect); +} + +skia::RefPtr<SkPicture> LayerImpl::GetPicture() { + return skia::RefPtr<SkPicture>(); +} + +bool LayerImpl::CanClipSelf() const { + return false; +} + +bool LayerImpl::AreVisibleResourcesReady() const { + return true; +} + +scoped_ptr<LayerImpl> LayerImpl::CreateLayerImpl(LayerTreeImpl* tree_impl) { + return LayerImpl::Create(tree_impl, layer_id_); +} + +void LayerImpl::PushPropertiesTo(LayerImpl* layer) { + layer->SetAnchorPoint(anchor_point_); + layer->SetAnchorPointZ(anchor_point_z_); + layer->SetBackgroundColor(background_color_); + layer->SetBounds(bounds_); + layer->SetContentBounds(content_bounds()); + layer->SetContentsScale(contents_scale_x(), contents_scale_y()); + layer->SetDebugName(debug_name_); + layer->SetDoubleSided(double_sided_); + layer->SetDrawCheckerboardForMissingTiles( + draw_checkerboard_for_missing_tiles_); + layer->SetForceRenderSurface(force_render_surface_); + layer->SetDrawsContent(DrawsContent()); + layer->SetFilters(filters()); + layer->SetFilter(filter()); + layer->SetBackgroundFilters(background_filters()); + layer->SetMasksToBounds(masks_to_bounds_); + layer->SetShouldScrollOnMainThread(should_scroll_on_main_thread_); + layer->SetHaveWheelEventHandlers(have_wheel_event_handlers_); + layer->SetNonFastScrollableRegion(non_fast_scrollable_region_); + layer->SetTouchEventHandlerRegion(touch_event_handler_region_); + layer->SetContentsOpaque(contents_opaque_); + layer->SetOpacity(opacity_); + layer->SetPosition(position_); + layer->SetIsContainerForFixedPositionLayers( + is_container_for_fixed_position_layers_); + layer->SetFixedToContainerLayer(fixed_to_container_layer_); + layer->SetPreserves3d(preserves_3d()); + layer->SetUseParentBackfaceVisibility(use_parent_backface_visibility_); + layer->SetSublayerTransform(sublayer_transform_); + layer->SetTransform(transform_); + + layer->SetScrollable(scrollable_); + layer->SetScrollOffset(scroll_offset_); + layer->SetMaxScrollOffset(max_scroll_offset_); + + // If the main thread commits multiple times before the impl thread actually + // draws, then damage tracking will become incorrect if we simply clobber the + // updateRect here. The LayerImpl's updateRect needs to accumulate (i.e. + // union) any update changes that have occurred on the main thread. + update_rect_.Union(layer->update_rect()); + layer->set_update_rect(update_rect_); + + layer->SetScrollDelta(layer->scroll_delta() - layer->sent_scroll_delta()); + layer->SetSentScrollDelta(gfx::Vector2d()); + + layer->SetStackingOrderChanged(stacking_order_changed_); + + layer_animation_controller_->PushAnimationUpdatesTo( + layer->layer_animation_controller()); + + // Reset any state that should be cleared for the next update. + stacking_order_changed_ = false; + update_rect_ = gfx::RectF(); +} + +std::string LayerImpl::IndentString(int indent) { + std::string str; + for (int i = 0; i != indent; ++i) + str.append(" "); + return str; +} + +void LayerImpl::DumpLayerProperties(std::string* str, int indent) const { + std::string indent_str = IndentString(indent); + str->append(indent_str); + base::StringAppendF(str, "layer ID: %d\n", layer_id_); + + str->append(indent_str); + base::StringAppendF( + str, "bounds: %d, %d\n", bounds().width(), bounds().height()); + + if (draw_properties_.render_target) { + str->append(indent_str); + base::StringAppendF( + str, "renderTarget: %d\n", draw_properties_.render_target->layer_id_); + } + + str->append(indent_str); + base::StringAppendF(str, "position: %f, %f\n", position_.x(), position_.y()); + + str->append(indent_str); + base::StringAppendF(str, "contentsOpaque: %d\n", contents_opaque_); + + str->append(indent_str); + const gfx::Transform& transform = draw_properties_.target_space_transform; + base::StringAppendF(str, + "drawTransform: %f, %f, %f, %f // %f, %f, %f, %f" + " // %f, %f, %f, %f // %f, %f, %f, %f\n", + transform.matrix().getDouble(0, 0), + transform.matrix().getDouble(0, 1), + transform.matrix().getDouble(0, 2), + transform.matrix().getDouble(0, 3), + transform.matrix().getDouble(1, 0), + transform.matrix().getDouble(1, 1), + transform.matrix().getDouble(1, 2), + transform.matrix().getDouble(1, 3), + transform.matrix().getDouble(2, 0), + transform.matrix().getDouble(2, 1), + transform.matrix().getDouble(2, 2), + transform.matrix().getDouble(2, 3), + transform.matrix().getDouble(3, 0), + transform.matrix().getDouble(3, 1), + transform.matrix().getDouble(3, 2), + transform.matrix().getDouble(3, 3)); + + str->append(indent_str); + base::StringAppendF( + str, "draws_content: %s\n", draws_content_ ? "yes" : "no"); +} + +std::string LayerImpl::LayerTreeAsText() const { + std::string str; + DumpLayer(&str, 0); + return str; +} + +void LayerImpl::DumpLayer(std::string* str, int indent) const { + str->append(IndentString(indent)); + base::StringAppendF(str, "%s(%s)\n", LayerTypeAsString(), debug_name_.data()); + DumpLayerProperties(str, indent+2); + if (replica_layer_) { + str->append(IndentString(indent+2)); + str->append("Replica:\n"); + replica_layer_->DumpLayer(str, indent+3); + } + if (mask_layer_) { + str->append(IndentString(indent+2)); + str->append("Mask:\n"); + mask_layer_->DumpLayer(str, indent+3); + } + for (size_t i = 0; i < children_.size(); ++i) + children_[i]->DumpLayer(str, indent+1); +} + +base::DictionaryValue* LayerImpl::LayerTreeAsJson() const { + base::ListValue* list; + base::DictionaryValue* result = new base::DictionaryValue; + result->SetString("LayerType", LayerTypeAsString()); + + list = new base::ListValue; + list->AppendInteger(bounds().width()); + list->AppendInteger(bounds().height()); + result->Set("Bounds", list); + + list = new base::ListValue; + list->AppendDouble(position_.x()); + list->AppendDouble(position_.y()); + result->Set("Position", list); + + const gfx::Transform& gfx_transform = draw_properties_.target_space_transform; + double transform[16]; + gfx_transform.matrix().asColMajord(transform); + list = new base::ListValue; + for (int i = 0; i < 16; ++i) + list->AppendDouble(transform[i]); + result->Set("DrawTransform", list); + + result->SetBoolean("DrawsContent", draws_content_); + result->SetDouble("Opacity", opacity()); + + list = new base::ListValue; + for (size_t i = 0; i < children_.size(); ++i) + list->Append(children_[i]->LayerTreeAsJson()); + result->Set("Children", list); + + return result; +} + +void LayerImpl::SetStackingOrderChanged(bool stacking_order_changed) { + if (stacking_order_changed) { + stacking_order_changed_ = true; + NoteLayerPropertyChangedForSubtree(); + } +} + +bool LayerImpl::LayerSurfacePropertyChanged() const { + if (layer_surface_property_changed_) + return true; + + // If this layer's surface property hasn't changed, we want to see if + // some layer above us has changed this property. This is done for the + // case when such parent layer does not draw content, and therefore will + // not be traversed by the damage tracker. We need to make sure that + // property change on such layer will be caught by its descendants. + LayerImpl* current = this->parent_; + while (current && !current->draw_properties_.render_surface) { + if (current->layer_surface_property_changed_) + return true; + current = current->parent_; + } + + return false; +} + +void LayerImpl::NoteLayerSurfacePropertyChanged() { + layer_surface_property_changed_ = true; + layer_tree_impl()->set_needs_update_draw_properties(); +} + +void LayerImpl::NoteLayerPropertyChanged() { + layer_property_changed_ = true; + layer_tree_impl()->set_needs_update_draw_properties(); +} + +void LayerImpl::NoteLayerPropertyChangedForSubtree() { + NoteLayerPropertyChanged(); + NoteLayerPropertyChangedForDescendants(); +} + +void LayerImpl::NoteLayerPropertyChangedForDescendants() { + layer_tree_impl()->set_needs_update_draw_properties(); + for (size_t i = 0; i < children_.size(); ++i) + children_[i]->NoteLayerPropertyChangedForSubtree(); +} + +const char* LayerImpl::LayerTypeAsString() const { + return "Layer"; +} + +void LayerImpl::ResetAllChangeTrackingForSubtree() { + layer_property_changed_ = false; + layer_surface_property_changed_ = false; + + update_rect_ = gfx::RectF(); + + if (draw_properties_.render_surface) + draw_properties_.render_surface->ResetPropertyChangedFlag(); + + if (mask_layer_) + mask_layer_->ResetAllChangeTrackingForSubtree(); + + if (replica_layer_) { + // This also resets the replica mask, if it exists. + replica_layer_->ResetAllChangeTrackingForSubtree(); + } + + for (size_t i = 0; i < children_.size(); ++i) + children_[i]->ResetAllChangeTrackingForSubtree(); +} + +bool LayerImpl::LayerIsAlwaysDamaged() const { + return false; +} + +void LayerImpl::OnOpacityAnimated(float opacity) { + SetOpacity(opacity); +} + +void LayerImpl::OnTransformAnimated(const gfx::Transform& transform) { + SetTransform(transform); +} + +bool LayerImpl::IsActive() const { + return layer_tree_impl_->IsActiveTree(); +} + +void LayerImpl::SetBounds(gfx::Size bounds) { + if (bounds_ == bounds) + return; + + bounds_ = bounds; + + if (masks_to_bounds()) + NoteLayerPropertyChangedForSubtree(); + else + NoteLayerPropertyChanged(); +} + +void LayerImpl::SetMaskLayer(scoped_ptr<LayerImpl> mask_layer) { + int new_layer_id = mask_layer ? mask_layer->id() : -1; + + if (mask_layer) { + DCHECK_EQ(layer_tree_impl(), mask_layer->layer_tree_impl()); + DCHECK_NE(new_layer_id, mask_layer_id_); + } else if (new_layer_id == mask_layer_id_) { + return; + } + + mask_layer_ = mask_layer.Pass(); + mask_layer_id_ = new_layer_id; + if (mask_layer_) + mask_layer_->set_parent(this); + NoteLayerPropertyChangedForSubtree(); +} + +scoped_ptr<LayerImpl> LayerImpl::TakeMaskLayer() { + mask_layer_id_ = -1; + return mask_layer_.Pass(); +} + +void LayerImpl::SetReplicaLayer(scoped_ptr<LayerImpl> replica_layer) { + int new_layer_id = replica_layer ? replica_layer->id() : -1; + + if (replica_layer) { + DCHECK_EQ(layer_tree_impl(), replica_layer->layer_tree_impl()); + DCHECK_NE(new_layer_id, replica_layer_id_); + } else if (new_layer_id == replica_layer_id_) { + return; + } + + replica_layer_ = replica_layer.Pass(); + replica_layer_id_ = new_layer_id; + if (replica_layer_) + replica_layer_->set_parent(this); + NoteLayerPropertyChangedForSubtree(); +} + +scoped_ptr<LayerImpl> LayerImpl::TakeReplicaLayer() { + replica_layer_id_ = -1; + return replica_layer_.Pass(); +} + +ScrollbarLayerImpl* LayerImpl::ToScrollbarLayer() { + return NULL; +} + +void LayerImpl::SetDrawsContent(bool draws_content) { + if (draws_content_ == draws_content) + return; + + draws_content_ = draws_content; + NoteLayerPropertyChanged(); +} + +void LayerImpl::SetAnchorPoint(gfx::PointF anchor_point) { + if (anchor_point_ == anchor_point) + return; + + anchor_point_ = anchor_point; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetAnchorPointZ(float anchor_point_z) { + if (anchor_point_z_ == anchor_point_z) + return; + + anchor_point_z_ = anchor_point_z; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetBackgroundColor(SkColor background_color) { + if (background_color_ == background_color) + return; + + background_color_ = background_color; + NoteLayerPropertyChanged(); +} + +void LayerImpl::SetFilters(const WebKit::WebFilterOperations& filters) { + if (filters_ == filters) + return; + + DCHECK(!filter_); + filters_ = filters; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetBackgroundFilters( + const WebKit::WebFilterOperations& filters) { + if (background_filters_ == filters) + return; + + background_filters_ = filters; + NoteLayerPropertyChanged(); +} + +void LayerImpl::SetFilter(const skia::RefPtr<SkImageFilter>& filter) { + if (filter_.get() == filter.get()) + return; + + DCHECK(filters_.isEmpty()); + filter_ = filter; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetMasksToBounds(bool masks_to_bounds) { + if (masks_to_bounds_ == masks_to_bounds) + return; + + masks_to_bounds_ = masks_to_bounds; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetContentsOpaque(bool opaque) { + if (contents_opaque_ == opaque) + return; + + contents_opaque_ = opaque; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetOpacity(float opacity) { + if (opacity_ == opacity) + return; + + opacity_ = opacity; + NoteLayerSurfacePropertyChanged(); +} + +bool LayerImpl::OpacityIsAnimating() const { + return layer_animation_controller_->IsAnimatingProperty(Animation::Opacity); +} + +bool LayerImpl::OpacityIsAnimatingOnImplOnly() const { + Animation* opacity_animation = + layer_animation_controller_->GetAnimation(Animation::Opacity); + return opacity_animation && opacity_animation->is_impl_only(); +} + +void LayerImpl::SetPosition(gfx::PointF position) { + if (position_ == position) + return; + + position_ = position; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetPreserves3d(bool preserves3_d) { + if (preserves_3d_ == preserves3_d) + return; + + preserves_3d_ = preserves3_d; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetSublayerTransform(const gfx::Transform& sublayer_transform) { + if (sublayer_transform_ == sublayer_transform) + return; + + sublayer_transform_ = sublayer_transform; + // Sublayer transform does not affect the current layer; it affects only its + // children. + NoteLayerPropertyChangedForDescendants(); +} + +void LayerImpl::SetTransform(const gfx::Transform& transform) { + if (transform_ == transform) + return; + + transform_ = transform; + NoteLayerSurfacePropertyChanged(); +} + +bool LayerImpl::TransformIsAnimating() const { + return layer_animation_controller_->IsAnimatingProperty(Animation::Transform); +} + +bool LayerImpl::TransformIsAnimatingOnImplOnly() const { + Animation* transform_animation = + layer_animation_controller_->GetAnimation(Animation::Transform); + return transform_animation && transform_animation->is_impl_only(); +} + +void LayerImpl::SetContentBounds(gfx::Size content_bounds) { + if (this->content_bounds() == content_bounds) + return; + + draw_properties_.content_bounds = content_bounds; + NoteLayerPropertyChanged(); +} + +void LayerImpl::SetContentsScale(float contents_scale_x, + float contents_scale_y) { + if (this->contents_scale_x() == contents_scale_x && + this->contents_scale_y() == contents_scale_y) + return; + + draw_properties_.contents_scale_x = contents_scale_x; + draw_properties_.contents_scale_y = contents_scale_y; + NoteLayerPropertyChanged(); +} + +void LayerImpl::CalculateContentsScale( + float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* content_bounds) { + // Base LayerImpl has all of its content scales and content bounds pushed + // from its Layer during commit and just reuses those values as-is. + *contents_scale_x = this->contents_scale_x(); + *contents_scale_y = this->contents_scale_y(); + *content_bounds = this->content_bounds(); +} + +void LayerImpl::UpdateScrollbarPositions() { + gfx::Vector2dF current_offset = scroll_offset_ + scroll_delta_; + + if (horizontal_scrollbar_layer_) { + horizontal_scrollbar_layer_->SetCurrentPos(current_offset.x()); + horizontal_scrollbar_layer_->SetTotalSize(bounds_.width()); + horizontal_scrollbar_layer_->SetMaximum(max_scroll_offset_.x()); + } + if (vertical_scrollbar_layer_) { + vertical_scrollbar_layer_->SetCurrentPos(current_offset.y()); + vertical_scrollbar_layer_->SetTotalSize(bounds_.height()); + vertical_scrollbar_layer_->SetMaximum(max_scroll_offset_.y()); + } + + if (current_offset == last_scroll_offset_) + return; + last_scroll_offset_ = current_offset; + + if (scrollbar_animation_controller_ && + !scrollbar_animation_controller_->isScrollGestureInProgress()) { + scrollbar_animation_controller_->didProgrammaticallyUpdateScroll( + base::TimeTicks::Now()); + } + + // Get the current_offset_.y() value for a sanity-check on scrolling + // benchmark metrics. Specifically, we want to make sure + // BasicMouseWheelSmoothScrollGesture has proper scroll curves. + if (layer_tree_impl()->IsActiveTree()) { + TRACE_COUNTER_ID1("gpu", "scroll_offset_y", this->id(), current_offset.y()); + } +} + +void LayerImpl::SetScrollOffset(gfx::Vector2d scroll_offset) { + if (scroll_offset_ == scroll_offset) + return; + + scroll_offset_ = scroll_offset; + NoteLayerPropertyChangedForSubtree(); + UpdateScrollbarPositions(); +} + +void LayerImpl::SetScrollDelta(gfx::Vector2dF scroll_delta) { + if (scroll_delta_ == scroll_delta) + return; + + if (layer_tree_impl()->IsActiveTree()) { + LayerImpl* pending_twin = layer_tree_impl()->FindPendingTreeLayerById(id()); + if (pending_twin) { + // The pending twin can't mirror the scroll delta of the active + // layer. Although the delta - sent scroll delta difference is + // identical for both twins, the sent scroll delta for the pending + // layer is zero, as anything that has been sent has been baked + // into the layer's position/scroll offset as a part of commit. + DCHECK(pending_twin->sent_scroll_delta().IsZero()); + pending_twin->SetScrollDelta(scroll_delta - sent_scroll_delta()); + } + } + + scroll_delta_ = scroll_delta; + NoteLayerPropertyChangedForSubtree(); + + UpdateScrollbarPositions(); +} + +gfx::Vector2dF LayerImpl::TotalScrollOffset() const { + return scroll_offset_ + scroll_delta_; +} + +void LayerImpl::SetImplTransform(const gfx::Transform& transform) { + if (impl_transform_ == transform) + return; + + impl_transform_ = transform; + NoteLayerPropertyChangedForSubtree(); +} + +void LayerImpl::SetDoubleSided(bool double_sided) { + if (double_sided_ == double_sided) + return; + + double_sided_ = double_sided; + NoteLayerPropertyChangedForSubtree(); +} + +Region LayerImpl::VisibleContentOpaqueRegion() const { + if (contents_opaque()) + return visible_content_rect(); + return Region(); +} + +void LayerImpl::DidLoseOutputSurface() {} + +void LayerImpl::SetMaxScrollOffset(gfx::Vector2d max_scroll_offset) { + if (max_scroll_offset_ == max_scroll_offset) + return; + max_scroll_offset_ = max_scroll_offset; + + layer_tree_impl()->set_needs_update_draw_properties(); + UpdateScrollbarPositions(); +} + +void LayerImpl::SetScrollbarOpacity(float opacity) { + if (horizontal_scrollbar_layer_) + horizontal_scrollbar_layer_->SetOpacity(opacity); + if (vertical_scrollbar_layer_) + vertical_scrollbar_layer_->SetOpacity(opacity); +} + +inline scoped_ptr<ScrollbarAnimationController> +CreateScrollbarAnimationControllerWithFade(LayerImpl* layer) { + base::TimeDelta fadeout_delay = base::TimeDelta::FromMilliseconds(300); + base::TimeDelta fadeout_length = base::TimeDelta::FromMilliseconds(300); + return ScrollbarAnimationControllerLinearFade::create( + layer, fadeout_delay, fadeout_length) + .PassAs<ScrollbarAnimationController>(); +} + +void LayerImpl::DidBecomeActive() { + if (!layer_tree_impl_->settings().useLinearFadeScrollbarAnimator) + return; + + bool need_scrollbar_animation_controller = horizontal_scrollbar_layer_ || + vertical_scrollbar_layer_; + if (need_scrollbar_animation_controller) { + if (!scrollbar_animation_controller_) { + scrollbar_animation_controller_ = + CreateScrollbarAnimationControllerWithFade(this); + } + } else { + scrollbar_animation_controller_.reset(); + } + +} +void LayerImpl::SetHorizontalScrollbarLayer( + ScrollbarLayerImpl* scrollbar_layer) { + horizontal_scrollbar_layer_ = scrollbar_layer; + if (horizontal_scrollbar_layer_) + horizontal_scrollbar_layer_->set_scroll_layer_id(id()); +} + +void LayerImpl::SetVerticalScrollbarLayer(ScrollbarLayerImpl* scrollbar_layer) { + vertical_scrollbar_layer_ = scrollbar_layer; + if (vertical_scrollbar_layer_) + vertical_scrollbar_layer_->set_scroll_layer_id(id()); +} + +void LayerImpl::AsValueInto(base::DictionaryValue* dict) const { + dict->SetInteger("id", id()); + dict->Set("bounds", MathUtil::AsValue(bounds()).release()); + dict->SetInteger("draws_content", DrawsContent()); + + bool clipped; + gfx::QuadF layer_quad = MathUtil::MapQuad( + screen_space_transform(), + gfx::QuadF(gfx::Rect(content_bounds())), + &clipped); + dict->Set("layer_quad", MathUtil::AsValue(layer_quad).release()); + +} + +scoped_ptr<base::Value> LayerImpl::AsValue() const { + scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue()); + AsValueInto(state.get()); + return state.PassAs<base::Value>(); +} + +} // namespace cc diff --git a/cc/layers/layer_impl.h b/cc/layers/layer_impl.h new file mode 100644 index 0000000..8b09e2e --- /dev/null +++ b/cc/layers/layer_impl.h @@ -0,0 +1,528 @@ +// Copyright 2011 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 CC_LAYERS_LAYER_IMPL_H_ +#define CC_LAYERS_LAYER_IMPL_H_ + +#include <string> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "cc/animation/layer_animation_controller.h" +#include "cc/animation/layer_animation_value_observer.h" +#include "cc/base/cc_export.h" +#include "cc/base/region.h" +#include "cc/base/scoped_ptr_vector.h" +#include "cc/input/input_handler.h" +#include "cc/layers/draw_properties.h" +#include "cc/layers/render_surface_impl.h" +#include "cc/quads/render_pass.h" +#include "cc/quads/shared_quad_state.h" +#include "cc/resources/resource_provider.h" +#include "skia/ext/refptr.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebFilterOperations.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/rect_f.h" +#include "ui/gfx/transform.h" + +namespace base { +class DictionaryValue; +} + +namespace cc { + +class LayerTreeHostImpl; +class LayerTreeImpl; +class QuadSink; +class Renderer; +class ScrollbarAnimationController; +class ScrollbarLayerImpl; +class Layer; + +struct AppendQuadsData; + +class CC_EXPORT LayerImpl : LayerAnimationValueObserver { + public: + typedef ScopedPtrVector<LayerImpl> LayerList; + + static scoped_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) { + return make_scoped_ptr(new LayerImpl(tree_impl, id)); + } + + virtual ~LayerImpl(); + + int id() const { return layer_id_; } + + // LayerAnimationValueObserver implementation. + virtual void OnOpacityAnimated(float opacity) OVERRIDE; + virtual void OnTransformAnimated(const gfx::Transform& transform) OVERRIDE; + virtual bool IsActive() const OVERRIDE; + + // Tree structure. + LayerImpl* parent() { return parent_; } + const LayerImpl* parent() const { return parent_; } + const LayerList& children() const { return children_; } + LayerList& children() { return children_; } + LayerImpl* child_at(size_t index) const { return children_[index]; } + void AddChild(scoped_ptr<LayerImpl> child); + scoped_ptr<LayerImpl> RemoveChild(LayerImpl* child); + void set_parent(LayerImpl* parent) { parent_ = parent; } + // Warning: This does not preserve tree structure invariants. + void ClearChildList(); + + void SetMaskLayer(scoped_ptr<LayerImpl> mask_layer); + LayerImpl* mask_layer() { return mask_layer_.get(); } + const LayerImpl* mask_layer() const { return mask_layer_.get(); } + scoped_ptr<LayerImpl> TakeMaskLayer(); + + void SetReplicaLayer(scoped_ptr<LayerImpl> replica_layer); + LayerImpl* replica_layer() { return replica_layer_.get(); } + const LayerImpl* replica_layer() const { return replica_layer_.get(); } + scoped_ptr<LayerImpl> TakeReplicaLayer(); + + bool has_mask() const { return mask_layer_; } + bool has_replica() const { return replica_layer_; } + bool replica_has_mask() const { + return replica_layer_ && (mask_layer_ || replica_layer_->mask_layer_); + } + + LayerTreeImpl* layer_tree_impl() const { return layer_tree_impl_; } + + scoped_ptr<SharedQuadState> CreateSharedQuadState() const; + // willDraw must be called before appendQuads. If willDraw is called, + // didDraw is guaranteed to be called before another willDraw or before + // the layer is destroyed. To enforce this, any class that overrides + // willDraw/didDraw must call the base class version. + virtual void WillDraw(ResourceProvider* resource_provider); + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) {} + virtual void DidDraw(ResourceProvider* resource_provider); + + virtual ResourceProvider::ResourceId ContentsResourceId() const; + + virtual bool HasDelegatedContent() const; + virtual bool HasContributingDelegatedRenderPasses() const; + virtual RenderPass::Id FirstContributingRenderPassId() const; + virtual RenderPass::Id NextContributingRenderPassId(RenderPass::Id id) const; + + virtual void UpdateTilePriorities() {} + + virtual ScrollbarLayerImpl* ToScrollbarLayer(); + + // Returns true if this layer has content to draw. + void SetDrawsContent(bool draws_content); + bool DrawsContent() const { return draws_content_; } + + bool force_render_surface() const { return force_render_surface_; } + void SetForceRenderSurface(bool force) { force_render_surface_ = force; } + + void SetAnchorPoint(gfx::PointF anchor_point); + gfx::PointF anchor_point() const { return anchor_point_; } + + void SetAnchorPointZ(float anchor_point_z); + float anchor_point_z() const { return anchor_point_z_; } + + void SetBackgroundColor(SkColor background_color); + SkColor background_color() const { return background_color_; } + + void SetFilters(const WebKit::WebFilterOperations& filters); + const WebKit::WebFilterOperations& filters() const { return filters_; } + + void SetBackgroundFilters(const WebKit::WebFilterOperations& filters); + const WebKit::WebFilterOperations& background_filters() const { + return background_filters_; + } + + void SetFilter(const skia::RefPtr<SkImageFilter>& filter); + skia::RefPtr<SkImageFilter> filter() const { return filter_; } + + void SetMasksToBounds(bool masks_to_bounds); + bool masks_to_bounds() const { return masks_to_bounds_; } + + void SetContentsOpaque(bool opaque); + bool contents_opaque() const { return contents_opaque_; } + + void SetOpacity(float opacity); + float opacity() const { return opacity_; } + bool OpacityIsAnimating() const; + bool OpacityIsAnimatingOnImplOnly() const; + + void SetPosition(gfx::PointF position); + gfx::PointF position() const { return position_; } + + void SetIsContainerForFixedPositionLayers(bool container) { + is_container_for_fixed_position_layers_ = container; + } + bool is_container_for_fixed_position_layers() const { + return is_container_for_fixed_position_layers_; + } + + void SetFixedToContainerLayer(bool fixed) { + fixed_to_container_layer_ = fixed; + } + bool fixed_to_container_layer() const { return fixed_to_container_layer_; } + + void SetPreserves3d(bool preserves_3d); + bool preserves_3d() const { return preserves_3d_; } + + void SetUseParentBackfaceVisibility(bool use) { + use_parent_backface_visibility_ = use; + } + bool use_parent_backface_visibility() const { + return use_parent_backface_visibility_; + } + + void SetSublayerTransform(const gfx::Transform& sublayer_transform); + const gfx::Transform& sublayer_transform() const { + return sublayer_transform_; + } + + // Debug layer name. + void SetDebugName(const std::string& debug_name) { debug_name_ = debug_name; } + std::string debug_name() const { return debug_name_; } + + bool ShowDebugBorders() const; + + // These invalidate the host's render surface layer list. The caller + // is responsible for calling setNeedsUpdateDrawProperties on the host + // so that its list can be recreated. + void CreateRenderSurface(); + void ClearRenderSurface() { draw_properties_.render_surface.reset(); } + + DrawProperties<LayerImpl, RenderSurfaceImpl>& draw_properties() { + return draw_properties_; + } + const DrawProperties<LayerImpl, RenderSurfaceImpl>& draw_properties() const { + return draw_properties_; + } + + // The following are shortcut accessors to get various information from + // draw_properties_ + const gfx::Transform& draw_transform() const { + return draw_properties_.target_space_transform; + } + const gfx::Transform& screen_space_transform() const { + return draw_properties_.screen_space_transform; + } + float draw_opacity() const { return draw_properties_.opacity; } + bool draw_opacity_is_animating() const { + return draw_properties_.opacity_is_animating; + } + bool draw_transform_is_animating() const { + return draw_properties_.target_space_transform_is_animating; + } + bool screen_space_transform_is_animating() const { + return draw_properties_.screen_space_transform_is_animating; + } + bool screen_space_opacity_is_animating() const { + return draw_properties_.screen_space_opacity_is_animating; + } + bool can_use_lcd_text() const { return draw_properties_.can_use_lcd_text; } + bool is_clipped() const { return draw_properties_.is_clipped; } + gfx::Rect clip_rect() const { return draw_properties_.clip_rect; } + gfx::Rect drawable_content_rect() const { + return draw_properties_.drawable_content_rect; + } + gfx::Rect visible_content_rect() const { + return draw_properties_.visible_content_rect; + } + LayerImpl* render_target() { + DCHECK(!draw_properties_.render_target || + draw_properties_.render_target->render_surface()); + return draw_properties_.render_target; + } + const LayerImpl* render_target() const { + DCHECK(!draw_properties_.render_target || + draw_properties_.render_target->render_surface()); + return draw_properties_.render_target; + } + RenderSurfaceImpl* render_surface() const { + return draw_properties_.render_surface.get(); + } + + // The client should be responsible for setting bounds, contentBounds and + // contentsScale to appropriate values. LayerImpl doesn't calculate any of + // them from the other values. + + void SetBounds(gfx::Size bounds); + gfx::Size bounds() const { return bounds_; } + + void SetContentBounds(gfx::Size content_bounds); + gfx::Size content_bounds() const { return draw_properties_.content_bounds; } + + float contents_scale_x() const { return draw_properties_.contents_scale_x; } + float contents_scale_y() const { return draw_properties_.contents_scale_y; } + void SetContentsScale(float contents_scale_x, float contents_scale_y); + + virtual void CalculateContentsScale(float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* contentBounds); + + void SetScrollOffset(gfx::Vector2d scroll_offset); + gfx::Vector2d scroll_offset() const { return scroll_offset_; } + + void SetMaxScrollOffset(gfx::Vector2d max_scroll_offset); + gfx::Vector2d max_scroll_offset() const { return max_scroll_offset_; } + + void SetScrollDelta(gfx::Vector2dF scroll_delta); + gfx::Vector2dF scroll_delta() const { return scroll_delta_; } + + gfx::Vector2dF TotalScrollOffset() const; + + void SetImplTransform(const gfx::Transform& transform); + const gfx::Transform& impl_transform() const { return impl_transform_; } + + void SetSentScrollDelta(gfx::Vector2d sent_scroll_delta); + gfx::Vector2d sent_scroll_delta() const { return sent_scroll_delta_; } + + // Returns the delta of the scroll that was outside of the bounds of the + // initial scroll + gfx::Vector2dF ScrollBy(gfx::Vector2dF scroll); + + void SetScrollable(bool scrollable) { scrollable_ = scrollable; } + bool scrollable() const { return scrollable_; } + + void SetShouldScrollOnMainThread(bool should_scroll_on_main_thread) { + should_scroll_on_main_thread_ = should_scroll_on_main_thread; + } + bool should_scroll_on_main_thread() const { + return should_scroll_on_main_thread_; + } + + void SetHaveWheelEventHandlers(bool have_wheel_event_handlers) { + have_wheel_event_handlers_ = have_wheel_event_handlers; + } + bool have_wheel_event_handlers() const { return have_wheel_event_handlers_; } + + void SetNonFastScrollableRegion(const Region& region) { + non_fast_scrollable_region_ = region; + } + const Region& non_fast_scrollable_region() const { + return non_fast_scrollable_region_; + } + + void SetTouchEventHandlerRegion(const Region& region) { + touch_event_handler_region_ = region; + } + const Region& touch_event_handler_region() const { + return touch_event_handler_region_; + } + + void SetDrawCheckerboardForMissingTiles(bool checkerboard) { + draw_checkerboard_for_missing_tiles_ = checkerboard; + } + bool DrawCheckerboardForMissingTiles() const; + + InputHandlerClient::ScrollStatus TryScroll( + gfx::PointF screen_space_point, + InputHandlerClient::ScrollInputType type) const; + + void SetDoubleSided(bool double_sided); + bool double_sided() const { return double_sided_; } + + void SetTransform(const gfx::Transform& transform); + const gfx::Transform& transform() const { return transform_; } + bool TransformIsAnimating() const; + bool TransformIsAnimatingOnImplOnly() const; + + void set_update_rect(const gfx::RectF& update_rect) { + update_rect_ = update_rect; + } + const gfx::RectF& update_rect() const { return update_rect_; } + + std::string LayerTreeAsText() const; + virtual base::DictionaryValue* LayerTreeAsJson() const; + + void SetStackingOrderChanged(bool stacking_order_changed); + + bool LayerPropertyChanged() const { + return layer_property_changed_ || LayerIsAlwaysDamaged(); + } + bool LayerSurfacePropertyChanged() const; + + void ResetAllChangeTrackingForSubtree(); + + virtual bool LayerIsAlwaysDamaged() const; + + LayerAnimationController* layer_animation_controller() { + return layer_animation_controller_.get(); + } + + virtual Region VisibleContentOpaqueRegion() const; + + virtual void DidBecomeActive(); + + // Indicates that the surface previously used to render this layer + // was lost and that a new one has been created. Won't be called + // until the new surface has been created successfully. + virtual void DidLoseOutputSurface(); + + ScrollbarAnimationController* scrollbar_animation_controller() const { + return scrollbar_animation_controller_.get(); + } + + void SetScrollbarOpacity(float opacity); + + void SetHorizontalScrollbarLayer(ScrollbarLayerImpl* scrollbar_layer); + ScrollbarLayerImpl* horizontal_scrollbar_layer() { + return horizontal_scrollbar_layer_; + } + + void SetVerticalScrollbarLayer(ScrollbarLayerImpl* scrollbar_layer); + ScrollbarLayerImpl* vertical_scrollbar_layer() { + return vertical_scrollbar_layer_; + } + + gfx::Rect LayerRectToContentRect(const gfx::RectF& layer_rect) const; + + virtual skia::RefPtr<SkPicture> GetPicture(); + + virtual bool CanClipSelf() const; + + virtual bool AreVisibleResourcesReady() const; + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl); + virtual void PushPropertiesTo(LayerImpl* layer); + + virtual scoped_ptr<base::Value> AsValue() const; + + protected: + LayerImpl(LayerTreeImpl* layer_impl, int id); + + // Get the color and size of the layer's debug border. + virtual void GetDebugBorderProperties(SkColor* color, float* width) const; + + void AppendDebugBorderQuad(QuadSink* quad_sink, + const SharedQuadState* shared_quad_state, + AppendQuadsData* append_quads_data) const; + + virtual void DumpLayerProperties(std::string* str, int indent) const; + static std::string IndentString(int indent); + + void AsValueInto(base::DictionaryValue* dict) const; + + void NoteLayerSurfacePropertyChanged(); + void NoteLayerPropertyChanged(); + void NoteLayerPropertyChangedForSubtree(); + + // Note carefully this does not affect the current layer. + void NoteLayerPropertyChangedForDescendants(); + + private: + void UpdateScrollbarPositions(); + + virtual const char* LayerTypeAsString() const; + + void DumpLayer(std::string* str, int indent) const; + + // Properties internal to LayerImpl + LayerImpl* parent_; + LayerList children_; + // mask_layer_ can be temporarily stolen during tree sync, we need this ID to + // confirm newly assigned layer is still the previous one + int mask_layer_id_; + scoped_ptr<LayerImpl> mask_layer_; + int replica_layer_id_; // ditto + scoped_ptr<LayerImpl> replica_layer_; + int layer_id_; + LayerTreeImpl* layer_tree_impl_; + + // Properties synchronized from the associated Layer. + gfx::PointF anchor_point_; + float anchor_point_z_; + gfx::Size bounds_; + gfx::Vector2d scroll_offset_; + bool scrollable_; + bool should_scroll_on_main_thread_; + bool have_wheel_event_handlers_; + Region non_fast_scrollable_region_; + Region touch_event_handler_region_; + SkColor background_color_; + bool stacking_order_changed_; + + // Whether the "back" of this layer should draw. + bool double_sided_; + + // Tracks if drawing-related properties have changed since last redraw. + bool layer_property_changed_; + + // Indicates that a property has changed on this layer that would not + // affect the pixels on its target surface, but would require redrawing + // the targetSurface onto its ancestor targetSurface. + // For layers that do not own a surface this flag acts as + // layer_property_changed_. + bool layer_surface_property_changed_; + + bool masks_to_bounds_; + bool contents_opaque_; + float opacity_; + gfx::PointF position_; + bool preserves_3d_; + bool use_parent_backface_visibility_; + bool draw_checkerboard_for_missing_tiles_; + gfx::Transform sublayer_transform_; + gfx::Transform transform_; + + bool draws_content_; + bool force_render_surface_; + + // Set for the layer that other layers are fixed to. + bool is_container_for_fixed_position_layers_; + // This is true if the layer should be fixed to the closest ancestor + // container. + bool fixed_to_container_layer_; + + gfx::Vector2dF scroll_delta_; + gfx::Vector2d sent_scroll_delta_; + gfx::Vector2d max_scroll_offset_; + gfx::Transform impl_transform_; + gfx::Vector2dF last_scroll_offset_; + + // The global depth value of the center of the layer. This value is used + // to sort layers from back to front. + float draw_depth_; + + // Debug layer name. + std::string debug_name_; + + WebKit::WebFilterOperations filters_; + WebKit::WebFilterOperations background_filters_; + skia::RefPtr<SkImageFilter> filter_; + +#ifndef NDEBUG + bool between_will_draw_and_did_draw_; +#endif + + // Rect indicating what was repainted/updated during update. + // Note that plugin layers bypass this and leave it empty. + // Uses layer's content space. + gfx::RectF update_rect_; + + // Manages animations for this layer. + scoped_refptr<LayerAnimationController> layer_animation_controller_; + + // Manages scrollbars for this layer + scoped_ptr<ScrollbarAnimationController> scrollbar_animation_controller_; + + // Weak pointers to this layer's scrollbars, if it has them. Updated during + // tree synchronization. + ScrollbarLayerImpl* horizontal_scrollbar_layer_; + ScrollbarLayerImpl* vertical_scrollbar_layer_; + + // Group of properties that need to be computed based on the layer tree + // hierarchy before layers can be drawn. + DrawProperties<LayerImpl, RenderSurfaceImpl> draw_properties_; + + DISALLOW_COPY_AND_ASSIGN(LayerImpl); +}; + +} + +#endif // CC_LAYERS_LAYER_IMPL_H_ diff --git a/cc/layers/layer_impl_unittest.cc b/cc/layers/layer_impl_unittest.cc new file mode 100644 index 0000000..6ff279a --- /dev/null +++ b/cc/layers/layer_impl_unittest.cc @@ -0,0 +1,255 @@ +// Copyright 2011 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 "cc/layers/layer_impl.h" + +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/fake_output_surface.h" +#include "cc/trees/layer_tree_impl.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebFilterOperation.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebFilterOperations.h" +#include "third_party/skia/include/effects/SkBlurImageFilter.h" + +using namespace WebKit; + +namespace cc { +namespace { + +#define EXECUTE_AND_VERIFY_SUBTREE_CHANGED(codeToTest) \ + root->ResetAllChangeTrackingForSubtree(); \ + codeToTest; \ + EXPECT_TRUE(root->LayerPropertyChanged()); \ + EXPECT_TRUE(child->LayerPropertyChanged()); \ + EXPECT_TRUE(grandChild->LayerPropertyChanged()); \ + EXPECT_FALSE(root->LayerSurfacePropertyChanged()) + + +#define EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(codeToTest) \ + root->ResetAllChangeTrackingForSubtree(); \ + codeToTest; \ + EXPECT_FALSE(root->LayerPropertyChanged()); \ + EXPECT_FALSE(child->LayerPropertyChanged()); \ + EXPECT_FALSE(grandChild->LayerPropertyChanged()); \ + EXPECT_FALSE(root->LayerSurfacePropertyChanged()) + +#define EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(codeToTest) \ + root->ResetAllChangeTrackingForSubtree(); \ + codeToTest; \ + EXPECT_TRUE(root->LayerPropertyChanged()); \ + EXPECT_FALSE(child->LayerPropertyChanged()); \ + EXPECT_FALSE(grandChild->LayerPropertyChanged()); \ + EXPECT_FALSE(root->LayerSurfacePropertyChanged()) + +#define EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(codeToTest) \ + root->ResetAllChangeTrackingForSubtree(); \ + codeToTest; \ + EXPECT_FALSE(root->LayerPropertyChanged()); \ + EXPECT_FALSE(child->LayerPropertyChanged()); \ + EXPECT_FALSE(grandChild->LayerPropertyChanged()); \ + EXPECT_TRUE(root->LayerSurfacePropertyChanged()) + +#define VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(codeToTest) \ + root->ResetAllChangeTrackingForSubtree(); \ + hostImpl.ForcePrepareToDraw(); \ + EXPECT_FALSE(hostImpl.active_tree()->needs_update_draw_properties()); \ + codeToTest; \ + EXPECT_TRUE(hostImpl.active_tree()->needs_update_draw_properties()); + +#define VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(codeToTest) \ + root->ResetAllChangeTrackingForSubtree(); \ + hostImpl.ForcePrepareToDraw(); \ + EXPECT_FALSE(hostImpl.active_tree()->needs_update_draw_properties()); \ + codeToTest; \ + EXPECT_FALSE(hostImpl.active_tree()->needs_update_draw_properties()); + +TEST(LayerImplTest, verifyLayerChangesAreTrackedProperly) +{ + // + // This test checks that layerPropertyChanged() has the correct behavior. + // + + // The constructor on this will fake that we are on the correct thread. + // Create a simple LayerImpl tree: + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + EXPECT_TRUE(hostImpl.InitializeRenderer(createFakeOutputSurface())); + scoped_ptr<LayerImpl> root = LayerImpl::Create(hostImpl.active_tree(), 1); + root->AddChild(LayerImpl::Create(hostImpl.active_tree(), 2)); + LayerImpl* child = root->children()[0]; + child->AddChild(LayerImpl::Create(hostImpl.active_tree(), 3)); + LayerImpl* grandChild = child->children()[0]; + + // Adding children is an internal operation and should not mark layers as changed. + EXPECT_FALSE(root->LayerPropertyChanged()); + EXPECT_FALSE(child->LayerPropertyChanged()); + EXPECT_FALSE(grandChild->LayerPropertyChanged()); + + gfx::PointF arbitraryPointF = gfx::PointF(0.125f, 0.25f); + float arbitraryNumber = 0.352f; + gfx::Size arbitrarySize = gfx::Size(111, 222); + gfx::Point arbitraryPoint = gfx::Point(333, 444); + gfx::Vector2d arbitraryVector2d = gfx::Vector2d(111, 222); + gfx::Rect arbitraryRect = gfx::Rect(arbitraryPoint, arbitrarySize); + gfx::RectF arbitraryRectF = gfx::RectF(arbitraryPointF, gfx::SizeF(1.234f, 5.678f)); + SkColor arbitraryColor = SkColorSetRGB(10, 20, 30); + gfx::Transform arbitraryTransform; + arbitraryTransform.Scale3d(0.1, 0.2, 0.3); + WebFilterOperations arbitraryFilters; + arbitraryFilters.append(WebFilterOperation::createOpacityFilter(0.5)); + skia::RefPtr<SkImageFilter> arbitraryFilter = skia::AdoptRef(new SkBlurImageFilter(SK_Scalar1, SK_Scalar1)); + + // These properties are internal, and should not be considered "change" when they are used. + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->set_update_rect(arbitraryRectF)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetMaxScrollOffset(arbitraryVector2d)); + + // Changing these properties affects the entire subtree of layers. + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetAnchorPoint(arbitraryPointF)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetAnchorPointZ(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetFilters(arbitraryFilters)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetFilters(WebFilterOperations())); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetFilter(arbitraryFilter)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetMaskLayer(LayerImpl::Create(hostImpl.active_tree(), 4))); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetMasksToBounds(true)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetContentsOpaque(true)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetReplicaLayer(LayerImpl::Create(hostImpl.active_tree(), 5))); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetPosition(arbitraryPointF)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetPreserves3d(true)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetDoubleSided(false)); // constructor initializes it to "true". + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->ScrollBy(arbitraryVector2d)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetScrollDelta(gfx::Vector2d())); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetScrollOffset(arbitraryVector2d)); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetImplTransform(arbitraryTransform)); + + // Changing these properties only affects the layer itself. + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetContentBounds(arbitrarySize)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetContentsScale(arbitraryNumber, arbitraryNumber)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetDrawsContent(true)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetBackgroundColor(SK_ColorGRAY)); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetBackgroundFilters(arbitraryFilters)); + + // Changing these properties only affects how render surface is drawn + EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(root->SetOpacity(arbitraryNumber)); + EXECUTE_AND_VERIFY_ONLY_SURFACE_CHANGED(root->SetTransform(arbitraryTransform)); + + // Special case: check that sublayer transform changes all layer's descendants, but not the layer itself. + root->ResetAllChangeTrackingForSubtree(); + root->SetSublayerTransform(arbitraryTransform); + EXPECT_FALSE(root->LayerPropertyChanged()); + EXPECT_TRUE(child->LayerPropertyChanged()); + EXPECT_TRUE(grandChild->LayerPropertyChanged()); + + // Special case: check that setBounds changes behavior depending on masksToBounds. + root->SetMasksToBounds(false); + EXECUTE_AND_VERIFY_ONLY_LAYER_CHANGED(root->SetBounds(gfx::Size(135, 246))); + root->SetMasksToBounds(true); + EXECUTE_AND_VERIFY_SUBTREE_CHANGED(root->SetBounds(arbitrarySize)); // should be a different size than previous call, to ensure it marks tree changed. + + // After setting all these properties already, setting to the exact same values again should + // not cause any change. + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetAnchorPoint(arbitraryPointF)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetAnchorPointZ(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetMasksToBounds(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetPosition(arbitraryPointF)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetPreserves3d(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetTransform(arbitraryTransform)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetDoubleSided(false)); // constructor initializes it to "true". + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetScrollDelta(gfx::Vector2d())); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetScrollOffset(arbitraryVector2d)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetImplTransform(arbitraryTransform)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetContentBounds(arbitrarySize)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetContentsScale(arbitraryNumber, arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetContentsOpaque(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetOpacity(arbitraryNumber)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetDrawsContent(true)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetSublayerTransform(arbitraryTransform)); + EXECUTE_AND_VERIFY_SUBTREE_DID_NOT_CHANGE(root->SetBounds(arbitrarySize)); +} + +TEST(LayerImplTest, VerifyNeedsUpdateDrawProperties) +{ + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + EXPECT_TRUE(hostImpl.InitializeRenderer(createFakeOutputSurface())); + scoped_ptr<LayerImpl> root = LayerImpl::Create(hostImpl.active_tree(), 1); + + gfx::PointF arbitraryPointF = gfx::PointF(0.125f, 0.25f); + float arbitraryNumber = 0.352f; + gfx::Size arbitrarySize = gfx::Size(111, 222); + gfx::Point arbitraryPoint = gfx::Point(333, 444); + gfx::Vector2d arbitraryVector2d = gfx::Vector2d(111, 222); + gfx::Vector2d largeVector2d = gfx::Vector2d(1000, 1000); + gfx::Rect arbitraryRect = gfx::Rect(arbitraryPoint, arbitrarySize); + gfx::RectF arbitraryRectF = gfx::RectF(arbitraryPointF, gfx::SizeF(1.234f, 5.678f)); + SkColor arbitraryColor = SkColorSetRGB(10, 20, 30); + gfx::Transform arbitraryTransform; + arbitraryTransform.Scale3d(0.1, 0.2, 0.3); + WebFilterOperations arbitraryFilters; + arbitraryFilters.append(WebFilterOperation::createOpacityFilter(0.5)); + skia::RefPtr<SkImageFilter> arbitraryFilter = skia::AdoptRef(new SkBlurImageFilter(SK_Scalar1, SK_Scalar1)); + + // Related filter functions. + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilters(arbitraryFilters)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilters(arbitraryFilters)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilters(WebFilterOperations())); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilter(arbitraryFilter)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilter(arbitraryFilter)); + + // Related scrolling functions. + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetMaxScrollOffset(largeVector2d)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetMaxScrollOffset(largeVector2d)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->ScrollBy(arbitraryVector2d)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->ScrollBy(gfx::Vector2d())); + root->SetScrollDelta(gfx::Vector2d(0, 0)); + hostImpl.ForcePrepareToDraw(); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetScrollDelta(arbitraryVector2d)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetScrollDelta(arbitraryVector2d)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetScrollOffset(arbitraryVector2d)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetScrollOffset(arbitraryVector2d)); + + // Unrelated functions, always set to new values, always set needs update. + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetAnchorPointZ(arbitraryNumber)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetMaskLayer(LayerImpl::Create(hostImpl.active_tree(), 4))); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetMasksToBounds(true)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentsOpaque(true)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetReplicaLayer(LayerImpl::Create(hostImpl.active_tree(), 5))); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetPosition(arbitraryPointF)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetPreserves3d(true)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetDoubleSided(false)); // constructor initializes it to "true". + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetImplTransform(arbitraryTransform)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentBounds(arbitrarySize)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentsScale(arbitraryNumber, arbitraryNumber)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetDrawsContent(true)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetBackgroundColor(SK_ColorGRAY)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetBackgroundFilters(arbitraryFilters)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetOpacity(arbitraryNumber)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetTransform(arbitraryTransform)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetSublayerTransform(arbitraryTransform)); + VERIFY_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetBounds(arbitrarySize)); + + // Unrelated functions, set to the same values, no needs update. + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetAnchorPointZ(arbitraryNumber)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetFilter(arbitraryFilter)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetMasksToBounds(true)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentsOpaque(true)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetPosition(arbitraryPointF)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetPreserves3d(true)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetDoubleSided(false)); // constructor initializes it to "true". + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetImplTransform(arbitraryTransform)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentBounds(arbitrarySize)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetContentsScale(arbitraryNumber, arbitraryNumber)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetDrawsContent(true)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetBackgroundColor(SK_ColorGRAY)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetBackgroundFilters(arbitraryFilters)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetOpacity(arbitraryNumber)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetTransform(arbitraryTransform)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetSublayerTransform(arbitraryTransform)); + VERIFY_NO_NEEDS_UPDATE_DRAW_PROPERTIES(root->SetBounds(arbitrarySize)); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/layer_iterator.cc b/cc/layers/layer_iterator.cc new file mode 100644 index 0000000..58a9175 --- /dev/null +++ b/cc/layers/layer_iterator.cc @@ -0,0 +1,147 @@ +// Copyright 2012 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 "cc/layers/layer_iterator.h" + +#include "cc/layers/layer.h" +#include "cc/layers/layer_impl.h" +#include "cc/layers/render_surface.h" +#include "cc/layers/render_surface_impl.h" + +namespace cc { + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void LayerIteratorActions::BackToFront::begin(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + it.m_targetRenderSurfaceLayerIndex = 0; + it.m_currentLayerIndex = LayerIteratorValue::LayerIndexRepresentingTargetRenderSurface; + + m_highestTargetRenderSurfaceLayer = 0; +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void LayerIteratorActions::BackToFront::end(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + it.m_targetRenderSurfaceLayerIndex = LayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex; + it.m_currentLayerIndex = 0; +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void LayerIteratorActions::BackToFront::next(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + // If the current layer has a RS, move to its layer list. Otherwise, visit the next layer in the current RS layer list. + if (it.currentLayerRepresentsContributingRenderSurface()) { + // Save our position in the childLayer list for the RenderSurfaceImpl, then jump to the next RenderSurfaceImpl. Save where we + // came from in the next RenderSurfaceImpl so we can get back to it. + it.targetRenderSurface()->current_layer_index_history_ = it.m_currentLayerIndex; + int previousTargetRenderSurfaceLayer = it.m_targetRenderSurfaceLayerIndex; + + it.m_targetRenderSurfaceLayerIndex = ++m_highestTargetRenderSurfaceLayer; + it.m_currentLayerIndex = LayerIteratorValue::LayerIndexRepresentingTargetRenderSurface; + + it.targetRenderSurface()->target_render_surface_layer_index_history_ = previousTargetRenderSurfaceLayer; + } else { + ++it.m_currentLayerIndex; + + int targetRenderSurfaceNumChildren = it.targetRenderSurfaceChildren().size(); + while (it.m_currentLayerIndex == targetRenderSurfaceNumChildren) { + // Jump back to the previous RenderSurfaceImpl, and get back the position where we were in that list, and move to the next position there. + if (!it.m_targetRenderSurfaceLayerIndex) { + // End of the list + it.m_targetRenderSurfaceLayerIndex = LayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex; + it.m_currentLayerIndex = 0; + return; + } + it.m_targetRenderSurfaceLayerIndex = it.targetRenderSurface()->target_render_surface_layer_index_history_; + it.m_currentLayerIndex = it.targetRenderSurface()->current_layer_index_history_ + 1; + + targetRenderSurfaceNumChildren = it.targetRenderSurfaceChildren().size(); + } + } +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void LayerIteratorActions::FrontToBack::begin(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + it.m_targetRenderSurfaceLayerIndex = 0; + it.m_currentLayerIndex = it.targetRenderSurfaceChildren().size() - 1; + goToHighestInSubtree(it); +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void LayerIteratorActions::FrontToBack::end(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + it.m_targetRenderSurfaceLayerIndex = LayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex; + it.m_currentLayerIndex = 0; +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void LayerIteratorActions::FrontToBack::next(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + // Moves to the previous layer in the current RS layer list. Then we check if the + // new current layer has its own RS, in which case there are things in that RS layer list that are higher, so + // we find the highest layer in that subtree. + // If we move back past the front of the list, we jump up to the previous RS layer list, picking up again where we + // had previously recursed into the current RS layer list. + + if (!it.currentLayerRepresentsTargetRenderSurface()) { + // Subtracting one here will eventually cause the current layer to become that layer + // representing the target render surface. + --it.m_currentLayerIndex; + goToHighestInSubtree(it); + } else { + while (it.currentLayerRepresentsTargetRenderSurface()) { + if (!it.m_targetRenderSurfaceLayerIndex) { + // End of the list + it.m_targetRenderSurfaceLayerIndex = LayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex; + it.m_currentLayerIndex = 0; + return; + } + it.m_targetRenderSurfaceLayerIndex = it.targetRenderSurface()->target_render_surface_layer_index_history_; + it.m_currentLayerIndex = it.targetRenderSurface()->current_layer_index_history_; + } + } +} + +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> +void LayerIteratorActions::FrontToBack::goToHighestInSubtree(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>& it) +{ + if (it.currentLayerRepresentsTargetRenderSurface()) + return; + while (it.currentLayerRepresentsContributingRenderSurface()) { + // Save where we were in the current target surface, move to the next one, and save the target surface that we + // came from there so we can go back to it. + it.targetRenderSurface()->current_layer_index_history_ = it.m_currentLayerIndex; + int previousTargetRenderSurfaceLayer = it.m_targetRenderSurfaceLayerIndex; + + for (LayerType* layer = it.currentLayer(); it.targetRenderSurfaceLayer() != layer; ++it.m_targetRenderSurfaceLayerIndex) { } + it.m_currentLayerIndex = it.targetRenderSurfaceChildren().size() - 1; + + it.targetRenderSurface()->target_render_surface_layer_index_history_ = previousTargetRenderSurfaceLayer; + } +} + +typedef std::vector<scoped_refptr<Layer> > LayerList; +typedef std::vector<LayerImpl*> LayerImplList; + +// Declare each of the above functions for Layer and LayerImpl classes so that they are linked. +template CC_EXPORT void LayerIteratorActions::BackToFront::begin(LayerIterator<Layer, LayerList, RenderSurface, BackToFront> &); +template CC_EXPORT void LayerIteratorActions::BackToFront::end(LayerIterator<Layer, LayerList, RenderSurface, BackToFront>&); +template CC_EXPORT void LayerIteratorActions::BackToFront::next(LayerIterator<Layer, LayerList, RenderSurface, BackToFront>&); + +template CC_EXPORT void LayerIteratorActions::BackToFront::begin(LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, BackToFront>&); +template CC_EXPORT void LayerIteratorActions::BackToFront::end(LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, BackToFront>&); +template CC_EXPORT void LayerIteratorActions::BackToFront::next(LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, BackToFront>&); + +template CC_EXPORT void LayerIteratorActions::FrontToBack::next(LayerIterator<Layer, LayerList, RenderSurface, FrontToBack>&); +template CC_EXPORT void LayerIteratorActions::FrontToBack::end(LayerIterator<Layer, LayerList, RenderSurface, FrontToBack>&); +template CC_EXPORT void LayerIteratorActions::FrontToBack::begin(LayerIterator<Layer, LayerList, RenderSurface, FrontToBack>&); +template CC_EXPORT void LayerIteratorActions::FrontToBack::goToHighestInSubtree(LayerIterator<Layer, LayerList, RenderSurface, FrontToBack>&); + +template CC_EXPORT void LayerIteratorActions::FrontToBack::next(LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, FrontToBack>&); +template CC_EXPORT void LayerIteratorActions::FrontToBack::end(LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, FrontToBack>&); +template CC_EXPORT void LayerIteratorActions::FrontToBack::begin(LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, FrontToBack>&); +template CC_EXPORT void LayerIteratorActions::FrontToBack::goToHighestInSubtree(LayerIterator<LayerImpl, LayerImplList, RenderSurfaceImpl, FrontToBack>&); + +} // namespace cc diff --git a/cc/layers/layer_iterator.h b/cc/layers/layer_iterator.h new file mode 100644 index 0000000..55b1d79 --- /dev/null +++ b/cc/layers/layer_iterator.h @@ -0,0 +1,208 @@ +// Copyright 2012 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 CC_LAYERS_LAYER_ITERATOR_H_ +#define CC_LAYERS_LAYER_ITERATOR_H_ + +#include "base/memory/ref_counted.h" +#include "cc/base/cc_export.h" +#include "cc/trees/layer_tree_host_common.h" + +namespace cc { + +// These classes provide means to iterate over the RenderSurfaceImpl-Layer tree. + +// Example code follows, for a tree of Layer/RenderSurface objects. See below for details. +// +// void doStuffOnLayers(const std::vector<scoped_refptr<Layer> >& renderSurfaceLayerList) +// { +// typedef LayerIterator<Layer, RenderSurface, LayerIteratorActions::FrontToBack> LayerIteratorType; +// +// LayerIteratorType end = LayerIteratorType::end(&renderSurfaceLayerList); +// for (LayerIteratorType it = LayerIteratorType::begin(&renderSurfaceLayerList); it != end; ++it) { +// // Only one of these will be true +// if (it.representsTargetRenderSurface()) +// foo(*it); // *it is a layer representing a target RenderSurfaceImpl +// if (it.representsContributingRenderSurface()) +// bar(*it); // *it is a layer representing a RenderSurfaceImpl that contributes to the layer's target RenderSurfaceImpl +// if (it.representsItself()) +// baz(*it); // *it is a layer representing itself, as it contributes to its own target RenderSurfaceImpl +// } +// } + +// A RenderSurfaceImpl R may be referred to in one of two different contexts. One RenderSurfaceImpl is "current" at any time, for +// whatever operation is being performed. This current surface is referred to as a target surface. For example, when R is +// being painted it would be the target surface. Once R has been painted, its contents may be included into another +// surface S. While S is considered the target surface when it is being painted, R is called a contributing surface +// in this context as it contributes to the content of the target surface S. +// +// The iterator's current position in the tree always points to some layer. The state of the iterator indicates the role of the +// layer, and will be one of the following three states. A single layer L will appear in the iteration process in at least one, +// and possibly all, of these states. +// 1. Representing the target surface: The iterator in this state, pointing at layer L, indicates that the target RenderSurfaceImpl +// is now the surface owned by L. This will occur exactly once for each RenderSurfaceImpl in the tree. +// 2. Representing a contributing surface: The iterator in this state, pointing at layer L, refers to the RenderSurfaceImpl owned +// by L as a contributing surface, without changing the current target RenderSurfaceImpl. +// 3. Representing itself: The iterator in this state, pointing at layer L, refers to the layer itself, as a child of the +// current target RenderSurfaceImpl. +// +// The BackToFront iterator will return a layer representing the target surface before returning layers representing themselves +// as children of the current target surface. Whereas the FrontToBack ordering will iterate over children layers of a surface +// before the layer representing the surface as a target surface. +// +// To use the iterators: +// +// Create a stepping iterator and end iterator by calling LayerIterator::begin() and LayerIterator::end() and passing in the +// list of layers owning target RenderSurfaces. Step through the tree by incrementing the stepping iterator while it is != to +// the end iterator. At each step the iterator knows what the layer is representing, and you can query the iterator to decide +// what actions to perform with the layer given what it represents. + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// Non-templated constants +struct LayerIteratorValue { + static const int InvalidTargetRenderSurfaceLayerIndex = -1; + // This must be -1 since the iterator action code assumes that this value can be + // reached by subtracting one from the position of the first layer in the current + // target surface's child layer list, which is 0. + static const int LayerIndexRepresentingTargetRenderSurface = -1; +}; + +// The position of a layer iterator that is independent of its many template types. +template <typename LayerType> +struct LayerIteratorPosition { + bool representsTargetRenderSurface; + bool representsContributingRenderSurface; + bool representsItself; + LayerType* targetRenderSurfaceLayer; + LayerType* currentLayer; +}; + +// An iterator class for walking over layers in the RenderSurfaceImpl-Layer tree. +template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename IteratorActionType> +class LayerIterator { + typedef LayerIterator<LayerType, LayerList, RenderSurfaceType, IteratorActionType> LayerIteratorType; + +public: + LayerIterator() : m_renderSurfaceLayerList(0) { } + + static LayerIteratorType begin(const LayerList* renderSurfaceLayerList) { return LayerIteratorType(renderSurfaceLayerList, true); } + static LayerIteratorType end(const LayerList* renderSurfaceLayerList) { return LayerIteratorType(renderSurfaceLayerList, false); } + + LayerIteratorType& operator++() { m_actions.next(*this); return *this; } + bool operator==(const LayerIterator& other) const + { + return m_targetRenderSurfaceLayerIndex == other.m_targetRenderSurfaceLayerIndex + && m_currentLayerIndex == other.m_currentLayerIndex; + } + bool operator!=(const LayerIteratorType& other) const { return !(*this == other); } + + LayerType* operator->() const { return currentLayer(); } + LayerType* operator*() const { return currentLayer(); } + + bool representsTargetRenderSurface() const { return currentLayerRepresentsTargetRenderSurface(); } + bool representsContributingRenderSurface() const { return !representsTargetRenderSurface() && currentLayerRepresentsContributingRenderSurface(); } + bool representsItself() const { return !representsTargetRenderSurface() && !representsContributingRenderSurface(); } + + LayerType* targetRenderSurfaceLayer() const { return getRawPtr((*m_renderSurfaceLayerList)[m_targetRenderSurfaceLayerIndex]); } + + operator const LayerIteratorPosition<LayerType>() const + { + LayerIteratorPosition<LayerType> position; + position.representsTargetRenderSurface = representsTargetRenderSurface(); + position.representsContributingRenderSurface = representsContributingRenderSurface(); + position.representsItself = representsItself(); + position.targetRenderSurfaceLayer = targetRenderSurfaceLayer(); + position.currentLayer = currentLayer(); + return position; + } + +private: + LayerIterator(const LayerList* renderSurfaceLayerList, bool start) + : m_renderSurfaceLayerList(renderSurfaceLayerList) + , m_targetRenderSurfaceLayerIndex(0) + { + for (size_t i = 0; i < renderSurfaceLayerList->size(); ++i) { + if (!(*renderSurfaceLayerList)[i]->render_surface()) { + NOTREACHED(); + m_actions.end(*this); + return; + } + } + + if (start && !renderSurfaceLayerList->empty()) + m_actions.begin(*this); + else + m_actions.end(*this); + } + + inline static Layer* getRawPtr(const scoped_refptr<Layer>& ptr) { return ptr.get(); } + inline static LayerImpl* getRawPtr(LayerImpl* ptr) { return ptr; } + + inline LayerType* currentLayer() const { return currentLayerRepresentsTargetRenderSurface() ? targetRenderSurfaceLayer() : getRawPtr(targetRenderSurfaceChildren()[m_currentLayerIndex]); } + + inline bool currentLayerRepresentsContributingRenderSurface() const { return LayerTreeHostCommon::renderSurfaceContributesToTarget<LayerType>(currentLayer(), targetRenderSurfaceLayer()->id()); } + inline bool currentLayerRepresentsTargetRenderSurface() const { return m_currentLayerIndex == LayerIteratorValue::LayerIndexRepresentingTargetRenderSurface; } + + inline RenderSurfaceType* targetRenderSurface() const { return targetRenderSurfaceLayer()->render_surface(); } + inline const LayerList& targetRenderSurfaceChildren() const { return targetRenderSurface()->layer_list(); } + + IteratorActionType m_actions; + const LayerList* m_renderSurfaceLayerList; + + // The iterator's current position. + + // A position in the renderSurfaceLayerList. This points to a layer which owns the current target surface. + // This is a value from 0 to n-1 (n = size of renderSurfaceLayerList = number of surfaces). A value outside of + // this range (for example, LayerIteratorValue::InvalidTargetRenderSurfaceLayerIndex) is used to + // indicate a position outside the bounds of the tree. + int m_targetRenderSurfaceLayerIndex; + // A position in the list of layers that are children of the current target surface. When pointing to one of + // these layers, this is a value from 0 to n-1 (n = number of children). Since the iterator must also stop at + // the layers representing the target surface, this is done by setting the currentLayerIndex to a value of + // LayerIteratorValue::LayerRepresentingTargetRenderSurface. + int m_currentLayerIndex; + + friend struct LayerIteratorActions; +}; + +// Orderings for iterating over the RenderSurfaceImpl-Layer tree. +struct CC_EXPORT LayerIteratorActions { + // Walks layers sorted by z-order from back to front. + class CC_EXPORT BackToFront { + public: + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void begin(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void end(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void next(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + private: + int m_highestTargetRenderSurfaceLayer; + }; + + // Walks layers sorted by z-order from front to back + class CC_EXPORT FrontToBack { + public: + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void begin(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void end(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void next(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + + private: + template <typename LayerType, typename LayerList, typename RenderSurfaceType, typename ActionType> + void goToHighestInSubtree(LayerIterator<LayerType, LayerList, RenderSurfaceType, ActionType>&); + }; +}; + +} // namespace cc + +#endif // CC_LAYERS_LAYER_ITERATOR_H_ diff --git a/cc/layers/layer_iterator_unittest.cc b/cc/layers/layer_iterator_unittest.cc new file mode 100644 index 0000000..9d4dc52 --- /dev/null +++ b/cc/layers/layer_iterator_unittest.cc @@ -0,0 +1,254 @@ +// Copyright 2012 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 "cc/layers/layer_iterator.h" + +#include "cc/layers/layer.h" +#include "cc/trees/layer_tree_host_common.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/transform.h" + +using ::testing::Mock; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::AnyNumber; + +namespace cc { +namespace { + +class TestLayer : public Layer { +public: + static scoped_refptr<TestLayer> Create() { return make_scoped_refptr(new TestLayer()); } + + int m_countRepresentingTargetSurface; + int m_countRepresentingContributingSurface; + int m_countRepresentingItself; + + virtual bool DrawsContent() const OVERRIDE { return m_drawsContent; } + void setDrawsContent(bool drawsContent) { m_drawsContent = drawsContent; } + +private: + TestLayer() + : Layer() + , m_drawsContent(true) + { + SetBounds(gfx::Size(100, 100)); + SetPosition(gfx::Point()); + SetAnchorPoint(gfx::Point()); + } + virtual ~TestLayer() + { + } + + bool m_drawsContent; +}; + +#define EXPECT_COUNT(layer, target, contrib, itself) \ + EXPECT_EQ(target, layer->m_countRepresentingTargetSurface); \ + EXPECT_EQ(contrib, layer->m_countRepresentingContributingSurface); \ + EXPECT_EQ(itself, layer->m_countRepresentingItself); + +typedef LayerIterator<Layer, std::vector<scoped_refptr<Layer> >, RenderSurface, LayerIteratorActions::FrontToBack> FrontToBack; +typedef LayerIterator<Layer, std::vector<scoped_refptr<Layer> >, RenderSurface, LayerIteratorActions::BackToFront> BackToFront; + +void resetCounts(std::vector<scoped_refptr<Layer> >& renderSurfaceLayerList) +{ + for (unsigned surfaceIndex = 0; surfaceIndex < renderSurfaceLayerList.size(); ++surfaceIndex) { + TestLayer* renderSurfaceLayer = static_cast<TestLayer*>(renderSurfaceLayerList[surfaceIndex].get()); + RenderSurface* renderSurface = renderSurfaceLayer->render_surface(); + + renderSurfaceLayer->m_countRepresentingTargetSurface = -1; + renderSurfaceLayer->m_countRepresentingContributingSurface = -1; + renderSurfaceLayer->m_countRepresentingItself = -1; + + for (unsigned layerIndex = 0; layerIndex < renderSurface->layer_list().size(); ++layerIndex) { + TestLayer* layer = static_cast<TestLayer*>(renderSurface->layer_list()[layerIndex].get()); + + layer->m_countRepresentingTargetSurface = -1; + layer->m_countRepresentingContributingSurface = -1; + layer->m_countRepresentingItself = -1; + } + } +} + +void iterateFrontToBack(std::vector<scoped_refptr<Layer> >* renderSurfaceLayerList) +{ + resetCounts(*renderSurfaceLayerList); + int count = 0; + for (FrontToBack it = FrontToBack::begin(renderSurfaceLayerList); it != FrontToBack::end(renderSurfaceLayerList); ++it, ++count) { + TestLayer* layer = static_cast<TestLayer*>(*it); + if (it.representsTargetRenderSurface()) + layer->m_countRepresentingTargetSurface = count; + if (it.representsContributingRenderSurface()) + layer->m_countRepresentingContributingSurface = count; + if (it.representsItself()) + layer->m_countRepresentingItself = count; + } +} + +void iterateBackToFront(std::vector<scoped_refptr<Layer> >* renderSurfaceLayerList) +{ + resetCounts(*renderSurfaceLayerList); + int count = 0; + for (BackToFront it = BackToFront::begin(renderSurfaceLayerList); it != BackToFront::end(renderSurfaceLayerList); ++it, ++count) { + TestLayer* layer = static_cast<TestLayer*>(*it); + if (it.representsTargetRenderSurface()) + layer->m_countRepresentingTargetSurface = count; + if (it.representsContributingRenderSurface()) + layer->m_countRepresentingContributingSurface = count; + if (it.representsItself()) + layer->m_countRepresentingItself = count; + } +} + +TEST(LayerIteratorTest, emptyTree) +{ + std::vector<scoped_refptr<Layer> > renderSurfaceLayerList; + + iterateBackToFront(&renderSurfaceLayerList); + iterateFrontToBack(&renderSurfaceLayerList); +} + +TEST(LayerIteratorTest, simpleTree) +{ + scoped_refptr<TestLayer> rootLayer = TestLayer::Create(); + scoped_refptr<TestLayer> first = TestLayer::Create(); + scoped_refptr<TestLayer> second = TestLayer::Create(); + scoped_refptr<TestLayer> third = TestLayer::Create(); + scoped_refptr<TestLayer> fourth = TestLayer::Create(); + + rootLayer->CreateRenderSurface(); + + rootLayer->AddChild(first); + rootLayer->AddChild(second); + rootLayer->AddChild(third); + rootLayer->AddChild(fourth); + + std::vector<scoped_refptr<Layer> > renderSurfaceLayerList; + LayerTreeHostCommon::calculateDrawProperties(rootLayer.get(), rootLayer->bounds(), 1, 1, 256, false, renderSurfaceLayerList); + + iterateBackToFront(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 0, -1, 1); + EXPECT_COUNT(first, -1, -1, 2); + EXPECT_COUNT(second, -1, -1, 3); + EXPECT_COUNT(third, -1, -1, 4); + EXPECT_COUNT(fourth, -1, -1, 5); + + iterateFrontToBack(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 5, -1, 4); + EXPECT_COUNT(first, -1, -1, 3); + EXPECT_COUNT(second, -1, -1, 2); + EXPECT_COUNT(third, -1, -1, 1); + EXPECT_COUNT(fourth, -1, -1, 0); + +} + +TEST(LayerIteratorTest, complexTree) +{ + scoped_refptr<TestLayer> rootLayer = TestLayer::Create(); + scoped_refptr<TestLayer> root1 = TestLayer::Create(); + scoped_refptr<TestLayer> root2 = TestLayer::Create(); + scoped_refptr<TestLayer> root3 = TestLayer::Create(); + scoped_refptr<TestLayer> root21 = TestLayer::Create(); + scoped_refptr<TestLayer> root22 = TestLayer::Create(); + scoped_refptr<TestLayer> root23 = TestLayer::Create(); + scoped_refptr<TestLayer> root221 = TestLayer::Create(); + scoped_refptr<TestLayer> root231 = TestLayer::Create(); + + rootLayer->CreateRenderSurface(); + + rootLayer->AddChild(root1); + rootLayer->AddChild(root2); + rootLayer->AddChild(root3); + root2->AddChild(root21); + root2->AddChild(root22); + root2->AddChild(root23); + root22->AddChild(root221); + root23->AddChild(root231); + + std::vector<scoped_refptr<Layer> > renderSurfaceLayerList; + LayerTreeHostCommon::calculateDrawProperties(rootLayer.get(), rootLayer->bounds(), 1, 1, 256, false, renderSurfaceLayerList); + + iterateBackToFront(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 0, -1, 1); + EXPECT_COUNT(root1, -1, -1, 2); + EXPECT_COUNT(root2, -1, -1, 3); + EXPECT_COUNT(root21, -1, -1, 4); + EXPECT_COUNT(root22, -1, -1, 5); + EXPECT_COUNT(root221, -1, -1, 6); + EXPECT_COUNT(root23, -1, -1, 7); + EXPECT_COUNT(root231, -1, -1, 8); + EXPECT_COUNT(root3, -1, -1, 9); + + iterateFrontToBack(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 9, -1, 8); + EXPECT_COUNT(root1, -1, -1, 7); + EXPECT_COUNT(root2, -1, -1, 6); + EXPECT_COUNT(root21, -1, -1, 5); + EXPECT_COUNT(root22, -1, -1, 4); + EXPECT_COUNT(root221, -1, -1, 3); + EXPECT_COUNT(root23, -1, -1, 2); + EXPECT_COUNT(root231, -1, -1, 1); + EXPECT_COUNT(root3, -1, -1, 0); + +} + +TEST(LayerIteratorTest, complexTreeMultiSurface) +{ + scoped_refptr<TestLayer> rootLayer = TestLayer::Create(); + scoped_refptr<TestLayer> root1 = TestLayer::Create(); + scoped_refptr<TestLayer> root2 = TestLayer::Create(); + scoped_refptr<TestLayer> root3 = TestLayer::Create(); + scoped_refptr<TestLayer> root21 = TestLayer::Create(); + scoped_refptr<TestLayer> root22 = TestLayer::Create(); + scoped_refptr<TestLayer> root23 = TestLayer::Create(); + scoped_refptr<TestLayer> root221 = TestLayer::Create(); + scoped_refptr<TestLayer> root231 = TestLayer::Create(); + + rootLayer->CreateRenderSurface(); + rootLayer->render_surface()->SetContentRect(gfx::Rect(gfx::Point(), rootLayer->bounds())); + + rootLayer->AddChild(root1); + rootLayer->AddChild(root2); + rootLayer->AddChild(root3); + root2->setDrawsContent(false); + root2->SetOpacity(0.5); + root2->SetForceRenderSurface(true); // Force the layer to own a new surface. + root2->AddChild(root21); + root2->AddChild(root22); + root2->AddChild(root23); + root22->SetOpacity(0.5); + root22->AddChild(root221); + root23->SetOpacity(0.5); + root23->AddChild(root231); + + std::vector<scoped_refptr<Layer> > renderSurfaceLayerList; + LayerTreeHostCommon::calculateDrawProperties(rootLayer.get(), rootLayer->bounds(), 1, 1, 256, false, renderSurfaceLayerList); + + iterateBackToFront(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 0, -1, 1); + EXPECT_COUNT(root1, -1, -1, 2); + EXPECT_COUNT(root2, 4, 3, -1); + EXPECT_COUNT(root21, -1, -1, 5); + EXPECT_COUNT(root22, 7, 6, 8); + EXPECT_COUNT(root221, -1, -1, 9); + EXPECT_COUNT(root23, 11, 10, 12); + EXPECT_COUNT(root231, -1, -1, 13); + EXPECT_COUNT(root3, -1, -1, 14); + + iterateFrontToBack(&renderSurfaceLayerList); + EXPECT_COUNT(rootLayer, 14, -1, 13); + EXPECT_COUNT(root1, -1, -1, 12); + EXPECT_COUNT(root2, 10, 11, -1); + EXPECT_COUNT(root21, -1, -1, 9); + EXPECT_COUNT(root22, 7, 8, 6); + EXPECT_COUNT(root221, -1, -1, 5); + EXPECT_COUNT(root23, 3, 4, 2); + EXPECT_COUNT(root231, -1, -1, 1); + EXPECT_COUNT(root3, -1, -1, 0); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/layer_unittest.cc b/cc/layers/layer_unittest.cc new file mode 100644 index 0000000..7b0dbcc --- /dev/null +++ b/cc/layers/layer_unittest.cc @@ -0,0 +1,977 @@ +// Copyright 2011 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 "cc/layers/layer.h" + +#include "cc/animation/keyframed_animation_curve.h" +#include "cc/base/math_util.h" +#include "cc/base/thread.h" +#include "cc/layers/layer_impl.h" +#include "cc/resources/layer_painter.h" +#include "cc/test/animation_test_common.h" +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_client.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/geometry_test_utils.h" +#include "cc/trees/layer_tree_host.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/transform.h" + +using ::testing::AnyNumber; +using ::testing::AtLeast; +using ::testing::Mock; +using ::testing::StrictMock; +using ::testing::_; + +#define EXPECT_SET_NEEDS_COMMIT(expect, codeToTest) do { \ + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times((expect)); \ + codeToTest; \ + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); \ + } while (0) + +#define EXPECT_SET_NEEDS_FULL_TREE_SYNC(expect, codeToTest) do { \ + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times((expect)); \ + codeToTest; \ + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); \ + } while (0) + + +namespace cc { +namespace { + +class MockLayerImplTreeHost : public LayerTreeHost { +public: + MockLayerImplTreeHost() + : LayerTreeHost(&m_fakeClient, LayerTreeSettings()) + { + Initialize(scoped_ptr<Thread>(NULL)); + } + + MOCK_METHOD0(SetNeedsCommit, void()); + MOCK_METHOD0(SetNeedsFullTreeSync, void()); + +private: + FakeLayerImplTreeHostClient m_fakeClient; +}; + +class MockLayerPainter : public LayerPainter { +public: + virtual void Paint(SkCanvas* canvas, gfx::Rect content_rect, gfx::RectF* opaque) OVERRIDE { } +}; + + +class LayerTest : public testing::Test { +public: + LayerTest() + : m_hostImpl(&m_proxy) + { + } + +protected: + virtual void SetUp() + { + layer_tree_host_.reset(new StrictMock<MockLayerImplTreeHost>); + } + + virtual void TearDown() + { + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AnyNumber()); + parent_ = NULL; + m_child1 = NULL; + m_child2 = NULL; + m_child3 = NULL; + m_grandChild1 = NULL; + m_grandChild2 = NULL; + m_grandChild3 = NULL; + + layer_tree_host_->SetRootLayer(NULL); + layer_tree_host_.reset(); + } + + void verifyTestTreeInitialState() const + { + ASSERT_EQ(3U, parent_->children().size()); + EXPECT_EQ(m_child1, parent_->children()[0]); + EXPECT_EQ(m_child2, parent_->children()[1]); + EXPECT_EQ(m_child3, parent_->children()[2]); + EXPECT_EQ(parent_.get(), m_child1->parent()); + EXPECT_EQ(parent_.get(), m_child2->parent()); + EXPECT_EQ(parent_.get(), m_child3->parent()); + + ASSERT_EQ(2U, m_child1->children().size()); + EXPECT_EQ(m_grandChild1, m_child1->children()[0]); + EXPECT_EQ(m_grandChild2, m_child1->children()[1]); + EXPECT_EQ(m_child1.get(), m_grandChild1->parent()); + EXPECT_EQ(m_child1.get(), m_grandChild2->parent()); + + ASSERT_EQ(1U, m_child2->children().size()); + EXPECT_EQ(m_grandChild3, m_child2->children()[0]); + EXPECT_EQ(m_child2.get(), m_grandChild3->parent()); + + ASSERT_EQ(0U, m_child3->children().size()); + } + + void createSimpleTestTree() + { + parent_ = Layer::Create(); + m_child1 = Layer::Create(); + m_child2 = Layer::Create(); + m_child3 = Layer::Create(); + m_grandChild1 = Layer::Create(); + m_grandChild2 = Layer::Create(); + m_grandChild3 = Layer::Create(); + + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AnyNumber()); + layer_tree_host_->SetRootLayer(parent_); + + parent_->AddChild(m_child1); + parent_->AddChild(m_child2); + parent_->AddChild(m_child3); + m_child1->AddChild(m_grandChild1); + m_child1->AddChild(m_grandChild2); + m_child2->AddChild(m_grandChild3); + + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + verifyTestTreeInitialState(); + } + + FakeImplProxy m_proxy; + FakeLayerTreeHostImpl m_hostImpl; + + scoped_ptr<StrictMock<MockLayerImplTreeHost> > layer_tree_host_; + scoped_refptr<Layer> parent_; + scoped_refptr<Layer> m_child1; + scoped_refptr<Layer> m_child2; + scoped_refptr<Layer> m_child3; + scoped_refptr<Layer> m_grandChild1; + scoped_refptr<Layer> m_grandChild2; + scoped_refptr<Layer> m_grandChild3; +}; + +TEST_F(LayerTest, basicCreateAndDestroy) +{ + scoped_refptr<Layer> testLayer = Layer::Create(); + ASSERT_TRUE(testLayer); + + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0); + testLayer->SetLayerTreeHost(layer_tree_host_.get()); +} + +TEST_F(LayerTest, addAndRemoveChild) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> child = Layer::Create(); + + // Upon creation, layers should not have children or parent. + ASSERT_EQ(0U, parent->children().size()); + EXPECT_FALSE(child->parent()); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, layer_tree_host_->SetRootLayer(parent)); + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->AddChild(child)); + + ASSERT_EQ(1U, parent->children().size()); + EXPECT_EQ(child.get(), parent->children()[0]); + EXPECT_EQ(parent.get(), child->parent()); + EXPECT_EQ(parent.get(), child->RootLayer()); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1), child->RemoveFromParent()); +} + +TEST_F(LayerTest, addSameChildTwice) +{ + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AtLeast(1)); + + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> child = Layer::Create(); + + layer_tree_host_->SetRootLayer(parent); + + ASSERT_EQ(0u, parent->children().size()); + + parent->AddChild(child); + ASSERT_EQ(1u, parent->children().size()); + EXPECT_EQ(parent.get(), child->parent()); + + parent->AddChild(child); + ASSERT_EQ(1u, parent->children().size()); + EXPECT_EQ(parent.get(), child->parent()); +} + +TEST_F(LayerTest, insertChild) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> child1 = Layer::Create(); + scoped_refptr<Layer> child2 = Layer::Create(); + scoped_refptr<Layer> child3 = Layer::Create(); + scoped_refptr<Layer> child4 = Layer::Create(); + + parent->SetLayerTreeHost(layer_tree_host_.get()); + + ASSERT_EQ(0U, parent->children().size()); + + // Case 1: inserting to empty list. + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child3, 0)); + ASSERT_EQ(1U, parent->children().size()); + EXPECT_EQ(child3, parent->children()[0]); + EXPECT_EQ(parent.get(), child3->parent()); + + // Case 2: inserting to beginning of list + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child1, 0)); + ASSERT_EQ(2U, parent->children().size()); + EXPECT_EQ(child1, parent->children()[0]); + EXPECT_EQ(child3, parent->children()[1]); + EXPECT_EQ(parent.get(), child1->parent()); + + // Case 3: inserting to middle of list + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child2, 1)); + ASSERT_EQ(3U, parent->children().size()); + EXPECT_EQ(child1, parent->children()[0]); + EXPECT_EQ(child2, parent->children()[1]); + EXPECT_EQ(child3, parent->children()[2]); + EXPECT_EQ(parent.get(), child2->parent()); + + // Case 4: inserting to end of list + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child4, 3)); + + ASSERT_EQ(4U, parent->children().size()); + EXPECT_EQ(child1, parent->children()[0]); + EXPECT_EQ(child2, parent->children()[1]); + EXPECT_EQ(child3, parent->children()[2]); + EXPECT_EQ(child4, parent->children()[3]); + EXPECT_EQ(parent.get(), child4->parent()); + + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AtLeast(1)); +} + +TEST_F(LayerTest, insertChildPastEndOfList) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> child1 = Layer::Create(); + scoped_refptr<Layer> child2 = Layer::Create(); + + ASSERT_EQ(0U, parent->children().size()); + + // insert to an out-of-bounds index + parent->InsertChild(child1, 53); + + ASSERT_EQ(1U, parent->children().size()); + EXPECT_EQ(child1, parent->children()[0]); + + // insert another child to out-of-bounds, when list is not already empty. + parent->InsertChild(child2, 2459); + + ASSERT_EQ(2U, parent->children().size()); + EXPECT_EQ(child1, parent->children()[0]); + EXPECT_EQ(child2, parent->children()[1]); +} + +TEST_F(LayerTest, insertSameChildTwice) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> child1 = Layer::Create(); + scoped_refptr<Layer> child2 = Layer::Create(); + + parent->SetLayerTreeHost(layer_tree_host_.get()); + + ASSERT_EQ(0U, parent->children().size()); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child1, 0)); + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, parent->InsertChild(child2, 1)); + + ASSERT_EQ(2U, parent->children().size()); + EXPECT_EQ(child1, parent->children()[0]); + EXPECT_EQ(child2, parent->children()[1]); + + // Inserting the same child again should cause the child to be removed and re-inserted at the new location. + EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1), parent->InsertChild(child1, 1)); + + // child1 should now be at the end of the list. + ASSERT_EQ(2U, parent->children().size()); + EXPECT_EQ(child2, parent->children()[0]); + EXPECT_EQ(child1, parent->children()[1]); + + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AtLeast(1)); +} + +TEST_F(LayerTest, replaceChildWithNewChild) +{ + createSimpleTestTree(); + scoped_refptr<Layer> child4 = Layer::Create(); + + EXPECT_FALSE(child4->parent()); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1), parent_->ReplaceChild(m_child2.get(), child4)); + EXPECT_FALSE(parent_->NeedsDisplayForTesting()); + EXPECT_FALSE(m_child1->NeedsDisplayForTesting()); + EXPECT_FALSE(m_child2->NeedsDisplayForTesting()); + EXPECT_FALSE(m_child3->NeedsDisplayForTesting()); + EXPECT_FALSE(child4->NeedsDisplayForTesting()); + + ASSERT_EQ(static_cast<size_t>(3), parent_->children().size()); + EXPECT_EQ(m_child1, parent_->children()[0]); + EXPECT_EQ(child4, parent_->children()[1]); + EXPECT_EQ(m_child3, parent_->children()[2]); + EXPECT_EQ(parent_.get(), child4->parent()); + + EXPECT_FALSE(m_child2->parent()); +} + +TEST_F(LayerTest, replaceChildWithNewChildAutomaticRasterScale) +{ + createSimpleTestTree(); + scoped_refptr<Layer> child4 = Layer::Create(); + EXPECT_SET_NEEDS_COMMIT(1, m_child1->SetAutomaticallyComputeRasterScale(true)); + EXPECT_SET_NEEDS_COMMIT(1, m_child2->SetAutomaticallyComputeRasterScale(true)); + EXPECT_SET_NEEDS_COMMIT(1, m_child3->SetAutomaticallyComputeRasterScale(true)); + + EXPECT_FALSE(child4->parent()); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1), parent_->ReplaceChild(m_child2.get(), child4)); + EXPECT_FALSE(parent_->NeedsDisplayForTesting()); + EXPECT_FALSE(m_child1->NeedsDisplayForTesting()); + EXPECT_FALSE(m_child2->NeedsDisplayForTesting()); + EXPECT_FALSE(m_child3->NeedsDisplayForTesting()); + EXPECT_FALSE(child4->NeedsDisplayForTesting()); + + ASSERT_EQ(3U, parent_->children().size()); + EXPECT_EQ(m_child1, parent_->children()[0]); + EXPECT_EQ(child4, parent_->children()[1]); + EXPECT_EQ(m_child3, parent_->children()[2]); + EXPECT_EQ(parent_.get(), child4->parent()); + + EXPECT_FALSE(m_child2->parent()); +} + +TEST_F(LayerTest, replaceChildWithNewChildThatHasOtherParent) +{ + createSimpleTestTree(); + + // create another simple tree with testLayer and child4. + scoped_refptr<Layer> testLayer = Layer::Create(); + scoped_refptr<Layer> child4 = Layer::Create(); + testLayer->AddChild(child4); + ASSERT_EQ(1U, testLayer->children().size()); + EXPECT_EQ(child4, testLayer->children()[0]); + EXPECT_EQ(testLayer.get(), child4->parent()); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1), parent_->ReplaceChild(m_child2.get(), child4)); + + ASSERT_EQ(3U, parent_->children().size()); + EXPECT_EQ(m_child1, parent_->children()[0]); + EXPECT_EQ(child4, parent_->children()[1]); + EXPECT_EQ(m_child3, parent_->children()[2]); + EXPECT_EQ(parent_.get(), child4->parent()); + + // testLayer should no longer have child4, + // and child2 should no longer have a parent. + ASSERT_EQ(0U, testLayer->children().size()); + EXPECT_FALSE(m_child2->parent()); +} + +TEST_F(LayerTest, replaceChildWithSameChild) +{ + createSimpleTestTree(); + + // SetNeedsFullTreeSync / SetNeedsCommit should not be called because its the same child + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(0); + parent_->ReplaceChild(m_child2.get(), m_child2); + + verifyTestTreeInitialState(); +} + +TEST_F(LayerTest, removeAllChildren) +{ + createSimpleTestTree(); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(3), parent_->RemoveAllChildren()); + + ASSERT_EQ(0U, parent_->children().size()); + EXPECT_FALSE(m_child1->parent()); + EXPECT_FALSE(m_child2->parent()); + EXPECT_FALSE(m_child3->parent()); +} + +TEST_F(LayerTest, setChildren) +{ + scoped_refptr<Layer> oldParent = Layer::Create(); + scoped_refptr<Layer> newParent = Layer::Create(); + + scoped_refptr<Layer> child1 = Layer::Create(); + scoped_refptr<Layer> child2 = Layer::Create(); + + std::vector<scoped_refptr<Layer> > newChildren; + newChildren.push_back(child1); + newChildren.push_back(child2); + + // Set up and verify initial test conditions: child1 has a parent, child2 has no parent. + oldParent->AddChild(child1); + ASSERT_EQ(0U, newParent->children().size()); + EXPECT_EQ(oldParent.get(), child1->parent()); + EXPECT_FALSE(child2->parent()); + + newParent->SetLayerTreeHost(layer_tree_host_.get()); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(AtLeast(1), newParent->SetChildren(newChildren)); + + ASSERT_EQ(2U, newParent->children().size()); + EXPECT_EQ(newParent.get(), child1->parent()); + EXPECT_EQ(newParent.get(), child2->parent()); + + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AtLeast(1)); +} + +TEST_F(LayerTest, getRootLayerAfterTreeManipulations) +{ + createSimpleTestTree(); + + // For this test we don't care about SetNeedsFullTreeSync calls. + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times(AnyNumber()); + + scoped_refptr<Layer> child4 = Layer::Create(); + + EXPECT_EQ(parent_.get(), parent_->RootLayer()); + EXPECT_EQ(parent_.get(), m_child1->RootLayer()); + EXPECT_EQ(parent_.get(), m_child2->RootLayer()); + EXPECT_EQ(parent_.get(), m_child3->RootLayer()); + EXPECT_EQ(child4.get(), child4->RootLayer()); + EXPECT_EQ(parent_.get(), m_grandChild1->RootLayer()); + EXPECT_EQ(parent_.get(), m_grandChild2->RootLayer()); + EXPECT_EQ(parent_.get(), m_grandChild3->RootLayer()); + + m_child1->RemoveFromParent(); + + // child1 and its children, grandChild1 and grandChild2 are now on a separate subtree. + EXPECT_EQ(parent_.get(), parent_->RootLayer()); + EXPECT_EQ(m_child1.get(), m_child1->RootLayer()); + EXPECT_EQ(parent_.get(), m_child2->RootLayer()); + EXPECT_EQ(parent_.get(), m_child3->RootLayer()); + EXPECT_EQ(child4.get(), child4->RootLayer()); + EXPECT_EQ(m_child1.get(), m_grandChild1->RootLayer()); + EXPECT_EQ(m_child1.get(), m_grandChild2->RootLayer()); + EXPECT_EQ(parent_.get(), m_grandChild3->RootLayer()); + + m_grandChild3->AddChild(child4); + + EXPECT_EQ(parent_.get(), parent_->RootLayer()); + EXPECT_EQ(m_child1.get(), m_child1->RootLayer()); + EXPECT_EQ(parent_.get(), m_child2->RootLayer()); + EXPECT_EQ(parent_.get(), m_child3->RootLayer()); + EXPECT_EQ(parent_.get(), child4->RootLayer()); + EXPECT_EQ(m_child1.get(), m_grandChild1->RootLayer()); + EXPECT_EQ(m_child1.get(), m_grandChild2->RootLayer()); + EXPECT_EQ(parent_.get(), m_grandChild3->RootLayer()); + + m_child2->ReplaceChild(m_grandChild3.get(), m_child1); + + // grandChild3 gets orphaned and the child1 subtree gets planted back into the tree under child2. + EXPECT_EQ(parent_.get(), parent_->RootLayer()); + EXPECT_EQ(parent_.get(), m_child1->RootLayer()); + EXPECT_EQ(parent_.get(), m_child2->RootLayer()); + EXPECT_EQ(parent_.get(), m_child3->RootLayer()); + EXPECT_EQ(m_grandChild3.get(), child4->RootLayer()); + EXPECT_EQ(parent_.get(), m_grandChild1->RootLayer()); + EXPECT_EQ(parent_.get(), m_grandChild2->RootLayer()); + EXPECT_EQ(m_grandChild3.get(), m_grandChild3->RootLayer()); +} + +TEST_F(LayerTest, checkSetNeedsDisplayCausesCorrectBehavior) +{ + // The semantics for setNeedsDisplay which are tested here: + // 1. sets needsDisplay flag appropriately. + // 2. indirectly calls SetNeedsCommit, exactly once for each call to setNeedsDisplay. + + scoped_refptr<Layer> testLayer = Layer::Create(); + testLayer->SetLayerTreeHost(layer_tree_host_.get()); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetIsDrawable(true)); + + gfx::Size testBounds = gfx::Size(501, 508); + + gfx::RectF dirty1 = gfx::RectF(10, 15, 1, 2); + gfx::RectF dirty2 = gfx::RectF(20, 25, 3, 4); + gfx::RectF emptyDirtyRect = gfx::RectF(40, 45, 0, 0); + gfx::RectF outOfBoundsDirtyRect = gfx::RectF(400, 405, 500, 502); + + // Before anything, testLayer should not be dirty. + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + + // This is just initialization, but SetNeedsCommit behavior is verified anyway to avoid warnings. + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetBounds(testBounds)); + EXPECT_TRUE(testLayer->NeedsDisplayForTesting()); + + // The real test begins here. + testLayer->ResetNeedsDisplayForTesting(); + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + + // Case 1: Layer should accept dirty rects that go beyond its bounds. + testLayer->ResetNeedsDisplayForTesting(); + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetNeedsDisplayRect(outOfBoundsDirtyRect)); + EXPECT_TRUE(testLayer->NeedsDisplayForTesting()); + testLayer->ResetNeedsDisplayForTesting(); + + // Case 2: SetNeedsDisplay() without the dirty rect arg. + testLayer->ResetNeedsDisplayForTesting(); + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetNeedsDisplay()); + EXPECT_TRUE(testLayer->NeedsDisplayForTesting()); + testLayer->ResetNeedsDisplayForTesting(); + + // Case 3: SetNeedsDisplay() with a non-drawable layer + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetIsDrawable(false)); + testLayer->ResetNeedsDisplayForTesting(); + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + EXPECT_SET_NEEDS_COMMIT(0, testLayer->SetNeedsDisplayRect(dirty1)); + EXPECT_TRUE(testLayer->NeedsDisplayForTesting()); +} + +TEST_F(LayerTest, checkPropertyChangeCausesCorrectBehavior) +{ + scoped_refptr<Layer> testLayer = Layer::Create(); + testLayer->SetLayerTreeHost(layer_tree_host_.get()); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetIsDrawable(true)); + + scoped_refptr<Layer> dummyLayer1 = Layer::Create(); // just a dummy layer for this test case. + scoped_refptr<Layer> dummyLayer2 = Layer::Create(); // just a dummy layer for this test case. + + // sanity check of initial test condition + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + + // Next, test properties that should call SetNeedsCommit (but not setNeedsDisplay) + // All properties need to be set to new values in order for SetNeedsCommit to be called. + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetAnchorPoint(gfx::PointF(1.23f, 4.56f))); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetAnchorPointZ(0.7f)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetBackgroundColor(SK_ColorLTGRAY)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetMasksToBounds(true)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetOpacity(0.5)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetContentsOpaque(true)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetPosition(gfx::PointF(4, 9))); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetSublayerTransform(gfx::Transform(0.0, 0.0, 0.0, 0.0, 0.0, 0.0))); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetScrollable(true)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetScrollOffset(gfx::Vector2d(10, 10))); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetShouldScrollOnMainThread(true)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetNonFastScrollableRegion(gfx::Rect(1, 1, 2, 2))); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetHaveWheelEventHandlers(true)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetTransform(gfx::Transform(0.0, 0.0, 0.0, 0.0, 0.0, 0.0))); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetDoubleSided(false)); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetDebugName("Test Layer")); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetDrawCheckerboardForMissingTiles(!testLayer->DrawCheckerboardForMissingTiles())); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetForceRenderSurface(true)); + + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, testLayer->SetMaskLayer(dummyLayer1.get())); + EXPECT_SET_NEEDS_FULL_TREE_SYNC(1, testLayer->SetReplicaLayer(dummyLayer2.get())); + + // The above tests should not have caused a change to the needsDisplay flag. + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + + // As layers are removed from the tree, they will cause a tree sync. + EXPECT_CALL(*layer_tree_host_, SetNeedsFullTreeSync()).Times((AnyNumber())); +} + +TEST_F(LayerTest, setBoundsTriggersSetNeedsRedrawAfterGettingNonEmptyBounds) +{ + scoped_refptr<Layer> testLayer = Layer::Create(); + testLayer->SetLayerTreeHost(layer_tree_host_.get()); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetIsDrawable(true)); + + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetBounds(gfx::Size(0, 10))); + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetBounds(gfx::Size(10, 10))); + EXPECT_TRUE(testLayer->NeedsDisplayForTesting()); + + testLayer->ResetNeedsDisplayForTesting(); + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); + + // Calling setBounds only invalidates on the first time. + EXPECT_SET_NEEDS_COMMIT(1, testLayer->SetBounds(gfx::Size(7, 10))); + EXPECT_FALSE(testLayer->NeedsDisplayForTesting()); +} + +TEST_F(LayerTest, verifyPushPropertiesAccumulatesUpdateRect) +{ + scoped_refptr<Layer> testLayer = Layer::Create(); + scoped_ptr<LayerImpl> implLayer = LayerImpl::Create(m_hostImpl.active_tree(), 1); + + testLayer->SetNeedsDisplayRect(gfx::RectF(gfx::PointF(), gfx::SizeF(5, 5))); + testLayer->PushPropertiesTo(implLayer.get()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(gfx::PointF(), gfx::SizeF(5, 5)), implLayer->update_rect()); + + // The LayerImpl's updateRect should be accumulated here, since we did not do anything to clear it. + testLayer->SetNeedsDisplayRect(gfx::RectF(gfx::PointF(10, 10), gfx::SizeF(5, 5))); + testLayer->PushPropertiesTo(implLayer.get()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(gfx::PointF(), gfx::SizeF(15, 15)), implLayer->update_rect()); + + // If we do clear the LayerImpl side, then the next updateRect should be fresh without accumulation. + implLayer->ResetAllChangeTrackingForSubtree(); + testLayer->SetNeedsDisplayRect(gfx::RectF(gfx::PointF(10, 10), gfx::SizeF(5, 5))); + testLayer->PushPropertiesTo(implLayer.get()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(gfx::PointF(10, 10), gfx::SizeF(5, 5)), implLayer->update_rect()); +} + +TEST_F(LayerTest, verifyPushPropertiesCausesSurfacePropertyChangedForTransform) +{ + scoped_refptr<Layer> testLayer = Layer::Create(); + scoped_ptr<LayerImpl> implLayer = LayerImpl::Create(m_hostImpl.active_tree(), 1); + + gfx::Transform transform; + transform.Rotate(45.0); + testLayer->SetTransform(transform); + + EXPECT_FALSE(implLayer->LayerSurfacePropertyChanged()); + + testLayer->PushPropertiesTo(implLayer.get()); + + EXPECT_TRUE(implLayer->LayerSurfacePropertyChanged()); +} + +TEST_F(LayerTest, verifyPushPropertiesCausesSurfacePropertyChangedForOpacity) +{ + scoped_refptr<Layer> testLayer = Layer::Create(); + scoped_ptr<LayerImpl> implLayer = LayerImpl::Create(m_hostImpl.active_tree(), 1); + + testLayer->SetOpacity(0.5); + + EXPECT_FALSE(implLayer->LayerSurfacePropertyChanged()); + + testLayer->PushPropertiesTo(implLayer.get()); + + EXPECT_TRUE(implLayer->LayerSurfacePropertyChanged()); +} + +TEST_F(LayerTest, verifyPushPropertiesDoesNotCauseSurfacePropertyChangedDuringImplOnlyTransformAnimation) +{ + scoped_refptr<Layer> testLayer = Layer::Create(); + scoped_ptr<LayerImpl> implLayer = LayerImpl::Create(m_hostImpl.active_tree(), 1); + + scoped_ptr<AnimationRegistrar> registrar = AnimationRegistrar::create(); + implLayer->layer_animation_controller()->SetAnimationRegistrar(registrar.get()); + + addAnimatedTransformToController(*implLayer->layer_animation_controller(), 1.0, 0, 100); + + gfx::Transform transform; + transform.Rotate(45.0); + testLayer->SetTransform(transform); + + EXPECT_FALSE(implLayer->LayerSurfacePropertyChanged()); + testLayer->PushPropertiesTo(implLayer.get()); + EXPECT_TRUE(implLayer->LayerSurfacePropertyChanged()); + + implLayer->ResetAllChangeTrackingForSubtree(); + addAnimatedTransformToController(*implLayer->layer_animation_controller(), 1.0, 0, 100); + implLayer->layer_animation_controller()->GetAnimation(Animation::Transform)->set_is_impl_only(true); + transform.Rotate(45.0); + testLayer->SetTransform(transform); + + EXPECT_FALSE(implLayer->LayerSurfacePropertyChanged()); + testLayer->PushPropertiesTo(implLayer.get()); + EXPECT_FALSE(implLayer->LayerSurfacePropertyChanged()); +} + +TEST_F(LayerTest, verifyPushPropertiesDoesNotCauseSurfacePropertyChangedDuringImplOnlyOpacityAnimation) +{ + scoped_refptr<Layer> testLayer = Layer::Create(); + scoped_ptr<LayerImpl> implLayer = LayerImpl::Create(m_hostImpl.active_tree(), 1); + + scoped_ptr<AnimationRegistrar> registrar = AnimationRegistrar::create(); + implLayer->layer_animation_controller()->SetAnimationRegistrar(registrar.get()); + + addOpacityTransitionToController(*implLayer->layer_animation_controller(), 1.0, 0.3f, 0.7f, false); + + testLayer->SetOpacity(0.5f); + + EXPECT_FALSE(implLayer->LayerSurfacePropertyChanged()); + testLayer->PushPropertiesTo(implLayer.get()); + EXPECT_TRUE(implLayer->LayerSurfacePropertyChanged()); + + implLayer->ResetAllChangeTrackingForSubtree(); + addOpacityTransitionToController(*implLayer->layer_animation_controller(), 1.0, 0.3f, 0.7f, false); + implLayer->layer_animation_controller()->GetAnimation(Animation::Opacity)->set_is_impl_only(true); + testLayer->SetOpacity(0.75f); + + EXPECT_FALSE(implLayer->LayerSurfacePropertyChanged()); + testLayer->PushPropertiesTo(implLayer.get()); + EXPECT_FALSE(implLayer->LayerSurfacePropertyChanged()); +} + + +TEST_F(LayerTest, maskAndReplicaHasParent) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> child = Layer::Create(); + scoped_refptr<Layer> mask = Layer::Create(); + scoped_refptr<Layer> replica = Layer::Create(); + scoped_refptr<Layer> replicaMask = Layer::Create(); + scoped_refptr<Layer> maskReplacement = Layer::Create(); + scoped_refptr<Layer> replicaReplacement = Layer::Create(); + scoped_refptr<Layer> replicaMaskReplacement = Layer::Create(); + + parent->AddChild(child); + child->SetMaskLayer(mask.get()); + child->SetReplicaLayer(replica.get()); + replica->SetMaskLayer(replicaMask.get()); + + EXPECT_EQ(parent, child->parent()); + EXPECT_EQ(child, mask->parent()); + EXPECT_EQ(child, replica->parent()); + EXPECT_EQ(replica, replicaMask->parent()); + + replica->SetMaskLayer(replicaMaskReplacement.get()); + EXPECT_EQ(NULL, replicaMask->parent()); + EXPECT_EQ(replica, replicaMaskReplacement->parent()); + + child->SetMaskLayer(maskReplacement.get()); + EXPECT_EQ(NULL, mask->parent()); + EXPECT_EQ(child, maskReplacement->parent()); + + child->SetReplicaLayer(replicaReplacement.get()); + EXPECT_EQ(NULL, replica->parent()); + EXPECT_EQ(child, replicaReplacement->parent()); + + EXPECT_EQ(replica, replica->mask_layer()->parent()); +} + +class FakeLayerImplTreeHost : public LayerTreeHost { +public: + static scoped_ptr<FakeLayerImplTreeHost> Create() + { + scoped_ptr<FakeLayerImplTreeHost> host(new FakeLayerImplTreeHost(LayerTreeSettings())); + // The initialize call will fail, since our client doesn't provide a valid GraphicsContext3D, but it doesn't matter in the tests that use this fake so ignore the return value. + host->Initialize(scoped_ptr<Thread>(NULL)); + return host.Pass(); + } + + static scoped_ptr<FakeLayerImplTreeHost> Create(LayerTreeSettings settings) + { + scoped_ptr<FakeLayerImplTreeHost> host(new FakeLayerImplTreeHost(settings)); + host->Initialize(scoped_ptr<Thread>(NULL)); + return host.Pass(); + } + +private: + FakeLayerImplTreeHost(const LayerTreeSettings& settings) + : LayerTreeHost(&m_client, settings) + { + } + + FakeLayerImplTreeHostClient m_client; +}; + +void assertLayerTreeHostMatchesForSubtree(Layer* layer, LayerTreeHost* host) +{ + EXPECT_EQ(host, layer->layer_tree_host()); + + for (size_t i = 0; i < layer->children().size(); ++i) + assertLayerTreeHostMatchesForSubtree(layer->children()[i].get(), host); + + if (layer->mask_layer()) + assertLayerTreeHostMatchesForSubtree(layer->mask_layer(), host); + + if (layer->replica_layer()) + assertLayerTreeHostMatchesForSubtree(layer->replica_layer(), host); +} + +TEST(LayerLayerTreeHostTest, enteringTree) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> child = Layer::Create(); + scoped_refptr<Layer> mask = Layer::Create(); + scoped_refptr<Layer> replica = Layer::Create(); + scoped_refptr<Layer> replicaMask = Layer::Create(); + + // Set up a detached tree of layers. The host pointer should be nil for these layers. + parent->AddChild(child); + child->SetMaskLayer(mask.get()); + child->SetReplicaLayer(replica.get()); + replica->SetMaskLayer(replicaMask.get()); + + assertLayerTreeHostMatchesForSubtree(parent.get(), 0); + + scoped_ptr<FakeLayerImplTreeHost> layerTreeHost(FakeLayerImplTreeHost::Create()); + // Setting the root layer should set the host pointer for all layers in the tree. + layerTreeHost->SetRootLayer(parent.get()); + + assertLayerTreeHostMatchesForSubtree(parent.get(), layerTreeHost.get()); + + // Clearing the root layer should also clear out the host pointers for all layers in the tree. + layerTreeHost->SetRootLayer(NULL); + + assertLayerTreeHostMatchesForSubtree(parent.get(), 0); +} + +TEST(LayerLayerTreeHostTest, addingLayerSubtree) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_ptr<FakeLayerImplTreeHost> layerTreeHost(FakeLayerImplTreeHost::Create()); + + layerTreeHost->SetRootLayer(parent.get()); + + EXPECT_EQ(parent->layer_tree_host(), layerTreeHost.get()); + + // Adding a subtree to a layer already associated with a host should set the host pointer on all layers in that subtree. + scoped_refptr<Layer> child = Layer::Create(); + scoped_refptr<Layer> grandChild = Layer::Create(); + child->AddChild(grandChild); + + // Masks, replicas, and replica masks should pick up the new host too. + scoped_refptr<Layer> childMask = Layer::Create(); + child->SetMaskLayer(childMask.get()); + scoped_refptr<Layer> childReplica = Layer::Create(); + child->SetReplicaLayer(childReplica.get()); + scoped_refptr<Layer> childReplicaMask = Layer::Create(); + childReplica->SetMaskLayer(childReplicaMask.get()); + + parent->AddChild(child); + assertLayerTreeHostMatchesForSubtree(parent.get(), layerTreeHost.get()); + + layerTreeHost->SetRootLayer(NULL); +} + +TEST(LayerLayerTreeHostTest, changeHost) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> child = Layer::Create(); + scoped_refptr<Layer> mask = Layer::Create(); + scoped_refptr<Layer> replica = Layer::Create(); + scoped_refptr<Layer> replicaMask = Layer::Create(); + + // Same setup as the previous test. + parent->AddChild(child); + child->SetMaskLayer(mask.get()); + child->SetReplicaLayer(replica.get()); + replica->SetMaskLayer(replicaMask.get()); + + scoped_ptr<FakeLayerImplTreeHost> firstLayerTreeHost(FakeLayerImplTreeHost::Create()); + firstLayerTreeHost->SetRootLayer(parent.get()); + + assertLayerTreeHostMatchesForSubtree(parent.get(), firstLayerTreeHost.get()); + + // Now re-root the tree to a new host (simulating what we do on a context lost event). + // This should update the host pointers for all layers in the tree. + scoped_ptr<FakeLayerImplTreeHost> secondLayerTreeHost(FakeLayerImplTreeHost::Create()); + secondLayerTreeHost->SetRootLayer(parent.get()); + + assertLayerTreeHostMatchesForSubtree(parent.get(), secondLayerTreeHost.get()); + + secondLayerTreeHost->SetRootLayer(NULL); +} + +TEST(LayerLayerTreeHostTest, changeHostInSubtree) +{ + scoped_refptr<Layer> firstParent = Layer::Create(); + scoped_refptr<Layer> firstChild = Layer::Create(); + scoped_refptr<Layer> secondParent = Layer::Create(); + scoped_refptr<Layer> secondChild = Layer::Create(); + scoped_refptr<Layer> secondGrandChild = Layer::Create(); + + // First put all children under the first parent and set the first host. + firstParent->AddChild(firstChild); + secondChild->AddChild(secondGrandChild); + firstParent->AddChild(secondChild); + + scoped_ptr<FakeLayerImplTreeHost> firstLayerTreeHost(FakeLayerImplTreeHost::Create()); + firstLayerTreeHost->SetRootLayer(firstParent.get()); + + assertLayerTreeHostMatchesForSubtree(firstParent.get(), firstLayerTreeHost.get()); + + // Now reparent the subtree starting at secondChild to a layer in a different tree. + scoped_ptr<FakeLayerImplTreeHost> secondLayerTreeHost(FakeLayerImplTreeHost::Create()); + secondLayerTreeHost->SetRootLayer(secondParent.get()); + + secondParent->AddChild(secondChild); + + // The moved layer and its children should point to the new host. + EXPECT_EQ(secondLayerTreeHost.get(), secondChild->layer_tree_host()); + EXPECT_EQ(secondLayerTreeHost.get(), secondGrandChild->layer_tree_host()); + + // Test over, cleanup time. + firstLayerTreeHost->SetRootLayer(NULL); + secondLayerTreeHost->SetRootLayer(NULL); +} + +TEST(LayerLayerTreeHostTest, replaceMaskAndReplicaLayer) +{ + scoped_refptr<Layer> parent = Layer::Create(); + scoped_refptr<Layer> mask = Layer::Create(); + scoped_refptr<Layer> replica = Layer::Create(); + scoped_refptr<Layer> maskChild = Layer::Create(); + scoped_refptr<Layer> replicaChild = Layer::Create(); + scoped_refptr<Layer> maskReplacement = Layer::Create(); + scoped_refptr<Layer> replicaReplacement = Layer::Create(); + + parent->SetMaskLayer(mask.get()); + parent->SetReplicaLayer(replica.get()); + mask->AddChild(maskChild); + replica->AddChild(replicaChild); + + scoped_ptr<FakeLayerImplTreeHost> layerTreeHost(FakeLayerImplTreeHost::Create()); + layerTreeHost->SetRootLayer(parent.get()); + + assertLayerTreeHostMatchesForSubtree(parent.get(), layerTreeHost.get()); + + // Replacing the mask should clear out the old mask's subtree's host pointers. + parent->SetMaskLayer(maskReplacement.get()); + EXPECT_EQ(0, mask->layer_tree_host()); + EXPECT_EQ(0, maskChild->layer_tree_host()); + + // Same for replacing a replica layer. + parent->SetReplicaLayer(replicaReplacement.get()); + EXPECT_EQ(0, replica->layer_tree_host()); + EXPECT_EQ(0, replicaChild->layer_tree_host()); + + // Test over, cleanup time. + layerTreeHost->SetRootLayer(NULL); +} + +TEST(LayerLayerTreeHostTest, destroyHostWithNonNullRootLayer) +{ + scoped_refptr<Layer> root = Layer::Create(); + scoped_refptr<Layer> child = Layer::Create(); + root->AddChild(child); + scoped_ptr<FakeLayerImplTreeHost> layerTreeHost(FakeLayerImplTreeHost::Create()); + layerTreeHost->SetRootLayer(root); +} + +static bool addTestAnimation(Layer* layer) +{ + scoped_ptr<KeyframedFloatAnimationCurve> curve(KeyframedFloatAnimationCurve::Create()); + curve->AddKeyframe(FloatKeyframe::Create(0.0, 0.3f, scoped_ptr<TimingFunction>())); + curve->AddKeyframe(FloatKeyframe::Create(1.0, 0.7f, scoped_ptr<TimingFunction>())); + scoped_ptr<Animation> animation(Animation::Create(curve.PassAs<AnimationCurve>(), 0, 0, Animation::Opacity)); + + return layer->AddAnimation(animation.Pass()); +} + +TEST(LayerLayerTreeHostTest, shouldNotAddAnimationWithoutAnimationRegistrar) +{ + scoped_refptr<Layer> layer = Layer::Create(); + + // Case 1: without a LayerTreeHost and without an AnimationRegistrar, the + // animation should not be accepted. + EXPECT_FALSE(addTestAnimation(layer.get())); + + scoped_ptr<AnimationRegistrar> registrar = AnimationRegistrar::create(); + layer->layer_animation_controller()->SetAnimationRegistrar(registrar.get()); + + // Case 2: with an AnimationRegistrar, the animation should be accepted. + EXPECT_TRUE(addTestAnimation(layer.get())); + + LayerTreeSettings settings; + settings.acceleratedAnimationEnabled = false; + scoped_ptr<FakeLayerImplTreeHost> layerTreeHost(FakeLayerImplTreeHost::Create(settings)); + layerTreeHost->SetRootLayer(layer); + layer->SetLayerTreeHost(layerTreeHost.get()); + assertLayerTreeHostMatchesForSubtree(layer.get(), layerTreeHost.get()); + + // Case 3: with a LayerTreeHost where accelerated animation is disabled, the + // animation should be rejected. + EXPECT_FALSE(addTestAnimation(layer.get())); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/nine_patch_layer.cc b/cc/layers/nine_patch_layer.cc new file mode 100644 index 0000000..b239222 --- /dev/null +++ b/cc/layers/nine_patch_layer.cc @@ -0,0 +1,116 @@ +// Copyright 2012 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 "cc/layers/nine_patch_layer.h" + +#include "cc/layers/nine_patch_layer_impl.h" +#include "cc/resources/prioritized_resource.h" +#include "cc/resources/resource_update.h" +#include "cc/resources/resource_update_queue.h" +#include "cc/trees/layer_tree_host.h" + +namespace cc { + +scoped_refptr<NinePatchLayer> NinePatchLayer::Create() { + return make_scoped_refptr(new NinePatchLayer()); +} + +NinePatchLayer::NinePatchLayer() + : bitmap_dirty_(false) {} + +NinePatchLayer::~NinePatchLayer() {} + +scoped_ptr<LayerImpl> NinePatchLayer::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return NinePatchLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void NinePatchLayer::SetTexturePriorities( + const PriorityCalculator& priority_calc) { + if (resource_ && !resource_->texture()->resourceManager()) { + // Release the resource here, as it is no longer tied to a resource manager. + resource_.reset(); + if (!bitmap_.isNull()) + CreateResource(); + } else if (needs_display_ && bitmap_dirty_ && DrawsContent()) { + CreateResource(); + } + + if (resource_) { + resource_->texture()->setRequestPriority( + PriorityCalculator::UIPriority(true)); + // FIXME: Need to support swizzle in the shader for + // !PlatformColor::sameComponentOrder(texture_format) + GLenum texture_format = + layer_tree_host()->GetRendererCapabilities().best_texture_format; + resource_->texture()->setDimensions( + gfx::Size(bitmap_.width(), bitmap_.height()), texture_format); + } +} + +void NinePatchLayer::SetBitmap(const SkBitmap& bitmap, gfx::Rect aperture) { + bitmap_ = bitmap; + image_aperture_ = aperture; + bitmap_dirty_ = true; + SetNeedsDisplay(); +} + +void NinePatchLayer::Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) { + CreateUpdaterIfNeeded(); + + if (resource_ && (bitmap_dirty_ || resource_->texture()->resourceId() == 0)) { + gfx::Rect contentRect(gfx::Point(), + gfx::Size(bitmap_.width(), bitmap_.height())); + ResourceUpdate upload = ResourceUpdate::Create(resource_->texture(), + &bitmap_, + contentRect, + contentRect, + gfx::Vector2d()); + queue->appendFullUpload(upload); + bitmap_dirty_ = false; + } +} + +void NinePatchLayer::CreateUpdaterIfNeeded() { + if (updater_) + return; + + updater_ = ImageLayerUpdater::Create(); +} + +void NinePatchLayer::CreateResource() { + DCHECK(!bitmap_.isNull()); + CreateUpdaterIfNeeded(); + updater_->set_bitmap(bitmap_); + needs_display_ = false; + + if (!resource_) { + resource_ = updater_->CreateResource( + layer_tree_host()->contents_texture_manager()); + } +} + +bool NinePatchLayer::DrawsContent() const { + bool draws = !bitmap_.isNull() && + Layer::DrawsContent() && + bitmap_.width() && + bitmap_.height(); + return draws; +} + +void NinePatchLayer::PushPropertiesTo(LayerImpl* layer) { + Layer::PushPropertiesTo(layer); + NinePatchLayerImpl* layer_impl = static_cast<NinePatchLayerImpl*>(layer); + + if (resource_) { + DCHECK(!bitmap_.isNull()); + layer_impl->SetResourceId(resource_->texture()->resourceId()); + layer_impl->SetLayout( + gfx::Size(bitmap_.width(), bitmap_.height()), image_aperture_); + } +} + +} diff --git a/cc/layers/nine_patch_layer.h b/cc/layers/nine_patch_layer.h new file mode 100644 index 0000000..21b8cb9 --- /dev/null +++ b/cc/layers/nine_patch_layer.h @@ -0,0 +1,61 @@ +// Copyright 2012 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 CC_LAYERS_NINE_PATCH_LAYER_H_ +#define CC_LAYERS_NINE_PATCH_LAYER_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/layers/layer.h" +#include "cc/resources/image_layer_updater.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/rect.h" + +namespace cc { + +class ResourceUpdateQueue; + +class CC_EXPORT NinePatchLayer : public Layer { + public: + static scoped_refptr<NinePatchLayer> Create(); + + virtual bool DrawsContent() const OVERRIDE; + virtual void SetTexturePriorities(const PriorityCalculator& priority_calc) + OVERRIDE; + virtual void Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + + // aperture is in the pixel space of the bitmap resource and refers to + // the center patch of the ninepatch (which is unused in this + // implementation). We split off eight rects surrounding it and stick them + // on the edges of the layer. The corners are unscaled, the top and bottom + // rects are x-stretched to fit, and the left and right rects are + // y-stretched to fit. + void SetBitmap(const SkBitmap& bitmap, gfx::Rect aperture); + + private: + NinePatchLayer(); + virtual ~NinePatchLayer(); + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + + void CreateUpdaterIfNeeded(); + void CreateResource(); + + scoped_refptr<ImageLayerUpdater> updater_; + scoped_ptr<LayerUpdater::Resource> resource_; + + SkBitmap bitmap_; + bool bitmap_dirty_; + + // The transparent center region that shows the parent layer's contents in + // image space. + gfx::Rect image_aperture_; +}; + +} // namespace cc + +#endif // CC_LAYERS_NINE_PATCH_LAYER_H_ diff --git a/cc/layers/nine_patch_layer_impl.cc b/cc/layers/nine_patch_layer_impl.cc new file mode 100644 index 0000000..53daedc --- /dev/null +++ b/cc/layers/nine_patch_layer_impl.cc @@ -0,0 +1,300 @@ +// Copyright 2012 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 "nine_patch_layer_impl.h" + +#include "base/stringprintf.h" +#include "base/values.h" +#include "cc/layers/quad_sink.h" +#include "cc/quads/texture_draw_quad.h" +#include "ui/gfx/rect_f.h" + +namespace cc { + +NinePatchLayerImpl::NinePatchLayerImpl(LayerTreeImpl* tree_impl, int id) + : LayerImpl(tree_impl, id), + resource_id_(0) {} + +NinePatchLayerImpl::~NinePatchLayerImpl() {} + +ResourceProvider::ResourceId NinePatchLayerImpl::ContentsResourceId() const { + return 0; +} + +scoped_ptr<LayerImpl> NinePatchLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return NinePatchLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void NinePatchLayerImpl::PushPropertiesTo(LayerImpl* layer) { + LayerImpl::PushPropertiesTo(layer); + NinePatchLayerImpl* layer_impl = static_cast<NinePatchLayerImpl*>(layer); + + if (!resource_id_) + return; + + layer_impl->SetResourceId(resource_id_); + layer_impl->SetLayout(image_bounds_, image_aperture_); +} + +void NinePatchLayerImpl::WillDraw(ResourceProvider* resource_provider) {} + +static gfx::RectF NormalizedRect(float x, + float y, + float width, + float height, + float total_width, + float total_height) { + return gfx::RectF(x / total_width, + y / total_height, + width / total_width, + height / total_height); +} + +void NinePatchLayerImpl::SetLayout(gfx::Size image_bounds, gfx::Rect aperture) { + image_bounds_ = image_bounds; + image_aperture_ = aperture; +} + +void NinePatchLayerImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + if (!resource_id_) + return; + + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(CreateSharedQuadState()); + AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data); + + static const bool flipped = false; + static const bool premultiplied_alpha = true; + + DCHECK(!bounds().IsEmpty()); + + // NinePatch border widths in bitmap pixel space + int left_width = image_aperture_.x(); + int top_height = image_aperture_.y(); + int right_width = image_bounds_.width() - image_aperture_.right(); + int bottom_height = image_bounds_.height() - image_aperture_.bottom(); + + // If layer can't fit the corners, clip to show the outer edges of the + // image. + int corner_total_width = left_width + right_width; + int middle_width = bounds().width() - corner_total_width; + if (middle_width < 0) { + float left_width_proportion = + static_cast<float>(left_width) / corner_total_width; + int left_width_crop = middle_width * left_width_proportion; + left_width += left_width_crop; + right_width = bounds().width() - left_width; + middle_width = 0; + } + int corner_total_height = top_height + bottom_height; + int middle_height = bounds().height() - corner_total_height; + if (middle_height < 0) { + float top_height_proportion = + static_cast<float>(top_height) / corner_total_height; + int top_height_crop = middle_height * top_height_proportion; + top_height += top_height_crop; + bottom_height = bounds().height() - top_height; + middle_height = 0; + } + + // Patch positions in layer space + gfx::Rect top_left(0, 0, left_width, top_height); + gfx::Rect topRight( + bounds().width() - right_width, 0, right_width, top_height); + gfx::Rect bottomLeft( + 0, bounds().height() - bottom_height, left_width, bottom_height); + gfx::Rect bottomRight( + topRight.x(), bottomLeft.y(), right_width, bottom_height); + gfx::Rect top(top_left.right(), 0, middle_width, top_height); + gfx::Rect left(0, top_left.bottom(), left_width, middle_height); + gfx::Rect right(topRight.x(), topRight.bottom(), right_width, left.height()); + gfx::Rect bottom(top.x(), bottomLeft.y(), top.width(), bottom_height); + + float img_width = image_bounds_.width(); + float img_height = image_bounds_.height(); + + // Patch positions in bitmap UV space (from zero to one) + gfx::RectF uv_top_left = NormalizedRect(0, + 0, + left_width, + top_height, + img_width, + img_height); + gfx::RectF uv_top_right = NormalizedRect(img_width - right_width, + 0, + right_width, + top_height, + img_width, + img_height); + gfx::RectF uv_bottom_left = NormalizedRect(0, + img_height - bottom_height, + left_width, + bottom_height, + img_width, + img_height); + gfx::RectF uv_bottom_right = NormalizedRect(img_width - right_width, + img_height - bottom_height, + right_width, + bottom_height, + img_width, + img_height); + gfx::RectF uvTop(uv_top_left.right(), + 0, + (img_width - left_width - right_width) / img_width, + (top_height) / img_height); + gfx::RectF uvLeft(0, + uv_top_left.bottom(), + left_width / img_width, + (img_height - top_height - bottom_height) / img_height); + gfx::RectF uvRight(uv_top_right.x(), + uv_top_right.bottom(), + right_width / img_width, + uvLeft.height()); + gfx::RectF uvBottom(uvTop.x(), + uv_bottom_left.y(), + uvTop.width(), + bottom_height / img_height); + + // Nothing is opaque here. + // TODO(danakj): Should we look at the SkBitmaps to determine opaqueness? + gfx::Rect opaque_rect; + const float vertex_opacity[] = {1.0f, 1.0f, 1.0f, 1.0f}; + scoped_ptr<TextureDrawQuad> quad; + + quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + top_left, + opaque_rect, + resource_id_, + premultiplied_alpha, + uv_top_left.origin(), + uv_top_left.bottom_right(), + vertex_opacity, flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + + quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + topRight, + opaque_rect, + resource_id_, + premultiplied_alpha, + uv_top_right.origin(), + uv_top_right.bottom_right(), + vertex_opacity, flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + + quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + bottomLeft, + opaque_rect, + resource_id_, + premultiplied_alpha, + uv_bottom_left.origin(), + uv_bottom_left.bottom_right(), + vertex_opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + + quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + bottomRight, + opaque_rect, + resource_id_, + premultiplied_alpha, + uv_bottom_right.origin(), + uv_bottom_right.bottom_right(), + vertex_opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + + quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + top, + opaque_rect, + resource_id_, + premultiplied_alpha, + uvTop.origin(), + uvTop.bottom_right(), + vertex_opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + + quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + left, + opaque_rect, + resource_id_, + premultiplied_alpha, + uvLeft.origin(), + uvLeft.bottom_right(), + vertex_opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + + quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + right, + opaque_rect, + resource_id_, + premultiplied_alpha, + uvRight.origin(), + uvRight.bottom_right(), + vertex_opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + + quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + bottom, + opaque_rect, + resource_id_, + premultiplied_alpha, + uvBottom.origin(), + uvBottom.bottom_right(), + vertex_opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); +} + +void NinePatchLayerImpl::DidDraw(ResourceProvider* resource_provider) {} + +void NinePatchLayerImpl::DidLoseOutputSurface() { + resource_id_ = 0; +} + +const char* NinePatchLayerImpl::LayerTypeAsString() const { + return "NinePatchLayer"; +} + +void NinePatchLayerImpl::DumpLayerProperties(std::string* str, int indent) + const { + str->append(IndentString(indent)); + base::StringAppendF( + str, "imageAperture: %s\n", image_aperture_.ToString().c_str()); + base::StringAppendF( + str, "image_bounds: %s\n", image_bounds_.ToString().c_str()); + LayerImpl::DumpLayerProperties(str, indent); +} + +base::DictionaryValue* NinePatchLayerImpl::LayerTreeAsJson() const { + base::DictionaryValue* result = LayerImpl::LayerTreeAsJson(); + + base::ListValue* list = new base::ListValue; + list->AppendInteger(image_aperture_.origin().x()); + list->AppendInteger(image_aperture_.origin().y()); + list->AppendInteger(image_aperture_.size().width()); + list->AppendInteger(image_aperture_.size().height()); + result->Set("ImageAperture", list); + + list = new base::ListValue; + list->AppendInteger(image_bounds_.width()); + list->AppendInteger(image_bounds_.height()); + result->Set("ImageBounds", list); + + return result; +} + +} // namespace cc diff --git a/cc/layers/nine_patch_layer_impl.h b/cc/layers/nine_patch_layer_impl.h new file mode 100644 index 0000000..7091f00 --- /dev/null +++ b/cc/layers/nine_patch_layer_impl.h @@ -0,0 +1,63 @@ +// Copyright 2012 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 CC_LAYERS_NINE_PATCH_LAYER_IMPL_H_ +#define CC_LAYERS_NINE_PATCH_LAYER_IMPL_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/layer_impl.h" +#include "cc/resources/resource_provider.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/size.h" + +namespace base { +class DictionaryValue; +} + +namespace cc { + +class CC_EXPORT NinePatchLayerImpl : public LayerImpl { + public: + static scoped_ptr<NinePatchLayerImpl> Create(LayerTreeImpl* tree_impl, + int id) { + return make_scoped_ptr(new NinePatchLayerImpl(tree_impl, id)); + } + virtual ~NinePatchLayerImpl(); + + void SetResourceId(unsigned id) { resource_id_ = id; } + void SetLayout(gfx::Size image_bounds, gfx::Rect aperture); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + + virtual void WillDraw(ResourceProvider* resource_provider) OVERRIDE; + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + virtual void DidDraw(ResourceProvider* FIXMENAME) OVERRIDE; + virtual ResourceProvider::ResourceId ContentsResourceId() const OVERRIDE; + virtual void DumpLayerProperties(std::string* str, int indent) const OVERRIDE; + virtual void DidLoseOutputSurface() OVERRIDE; + + virtual base::DictionaryValue* LayerTreeAsJson() const OVERRIDE; + + protected: + NinePatchLayerImpl(LayerTreeImpl* tree_impl, int id); + + private: + virtual const char* LayerTypeAsString() const OVERRIDE; + + // The size of the NinePatch bitmap in pixels. + gfx::Size image_bounds_; + + // The transparent center region that shows the parent layer's contents in + // image space. + gfx::Rect image_aperture_; + + ResourceProvider::ResourceId resource_id_; +}; + +} // namespace cc + +#endif // CC_LAYERS_NINE_PATCH_LAYER_IMPL_H_ diff --git a/cc/layers/nine_patch_layer_impl_unittest.cc b/cc/layers/nine_patch_layer_impl_unittest.cc new file mode 100644 index 0000000..b4af387 --- /dev/null +++ b/cc/layers/nine_patch_layer_impl_unittest.cc @@ -0,0 +1,150 @@ +// Copyright 2012 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 <stdio.h> + +#include "cc/layers/append_quads_data.h" +#include "cc/layers/nine_patch_layer_impl.h" +#include "cc/quads/texture_draw_quad.h" +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/geometry_test_utils.h" +#include "cc/test/layer_test_common.h" +#include "cc/test/mock_quad_culler.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect_conversions.h" +#include "ui/gfx/safe_integer_conversions.h" +#include "ui/gfx/transform.h" + +namespace cc { +namespace { + +gfx::Rect ToRoundedIntRect(gfx::RectF rect_f) { + return gfx::Rect(gfx::ToRoundedInt(rect_f.x()), gfx::ToRoundedInt(rect_f.y()), gfx::ToRoundedInt(rect_f.width()), gfx::ToRoundedInt(rect_f.height())); +} + +TEST(NinePatchLayerImplTest, verifyDrawQuads) +{ + // Input is a 100x100 bitmap with a 40x50 aperture at x=20, y=30. + // The bounds of the layer are set to 400x400, so the draw quads + // generated should leave the border width (40) intact. + MockQuadCuller quadCuller; + gfx::Size bitmapSize(100, 100); + gfx::Size layerSize(400, 400); + gfx::Rect visibleContentRect(gfx::Point(), layerSize); + gfx::Rect apertureRect(20, 30, 40, 50); + gfx::Rect scaledApertureNonUniform(20, 30, 340, 350); + + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + scoped_ptr<NinePatchLayerImpl> layer = NinePatchLayerImpl::Create(hostImpl.active_tree(), 1); + layer->draw_properties().visible_content_rect = visibleContentRect; + layer->SetBounds(layerSize); + layer->SetContentBounds(layerSize); + layer->CreateRenderSurface(); + layer->draw_properties().render_target = layer.get(); + layer->SetLayout(bitmapSize, apertureRect); + layer->SetResourceId(1); + + // This scale should not affect the generated quad geometry, but only + // the shared draw transform. + gfx::Transform transform; + transform.Scale(10, 10); + layer->draw_properties().target_space_transform = transform; + + AppendQuadsData data; + layer->AppendQuads(&quadCuller, &data); + + // Verify quad rects + const QuadList& quads = quadCuller.quadList(); + EXPECT_EQ(8, quads.size()); + Region remaining(visibleContentRect); + for (size_t i = 0; i < quads.size(); ++i) { + DrawQuad* quad = quads[i]; + gfx::Rect quadRect = quad->rect; + + EXPECT_TRUE(visibleContentRect.Contains(quadRect)) << i; + EXPECT_TRUE(remaining.Contains(quadRect)) << i; + EXPECT_EQ(transform, quad->quadTransform()); + remaining.Subtract(Region(quadRect)); + } + EXPECT_RECT_EQ(scaledApertureNonUniform, remaining.bounds()); + Region scaledApertureRegion(scaledApertureNonUniform); + EXPECT_EQ(scaledApertureRegion, remaining); + + // Verify UV rects + gfx::Rect bitmapRect(gfx::Point(), bitmapSize); + Region texRemaining(bitmapRect); + for (size_t i = 0; i < quads.size(); ++i) { + DrawQuad* quad = quads[i]; + const TextureDrawQuad* texQuad = TextureDrawQuad::MaterialCast(quad); + gfx::RectF texRect = gfx::BoundingRect(texQuad->uv_top_left, texQuad->uv_bottom_right); + texRect.Scale(bitmapSize.width(), bitmapSize.height()); + texRemaining.Subtract(Region(ToRoundedIntRect(texRect))); + } + EXPECT_RECT_EQ(apertureRect, texRemaining.bounds()); + Region apertureRegion(apertureRect); + EXPECT_EQ(apertureRegion, texRemaining); +} + +TEST(NinePatchLayerImplTest, verifyDrawQuadsForSqueezedLayer) +{ + // Test with a layer much smaller than the bitmap. + MockQuadCuller quadCuller; + gfx::Size bitmapSize(101, 101); + gfx::Size layerSize(51, 51); + gfx::Rect visibleContentRect(gfx::Point(), layerSize); + gfx::Rect apertureRect(20, 30, 40, 45); // rightWidth: 40, botHeight: 25 + + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + scoped_ptr<NinePatchLayerImpl> layer = NinePatchLayerImpl::Create(hostImpl.active_tree(), 1); + layer->draw_properties().visible_content_rect = visibleContentRect; + layer->SetBounds(layerSize); + layer->SetContentBounds(layerSize); + layer->CreateRenderSurface(); + layer->draw_properties().render_target = layer.get(); + layer->SetLayout(bitmapSize, apertureRect); + layer->SetResourceId(1); + + AppendQuadsData data; + layer->AppendQuads(&quadCuller, &data); + + // Verify corner rects fill the layer and don't overlap + const QuadList& quads = quadCuller.quadList(); + EXPECT_EQ(4, quads.size()); + Region filled; + for (size_t i = 0; i < quads.size(); ++i) { + DrawQuad* quad = quads[i]; + gfx::Rect quadRect = quad->rect; + + EXPECT_FALSE(filled.Intersects(quadRect)); + filled.Union(quadRect); + } + Region expectedFull(visibleContentRect); + EXPECT_EQ(expectedFull, filled); + + // Verify UV rects cover the corners of the bitmap and the crop is weighted + // proportionately to the relative corner sizes (for uneven apertures). + gfx::Rect bitmapRect(gfx::Point(), bitmapSize); + Region texRemaining(bitmapRect); + for (size_t i = 0; i < quads.size(); ++i) { + DrawQuad* quad = quads[i]; + const TextureDrawQuad* texQuad = TextureDrawQuad::MaterialCast(quad); + gfx::RectF texRect = gfx::BoundingRect(texQuad->uv_top_left, texQuad->uv_bottom_right); + texRect.Scale(bitmapSize.width(), bitmapSize.height()); + texRemaining.Subtract(Region(ToRoundedIntRect(texRect))); + } + Region expectedRemainingRegion = Region(gfx::Rect(bitmapSize)); + expectedRemainingRegion.Subtract(gfx::Rect(0, 0, 17, 28)); + expectedRemainingRegion.Subtract(gfx::Rect(67, 0, 34, 28)); + expectedRemainingRegion.Subtract(gfx::Rect(0, 78, 17, 23)); + expectedRemainingRegion.Subtract(gfx::Rect(67, 78, 34, 23)); + EXPECT_EQ(expectedRemainingRegion, texRemaining); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/nine_patch_layer_unittest.cc b/cc/layers/nine_patch_layer_unittest.cc new file mode 100644 index 0000000..f9c00aa --- /dev/null +++ b/cc/layers/nine_patch_layer_unittest.cc @@ -0,0 +1,148 @@ +// Copyright 2012 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 "cc/layers/nine_patch_layer.h" + +#include "cc/debug/overdraw_metrics.h" +#include "cc/resources/prioritized_resource_manager.h" +#include "cc/resources/resource_provider.h" +#include "cc/resources/resource_update_queue.h" +#include "cc/scheduler/texture_uploader.h" +#include "cc/test/fake_layer_tree_host_client.h" +#include "cc/test/fake_output_surface.h" +#include "cc/test/geometry_test_utils.h" +#include "cc/test/layer_tree_test_common.h" +#include "cc/trees/layer_tree_host.h" +#include "cc/trees/occlusion_tracker.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" + +using ::testing::Mock; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::AnyNumber; + +namespace cc { +namespace { + +class MockLayerTreeHost : public LayerTreeHost { +public: + MockLayerTreeHost() + : LayerTreeHost(&m_fakeClient, LayerTreeSettings()) + { + Initialize(scoped_ptr<Thread>(NULL)); + } + +private: + FakeLayerImplTreeHostClient m_fakeClient; +}; + + +class NinePatchLayerTest : public testing::Test { +public: + NinePatchLayerTest() + { + } + + Proxy* proxy() const { return layer_tree_host_->proxy(); } + +protected: + virtual void SetUp() + { + layer_tree_host_.reset(new MockLayerTreeHost); + } + + virtual void TearDown() + { + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + } + + scoped_ptr<MockLayerTreeHost> layer_tree_host_; +}; + +TEST_F(NinePatchLayerTest, triggerFullUploadOnceWhenChangingBitmap) +{ + scoped_refptr<NinePatchLayer> testLayer = NinePatchLayer::Create(); + ASSERT_TRUE(testLayer); + testLayer->SetIsDrawable(true); + testLayer->SetBounds(gfx::Size(100, 100)); + + layer_tree_host_->SetRootLayer(testLayer); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + EXPECT_EQ(testLayer->layer_tree_host(), layer_tree_host_.get()); + + layer_tree_host_->InitializeRendererIfNeeded(); + + PriorityCalculator calculator; + ResourceUpdateQueue queue; + OcclusionTracker occlusionTracker(gfx::Rect(), false); + + // No bitmap set should not trigger any uploads. + testLayer->SetTexturePriorities(calculator); + testLayer->Update(&queue, &occlusionTracker, NULL); + EXPECT_EQ(queue.fullUploadSize(), 0); + EXPECT_EQ(queue.partialUploadSize(), 0); + + // Setting a bitmap set should trigger a single full upload. + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, 10, 10); + bitmap.allocPixels(); + testLayer->SetBitmap(bitmap, gfx::Rect(5, 5, 1, 1)); + testLayer->SetTexturePriorities(calculator); + testLayer->Update(&queue, &occlusionTracker, NULL); + EXPECT_EQ(queue.fullUploadSize(), 1); + EXPECT_EQ(queue.partialUploadSize(), 0); + ResourceUpdate params = queue.takeFirstFullUpload(); + EXPECT_TRUE(params.texture != NULL); + + // Upload the texture. + layer_tree_host_->contents_texture_manager()->setMaxMemoryLimitBytes(1024 * 1024); + layer_tree_host_->contents_texture_manager()->prioritizeTextures(); + + scoped_ptr<OutputSurface> outputSurface; + scoped_ptr<ResourceProvider> resourceProvider; + { + DebugScopedSetImplThread implThread(proxy()); + DebugScopedSetMainThreadBlocked mainThreadBlocked(proxy()); + outputSurface = createFakeOutputSurface(); + resourceProvider = ResourceProvider::Create(outputSurface.get()); + params.texture->acquireBackingTexture(resourceProvider.get()); + ASSERT_TRUE(params.texture->haveBackingTexture()); + } + + // Nothing changed, so no repeated upload. + testLayer->SetTexturePriorities(calculator); + testLayer->Update(&queue, &occlusionTracker, NULL); + EXPECT_EQ(queue.fullUploadSize(), 0); + EXPECT_EQ(queue.partialUploadSize(), 0); + + { + DebugScopedSetImplThread implThread(proxy()); + DebugScopedSetMainThreadBlocked mainThreadBlocked(proxy()); + layer_tree_host_->contents_texture_manager()->clearAllMemory(resourceProvider.get()); + } + + // Reupload after eviction + testLayer->SetTexturePriorities(calculator); + testLayer->Update(&queue, &occlusionTracker, NULL); + EXPECT_EQ(queue.fullUploadSize(), 1); + EXPECT_EQ(queue.partialUploadSize(), 0); + + // PrioritizedResourceManager clearing + layer_tree_host_->contents_texture_manager()->unregisterTexture(params.texture); + EXPECT_EQ(NULL, params.texture->resourceManager()); + testLayer->SetTexturePriorities(calculator); + ResourceUpdateQueue queue2; + testLayer->Update(&queue2, &occlusionTracker, NULL); + EXPECT_EQ(queue2.fullUploadSize(), 1); + EXPECT_EQ(queue2.partialUploadSize(), 0); + params = queue2.takeFirstFullUpload(); + EXPECT_TRUE(params.texture != NULL); + EXPECT_EQ(params.texture->resourceManager(), layer_tree_host_->contents_texture_manager()); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/picture_image_layer.cc b/cc/layers/picture_image_layer.cc new file mode 100644 index 0000000..03e3a9e --- /dev/null +++ b/cc/layers/picture_image_layer.cc @@ -0,0 +1,58 @@ +// Copyright 2010 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 "cc/layers/picture_image_layer.h" + +#include "cc/layers/picture_image_layer_impl.h" +#include "third_party/skia/include/core/SkCanvas.h" + +namespace cc { + +scoped_refptr<PictureImageLayer> PictureImageLayer::Create() { + return make_scoped_refptr(new PictureImageLayer()); +} + +PictureImageLayer::PictureImageLayer() : PictureLayer(this) {} + +PictureImageLayer::~PictureImageLayer() { + ClearClient(); +} + +scoped_ptr<LayerImpl> PictureImageLayer::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return PictureImageLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +bool PictureImageLayer::DrawsContent() const { + return !bitmap_.isNull() && PictureLayer::DrawsContent(); +} + +void PictureImageLayer::SetBitmap(const SkBitmap& bitmap) { + // SetBitmap() currently gets called whenever there is any + // style change that affects the layer even if that change doesn't + // affect the actual contents of the image (e.g. a CSS animation). + // With this check in place we avoid unecessary texture uploads. + if (bitmap.pixelRef() && bitmap.pixelRef() == bitmap_.pixelRef()) + return; + + bitmap_ = bitmap; + SetNeedsDisplay(); +} + +void PictureImageLayer::PaintContents(SkCanvas* canvas, + gfx::Rect clip, + gfx::RectF* opaque) { + if (!bitmap_.width() || !bitmap_.height()) + return; + + SkScalar content_to_layer_scale_x = + SkFloatToScalar(static_cast<float>(bounds().width()) / bitmap_.width()); + SkScalar content_to_layer_scale_y = + SkFloatToScalar(static_cast<float>(bounds().height()) / bitmap_.height()); + canvas->scale(content_to_layer_scale_x, content_to_layer_scale_y); + + canvas->drawBitmap(bitmap_, 0, 0); +} + +} // namespace cc diff --git a/cc/layers/picture_image_layer.h b/cc/layers/picture_image_layer.h new file mode 100644 index 0000000..fb4b5be --- /dev/null +++ b/cc/layers/picture_image_layer.h @@ -0,0 +1,42 @@ +// Copyright 2010 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 CC_LAYERS_PICTURE_IMAGE_LAYER_H_ +#define CC_LAYERS_PICTURE_IMAGE_LAYER_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/content_layer_client.h" +#include "cc/layers/picture_layer.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/size.h" + +namespace cc { + +class CC_EXPORT PictureImageLayer : public PictureLayer, ContentLayerClient { + public: + static scoped_refptr<PictureImageLayer> Create(); + + void SetBitmap(const SkBitmap& image); + + // Layer implementation. + virtual scoped_ptr<LayerImpl> CreateLayerImpl( + LayerTreeImpl* tree_impl) OVERRIDE; + virtual bool DrawsContent() const OVERRIDE; + + // ContentLayerClient implementation. + virtual void PaintContents( + SkCanvas* canvas, + gfx::Rect clip, + gfx::RectF* opaque) OVERRIDE; + + private: + PictureImageLayer(); + virtual ~PictureImageLayer(); + + SkBitmap bitmap_; +}; + +} // namespace cc + +#endif // CC_LAYERS_PICTURE_IMAGE_LAYER_H_ diff --git a/cc/layers/picture_image_layer_impl.cc b/cc/layers/picture_image_layer_impl.cc new file mode 100644 index 0000000..9c478f0 --- /dev/null +++ b/cc/layers/picture_image_layer_impl.cc @@ -0,0 +1,45 @@ +// Copyright 2013 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 "cc/layers/picture_image_layer_impl.h" + +#include "cc/debug/debug_colors.h" +#include "cc/trees/layer_tree_impl.h" + +namespace cc { + +PictureImageLayerImpl::PictureImageLayerImpl(LayerTreeImpl* treeImpl, int id) + : PictureLayerImpl(treeImpl, id) { +} + +PictureImageLayerImpl::~PictureImageLayerImpl() { +} + +const char* PictureImageLayerImpl::LayerTypeAsString() const { + return "PictureImageLayer"; +} + +scoped_ptr<LayerImpl> PictureImageLayerImpl::CreateLayerImpl( + LayerTreeImpl* treeImpl) { + return PictureImageLayerImpl::Create(treeImpl, id()).PassAs<LayerImpl>(); +} + +void PictureImageLayerImpl::GetDebugBorderProperties( + SkColor* color, float* width) const { + *color = DebugColors::ImageLayerBorderColor(); + *width = DebugColors::ImageLayerBorderWidth(layer_tree_impl()); +} + +void PictureImageLayerImpl::CalculateRasterContentsScale( + bool animating_transform_to_screen, + float* raster_contents_scale, + float* low_res_raster_contents_scale) { + // Don't scale images during rastering to ensure image quality, save memory + // and avoid frequent re-rastering on change of scale. + *raster_contents_scale = std::max(1.f, MinimumContentsScale()); + // We don't need low res tiles. + *low_res_raster_contents_scale = *raster_contents_scale; +} + +} // namespace cc diff --git a/cc/layers/picture_image_layer_impl.h b/cc/layers/picture_image_layer_impl.h new file mode 100644 index 0000000..9c3f46d --- /dev/null +++ b/cc/layers/picture_image_layer_impl.h @@ -0,0 +1,39 @@ +// Copyright 2013 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 CC_LAYERS_PICTURE_IMAGE_LAYER_IMPL_H_ +#define CC_LAYERS_PICTURE_IMAGE_LAYER_IMPL_H_ + +#include "cc/layers/picture_layer_impl.h" + +namespace cc { + +class CC_EXPORT PictureImageLayerImpl : public PictureLayerImpl { + public: + static scoped_ptr<PictureImageLayerImpl> Create(LayerTreeImpl* treeImpl, + int id) { + return make_scoped_ptr(new PictureImageLayerImpl(treeImpl, id)); + } + virtual ~PictureImageLayerImpl(); + + virtual const char* LayerTypeAsString() const OVERRIDE; + virtual scoped_ptr<LayerImpl> CreateLayerImpl( + LayerTreeImpl* treeImpl) OVERRIDE; + + protected: + PictureImageLayerImpl(LayerTreeImpl* treeImpl, int id); + + virtual void CalculateRasterContentsScale( + bool animating_transform_to_screen, + float* raster_contents_scale, + float* low_res_raster_contents_scale) OVERRIDE; + virtual void GetDebugBorderProperties( + SkColor* color, float* width) const OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(PictureImageLayerImpl); +}; + +} + +#endif // CC_LAYERS_PICTURE_IMAGE_LAYER_IMPL_H_ diff --git a/cc/layers/picture_layer.cc b/cc/layers/picture_layer.cc new file mode 100644 index 0000000..e2d49a9 --- /dev/null +++ b/cc/layers/picture_layer.cc @@ -0,0 +1,97 @@ +// Copyright 2012 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 "cc/layers/picture_layer.h" + +#include "cc/debug/devtools_instrumentation.h" +#include "cc/layers/picture_layer_impl.h" +#include "cc/trees/layer_tree_impl.h" +#include "ui/gfx/rect_conversions.h" + +namespace cc { + +scoped_refptr<PictureLayer> PictureLayer::Create(ContentLayerClient* client) { + return make_scoped_refptr(new PictureLayer(client)); +} + +PictureLayer::PictureLayer(ContentLayerClient* client) : + client_(client), + pile_(make_scoped_refptr(new PicturePile())), + instrumentation_object_tracker_(id()), + is_mask_(false) { +} + +PictureLayer::~PictureLayer() { +} + +bool PictureLayer::DrawsContent() const { + return Layer::DrawsContent() && client_; +} + +scoped_ptr<LayerImpl> PictureLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) { + return PictureLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void PictureLayer::PushPropertiesTo(LayerImpl* base_layer) { + Layer::PushPropertiesTo(base_layer); + + PictureLayerImpl* layer_impl = static_cast<PictureLayerImpl*>(base_layer); + layer_impl->SetIsMask(is_mask_); + layer_impl->CreateTilingSet(); + layer_impl->invalidation_.Clear(); + layer_impl->invalidation_.Swap(pile_invalidation_); + layer_impl->pile_ = PicturePileImpl::CreateFromOther(pile_); + + layer_impl->SyncFromActiveLayer(); +} + +void PictureLayer::SetLayerTreeHost(LayerTreeHost* host) { + Layer::SetLayerTreeHost(host); + if (host) { + pile_->SetMinContentsScale(host->settings().minimumContentsScale); + pile_->SetTileGridSize(host->settings().defaultTileSize); + pile_->set_num_raster_threads(host->settings().numRasterThreads); + pile_->set_slow_down_raster_scale_factor( + host->debug_state().slowDownRasterScaleFactor); + } +} + +void PictureLayer::SetNeedsDisplayRect(const gfx::RectF& layer_rect) { + gfx::Rect rect = gfx::ToEnclosedRect(layer_rect); + if (!rect.IsEmpty()) { + // Clamp invalidation to the layer bounds. + rect.Intersect(gfx::Rect(bounds())); + pending_invalidation_.Union(rect); + } + Layer::SetNeedsDisplayRect(layer_rect); +} + +void PictureLayer::Update(ResourceUpdateQueue*, + const OcclusionTracker*, + RenderingStats* stats) { + // Do not early-out of this function so that PicturePile::Update has a chance + // to record pictures due to changing visibility of this layer. + + pile_->Resize(bounds()); + + // Calling paint in WebKit can sometimes cause invalidations, so save + // off the invalidation prior to calling update. + pile_invalidation_.Swap(pending_invalidation_); + pending_invalidation_.Clear(); + + gfx::Rect visible_layer_rect = gfx::ToEnclosingRect( + gfx::ScaleRect(visible_content_rect(), 1.f / contents_scale_x())); + devtools_instrumentation::ScopedPaintLayer paint_layer(id()); + pile_->Update(client_, + background_color(), + pile_invalidation_, + visible_layer_rect, + stats); +} + +void PictureLayer::SetIsMask(bool is_mask) { + is_mask_ = is_mask; +} + +} // namespace cc diff --git a/cc/layers/picture_layer.h b/cc/layers/picture_layer.h new file mode 100644 index 0000000..0ba4031 --- /dev/null +++ b/cc/layers/picture_layer.h @@ -0,0 +1,57 @@ +// Copyright 2012 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 CC_LAYERS_PICTURE_LAYER_H_ +#define CC_LAYERS_PICTURE_LAYER_H_ + +#include "cc/debug/devtools_instrumentation.h" +#include "cc/layers/contents_scaling_layer.h" +#include "cc/layers/layer.h" +#include "cc/resources/picture_pile.h" +#include "cc/trees/occlusion_tracker.h" + +namespace cc { + +class ContentLayerClient; +class ResourceUpdateQueue; +struct RenderingStats; + +class CC_EXPORT PictureLayer : public ContentsScalingLayer { + public: + static scoped_refptr<PictureLayer> Create(ContentLayerClient* client); + + void ClearClient() { client_ = NULL; } + + // Implement Layer interface + virtual bool DrawsContent() const OVERRIDE; + virtual scoped_ptr<LayerImpl> CreateLayerImpl( + LayerTreeImpl* tree_impl) OVERRIDE; + virtual void SetLayerTreeHost(LayerTreeHost* host) OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + virtual void SetNeedsDisplayRect(const gfx::RectF& layer_rect) OVERRIDE; + virtual void Update( + ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) OVERRIDE; + virtual void SetIsMask(bool is_mask) OVERRIDE; + + protected: + explicit PictureLayer(ContentLayerClient* client); + virtual ~PictureLayer(); + + private: + ContentLayerClient* client_; + scoped_refptr<PicturePile> pile_; + devtools_instrumentation:: + ScopedLayerObjectTracker instrumentation_object_tracker_; + // Invalidation to use the next time update is called. + Region pending_invalidation_; + // Invalidation from the last time update was called. + Region pile_invalidation_; + bool is_mask_; +}; + +} // namespace cc + +#endif // CC_LAYERS_PICTURE_LAYER_H_ diff --git a/cc/layers/picture_layer_impl.cc b/cc/layers/picture_layer_impl.cc new file mode 100644 index 0000000..0255621 --- /dev/null +++ b/cc/layers/picture_layer_impl.cc @@ -0,0 +1,828 @@ +// Copyright 2012 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 "cc/layers/picture_layer_impl.h" + +#include "base/time.h" +#include "cc/base/math_util.h" +#include "cc/base/util.h" +#include "cc/debug/debug_colors.h" +#include "cc/layers/append_quads_data.h" +#include "cc/layers/quad_sink.h" +#include "cc/quads/checkerboard_draw_quad.h" +#include "cc/quads/debug_border_draw_quad.h" +#include "cc/quads/solid_color_draw_quad.h" +#include "cc/quads/tile_draw_quad.h" +#include "cc/trees/layer_tree_impl.h" +#include "ui/gfx/quad_f.h" +#include "ui/gfx/rect_conversions.h" +#include "ui/gfx/size_conversions.h" + +namespace { +const float kMaxScaleRatioDuringPinch = 2.0f; +} + +namespace cc { + +PictureLayerImpl::PictureLayerImpl(LayerTreeImpl* tree_impl, int id) + : LayerImpl(tree_impl, id), + pile_(PicturePileImpl::Create()), + last_content_scale_(0), + ideal_contents_scale_(0), + is_mask_(false), + ideal_page_scale_(0.f), + ideal_device_scale_(0.f), + ideal_source_scale_(0.f), + raster_page_scale_(0.f), + raster_device_scale_(0.f), + raster_source_scale_(0.f), + raster_source_scale_was_animating_(false) { +} + +PictureLayerImpl::~PictureLayerImpl() { +} + +const char* PictureLayerImpl::LayerTypeAsString() const { + return "PictureLayer"; +} + +scoped_ptr<LayerImpl> PictureLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return PictureLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void PictureLayerImpl::CreateTilingSet() { + DCHECK(layer_tree_impl()->IsPendingTree()); + DCHECK(!tilings_); + tilings_.reset(new PictureLayerTilingSet(this)); + tilings_->SetLayerBounds(bounds()); +} + +void PictureLayerImpl::TransferTilingSet( + scoped_ptr<PictureLayerTilingSet> tilings) { + DCHECK(layer_tree_impl()->IsActiveTree()); + tilings->SetClient(this); + tilings_ = tilings.Pass(); +} + +void PictureLayerImpl::PushPropertiesTo(LayerImpl* base_layer) { + LayerImpl::PushPropertiesTo(base_layer); + + PictureLayerImpl* layer_impl = static_cast<PictureLayerImpl*>(base_layer); + + layer_impl->SetIsMask(is_mask_); + layer_impl->TransferTilingSet(tilings_.Pass()); + layer_impl->pile_ = pile_; + pile_ = PicturePileImpl::Create(); + + layer_impl->raster_page_scale_ = raster_page_scale_; + layer_impl->raster_device_scale_ = raster_device_scale_; + layer_impl->raster_source_scale_ = raster_source_scale_; +} + + +void PictureLayerImpl::AppendQuads(QuadSink* quadSink, + AppendQuadsData* appendQuadsData) { + const gfx::Rect& rect = visible_content_rect(); + gfx::Rect content_rect(content_bounds()); + + SharedQuadState* sharedQuadState = + quadSink->UseSharedQuadState(CreateSharedQuadState()); + AppendDebugBorderQuad(quadSink, sharedQuadState, appendQuadsData); + + bool clipped = false; + gfx::QuadF target_quad = MathUtil::MapQuad( + draw_transform(), + gfx::QuadF(rect), + &clipped); + bool is_axis_aligned_in_target = !clipped && target_quad.IsRectilinear(); + + bool is_pixel_aligned = is_axis_aligned_in_target && + draw_transform().IsIdentityOrIntegerTranslation(); + PictureLayerTiling::LayerDeviceAlignment layerDeviceAlignment = + is_pixel_aligned ? PictureLayerTiling::LayerAlignedToDevice + : PictureLayerTiling::LayerNotAlignedToDevice; + + if (ShowDebugBorders()) { + for (PictureLayerTilingSet::Iterator iter(tilings_.get(), + contents_scale_x(), + rect, + ideal_contents_scale_, + layerDeviceAlignment); + iter; + ++iter) { + SkColor color; + float width; + if (*iter && iter->drawing_info().IsReadyToDraw()) { + ManagedTileState::DrawingInfo::Mode mode = iter->drawing_info().mode(); + if (mode == ManagedTileState::DrawingInfo::SOLID_COLOR_MODE || + mode == ManagedTileState::DrawingInfo::TRANSPARENT_MODE) { + color = DebugColors::SolidColorTileBorderColor(); + width = DebugColors::SolidColorTileBorderWidth(layer_tree_impl()); + } else if (iter->priority(ACTIVE_TREE).resolution == HIGH_RESOLUTION) { + color = DebugColors::HighResTileBorderColor(); + width = DebugColors::HighResTileBorderWidth(layer_tree_impl()); + } else if (iter->priority(ACTIVE_TREE).resolution == LOW_RESOLUTION) { + color = DebugColors::LowResTileBorderColor(); + width = DebugColors::LowResTileBorderWidth(layer_tree_impl()); + } else if (iter->contents_scale() > contents_scale_x()) { + color = DebugColors::ExtraHighResTileBorderColor(); + width = DebugColors::ExtraHighResTileBorderWidth(layer_tree_impl()); + } else { + color = DebugColors::ExtraLowResTileBorderColor(); + width = DebugColors::ExtraLowResTileBorderWidth(layer_tree_impl()); + } + } else { + color = DebugColors::MissingTileBorderColor(); + width = DebugColors::MissingTileBorderWidth(layer_tree_impl()); + } + + scoped_ptr<DebugBorderDrawQuad> debugBorderQuad = + DebugBorderDrawQuad::Create(); + gfx::Rect geometry_rect = iter.geometry_rect(); + debugBorderQuad->SetNew(sharedQuadState, geometry_rect, color, width); + quadSink->Append(debugBorderQuad.PassAs<DrawQuad>(), appendQuadsData); + } + } + + // Keep track of the tilings that were used so that tilings that are + // unused can be considered for removal. + std::vector<PictureLayerTiling*> seen_tilings; + + for (PictureLayerTilingSet::Iterator iter(tilings_.get(), + contents_scale_x(), + rect, + ideal_contents_scale_, + layerDeviceAlignment); + iter; + ++iter) { + + gfx::Rect geometry_rect = iter.geometry_rect(); + if (!*iter || !iter->drawing_info().IsReadyToDraw()) { + if (DrawCheckerboardForMissingTiles()) { + // TODO(enne): Figure out how to show debug "invalidated checker" color + scoped_ptr<CheckerboardDrawQuad> quad = CheckerboardDrawQuad::Create(); + SkColor color = DebugColors::DefaultCheckerboardColor(); + quad->SetNew(sharedQuadState, geometry_rect, color); + if (quadSink->Append(quad.PassAs<DrawQuad>(), appendQuadsData)) + appendQuadsData->numMissingTiles++; + } else { + scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create(); + quad->SetNew(sharedQuadState, geometry_rect, background_color()); + if (quadSink->Append(quad.PassAs<DrawQuad>(), appendQuadsData)) + appendQuadsData->numMissingTiles++; + } + + appendQuadsData->hadIncompleteTile = true; + continue; + } + + const ManagedTileState::DrawingInfo& drawing_info = iter->drawing_info(); + switch (drawing_info.mode()) { + case ManagedTileState::DrawingInfo::TEXTURE_MODE: { + if (iter->contents_scale() != ideal_contents_scale_) + appendQuadsData->hadIncompleteTile = true; + + gfx::RectF texture_rect = iter.texture_rect(); + gfx::Rect opaque_rect = iter->opaque_rect(); + opaque_rect.Intersect(content_rect); + + scoped_ptr<TileDrawQuad> quad = TileDrawQuad::Create(); + quad->SetNew(sharedQuadState, + geometry_rect, + opaque_rect, + drawing_info.get_resource_id(), + texture_rect, + iter.texture_size(), + drawing_info.contents_swizzled()); + quadSink->Append(quad.PassAs<DrawQuad>(), appendQuadsData); + break; + } + case ManagedTileState::DrawingInfo::SOLID_COLOR_MODE: { + scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create(); + quad->SetNew(sharedQuadState, + geometry_rect, + drawing_info.get_solid_color()); + quadSink->Append(quad.PassAs<DrawQuad>(), appendQuadsData); + break; + } + case ManagedTileState::DrawingInfo::TRANSPARENT_MODE: + break; + case ManagedTileState::DrawingInfo::PICTURE_PILE_MODE: + // TODO: crbug.com/173011 would fill this part in. + default: + NOTREACHED(); + } + + if (!seen_tilings.size() || seen_tilings.back() != iter.CurrentTiling()) + seen_tilings.push_back(iter.CurrentTiling()); + } + + // Aggressively remove any tilings that are not seen to save memory. Note + // that this is at the expense of doing cause more frequent re-painting. A + // better scheme would be to maintain a tighter visibleContentRect for the + // finer tilings. + CleanUpTilingsOnActiveLayer(seen_tilings); +} + +void PictureLayerImpl::DumpLayerProperties(std::string*, int indent) const { + // TODO(enne): implement me +} + +void PictureLayerImpl::UpdateTilePriorities() { + int current_source_frame_number = layer_tree_impl()->source_frame_number(); + double current_frame_time = + (layer_tree_impl()->CurrentFrameTime() - base::TimeTicks()).InSecondsF(); + + gfx::Transform current_screen_space_transform = screen_space_transform(); + + gfx::Rect viewport_in_content_space; + gfx::Transform screen_to_layer(gfx::Transform::kSkipInitialization); + if (screen_space_transform().GetInverse(&screen_to_layer)) { + gfx::Rect device_viewport(layer_tree_impl()->device_viewport_size()); + viewport_in_content_space = gfx::ToEnclosingRect( + MathUtil::ProjectClippedRect(screen_to_layer, device_viewport)); + } + + WhichTree tree = + layer_tree_impl()->IsActiveTree() ? ACTIVE_TREE : PENDING_TREE; + bool store_screen_space_quads_on_tiles = + layer_tree_impl()->debug_state().traceAllRenderedFrames; + tilings_->UpdateTilePriorities( + tree, + layer_tree_impl()->device_viewport_size(), + viewport_in_content_space, + last_bounds_, + bounds(), + last_content_scale_, + contents_scale_x(), + last_screen_space_transform_, + current_screen_space_transform, + current_source_frame_number, + current_frame_time, + store_screen_space_quads_on_tiles); + + last_screen_space_transform_ = current_screen_space_transform; + last_bounds_ = bounds(); + last_content_scale_ = contents_scale_x(); +} + +void PictureLayerImpl::DidBecomeActive() { + LayerImpl::DidBecomeActive(); + tilings_->DidBecomeActive(); +} + +void PictureLayerImpl::DidLoseOutputSurface() { + if (tilings_) + tilings_->RemoveAllTilings(); +} + +void PictureLayerImpl::CalculateContentsScale( + float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* content_bounds) { + if (!DrawsContent()) { + DCHECK(!tilings_->num_tilings()); + return; + } + + float min_contents_scale = MinimumContentsScale(); + float min_page_scale = layer_tree_impl()->min_page_scale_factor(); + float min_device_scale = 1.f; + float min_source_scale = + min_contents_scale / min_page_scale / min_device_scale; + + float ideal_page_scale = layer_tree_impl()->total_page_scale_factor(); + float ideal_device_scale = layer_tree_impl()->device_scale_factor(); + float ideal_source_scale = + ideal_contents_scale / ideal_page_scale / ideal_device_scale; + + ideal_contents_scale_ = std::max(ideal_contents_scale, min_contents_scale); + ideal_page_scale_ = ideal_page_scale; + ideal_device_scale_ = ideal_device_scale; + ideal_source_scale_ = std::max(ideal_source_scale, min_source_scale); + + ManageTilings(animating_transform_to_screen); + + // The content scale and bounds for a PictureLayerImpl is somewhat fictitious. + // There are (usually) several tilings at different scales. However, the + // content bounds is the (integer!) space in which quads are generated. + // In order to guarantee that we can fill this integer space with any set of + // tilings (and then map back to floating point texture coordinates), the + // contents scale must be at least as large as the largest of the tilings. + float max_contents_scale = min_contents_scale; + for (size_t i = 0; i < tilings_->num_tilings(); ++i) { + const PictureLayerTiling* tiling = tilings_->tiling_at(i); + max_contents_scale = std::max(max_contents_scale, tiling->contents_scale()); + } + + *contents_scale_x = max_contents_scale; + *contents_scale_y = max_contents_scale; + *content_bounds = gfx::ToCeiledSize( + gfx::ScaleSize(bounds(), max_contents_scale, max_contents_scale)); +} + +skia::RefPtr<SkPicture> PictureLayerImpl::GetPicture() { + return pile_->GetFlattenedPicture(); +} + +scoped_refptr<Tile> PictureLayerImpl::CreateTile(PictureLayerTiling* tiling, + gfx::Rect content_rect) { + if (!pile_->CanRaster(tiling->contents_scale(), content_rect)) + return scoped_refptr<Tile>(); + + return make_scoped_refptr(new Tile( + layer_tree_impl()->tile_manager(), + pile_.get(), + content_rect.size(), + GL_RGBA, + content_rect, + contents_opaque() ? content_rect : gfx::Rect(), + tiling->contents_scale(), + id())); +} + +void PictureLayerImpl::UpdatePile(Tile* tile) { + tile->set_picture_pile(pile_); +} + +gfx::Size PictureLayerImpl::CalculateTileSize( + gfx::Size current_tile_size, + gfx::Size content_bounds) { + if (is_mask_) { + int max_size = layer_tree_impl()->MaxTextureSize(); + return gfx::Size( + std::min(max_size, content_bounds.width()), + std::min(max_size, content_bounds.height())); + } + + gfx::Size default_tile_size = layer_tree_impl()->settings().defaultTileSize; + gfx::Size max_untiled_content_size = + layer_tree_impl()->settings().maxUntiledLayerSize; + + bool any_dimension_too_large = + content_bounds.width() > max_untiled_content_size.width() || + content_bounds.height() > max_untiled_content_size.height(); + + bool any_dimension_one_tile = + content_bounds.width() <= default_tile_size.width() || + content_bounds.height() <= default_tile_size.height(); + + // If long and skinny, tile at the max untiled content size, and clamp + // the smaller dimension to the content size, e.g. 1000x12 layer with + // 500x500 max untiled size would get 500x12 tiles. Also do this + // if the layer is small. + if (any_dimension_one_tile || !any_dimension_too_large) { + int width = + std::min(max_untiled_content_size.width(), content_bounds.width()); + int height = + std::min(max_untiled_content_size.height(), content_bounds.height()); + // Round width and height up to the closest multiple of 64, or 56 if + // we should avoid power-of-two textures. This helps reduce the number + // of different textures sizes to help recycling, and also keeps all + // textures multiple-of-eight, which is preferred on some drivers (IMG). + bool avoid_pow2 = + layer_tree_impl()->rendererCapabilities().avoid_pow2_textures; + int round_up_to = avoid_pow2 ? 56 : 64; + width = RoundUp(width, round_up_to); + height = RoundUp(height, round_up_to); + return gfx::Size(width, height); + } + + return default_tile_size; +} + +void PictureLayerImpl::SyncFromActiveLayer() { + DCHECK(layer_tree_impl()->IsPendingTree()); + + if (!DrawsContent()) { + raster_page_scale_ = 0; + raster_device_scale_ = 0; + raster_source_scale_ = 0; + return; + } + + // If there is an active tree version of this layer, get a copy of its + // tiles. This needs to be done last, after setting invalidation and the + // pile. + if (PictureLayerImpl* active_twin = ActiveTwin()) + SyncFromActiveLayer(active_twin); +} + +void PictureLayerImpl::SyncFromActiveLayer(const PictureLayerImpl* other) { + raster_page_scale_ = other->raster_page_scale_; + raster_device_scale_ = other->raster_device_scale_; + raster_source_scale_ = other->raster_source_scale_; + + // Add synthetic invalidations for any recordings that were dropped. As + // tiles are updated to point to this new pile, this will force the dropping + // of tiles that can no longer be rastered. This is not ideal, but is a + // trade-off for memory (use the same pile as much as possible, by switching + // during DidBecomeActive) and for time (don't bother checking every tile + // during activation to see if the new pile can still raster it). + // + // TODO(enne): Clean up this double loop. + for (int x = 0; x < pile_->num_tiles_x(); ++x) { + for (int y = 0; y < pile_->num_tiles_y(); ++y) { + bool previously_had = other->pile_->HasRecordingAt(x, y); + bool now_has = pile_->HasRecordingAt(x, y); + if (now_has || !previously_had) + continue; + gfx::Rect layer_rect = pile_->tile_bounds(x, y); + invalidation_.Union(layer_rect); + } + } + + tilings_->CloneAll(*other->tilings_, invalidation_, MinimumContentsScale()); + DCHECK(bounds() == tilings_->LayerBounds()); + + // It's a sad but unfortunate fact that PicturePile tiling edges do not line + // up with PictureLayerTiling edges. Tiles can only be added if they are + // entirely covered by recordings (that may come from multiple PicturePile + // tiles). This check happens in this class's CreateTile() call. + for (int x = 0; x < pile_->num_tiles_x(); ++x) { + for (int y = 0; y < pile_->num_tiles_y(); ++y) { + bool previously_had = other->pile_->HasRecordingAt(x, y); + bool now_has = pile_->HasRecordingAt(x, y); + if (!now_has || previously_had) + continue; + gfx::Rect layer_rect = pile_->tile_bounds(x, y); + tilings_->CreateTilesFromLayerRect(layer_rect); + } + } +} + +void PictureLayerImpl::SyncTiling( + const PictureLayerTiling* tiling, + const Region& pending_layer_invalidation) { + if (!DrawsContent() || tiling->contents_scale() < MinimumContentsScale()) + return; + tilings_->Clone(tiling, pending_layer_invalidation); +} + +void PictureLayerImpl::SetIsMask(bool is_mask) { + if (is_mask_ == is_mask) + return; + is_mask_ = is_mask; + if (tilings_) + tilings_->RemoveAllTiles(); +} + +ResourceProvider::ResourceId PictureLayerImpl::ContentsResourceId() const { + gfx::Rect content_rect(content_bounds()); + float scale = contents_scale_x(); + for (PictureLayerTilingSet::Iterator + iter(tilings_.get(), + scale, + content_rect, + ideal_contents_scale_, + PictureLayerTiling::LayerDeviceAlignmentUnknown); + iter; + ++iter) { + // Mask resource not ready yet. + if (!*iter || + iter->drawing_info().mode() != + ManagedTileState::DrawingInfo::TEXTURE_MODE || + !iter->drawing_info().IsReadyToDraw()) + return 0; + // Masks only supported if they fit on exactly one tile. + if (iter.geometry_rect() != content_rect) + return 0; + return iter->drawing_info().get_resource_id(); + } + return 0; +} + +bool PictureLayerImpl::AreVisibleResourcesReady() const { + DCHECK(layer_tree_impl()->IsPendingTree()); + DCHECK(ideal_contents_scale_); + + const gfx::Rect& rect = visible_content_rect(); + + float raster_contents_scale = + raster_page_scale_ * + raster_device_scale_ * + raster_source_scale_; + + float min_acceptable_scale = + std::min(raster_contents_scale, ideal_contents_scale_); + + TreePriority tree_priority = + layer_tree_impl()->tile_manager()->GlobalState().tree_priority; + bool should_force_uploads = + tree_priority != SMOOTHNESS_TAKES_PRIORITY && + layer_tree_impl()->animationRegistrar()-> + active_animation_controllers().empty(); + + if (PictureLayerImpl* twin = ActiveTwin()) { + float twin_raster_contents_scale = + twin->raster_page_scale_ * + twin->raster_device_scale_ * + twin->raster_source_scale_; + + min_acceptable_scale = std::min( + min_acceptable_scale, + std::min(twin->ideal_contents_scale_, twin_raster_contents_scale)); + } + + Region missing_region = rect; + for (size_t i = 0; i < tilings_->num_tilings(); ++i) { + PictureLayerTiling* tiling = tilings_->tiling_at(i); + + if (tiling->contents_scale() < min_acceptable_scale) + continue; + + for (PictureLayerTiling::Iterator + iter(tiling, + contents_scale_x(), + rect, + PictureLayerTiling::LayerDeviceAlignmentUnknown); + iter; + ++iter) { + if (should_force_uploads && iter) + layer_tree_impl()->tile_manager()->ForceTileUploadToComplete(*iter); + // A null tile (i.e. no recording) is considered "ready". + if (!*iter || iter->drawing_info().IsReadyToDraw()) + missing_region.Subtract(iter.geometry_rect()); + } + } + + return missing_region.IsEmpty(); +} + +PictureLayerTiling* PictureLayerImpl::AddTiling(float contents_scale) { + DCHECK(contents_scale >= MinimumContentsScale()); + + PictureLayerTiling* tiling = tilings_->AddTiling(contents_scale); + + const Region& recorded = pile_->recorded_region(); + DCHECK(!recorded.IsEmpty()); + + for (Region::Iterator iter(recorded); iter.has_rect(); iter.next()) + tiling->CreateTilesFromLayerRect(iter.rect()); + + PictureLayerImpl* twin = + layer_tree_impl()->IsPendingTree() ? ActiveTwin() : PendingTwin(); + if (!twin) + return tiling; + + if (layer_tree_impl()->IsPendingTree()) + twin->SyncTiling(tiling, invalidation_); + else + twin->SyncTiling(tiling, twin->invalidation_); + + return tiling; +} + +void PictureLayerImpl::RemoveTiling(float contents_scale) { + for (size_t i = 0; i < tilings_->num_tilings(); ++i) { + PictureLayerTiling* tiling = tilings_->tiling_at(i); + if (tiling->contents_scale() == contents_scale) { + tilings_->Remove(tiling); + break; + } + } +} + +namespace { + +inline float PositiveRatio(float float1, float float2) { + DCHECK(float1 > 0); + DCHECK(float2 > 0); + return float1 > float2 ? float1 / float2 : float2 / float1; +} + +inline bool IsCloserToThan( + PictureLayerTiling* layer1, + PictureLayerTiling* layer2, + float contents_scale) { + // Absolute value for ratios. + float ratio1 = PositiveRatio(layer1->contents_scale(), contents_scale); + float ratio2 = PositiveRatio(layer2->contents_scale(), contents_scale); + return ratio1 < ratio2; +} + +} // namespace + +void PictureLayerImpl::ManageTilings(bool animating_transform_to_screen) { + DCHECK(ideal_contents_scale_); + DCHECK(ideal_page_scale_); + DCHECK(ideal_device_scale_); + DCHECK(ideal_source_scale_); + + if (pile_->recorded_region().IsEmpty()) + return; + + bool is_active_layer = layer_tree_impl()->IsActiveTree(); + bool is_pinching = layer_tree_impl()->PinchGestureActive(); + + bool change_target_tiling = false; + + if (!raster_page_scale_ || !raster_device_scale_ || !raster_source_scale_) + change_target_tiling = true; + + // TODO(danakj): Adjust raster_source_scale_ closer to ideal_source_scale_ at + // a throttled rate. Possibly make use of invalidation_.IsEmpty() on pending + // tree. This will allow CSS scale changes to get re-rastered at an + // appropriate rate. + + if (is_active_layer) { + if (raster_source_scale_was_animating_ && !animating_transform_to_screen) + change_target_tiling = true; + raster_source_scale_was_animating_ = animating_transform_to_screen; + } + + if (is_active_layer && is_pinching && raster_page_scale_) { + // If the page scale diverges too far during pinch, change raster target to + // the current page scale. + float ratio = PositiveRatio(ideal_page_scale_, raster_page_scale_); + if (ratio >= kMaxScaleRatioDuringPinch) + change_target_tiling = true; + } + + if (!is_pinching) { + // When not pinching, match the ideal page scale factor. + if (raster_page_scale_ != ideal_page_scale_) + change_target_tiling = true; + } + + // Always match the ideal device scale factor. + if (raster_device_scale_ != ideal_device_scale_) + change_target_tiling = true; + + if (!change_target_tiling) + return; + + raster_page_scale_ = ideal_page_scale_; + raster_device_scale_ = ideal_device_scale_; + raster_source_scale_ = ideal_source_scale_; + + float raster_contents_scale; + float low_res_raster_contents_scale; + CalculateRasterContentsScale(animating_transform_to_screen, + &raster_contents_scale, + &low_res_raster_contents_scale); + + PictureLayerTiling* high_res = NULL; + PictureLayerTiling* low_res = NULL; + + for (size_t i = 0; i < tilings_->num_tilings(); ++i) { + PictureLayerTiling* tiling = tilings_->tiling_at(i); + if (tiling->contents_scale() == raster_contents_scale) + high_res = tiling; + if (tiling->contents_scale() == low_res_raster_contents_scale) + low_res = tiling; + + // Reset all tilings to non-ideal until the end of this function. + tiling->set_resolution(NON_IDEAL_RESOLUTION); + } + + if (!high_res) { + high_res = AddTiling(raster_contents_scale); + if (raster_contents_scale == low_res_raster_contents_scale) + low_res = high_res; + } + if (!low_res && low_res != high_res) + low_res = AddTiling(low_res_raster_contents_scale); + + if (high_res) + high_res->set_resolution(HIGH_RESOLUTION); + if (low_res && low_res != high_res) + low_res->set_resolution(LOW_RESOLUTION); +} + +void PictureLayerImpl::CalculateRasterContentsScale( + bool animating_transform_to_screen, + float* raster_contents_scale, + float* low_res_raster_contents_scale) { + *raster_contents_scale = ideal_contents_scale_; + + // Don't allow animating CSS scales to drop below 1. + if (animating_transform_to_screen) { + *raster_contents_scale = std::max( + *raster_contents_scale, 1.f * ideal_page_scale_ * ideal_device_scale_); + } + + float low_res_factor = + layer_tree_impl()->settings().lowResContentsScaleFactor; + *low_res_raster_contents_scale = std::max( + *raster_contents_scale * low_res_factor, + MinimumContentsScale()); +} + +void PictureLayerImpl::CleanUpTilingsOnActiveLayer( + std::vector<PictureLayerTiling*> used_tilings) { + DCHECK(layer_tree_impl()->IsActiveTree()); + + float raster_contents_scale = + raster_page_scale_ * raster_device_scale_ * raster_source_scale_; + + float min_acceptable_high_res_scale = std::min( + raster_contents_scale, ideal_contents_scale_); + float max_acceptable_high_res_scale = std::max( + raster_contents_scale, ideal_contents_scale_); + + PictureLayerImpl* twin = PendingTwin(); + if (twin) { + float twin_raster_contents_scale = + twin->raster_page_scale_ * + twin->raster_device_scale_ * + twin->raster_source_scale_; + + min_acceptable_high_res_scale = std::min( + min_acceptable_high_res_scale, + std::min(twin_raster_contents_scale, twin->ideal_contents_scale_)); + max_acceptable_high_res_scale = std::max( + max_acceptable_high_res_scale, + std::max(twin_raster_contents_scale, twin->ideal_contents_scale_)); + } + + float low_res_factor = + layer_tree_impl()->settings().lowResContentsScaleFactor; + + float min_acceptable_low_res_scale = + low_res_factor * min_acceptable_high_res_scale; + float max_acceptable_low_res_scale = + low_res_factor * max_acceptable_high_res_scale; + + std::vector<PictureLayerTiling*> to_remove; + for (size_t i = 0; i < tilings_->num_tilings(); ++i) { + PictureLayerTiling* tiling = tilings_->tiling_at(i); + + if (tiling->contents_scale() >= min_acceptable_high_res_scale && + tiling->contents_scale() <= max_acceptable_high_res_scale) + continue; + + if (tiling->contents_scale() >= min_acceptable_low_res_scale && + tiling->contents_scale() <= max_acceptable_low_res_scale) + continue; + + // Don't remove tilings that are being used and expected to stay around. + if (std::find(used_tilings.begin(), used_tilings.end(), tiling) != + used_tilings.end()) + continue; + + to_remove.push_back(tiling); + } + + for (size_t i = 0; i < to_remove.size(); ++i) { + if (twin) + twin->RemoveTiling(to_remove[i]->contents_scale()); + tilings_->Remove(to_remove[i]); + } +} + +PictureLayerImpl* PictureLayerImpl::PendingTwin() const { + DCHECK(layer_tree_impl()->IsActiveTree()); + + PictureLayerImpl* twin = static_cast<PictureLayerImpl*>( + layer_tree_impl()->FindPendingTreeLayerById(id())); + if (twin) + DCHECK_EQ(id(), twin->id()); + return twin; +} + +PictureLayerImpl* PictureLayerImpl::ActiveTwin() const { + DCHECK(layer_tree_impl()->IsPendingTree()); + + PictureLayerImpl* twin = static_cast<PictureLayerImpl*>( + layer_tree_impl()->FindActiveTreeLayerById(id())); + if (twin) + DCHECK_EQ(id(), twin->id()); + return twin; +} + +float PictureLayerImpl::MinimumContentsScale() const { + float setting_min = layer_tree_impl()->settings().minimumContentsScale; + + // If the contents scale is less than 1 / width (also for height), + // then it will end up having less than one pixel of content in that + // dimension. Bump the minimum contents scale up in this case to prevent + // this from happening. + int min_dimension = std::min(bounds().width(), bounds().height()); + if (!min_dimension) + return setting_min; + + return std::max(1.f / min_dimension, setting_min); +} + +void PictureLayerImpl::GetDebugBorderProperties( + SkColor* color, + float* width) const { + *color = DebugColors::TiledContentLayerBorderColor(); + *width = DebugColors::TiledContentLayerBorderWidth(layer_tree_impl()); +} + +scoped_ptr<base::Value> PictureLayerImpl::AsValue() const { + scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue()); + LayerImpl::AsValueInto(state.get()); + + state->SetDouble("ideal_contents_scale", ideal_contents_scale_); + state->Set("tilings", tilings_->AsValue().release()); + return state.PassAs<base::Value>(); +} + +} // namespace cc diff --git a/cc/layers/picture_layer_impl.h b/cc/layers/picture_layer_impl.h new file mode 100644 index 0000000..d10b93f --- /dev/null +++ b/cc/layers/picture_layer_impl.h @@ -0,0 +1,116 @@ +// Copyright 2012 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 CC_LAYERS_PICTURE_LAYER_IMPL_H_ +#define CC_LAYERS_PICTURE_LAYER_IMPL_H_ + +#include "cc/base/scoped_ptr_vector.h" +#include "cc/layers/layer_impl.h" +#include "cc/resources/picture_layer_tiling.h" +#include "cc/resources/picture_layer_tiling_set.h" +#include "cc/resources/picture_pile_impl.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/core/SkPicture.h" + +namespace cc { + +struct AppendQuadsData; +class QuadSink; + +class CC_EXPORT PictureLayerImpl : public LayerImpl, + public PictureLayerTilingClient { + public: + static scoped_ptr<PictureLayerImpl> Create(LayerTreeImpl* tree_impl, int id) { + return make_scoped_ptr(new PictureLayerImpl(tree_impl, id)); + } + virtual ~PictureLayerImpl(); + + // LayerImpl overrides. + virtual const char* LayerTypeAsString() const OVERRIDE; + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + virtual void DumpLayerProperties(std::string* str, int indent) const OVERRIDE; + virtual void UpdateTilePriorities() OVERRIDE; + virtual void DidBecomeActive() OVERRIDE; + virtual void DidLoseOutputSurface() OVERRIDE; + virtual void CalculateContentsScale(float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* content_bounds) OVERRIDE; + virtual skia::RefPtr<SkPicture> GetPicture() OVERRIDE; + + // PictureLayerTilingClient overrides. + virtual scoped_refptr<Tile> CreateTile(PictureLayerTiling* tiling, + gfx::Rect content_rect) OVERRIDE; + virtual void UpdatePile(Tile* tile) OVERRIDE; + virtual gfx::Size CalculateTileSize( + gfx::Size current_tile_size, + gfx::Size content_bounds) OVERRIDE; + + // PushPropertiesTo active tree => pending tree + void SyncFromActiveLayer(); + void SyncTiling( + const PictureLayerTiling* tiling, + const Region& pending_layer_invalidation); + + void CreateTilingSet(); + void TransferTilingSet(scoped_ptr<PictureLayerTilingSet> tilings); + + // Mask-related functions + void SetIsMask(bool is_mask); + virtual ResourceProvider::ResourceId ContentsResourceId() const OVERRIDE; + + virtual bool AreVisibleResourcesReady() const OVERRIDE; + + virtual scoped_ptr<base::Value> AsValue() const OVERRIDE; + + protected: + PictureLayerImpl(LayerTreeImpl* tree_impl, int id); + PictureLayerTiling* AddTiling(float contents_scale); + void RemoveTiling(float contents_scale); + void SyncFromActiveLayer(const PictureLayerImpl* other); + void ManageTilings(bool animating_transform_to_screen); + virtual void CalculateRasterContentsScale( + bool animating_transform_to_screen, + float* raster_contents_scale, + float* low_res_raster_contents_scale); + void CleanUpTilingsOnActiveLayer( + std::vector<PictureLayerTiling*> used_tilings); + PictureLayerImpl* PendingTwin() const; + PictureLayerImpl* ActiveTwin() const; + float MinimumContentsScale() const; + + virtual void GetDebugBorderProperties( + SkColor* color, float* width) const OVERRIDE; + + scoped_ptr<PictureLayerTilingSet> tilings_; + scoped_refptr<PicturePileImpl> pile_; + Region invalidation_; + + gfx::Transform last_screen_space_transform_; + gfx::Size last_bounds_; + float last_content_scale_; + float ideal_contents_scale_; + bool is_mask_; + + float ideal_page_scale_; + float ideal_device_scale_; + float ideal_source_scale_; + + float raster_page_scale_; + float raster_device_scale_; + float raster_source_scale_; + bool raster_source_scale_was_animating_; + + friend class PictureLayer; + DISALLOW_COPY_AND_ASSIGN(PictureLayerImpl); +}; + +} + +#endif // CC_LAYERS_PICTURE_LAYER_IMPL_H_ diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc new file mode 100644 index 0000000..48a5f51 --- /dev/null +++ b/cc/layers/picture_layer_impl_unittest.cc @@ -0,0 +1,708 @@ +// Copyright 2013 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 "cc/layers/picture_layer_impl.h" + +#include "cc/layers/picture_layer.h" +#include "cc/test/fake_content_layer_client.h" +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/fake_output_surface.h" +#include "cc/trees/layer_tree_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkDevice.h" +#include "ui/gfx/rect_conversions.h" + +namespace cc { +namespace { + +class TestablePictureLayerImpl : public PictureLayerImpl { + public: + static scoped_ptr<TestablePictureLayerImpl> Create( + LayerTreeImpl* tree_impl, + int id, + scoped_refptr<PicturePileImpl> pile) + { + return make_scoped_ptr(new TestablePictureLayerImpl(tree_impl, id, pile)); + } + + PictureLayerTilingSet& tilings() { return *tilings_; } + Region& invalidation() { return invalidation_; } + + virtual gfx::Size CalculateTileSize( + gfx::Size current_tile_size, + gfx::Size content_bounds) OVERRIDE { + if (current_tile_size.IsEmpty()) + return gfx::Size(100, 100); + return current_tile_size; + } + + using PictureLayerImpl::AddTiling; + using PictureLayerImpl::CleanUpTilingsOnActiveLayer; + + private: + TestablePictureLayerImpl( + LayerTreeImpl* tree_impl, + int id, + scoped_refptr<PicturePileImpl> pile) + : PictureLayerImpl(tree_impl, id) { + pile_ = pile; + SetBounds(pile_->size()); + CreateTilingSet(); + } +}; + +class ImplSidePaintingSettings : public LayerTreeSettings { + public: + ImplSidePaintingSettings() { + implSidePainting = true; + } +}; + +class TestablePicturePileImpl : public PicturePileImpl { + public: + static scoped_refptr<TestablePicturePileImpl> CreateFilledPile( + gfx::Size tile_size, + gfx::Size layer_bounds) { + scoped_refptr<TestablePicturePileImpl> pile(new TestablePicturePileImpl()); + pile->tiling().SetTotalSize(layer_bounds); + pile->tiling().SetMaxTextureSize(tile_size); + pile->SetTileGridSize(ImplSidePaintingSettings().defaultTileSize); + for (int x = 0; x < pile->tiling().num_tiles_x(); ++x) { + for (int y = 0; y < pile->tiling().num_tiles_y(); ++y) + pile->AddRecordingAt(x, y); + } + pile->UpdateRecordedRegion(); + return pile; + } + + static scoped_refptr<TestablePicturePileImpl> CreateEmptyPile( + gfx::Size tile_size, + gfx::Size layer_bounds) { + scoped_refptr<TestablePicturePileImpl> pile(new TestablePicturePileImpl()); + pile->tiling().SetTotalSize(layer_bounds); + pile->tiling().SetMaxTextureSize(tile_size); + pile->SetTileGridSize(ImplSidePaintingSettings().defaultTileSize); + pile->UpdateRecordedRegion(); + return pile; + } + + TilingData& tiling() { return tiling_; } + + void AddRecordingAt(int x, int y) { + EXPECT_GE(x, 0); + EXPECT_GE(y, 0); + EXPECT_LT(x, tiling_.num_tiles_x()); + EXPECT_LT(y, tiling_.num_tiles_y()); + + if (HasRecordingAt(x, y)) + return; + gfx::Rect bounds(tiling().TileBounds(x, y)); + scoped_refptr<Picture> picture(Picture::Create(bounds)); + picture->Record(&client_, NULL, tile_grid_info_); + picture_list_map_[std::pair<int, int>(x, y)].push_back(picture); + EXPECT_TRUE(HasRecordingAt(x, y)); + + UpdateRecordedRegion(); + } + + void RemoveRecordingAt(int x, int y) { + EXPECT_GE(x, 0); + EXPECT_GE(y, 0); + EXPECT_LT(x, tiling_.num_tiles_x()); + EXPECT_LT(y, tiling_.num_tiles_y()); + + if (!HasRecordingAt(x, y)) + return; + picture_list_map_.erase(std::pair<int, int>(x, y)); + EXPECT_FALSE(HasRecordingAt(x, y)); + + UpdateRecordedRegion(); + } + + void addDrawRect(const gfx::Rect& rect) { + client_.addDrawRect(rect); + } + + protected: + virtual ~TestablePicturePileImpl() { + } + + FakeContentLayerClient client_; +}; + +class MockCanvas : public SkCanvas { + public: + explicit MockCanvas(SkDevice* device) : SkCanvas(device) {} + + virtual void drawRect(const SkRect& rect, const SkPaint& paint) OVERRIDE { + // Capture calls before SkCanvas quickReject kicks in + rects_.push_back(rect); + } + + std::vector<SkRect> rects_; +}; + +class PictureLayerImplTest : public testing::Test { + public: + PictureLayerImplTest() + : host_impl_(ImplSidePaintingSettings(), &proxy_), + id_(7) { + host_impl_.InitializeRenderer(createFakeOutputSurface()); + } + + virtual ~PictureLayerImplTest() { + } + + void SetupTrees( + scoped_refptr<PicturePileImpl> pending_pile, + scoped_refptr<PicturePileImpl> active_pile) { + SetupPendingTree(active_pile); + host_impl_.ActivatePendingTree(); + + active_layer_ = static_cast<TestablePictureLayerImpl*>( + host_impl_.active_tree()->LayerById(id_)); + + SetupPendingTree(pending_pile); + pending_layer_ = static_cast<TestablePictureLayerImpl*>( + host_impl_.pending_tree()->LayerById(id_)); + } + + void AddDefaultTilingsWithInvalidation(const Region& invalidation) { + active_layer_->AddTiling(2.3f); + active_layer_->AddTiling(1.0f); + active_layer_->AddTiling(0.5f); + pending_layer_->invalidation() = invalidation; + pending_layer_->SyncFromActiveLayer(); + } + + void SetupPendingTree( + scoped_refptr<PicturePileImpl> pile) { + host_impl_.CreatePendingTree(); + LayerTreeImpl* pending_tree = host_impl_.pending_tree(); + // Clear recycled tree. + pending_tree->DetachLayerTree(); + + scoped_ptr<TestablePictureLayerImpl> pending_layer = + TestablePictureLayerImpl::Create(pending_tree, id_, pile); + pending_layer->SetDrawsContent(true); + pending_tree->SetRootLayer(pending_layer.PassAs<LayerImpl>()); + } + + static void VerifyAllTilesExistAndHavePile( + const PictureLayerTiling* tiling, + PicturePileImpl* pile) { + for (PictureLayerTiling::Iterator + iter(tiling, + tiling->contents_scale(), + tiling->ContentRect(), + PictureLayerTiling::LayerDeviceAlignmentUnknown); + iter; + ++iter) { + EXPECT_TRUE(*iter); + EXPECT_EQ(pile, iter->picture_pile()); + } + } + + void SetContentsScaleOnBothLayers(float scale, bool animating_transform) { + float result_scale_x, result_scale_y; + gfx::Size result_bounds; + pending_layer_->CalculateContentsScale( + scale, animating_transform, + &result_scale_x, &result_scale_y, &result_bounds); + active_layer_->CalculateContentsScale( + scale, animating_transform, + &result_scale_x, &result_scale_y, &result_bounds); + } + + protected: + void TestTileGridAlignmentCommon() { + // Layer to span 4 raster tiles in x and in y + ImplSidePaintingSettings settings; + gfx::Size layer_size( + settings.defaultTileSize.width() * 7 / 2, + settings.defaultTileSize.height() * 7 / 2); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateFilledPile(layer_size, layer_size); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateFilledPile(layer_size, layer_size); + + SetupTrees(pending_pile, active_pile); + + host_impl_.active_tree()->SetPageScaleFactorAndLimits(1.f, 1.f, 1.f); + float result_scale_x, result_scale_y; + gfx::Size result_bounds; + active_layer_->CalculateContentsScale( + 1.f, false, &result_scale_x, &result_scale_y, &result_bounds); + + // Add 1x1 rects at the centers of each tile, then re-record pile contents + std::vector<Tile*> tiles = + active_layer_->tilings().tiling_at(0)->AllTilesForTesting(); + EXPECT_EQ(16, tiles.size()); + std::vector<SkRect> rects; + std::vector<Tile*>::const_iterator tile_iter; + for (tile_iter = tiles.begin(); tile_iter < tiles.end(); tile_iter++) { + gfx::Point tile_center = (*tile_iter)->content_rect().CenterPoint(); + gfx::Rect rect(tile_center.x(), tile_center.y(), 1, 1); + active_pile->addDrawRect(rect); + rects.push_back(SkRect::MakeXYWH(rect.x(), rect.y(), 1, 1)); + } + // Force re-record with newly injected content + active_pile->RemoveRecordingAt(0, 0); + active_pile->AddRecordingAt(0, 0); + + SkBitmap store; + store.setConfig(SkBitmap::kNo_Config, 1000, 1000); + SkDevice device(store); + int64 pixelsRasterized; + + std::vector<SkRect>::const_iterator rect_iter = rects.begin(); + for (tile_iter = tiles.begin(); tile_iter < tiles.end(); tile_iter++) { + MockCanvas mock_canvas(&device); + active_pile->Raster(&mock_canvas, (*tile_iter)->content_rect(), + 1.0f, &pixelsRasterized); + + // This test verifies that when drawing the contents of a specific tile + // at content scale 1.0, the playback canvas never receives content from + // neighboring tiles which indicates that the tile grid embedded in + // SkPicture is perfectly aligned with the compositor's tiles. + // Note: There are two rects: the initial clear and the explicitly + // recorded rect. We only care about the second one. + EXPECT_EQ(2, mock_canvas.rects_.size()); + EXPECT_EQ(*rect_iter, mock_canvas.rects_[1]); + rect_iter++; + } + } + + FakeImplProxy proxy_; + FakeLayerTreeHostImpl host_impl_; + int id_; + TestablePictureLayerImpl* pending_layer_; + TestablePictureLayerImpl* active_layer_; + + DISALLOW_COPY_AND_ASSIGN(PictureLayerImplTest); +}; + +TEST_F(PictureLayerImplTest, tileGridAlignment) { + host_impl_.SetDeviceScaleFactor(1.f); + TestTileGridAlignmentCommon(); +} + +TEST_F(PictureLayerImplTest, tileGridAlignmentHiDPI) { + host_impl_.SetDeviceScaleFactor(2.f); + TestTileGridAlignmentCommon(); +} + +TEST_F(PictureLayerImplTest, cloneNoInvalidation) { + gfx::Size tile_size(100, 100); + gfx::Size layer_bounds(400, 400); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + + SetupTrees(pending_pile, active_pile); + + Region invalidation; + AddDefaultTilingsWithInvalidation(invalidation); + + EXPECT_EQ(pending_layer_->tilings().num_tilings(), + active_layer_->tilings().num_tilings()); + + const PictureLayerTilingSet& tilings = pending_layer_->tilings(); + EXPECT_GT(tilings.num_tilings(), 0u); + for (size_t i = 0; i < tilings.num_tilings(); ++i) + VerifyAllTilesExistAndHavePile(tilings.tiling_at(i), active_pile.get()); +} + +TEST_F(PictureLayerImplTest, clonePartialInvalidation) { + gfx::Size tile_size(100, 100); + gfx::Size layer_bounds(400, 400); + gfx::Rect layer_invalidation(150, 200, 30, 180); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + + SetupTrees(pending_pile, active_pile); + + Region invalidation(layer_invalidation); + AddDefaultTilingsWithInvalidation(invalidation); + + const PictureLayerTilingSet& tilings = pending_layer_->tilings(); + EXPECT_GT(tilings.num_tilings(), 0u); + for (size_t i = 0; i < tilings.num_tilings(); ++i) { + const PictureLayerTiling* tiling = tilings.tiling_at(i); + gfx::Rect content_invalidation = gfx::ToEnclosingRect(gfx::ScaleRect( + layer_invalidation, + tiling->contents_scale())); + for (PictureLayerTiling::Iterator + iter(tiling, + tiling->contents_scale(), + tiling->ContentRect(), + PictureLayerTiling::LayerDeviceAlignmentUnknown); + iter; + ++iter) { + EXPECT_TRUE(*iter); + EXPECT_FALSE(iter.geometry_rect().IsEmpty()); + if (iter.geometry_rect().Intersects(content_invalidation)) + EXPECT_EQ(pending_pile, iter->picture_pile()); + else + EXPECT_EQ(active_pile, iter->picture_pile()); + } + } +} + +TEST_F(PictureLayerImplTest, cloneFullInvalidation) { + gfx::Size tile_size(90, 80); + gfx::Size layer_bounds(300, 500); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + + SetupTrees(pending_pile, active_pile); + + Region invalidation((gfx::Rect(layer_bounds))); + AddDefaultTilingsWithInvalidation(invalidation); + + EXPECT_EQ(pending_layer_->tilings().num_tilings(), + active_layer_->tilings().num_tilings()); + + const PictureLayerTilingSet& tilings = pending_layer_->tilings(); + EXPECT_GT(tilings.num_tilings(), 0u); + for (size_t i = 0; i < tilings.num_tilings(); ++i) + VerifyAllTilesExistAndHavePile(tilings.tiling_at(i), pending_pile.get()); +} + +TEST_F(PictureLayerImplTest, noInvalidationBoundsChange) { + gfx::Size tile_size(90, 80); + gfx::Size active_layer_bounds(300, 500); + gfx::Size pending_layer_bounds(400, 800); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, + pending_layer_bounds); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, active_layer_bounds); + + SetupTrees(pending_pile, active_pile); + + Region invalidation; + AddDefaultTilingsWithInvalidation(invalidation); + + const PictureLayerTilingSet& tilings = pending_layer_->tilings(); + EXPECT_GT(tilings.num_tilings(), 0u); + for (size_t i = 0; i < tilings.num_tilings(); ++i) { + const PictureLayerTiling* tiling = tilings.tiling_at(i); + gfx::Rect active_content_bounds = gfx::ToEnclosingRect(gfx::ScaleRect( + gfx::Rect(active_layer_bounds), + tiling->contents_scale())); + for (PictureLayerTiling::Iterator + iter(tiling, + tiling->contents_scale(), + tiling->ContentRect(), + PictureLayerTiling::LayerDeviceAlignmentUnknown); + iter; + ++iter) { + EXPECT_TRUE(*iter); + EXPECT_FALSE(iter.geometry_rect().IsEmpty()); + if (iter.geometry_rect().right() >= active_content_bounds.width() || + iter.geometry_rect().bottom() >= active_content_bounds.height()) { + EXPECT_EQ(pending_pile, iter->picture_pile()); + } else { + EXPECT_EQ(active_pile, iter->picture_pile()); + } + } + } +} + +TEST_F(PictureLayerImplTest, addTilesFromNewRecording) { + gfx::Size tile_size(400, 400); + gfx::Size layer_bounds(1300, 1900); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds); + + // Fill in some of active pile, but more of pending pile. + int hole_count = 0; + for (int x = 0; x < active_pile->tiling().num_tiles_x(); ++x) { + for (int y = 0; y < active_pile->tiling().num_tiles_y(); ++y) { + if ((x + y) % 2) { + pending_pile->AddRecordingAt(x, y); + active_pile->AddRecordingAt(x, y); + } else { + hole_count++; + if (hole_count % 2) + pending_pile->AddRecordingAt(x, y); + } + } + } + + SetupTrees(pending_pile, active_pile); + Region invalidation; + AddDefaultTilingsWithInvalidation(invalidation); + + const PictureLayerTilingSet& tilings = pending_layer_->tilings(); + EXPECT_GT(tilings.num_tilings(), 0u); + for (size_t i = 0; i < tilings.num_tilings(); ++i) { + const PictureLayerTiling* tiling = tilings.tiling_at(i); + + for (PictureLayerTiling::Iterator + iter(tiling, + tiling->contents_scale(), + tiling->ContentRect(), + PictureLayerTiling::LayerDeviceAlignmentUnknown); + iter; + ++iter) { + EXPECT_FALSE(iter.full_tile_geometry_rect().IsEmpty()); + // Ensure there is a recording for this tile. + gfx::Rect layer_rect = gfx::ToEnclosingRect(gfx::ScaleRect( + iter.full_tile_geometry_rect(), 1.f / tiling->contents_scale())); + layer_rect.Intersect(gfx::Rect(layer_bounds)); + + bool in_pending = pending_pile->recorded_region().Contains(layer_rect); + bool in_active = active_pile->recorded_region().Contains(layer_rect); + + if (in_pending && !in_active) + EXPECT_EQ(pending_pile, iter->picture_pile()); + else if (in_active) + EXPECT_EQ(active_pile, iter->picture_pile()); + else + EXPECT_FALSE(*iter); + } + } +} + +TEST_F(PictureLayerImplTest, ManageTilingsWithNoRecording) { + gfx::Size tile_size(400, 400); + gfx::Size layer_bounds(1300, 1900); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateEmptyPile(tile_size, layer_bounds); + + float result_scale_x, result_scale_y; + gfx::Size result_bounds; + + SetupTrees(pending_pile, active_pile); + + // These are included in the scale given to the layer. + host_impl_.SetDeviceScaleFactor(1.f); + host_impl_.pending_tree()->SetPageScaleFactorAndLimits(1.f, 1.f, 1.f); + + pending_layer_->CalculateContentsScale( + 1.f, false, &result_scale_x, &result_scale_y, &result_bounds); + + EXPECT_EQ(0u, pending_layer_->tilings().num_tilings()); +} + +TEST_F(PictureLayerImplTest, ManageTilingsCreatesTilings) { + gfx::Size tile_size(400, 400); + gfx::Size layer_bounds(1300, 1900); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + + float result_scale_x, result_scale_y; + gfx::Size result_bounds; + + SetupTrees(pending_pile, active_pile); + EXPECT_EQ(0u, pending_layer_->tilings().num_tilings()); + + float low_res_factor = host_impl_.settings().lowResContentsScaleFactor; + EXPECT_LT(low_res_factor, 1.f); + + // These are included in the scale given to the layer. + host_impl_.SetDeviceScaleFactor(1.7f); + host_impl_.pending_tree()->SetPageScaleFactorAndLimits(3.2f, 3.2f, 3.2f); + + pending_layer_->CalculateContentsScale( + 1.3f, false, &result_scale_x, &result_scale_y, &result_bounds); + ASSERT_EQ(2u, pending_layer_->tilings().num_tilings()); + EXPECT_FLOAT_EQ( + 1.3f, + pending_layer_->tilings().tiling_at(0)->contents_scale()); + EXPECT_FLOAT_EQ( + 1.3f * low_res_factor, + pending_layer_->tilings().tiling_at(1)->contents_scale()); + + // If we change the layer's CSS scale factor, then we should not get new + // tilings. + pending_layer_->CalculateContentsScale( + 1.8f, false, &result_scale_x, &result_scale_y, &result_bounds); + ASSERT_EQ(2u, pending_layer_->tilings().num_tilings()); + EXPECT_FLOAT_EQ( + 1.3f, + pending_layer_->tilings().tiling_at(0)->contents_scale()); + EXPECT_FLOAT_EQ( + 1.3f * low_res_factor, + pending_layer_->tilings().tiling_at(1)->contents_scale()); + + // If we change the page scale factor, then we should get new tilings. + host_impl_.pending_tree()->SetPageScaleFactorAndLimits(2.2f, 2.2f, 2.2f); + + pending_layer_->CalculateContentsScale( + 1.8f, false, &result_scale_x, &result_scale_y, &result_bounds); + ASSERT_EQ(4u, pending_layer_->tilings().num_tilings()); + EXPECT_FLOAT_EQ( + 1.8f, + pending_layer_->tilings().tiling_at(0)->contents_scale()); + EXPECT_FLOAT_EQ( + 1.8f * low_res_factor, + pending_layer_->tilings().tiling_at(2)->contents_scale()); + + // If we change the device scale factor, then we should get new tilings. + host_impl_.SetDeviceScaleFactor(1.4f); + + pending_layer_->CalculateContentsScale( + 1.9f, false, &result_scale_x, &result_scale_y, &result_bounds); + ASSERT_EQ(6u, pending_layer_->tilings().num_tilings()); + EXPECT_FLOAT_EQ( + 1.9f, + pending_layer_->tilings().tiling_at(0)->contents_scale()); + EXPECT_FLOAT_EQ( + 1.9f * low_res_factor, + pending_layer_->tilings().tiling_at(3)->contents_scale()); + + // If we change the device scale factor, but end up at the same total scale + // factor somehow, then we don't get new tilings. + host_impl_.SetDeviceScaleFactor(2.2f); + host_impl_.pending_tree()->SetPageScaleFactorAndLimits(1.4f, 1.4f, 1.4f); + + pending_layer_->CalculateContentsScale( + 1.9f, false, &result_scale_x, &result_scale_y, &result_bounds); + ASSERT_EQ(6u, pending_layer_->tilings().num_tilings()); + EXPECT_FLOAT_EQ( + 1.9f, + pending_layer_->tilings().tiling_at(0)->contents_scale()); + EXPECT_FLOAT_EQ( + 1.9f * low_res_factor, + pending_layer_->tilings().tiling_at(3)->contents_scale()); +} + +TEST_F(PictureLayerImplTest, CleanUpTilings) { + gfx::Size tile_size(400, 400); + gfx::Size layer_bounds(1300, 1900); + + scoped_refptr<TestablePicturePileImpl> pending_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + scoped_refptr<TestablePicturePileImpl> active_pile = + TestablePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + + float result_scale_x, result_scale_y; + gfx::Size result_bounds; + std::vector<PictureLayerTiling*> used_tilings; + + SetupTrees(pending_pile, active_pile); + EXPECT_EQ(0u, pending_layer_->tilings().num_tilings()); + + float low_res_factor = host_impl_.settings().lowResContentsScaleFactor; + EXPECT_LT(low_res_factor, 1.f); + + // These are included in the scale given to the layer. + host_impl_.SetDeviceScaleFactor(1.7f); + host_impl_.pending_tree()->SetPageScaleFactorAndLimits(3.2f, 3.2f, 3.2f); + host_impl_.active_tree()->SetPageScaleFactorAndLimits(3.2f, 3.2f, 3.2f); + + SetContentsScaleOnBothLayers(1.f, false); + ASSERT_EQ(2u, active_layer_->tilings().num_tilings()); + + // We only have ideal tilings, so they aren't removed. + used_tilings.clear(); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(2u, active_layer_->tilings().num_tilings()); + + // Changing the ideal but not creating new tilings. + SetContentsScaleOnBothLayers(1.5f, false); + ASSERT_EQ(2u, active_layer_->tilings().num_tilings()); + + // The tilings are still our target scale, so they aren't removed. + used_tilings.clear(); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(2u, active_layer_->tilings().num_tilings()); + + // Create a 1.2 scale tiling. Now we have 1.0 and 1.2 tilings. Ideal = 1.2. + host_impl_.pending_tree()->SetPageScaleFactorAndLimits(1.2f, 1.2f, 1.2f); + host_impl_.active_tree()->SetPageScaleFactorAndLimits(1.2f, 1.2f, 1.2f); + SetContentsScaleOnBothLayers(1.2f, false); + ASSERT_EQ(4u, active_layer_->tilings().num_tilings()); + EXPECT_FLOAT_EQ( + 1.f, + active_layer_->tilings().tiling_at(1)->contents_scale()); + EXPECT_FLOAT_EQ( + 1.f * low_res_factor, + active_layer_->tilings().tiling_at(3)->contents_scale()); + + // Mark the non-ideal tilings as used. They won't be removed. + used_tilings.clear(); + used_tilings.push_back(active_layer_->tilings().tiling_at(1)); + used_tilings.push_back(active_layer_->tilings().tiling_at(3)); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(4u, active_layer_->tilings().num_tilings()); + + // Now move the ideal scale to 0.5. Our target stays 1.2. + SetContentsScaleOnBothLayers(0.5f, false); + + // All the tilings are between are target and the ideal, so they are not + // removed. + used_tilings.clear(); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(4u, active_layer_->tilings().num_tilings()); + + // Now move the ideal scale to 1.0. Our target stays 1.2. + SetContentsScaleOnBothLayers(1.f, false); + + // All the tilings are between are target and the ideal, so they are not + // removed. + used_tilings.clear(); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(4u, active_layer_->tilings().num_tilings()); + + // Now move the ideal scale to 1.1 on the active layer. Our target stays 1.2. + active_layer_->CalculateContentsScale( + 1.1f, false, &result_scale_x, &result_scale_y, &result_bounds); + + // Because the pending layer's ideal scale is still 1.0, our tilings fall + // in the range [1.0,1.2] and are kept. + used_tilings.clear(); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(4u, active_layer_->tilings().num_tilings()); + + // Move the ideal scale on the pending layer to 1.1 as well. Our target stays + // 1.2 still. + pending_layer_->CalculateContentsScale( + 1.1f, false, &result_scale_x, &result_scale_y, &result_bounds); + + // Our 1.0 tiling now falls outside the range between our ideal scale and our + // target raster scale. But it is in our used tilings set, so nothing is + // deleted. + used_tilings.clear(); + used_tilings.push_back(active_layer_->tilings().tiling_at(1)); + used_tilings.push_back(active_layer_->tilings().tiling_at(3)); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(4u, active_layer_->tilings().num_tilings()); + + // If we remove it from our used tilings set, it is outside the range to keep + // so it is deleted. Try one tiling at a time. + used_tilings.clear(); + used_tilings.push_back(active_layer_->tilings().tiling_at(1)); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(3u, active_layer_->tilings().num_tilings()); + used_tilings.clear(); + active_layer_->CleanUpTilingsOnActiveLayer(used_tilings); + ASSERT_EQ(2u, active_layer_->tilings().num_tilings()); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/quad_sink.h b/cc/layers/quad_sink.h new file mode 100644 index 0000000..041b655 --- /dev/null +++ b/cc/layers/quad_sink.h @@ -0,0 +1,36 @@ +// Copyright 2012 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 CC_LAYERS_QUAD_SINK_H_ +#define CC_LAYERS_QUAD_SINK_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" + +namespace cc { + +class DrawQuad; +class SharedQuadState; + +struct AppendQuadsData; + +class CC_EXPORT QuadSink { + public: + virtual ~QuadSink() {} + + // Call this to add a SharedQuadState before appending quads that refer to it. + // Returns a pointer to the given SharedQuadState for convenience, that can be + // set on the quads to append. + virtual SharedQuadState* UseSharedQuadState( + scoped_ptr<SharedQuadState> shared_quad_state) = 0; + + // Returns true if the quad is added to the list, and false if the quad is + // entirely culled. + virtual bool Append(scoped_ptr<DrawQuad> draw_quad, + AppendQuadsData* append_quads_data) = 0; +}; + +} // namespace cc + +#endif // CC_LAYERS_QUAD_SINK_H_ diff --git a/cc/layers/render_pass_sink.h b/cc/layers/render_pass_sink.h new file mode 100644 index 0000000..d307739e6 --- /dev/null +++ b/cc/layers/render_pass_sink.h @@ -0,0 +1,21 @@ +// Copyright 2012 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 CC_LAYERS_RENDER_PASS_SINK_H_ +#define CC_LAYERS_RENDER_PASS_SINK_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" + +namespace cc { +class RenderPass; + +class CC_EXPORT RenderPassSink { + public: + virtual void AppendRenderPass(scoped_ptr<RenderPass> render_pass) = 0; +}; + +} // namespace cc + +#endif // CC_LAYERS_RENDER_PASS_SINK_H_ diff --git a/cc/layers/render_surface.cc b/cc/layers/render_surface.cc new file mode 100644 index 0000000..4eb5d34 --- /dev/null +++ b/cc/layers/render_surface.cc @@ -0,0 +1,33 @@ +// Copyright 2010 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 "cc/layers/render_surface.h" + +#include "cc/base/math_util.h" +#include "cc/layers/layer.h" +#include "ui/gfx/transform.h" + +namespace cc { + +RenderSurface::RenderSurface(Layer* owning_layer) + : owning_layer_(owning_layer), + draw_opacity_(1), + draw_opacity_is_animating_(false), + target_surface_transforms_are_animating_(false), + screen_space_transforms_are_animating_(false), + is_clipped_(false), + nearest_ancestor_that_moves_pixels_(NULL) {} + +RenderSurface::~RenderSurface() {} + +gfx::RectF RenderSurface::DrawableContentRect() const { + gfx::RectF drawable_content_rect = + MathUtil::MapClippedRect(draw_transform_, content_rect_); + if (owning_layer_->has_replica()) + drawable_content_rect.Union( + MathUtil::MapClippedRect(replica_draw_transform_, content_rect_)); + return drawable_content_rect; +} + +} // namespace cc diff --git a/cc/layers/render_surface.h b/cc/layers/render_surface.h new file mode 100644 index 0000000..4373e34 --- /dev/null +++ b/cc/layers/render_surface.h @@ -0,0 +1,139 @@ +// Copyright 2010 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 CC_LAYERS_RENDER_SURFACE_H_ +#define CC_LAYERS_RENDER_SURFACE_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "cc/base/cc_export.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/rect_f.h" +#include "ui/gfx/transform.h" + +namespace cc { + +class Layer; + +class CC_EXPORT RenderSurface { + public: + explicit RenderSurface(Layer* owning_layer); + ~RenderSurface(); + + // Returns the rect that encloses the RenderSurfaceImpl including any + // reflection. + gfx::RectF DrawableContentRect() const; + + void SetContentRect(gfx::Rect content_rect) { content_rect_ = content_rect; } + gfx::Rect content_rect() const { return content_rect_; } + + void SetDrawOpacity(float opacity) { draw_opacity_ = opacity; } + float draw_opacity() const { return draw_opacity_; } + + void SetDrawOpacityIsAnimating(bool draw_opacity_is_animating) { + draw_opacity_is_animating_ = draw_opacity_is_animating; + } + bool draw_opacity_is_animating() const { return draw_opacity_is_animating_; } + + void SetDrawTransform(const gfx::Transform& draw_transform) { + draw_transform_ = draw_transform; + } + const gfx::Transform& draw_transform() const { return draw_transform_; } + + void SetScreenSpaceTransform(const gfx::Transform& screen_space_transform) { + screen_space_transform_ = screen_space_transform; + } + const gfx::Transform& screen_space_transform() const { + return screen_space_transform_; + } + + void SetReplicaDrawTransform(const gfx::Transform& replica_draw_transform) { + replica_draw_transform_ = replica_draw_transform; + } + const gfx::Transform& replica_draw_transform() const { + return replica_draw_transform_; + } + + void SetReplicaScreenSpaceTransform( + const gfx::Transform& replica_screen_space_transform) { + replica_screen_space_transform_ = replica_screen_space_transform; + } + const gfx::Transform& replica_screen_space_transform() const { + return replica_screen_space_transform_; + } + + void SetTargetSurfaceTransformsAreAnimating(bool animating) { + target_surface_transforms_are_animating_ = animating; + } + bool target_surface_transforms_are_animating() const { + return target_surface_transforms_are_animating_; + } + void SetScreenSpaceTransformsAreAnimating(bool animating) { + screen_space_transforms_are_animating_ = animating; + } + bool screen_space_transforms_are_animating() const { + return screen_space_transforms_are_animating_; + } + + bool is_clipped() const { return is_clipped_; } + void SetIsClipped(bool is_clipped) { is_clipped_ = is_clipped; } + + gfx::Rect clip_rect() const { return clip_rect_; } + void SetClipRect(gfx::Rect clip_rect) { clip_rect_ = clip_rect; } + + typedef std::vector<scoped_refptr<Layer> > LayerList; + LayerList& layer_list() { return layer_list_; } + // A no-op since DelegatedRendererLayers on the main thread don't have any + // RenderPasses so they can't contribute to a surface. + void AddContributingDelegatedRenderPassLayer(Layer* layer) {} + void ClearLayerLists() { layer_list_.clear(); } + + void SetNearestAncestorThatMovesPixels(RenderSurface* surface) { + nearest_ancestor_that_moves_pixels_ = surface; + } + const RenderSurface* nearest_ancestor_that_moves_pixels() const { + return nearest_ancestor_that_moves_pixels_; + } + + private: + friend struct LayerIteratorActions; + + Layer* owning_layer_; + + // Uses this surface's space. + gfx::Rect content_rect_; + + float draw_opacity_; + bool draw_opacity_is_animating_; + gfx::Transform draw_transform_; + gfx::Transform screen_space_transform_; + gfx::Transform replica_draw_transform_; + gfx::Transform replica_screen_space_transform_; + bool target_surface_transforms_are_animating_; + bool screen_space_transforms_are_animating_; + + bool is_clipped_; + + // Uses the space of the surface's target surface. + gfx::Rect clip_rect_; + + LayerList layer_list_; + + // The nearest ancestor target surface that will contain the contents of this + // surface, and that is going to move pixels within the surface (such as with + // a blur). This can point to itself. + RenderSurface* nearest_ancestor_that_moves_pixels_; + + // For LayerIteratorActions + int target_render_surface_layer_index_history_; + int current_layer_index_history_; + + DISALLOW_COPY_AND_ASSIGN(RenderSurface); +}; + +} +#endif // CC_LAYERS_RENDER_SURFACE_H_ diff --git a/cc/layers/render_surface_impl.cc b/cc/layers/render_surface_impl.cc new file mode 100644 index 0000000..b437b2c --- /dev/null +++ b/cc/layers/render_surface_impl.cc @@ -0,0 +1,290 @@ +// Copyright 2011 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 "cc/layers/render_surface_impl.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/stringprintf.h" +#include "cc/base/math_util.h" +#include "cc/debug/debug_colors.h" +#include "cc/layers/delegated_renderer_layer_impl.h" +#include "cc/layers/layer_impl.h" +#include "cc/layers/quad_sink.h" +#include "cc/layers/render_pass_sink.h" +#include "cc/quads/debug_border_draw_quad.h" +#include "cc/quads/render_pass.h" +#include "cc/quads/render_pass_draw_quad.h" +#include "cc/quads/shared_quad_state.h" +#include "cc/trees/damage_tracker.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "ui/gfx/rect_conversions.h" +#include "ui/gfx/transform.h" + +namespace cc { + +RenderSurfaceImpl::RenderSurfaceImpl(LayerImpl* owning_layer) + : owning_layer_(owning_layer), + surface_property_changed_(false), + draw_opacity_(1), + draw_opacity_is_animating_(false), + target_surface_transforms_are_animating_(false), + screen_space_transforms_are_animating_(false), + is_clipped_(false), + nearest_ancestor_that_moves_pixels_(NULL), + target_render_surface_layer_index_history_(0), + current_layer_index_history_(0) { + damage_tracker_ = DamageTracker::Create(); +} + +RenderSurfaceImpl::~RenderSurfaceImpl() {} + +gfx::RectF RenderSurfaceImpl::DrawableContentRect() const { + gfx::RectF drawable_content_rect = + MathUtil::MapClippedRect(draw_transform_, content_rect_); + if (owning_layer_->has_replica()) { + drawable_content_rect.Union( + MathUtil::MapClippedRect(replica_draw_transform_, content_rect_)); + } + + return drawable_content_rect; +} + +std::string RenderSurfaceImpl::Name() const { + return base::StringPrintf("RenderSurfaceImpl(id=%i,owner=%s)", + owning_layer_->id(), + owning_layer_->debug_name().data()); +} + +static std::string IndentString(int indent) { + std::string str; + for (int i = 0; i != indent; ++i) + str.append(" "); + return str; +} + +void RenderSurfaceImpl::DumpSurface(std::string* str, int indent) const { + std::string indent_str = IndentString(indent); + str->append(indent_str); + base::StringAppendF(str, "%s\n", Name().data()); + + indent_str.append(" "); + str->append(indent_str); + base::StringAppendF(str, + "content_rect: (%d, %d, %d, %d)\n", + content_rect_.x(), + content_rect_.y(), + content_rect_.width(), + content_rect_.height()); + + str->append(indent_str); + base::StringAppendF(str, + "draw_transform: " + "%f, %f, %f, %f, " + "%f, %f, %f, %f, " + "%f, %f, %f, %f, " + "%f, %f, %f, %f\n", + draw_transform_.matrix().getDouble(0, 0), + draw_transform_.matrix().getDouble(0, 1), + draw_transform_.matrix().getDouble(0, 2), + draw_transform_.matrix().getDouble(0, 3), + draw_transform_.matrix().getDouble(1, 0), + draw_transform_.matrix().getDouble(1, 1), + draw_transform_.matrix().getDouble(1, 2), + draw_transform_.matrix().getDouble(1, 3), + draw_transform_.matrix().getDouble(2, 0), + draw_transform_.matrix().getDouble(2, 1), + draw_transform_.matrix().getDouble(2, 2), + draw_transform_.matrix().getDouble(2, 3), + draw_transform_.matrix().getDouble(3, 0), + draw_transform_.matrix().getDouble(3, 1), + draw_transform_.matrix().getDouble(3, 2), + draw_transform_.matrix().getDouble(3, 3)); + + str->append(indent_str); + base::StringAppendF(str, + "damageRect is pos(%f, %f), size(%f, %f)\n", + damage_tracker_->current_damage_rect().x(), + damage_tracker_->current_damage_rect().y(), + damage_tracker_->current_damage_rect().width(), + damage_tracker_->current_damage_rect().height()); +} + +int RenderSurfaceImpl::OwningLayerId() const { + return owning_layer_ ? owning_layer_->id() : 0; +} + + +void RenderSurfaceImpl::SetClipRect(gfx::Rect clip_rect) { + if (clip_rect_ == clip_rect) + return; + + surface_property_changed_ = true; + clip_rect_ = clip_rect; +} + +bool RenderSurfaceImpl::ContentsChanged() const { + return !damage_tracker_->current_damage_rect().IsEmpty(); +} + +void RenderSurfaceImpl::SetContentRect(gfx::Rect content_rect) { + if (content_rect_ == content_rect) + return; + + surface_property_changed_ = true; + content_rect_ = content_rect; +} + +bool RenderSurfaceImpl::SurfacePropertyChanged() const { + // Surface property changes are tracked as follows: + // + // - surface_property_changed_ is flagged when the clip_rect or content_rect + // change. As of now, these are the only two properties that can be affected + // by descendant layers. + // + // - all other property changes come from the owning layer (or some ancestor + // layer that propagates its change to the owning layer). + // + DCHECK(owning_layer_); + return surface_property_changed_ || owning_layer_->LayerPropertyChanged(); +} + +bool RenderSurfaceImpl::SurfacePropertyChangedOnlyFromDescendant() const { + return surface_property_changed_ && !owning_layer_->LayerPropertyChanged(); +} + +void RenderSurfaceImpl::AddContributingDelegatedRenderPassLayer( + LayerImpl* layer) { + DCHECK(std::find(layer_list_.begin(), layer_list_.end(), layer) != + layer_list_.end()); + DelegatedRendererLayerImpl* delegated_renderer_layer = + static_cast<DelegatedRendererLayerImpl*>(layer); + contributing_delegated_render_pass_layer_list_.push_back( + delegated_renderer_layer); +} + +void RenderSurfaceImpl::ClearLayerLists() { + layer_list_.clear(); + contributing_delegated_render_pass_layer_list_.clear(); +} + +RenderPass::Id RenderSurfaceImpl::RenderPassId() { + int layer_id = owning_layer_->id(); + int sub_id = 0; + DCHECK_GT(layer_id, 0); + return RenderPass::Id(layer_id, sub_id); +} + +void RenderSurfaceImpl::AppendRenderPasses(RenderPassSink* pass_sink) { + for (size_t i = 0; + i < contributing_delegated_render_pass_layer_list_.size(); + ++i) { + DelegatedRendererLayerImpl* delegated_renderer_layer = + contributing_delegated_render_pass_layer_list_[i]; + delegated_renderer_layer->AppendContributingRenderPasses(pass_sink); + } + + scoped_ptr<RenderPass> pass = RenderPass::Create(); + pass->SetNew(RenderPassId(), + content_rect_, + damage_tracker_->current_damage_rect(), + screen_space_transform_); + pass_sink->AppendRenderPass(pass.Pass()); +} + +void RenderSurfaceImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data, + bool for_replica, + RenderPass::Id render_pass_id) { + DCHECK(!for_replica || owning_layer_->has_replica()); + + const gfx::Transform& draw_transform = + for_replica ? replica_draw_transform_ : draw_transform_; + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(SharedQuadState::Create()); + shared_quad_state->SetAll(draw_transform, + content_rect_.size(), + content_rect_, + clip_rect_, + is_clipped_, + draw_opacity_); + + if (owning_layer_->ShowDebugBorders()) { + SkColor color = for_replica ? + DebugColors::SurfaceReplicaBorderColor() : + DebugColors::SurfaceBorderColor(); + float width = for_replica ? + DebugColors::SurfaceReplicaBorderWidth( + owning_layer_->layer_tree_impl()) : + DebugColors::SurfaceBorderWidth( + owning_layer_->layer_tree_impl()); + scoped_ptr<DebugBorderDrawQuad> debug_border_quad = + DebugBorderDrawQuad::Create(); + debug_border_quad->SetNew(shared_quad_state, content_rect_, color, width); + quad_sink->Append(debug_border_quad.PassAs<DrawQuad>(), append_quads_data); + } + + // FIXME: By using the same RenderSurfaceImpl for both the content and its + // reflection, it's currently not possible to apply a separate mask to the + // reflection layer or correctly handle opacity in reflections (opacity must + // be applied after drawing both the layer and its reflection). The solution + // is to introduce yet another RenderSurfaceImpl to draw the layer and its + // reflection in. For now we only apply a separate reflection mask if the + // contents don't have a mask of their own. + LayerImpl* mask_layer = owning_layer_->mask_layer(); + if (mask_layer && + (!mask_layer->DrawsContent() || mask_layer->bounds().IsEmpty())) + mask_layer = NULL; + + if (!mask_layer && for_replica) { + mask_layer = owning_layer_->replica_layer()->mask_layer(); + if (mask_layer && + (!mask_layer->DrawsContent() || mask_layer->bounds().IsEmpty())) + mask_layer = NULL; + } + + gfx::RectF mask_uv_rect(0.f, 0.f, 1.f, 1.f); + if (mask_layer) { + gfx::Vector2dF owning_layer_draw_scale = + MathUtil::ComputeTransform2dScaleComponents( + owning_layer_->draw_transform(), 1.f); + gfx::SizeF unclipped_surface_size = gfx::ScaleSize( + owning_layer_->content_bounds(), + owning_layer_draw_scale.x(), + owning_layer_draw_scale.y()); + // This assumes that the owning layer clips its subtree when a mask is + // present. + DCHECK(gfx::RectF(unclipped_surface_size).Contains(content_rect_)); + + float uv_scale_x = content_rect_.width() / unclipped_surface_size.width(); + float uv_scale_y = content_rect_.height() / unclipped_surface_size.height(); + + mask_uv_rect = gfx::RectF( + uv_scale_x * content_rect_.x() / content_rect_.width(), + uv_scale_y * content_rect_.y() / content_rect_.height(), + uv_scale_x, + uv_scale_y); + } + + ResourceProvider::ResourceId mask_resource_id = + mask_layer ? mask_layer->ContentsResourceId() : 0; + gfx::Rect contents_changed_since_last_frame = + ContentsChanged() ? content_rect_ : gfx::Rect(); + + scoped_ptr<RenderPassDrawQuad> quad = RenderPassDrawQuad::Create(); + quad->SetNew(shared_quad_state, + content_rect_, + render_pass_id, + for_replica, + mask_resource_id, + contents_changed_since_last_frame, + mask_uv_rect, + owning_layer_->filters(), + owning_layer_->filter(), + owning_layer_->background_filters()); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); +} + +} // namespace cc diff --git a/cc/layers/render_surface_impl.h b/cc/layers/render_surface_impl.h new file mode 100644 index 0000000..1466bca --- /dev/null +++ b/cc/layers/render_surface_impl.h @@ -0,0 +1,171 @@ +// Copyright 2011 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 CC_LAYERS_RENDER_SURFACE_IMPL_H_ +#define CC_LAYERS_RENDER_SURFACE_IMPL_H_ + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/quads/render_pass.h" +#include "cc/quads/shared_quad_state.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/rect_f.h" +#include "ui/gfx/transform.h" + +namespace cc { + +class DamageTracker; +class DelegatedRendererLayerImpl; +class QuadSink; +class RenderPassSink; +class LayerImpl; + +struct AppendQuadsData; + +class CC_EXPORT RenderSurfaceImpl { + public: + explicit RenderSurfaceImpl(LayerImpl* owning_layer); + virtual ~RenderSurfaceImpl(); + + std::string Name() const; + void DumpSurface(std::string* str, int indent) const; + + gfx::PointF ContentRectCenter() const { + return gfx::RectF(content_rect_).CenterPoint(); + } + + // Returns the rect that encloses the RenderSurfaceImpl including any + // reflection. + gfx::RectF DrawableContentRect() const; + + void SetDrawOpacity(float opacity) { draw_opacity_ = opacity; } + float draw_opacity() const { return draw_opacity_; } + + void SetNearestAncestorThatMovesPixels(RenderSurfaceImpl* surface) { + nearest_ancestor_that_moves_pixels_ = surface; + } + const RenderSurfaceImpl* nearest_ancestor_that_moves_pixels() const { + return nearest_ancestor_that_moves_pixels_; + } + + void SetDrawOpacityIsAnimating(bool draw_opacity_is_animating) { + draw_opacity_is_animating_ = draw_opacity_is_animating; + } + bool draw_opacity_is_animating() const { return draw_opacity_is_animating_; } + + void SetDrawTransform(const gfx::Transform& draw_transform) { + draw_transform_ = draw_transform; + } + const gfx::Transform& draw_transform() const { return draw_transform_; } + + void SetScreenSpaceTransform(const gfx::Transform& screen_space_transform) { + screen_space_transform_ = screen_space_transform; + } + const gfx::Transform& screen_space_transform() const { + return screen_space_transform_; + } + + void SetReplicaDrawTransform(const gfx::Transform& replica_draw_transform) { + replica_draw_transform_ = replica_draw_transform; + } + const gfx::Transform& replica_draw_transform() const { + return replica_draw_transform_; + } + + void SetReplicaScreenSpaceTransform( + const gfx::Transform& replica_screen_space_transform) { + replica_screen_space_transform_ = replica_screen_space_transform; + } + const gfx::Transform& replica_screen_space_transform() const { + return replica_screen_space_transform_; + } + + void SetTargetSurfaceTransformsAreAnimating(bool animating) { + target_surface_transforms_are_animating_ = animating; + } + bool target_surface_transforms_are_animating() const { + return target_surface_transforms_are_animating_; + } + void SetScreenSpaceTransformsAreAnimating(bool animating) { + screen_space_transforms_are_animating_ = animating; + } + bool screen_space_transforms_are_animating() const { + return screen_space_transforms_are_animating_; + } + + void SetIsClipped(bool is_clipped) { is_clipped_ = is_clipped; } + bool is_clipped() const { return is_clipped_; } + + void SetClipRect(gfx::Rect clip_rect); + gfx::Rect clip_rect() const { return clip_rect_; } + + bool ContentsChanged() const; + + void SetContentRect(gfx::Rect content_rect); + gfx::Rect content_rect() const { return content_rect_; } + + std::vector<LayerImpl*>& layer_list() { return layer_list_; } + void AddContributingDelegatedRenderPassLayer(LayerImpl* layer); + void ClearLayerLists(); + + int OwningLayerId() const; + + void ResetPropertyChangedFlag() { surface_property_changed_ = false; } + bool SurfacePropertyChanged() const; + bool SurfacePropertyChangedOnlyFromDescendant() const; + + DamageTracker* damage_tracker() const { return damage_tracker_.get(); } + + RenderPass::Id RenderPassId(); + + void AppendRenderPasses(RenderPassSink* pass_sink); + void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data, + bool for_replica, + RenderPass::Id render_pass_id); + + private: + LayerImpl* owning_layer_; + + // Uses this surface's space. + gfx::Rect content_rect_; + bool surface_property_changed_; + + float draw_opacity_; + bool draw_opacity_is_animating_; + gfx::Transform draw_transform_; + gfx::Transform screen_space_transform_; + gfx::Transform replica_draw_transform_; + gfx::Transform replica_screen_space_transform_; + bool target_surface_transforms_are_animating_; + bool screen_space_transforms_are_animating_; + + bool is_clipped_; + + // Uses the space of the surface's target surface. + gfx::Rect clip_rect_; + + std::vector<LayerImpl*> layer_list_; + std::vector<DelegatedRendererLayerImpl*> + contributing_delegated_render_pass_layer_list_; + + // The nearest ancestor target surface that will contain the contents of this + // surface, and that is going to move pixels within the surface (such as with + // a blur). This can point to itself. + RenderSurfaceImpl* nearest_ancestor_that_moves_pixels_; + + scoped_ptr<DamageTracker> damage_tracker_; + + // For LayerIteratorActions + int target_render_surface_layer_index_history_; + int current_layer_index_history_; + + friend struct LayerIteratorActions; + + DISALLOW_COPY_AND_ASSIGN(RenderSurfaceImpl); +}; + +} +#endif // CC_LAYERS_RENDER_SURFACE_IMPL_H_ diff --git a/cc/layers/render_surface_unittest.cc b/cc/layers/render_surface_unittest.cc new file mode 100644 index 0000000..30d9480 --- /dev/null +++ b/cc/layers/render_surface_unittest.cc @@ -0,0 +1,159 @@ +// Copyright 2011 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 "cc/base/scoped_ptr_vector.h" +#include "cc/layers/append_quads_data.h" +#include "cc/layers/layer_impl.h" +#include "cc/layers/render_pass_sink.h" +#include "cc/layers/render_surface_impl.h" +#include "cc/quads/shared_quad_state.h" +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/geometry_test_utils.h" +#include "cc/test/mock_quad_culler.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/transform.h" + +namespace cc { +namespace { + +#define EXECUTE_AND_VERIFY_SURFACE_CHANGED(codeToTest) \ + renderSurface->ResetPropertyChangedFlag(); \ + codeToTest; \ + EXPECT_TRUE(renderSurface->SurfacePropertyChanged()) + +#define EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(codeToTest) \ + renderSurface->ResetPropertyChangedFlag(); \ + codeToTest; \ + EXPECT_FALSE(renderSurface->SurfacePropertyChanged()) + +TEST(RenderSurfaceTest, verifySurfaceChangesAreTrackedProperly) +{ + // + // This test checks that SurfacePropertyChanged() has the correct behavior. + // + + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + scoped_ptr<LayerImpl> owningLayer = LayerImpl::Create(hostImpl.active_tree(), 1); + owningLayer->CreateRenderSurface(); + ASSERT_TRUE(owningLayer->render_surface()); + RenderSurfaceImpl* renderSurface = owningLayer->render_surface(); + gfx::Rect testRect = gfx::Rect(gfx::Point(3, 4), gfx::Size(5, 6)); + owningLayer->ResetAllChangeTrackingForSubtree(); + + // Currently, the contentRect, clipRect, and owningLayer->layerPropertyChanged() are + // the only sources of change. + EXECUTE_AND_VERIFY_SURFACE_CHANGED(renderSurface->SetClipRect(testRect)); + EXECUTE_AND_VERIFY_SURFACE_CHANGED(renderSurface->SetContentRect(testRect)); + + owningLayer->SetOpacity(0.5f); + EXPECT_TRUE(renderSurface->SurfacePropertyChanged()); + owningLayer->ResetAllChangeTrackingForSubtree(); + + // Setting the surface properties to the same values again should not be considered "change". + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->SetClipRect(testRect)); + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->SetContentRect(testRect)); + + scoped_ptr<LayerImpl> dummyMask = LayerImpl::Create(hostImpl.active_tree(), 2); + gfx::Transform dummyMatrix; + dummyMatrix.Translate(1.0, 2.0); + + // The rest of the surface properties are either internal and should not cause change, + // or they are already accounted for by the owninglayer->layerPropertyChanged(). + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->SetDrawOpacity(0.5f)); + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->SetDrawTransform(dummyMatrix)); + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->SetReplicaDrawTransform(dummyMatrix)); + EXECUTE_AND_VERIFY_SURFACE_DID_NOT_CHANGE(renderSurface->ClearLayerLists()); +} + +TEST(RenderSurfaceTest, sanityCheckSurfaceCreatesCorrectSharedQuadState) +{ + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + scoped_ptr<LayerImpl> rootLayer = LayerImpl::Create(hostImpl.active_tree(), 1); + + scoped_ptr<LayerImpl> owningLayer = LayerImpl::Create(hostImpl.active_tree(), 2); + owningLayer->CreateRenderSurface(); + ASSERT_TRUE(owningLayer->render_surface()); + owningLayer->draw_properties().render_target = owningLayer.get(); + RenderSurfaceImpl* renderSurface = owningLayer->render_surface(); + + rootLayer->AddChild(owningLayer.Pass()); + + gfx::Rect contentRect = gfx::Rect(gfx::Point(), gfx::Size(50, 50)); + gfx::Rect clipRect = gfx::Rect(gfx::Point(5, 5), gfx::Size(40, 40)); + gfx::Transform origin; + + origin.Translate(30, 40); + + renderSurface->SetDrawTransform(origin); + renderSurface->SetContentRect(contentRect); + renderSurface->SetClipRect(clipRect); + renderSurface->SetDrawOpacity(1.f); + + QuadList quadList; + SharedQuadStateList sharedStateList; + MockQuadCuller mockQuadCuller(quadList, sharedStateList); + AppendQuadsData appendQuadsData; + + bool forReplica = false; + renderSurface->AppendQuads(&mockQuadCuller, &appendQuadsData, forReplica, RenderPass::Id(2, 0)); + + ASSERT_EQ(1u, sharedStateList.size()); + SharedQuadState* sharedQuadState = sharedStateList[0]; + + EXPECT_EQ(30, sharedQuadState->content_to_target_transform.matrix().getDouble(0, 3)); + EXPECT_EQ(40, sharedQuadState->content_to_target_transform.matrix().getDouble(1, 3)); + EXPECT_RECT_EQ(contentRect, gfx::Rect(sharedQuadState->visible_content_rect)); + EXPECT_EQ(1, sharedQuadState->opacity); +} + +class TestRenderPassSink : public RenderPassSink { +public: + virtual void AppendRenderPass(scoped_ptr<RenderPass> renderPass) OVERRIDE { m_renderPasses.push_back(renderPass.Pass()); } + + const ScopedPtrVector<RenderPass>& renderPasses() const { return m_renderPasses; } + +private: + ScopedPtrVector<RenderPass> m_renderPasses; +}; + +TEST(RenderSurfaceTest, sanityCheckSurfaceCreatesCorrectRenderPass) +{ + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + scoped_ptr<LayerImpl> rootLayer = LayerImpl::Create(hostImpl.active_tree(), 1); + + scoped_ptr<LayerImpl> owningLayer = LayerImpl::Create(hostImpl.active_tree(), 2); + owningLayer->CreateRenderSurface(); + ASSERT_TRUE(owningLayer->render_surface()); + owningLayer->draw_properties().render_target = owningLayer.get(); + RenderSurfaceImpl* renderSurface = owningLayer->render_surface(); + + rootLayer->AddChild(owningLayer.Pass()); + + gfx::Rect contentRect = gfx::Rect(gfx::Point(), gfx::Size(50, 50)); + gfx::Transform origin; + origin.Translate(30, 40); + + renderSurface->SetScreenSpaceTransform(origin); + renderSurface->SetContentRect(contentRect); + + TestRenderPassSink passSink; + + renderSurface->AppendRenderPasses(&passSink); + + ASSERT_EQ(1u, passSink.renderPasses().size()); + RenderPass* pass = passSink.renderPasses()[0]; + + EXPECT_EQ(RenderPass::Id(2, 0), pass->id); + EXPECT_RECT_EQ(contentRect, pass->output_rect); + EXPECT_EQ(origin, pass->transform_to_root_target); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/scrollbar_geometry_fixed_thumb.cc b/cc/layers/scrollbar_geometry_fixed_thumb.cc new file mode 100644 index 0000000..07c1cb5 --- /dev/null +++ b/cc/layers/scrollbar_geometry_fixed_thumb.cc @@ -0,0 +1,83 @@ +// Copyright 2012 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 "cc/layers/scrollbar_geometry_fixed_thumb.h" + +#include <cmath> + +#include "third_party/WebKit/Source/Platform/chromium/public/WebRect.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebScrollbar.h" + +using WebKit::WebRect; +using WebKit::WebScrollbar; +using WebKit::WebScrollbarThemeGeometry; + +namespace cc { + +scoped_ptr<ScrollbarGeometryFixedThumb> ScrollbarGeometryFixedThumb::create(scoped_ptr<WebScrollbarThemeGeometry> geometry) +{ + return make_scoped_ptr(new ScrollbarGeometryFixedThumb(geometry.Pass())); +} + +ScrollbarGeometryFixedThumb::~ScrollbarGeometryFixedThumb() +{ +} + +WebScrollbarThemeGeometry* ScrollbarGeometryFixedThumb::clone() const +{ + ScrollbarGeometryFixedThumb* geometry = new ScrollbarGeometryFixedThumb(make_scoped_ptr(ScrollbarGeometryStub::clone())); + geometry->m_thumbSize = m_thumbSize; + return geometry; +} + +int ScrollbarGeometryFixedThumb::thumbLength(WebScrollbar* scrollbar) +{ + if (scrollbar->orientation() == WebScrollbar::Horizontal) + return m_thumbSize.width(); + return m_thumbSize.height(); +} + +int ScrollbarGeometryFixedThumb::thumbPosition(WebScrollbar* scrollbar) +{ + if (scrollbar->enabled()) { + float size = scrollbar->maximum(); + if (!size) + return 1; + int value = std::min(std::max(0, scrollbar->value()), scrollbar->maximum()); + float pos = (trackLength(scrollbar) - thumbLength(scrollbar)) * value / size; + return static_cast<int>(floorf((pos < 1 && pos > 0) ? 1 : pos)); + } + return 0; +} +void ScrollbarGeometryFixedThumb::splitTrack(WebScrollbar* scrollbar, const WebRect& unconstrainedTrackRect, WebRect& beforeThumbRect, WebRect& thumbRect, WebRect& afterThumbRect) +{ + // This is a reimplementation of ScrollbarThemeComposite::splitTrack. + // Because the WebScrollbarThemeGeometry functions call down to native + // ScrollbarThemeComposite code which uses ScrollbarThemeComposite virtual + // helpers, there's no way to override a helper like thumbLength from + // the WebScrollbarThemeGeometry level. So, these three functions + // (splitTrack, thumbPosition, thumbLength) are copied here so that the + // WebScrollbarThemeGeometry helper functions are used instead and + // a fixed size thumbLength can be used. + + WebRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect); + int thickness = scrollbar->orientation() == WebScrollbar::Horizontal ? scrollbar->size().height : scrollbar->size().width; + int thumbPos = thumbPosition(scrollbar); + if (scrollbar->orientation() == WebScrollbar::Horizontal) { + thumbRect = WebRect(trackRect.x + thumbPos, trackRect.y + (trackRect.height - thickness) / 2, thumbLength(scrollbar), thickness); + beforeThumbRect = WebRect(trackRect.x, trackRect.y, thumbPos + thumbRect.width / 2, trackRect.height); + afterThumbRect = WebRect(trackRect.x + beforeThumbRect.width, trackRect.y, trackRect.x + trackRect.width - beforeThumbRect.x - beforeThumbRect.width, trackRect.height); + } else { + thumbRect = WebRect(trackRect.x + (trackRect.width - thickness) / 2, trackRect.y + thumbPos, thickness, thumbLength(scrollbar)); + beforeThumbRect = WebRect(trackRect.x, trackRect.y, trackRect.width, thumbPos + thumbRect.height / 2); + afterThumbRect = WebRect(trackRect.x, trackRect.y + beforeThumbRect.height, trackRect.width, trackRect.y + trackRect.height - beforeThumbRect.y - beforeThumbRect.height); + } +} + +ScrollbarGeometryFixedThumb::ScrollbarGeometryFixedThumb(scoped_ptr<WebScrollbarThemeGeometry> geometry) + : ScrollbarGeometryStub(geometry.Pass()) +{ +} + +} // namespace cc diff --git a/cc/layers/scrollbar_geometry_fixed_thumb.h b/cc/layers/scrollbar_geometry_fixed_thumb.h new file mode 100644 index 0000000..f941104 --- /dev/null +++ b/cc/layers/scrollbar_geometry_fixed_thumb.h @@ -0,0 +1,39 @@ +// Copyright 2012 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 CC_LAYERS_SCROLLBAR_GEOMETRY_FIXED_THUMB_H_ +#define CC_LAYERS_SCROLLBAR_GEOMETRY_FIXED_THUMB_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/scrollbar_geometry_stub.h" +#include "ui/gfx/size.h" + +namespace cc { + +// This scrollbar geometry class behaves exactly like a normal geometry except +// it always returns a fixed thumb length. This allows a page to zoom (changing +// the total size of the scrollable area, changing the thumb length) while not +// requiring the thumb resource to be repainted. +class CC_EXPORT ScrollbarGeometryFixedThumb : public ScrollbarGeometryStub { +public: + static scoped_ptr<ScrollbarGeometryFixedThumb> create(scoped_ptr<WebKit::WebScrollbarThemeGeometry>); + virtual ~ScrollbarGeometryFixedThumb(); + + void setThumbSize(gfx::Size size) { m_thumbSize = size; } + + // WebScrollbarThemeGeometry interface + virtual WebKit::WebScrollbarThemeGeometry* clone() const OVERRIDE; + virtual int thumbLength(WebKit::WebScrollbar*) OVERRIDE; + virtual int thumbPosition(WebKit::WebScrollbar*) OVERRIDE; + virtual void splitTrack(WebKit::WebScrollbar*, const WebKit::WebRect& track, WebKit::WebRect& startTrack, WebKit::WebRect& thumb, WebKit::WebRect& endTrack) OVERRIDE; + +private: + explicit ScrollbarGeometryFixedThumb(scoped_ptr<WebKit::WebScrollbarThemeGeometry>); + + gfx::Size m_thumbSize; +}; + +} + +#endif // CC_LAYERS_SCROLLBAR_GEOMETRY_FIXED_THUMB_H_ diff --git a/cc/layers/scrollbar_geometry_stub.cc b/cc/layers/scrollbar_geometry_stub.cc new file mode 100644 index 0000000..fa7b1da --- /dev/null +++ b/cc/layers/scrollbar_geometry_stub.cc @@ -0,0 +1,109 @@ +// Copyright 2012 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 "cc/layers/scrollbar_geometry_stub.h" + +#include <cmath> + +using WebKit::WebRect; +using WebKit::WebScrollbar; +using WebKit::WebScrollbarThemeGeometry; + +namespace cc { + +ScrollbarGeometryStub::ScrollbarGeometryStub(scoped_ptr<WebScrollbarThemeGeometry> geometry) + : m_geometry(geometry.Pass()) +{ +} + +ScrollbarGeometryStub::~ScrollbarGeometryStub() +{ +} + +WebScrollbarThemeGeometry* ScrollbarGeometryStub::clone() const +{ + return m_geometry->clone(); +} + +int ScrollbarGeometryStub::thumbPosition(WebScrollbar* scrollbar) +{ + return m_geometry->thumbPosition(scrollbar); +} + +int ScrollbarGeometryStub::thumbLength(WebScrollbar* scrollbar) +{ + return std::max(0, m_geometry->thumbLength(scrollbar)); +} + +int ScrollbarGeometryStub::trackPosition(WebScrollbar* scrollbar) +{ + return m_geometry->trackPosition(scrollbar); +} + +int ScrollbarGeometryStub::trackLength(WebScrollbar* scrollbar) +{ + return m_geometry->trackLength(scrollbar); +} + +bool ScrollbarGeometryStub::hasButtons(WebScrollbar* scrollbar) +{ + return m_geometry->hasButtons(scrollbar); +} + +bool ScrollbarGeometryStub::hasThumb(WebScrollbar* scrollbar) +{ + return m_geometry->hasThumb(scrollbar); +} + +WebRect ScrollbarGeometryStub::trackRect(WebScrollbar* scrollbar) +{ + return m_geometry->trackRect(scrollbar); +} + +WebRect ScrollbarGeometryStub::thumbRect(WebScrollbar* scrollbar) +{ + return m_geometry->thumbRect(scrollbar); +} + +int ScrollbarGeometryStub::minimumThumbLength(WebScrollbar* scrollbar) +{ + return m_geometry->minimumThumbLength(scrollbar); +} + +int ScrollbarGeometryStub::scrollbarThickness(WebScrollbar* scrollbar) +{ + return m_geometry->scrollbarThickness(scrollbar); +} + +WebRect ScrollbarGeometryStub::backButtonStartRect(WebScrollbar* scrollbar) +{ + return m_geometry->backButtonStartRect(scrollbar); +} + +WebRect ScrollbarGeometryStub::backButtonEndRect(WebScrollbar* scrollbar) +{ + return m_geometry->backButtonEndRect(scrollbar); +} + +WebRect ScrollbarGeometryStub::forwardButtonStartRect(WebScrollbar* scrollbar) +{ + return m_geometry->forwardButtonStartRect(scrollbar); +} + +WebRect ScrollbarGeometryStub::forwardButtonEndRect(WebScrollbar* scrollbar) +{ + return m_geometry->forwardButtonEndRect(scrollbar); +} + +WebRect ScrollbarGeometryStub::constrainTrackRectToTrackPieces(WebScrollbar* scrollbar, const WebRect& rect) +{ + return m_geometry->constrainTrackRectToTrackPieces(scrollbar, rect); +} + +void ScrollbarGeometryStub::splitTrack(WebScrollbar* scrollbar, const WebRect& unconstrainedTrackRect, WebRect& beforeThumbRect, WebRect& thumbRect, WebRect& afterThumbRect) +{ + m_geometry->splitTrack(scrollbar, unconstrainedTrackRect, beforeThumbRect, thumbRect, afterThumbRect); +} + +} // namespace cc diff --git a/cc/layers/scrollbar_geometry_stub.h b/cc/layers/scrollbar_geometry_stub.h new file mode 100644 index 0000000..d671475 --- /dev/null +++ b/cc/layers/scrollbar_geometry_stub.h @@ -0,0 +1,54 @@ +// Copyright 2012 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 CC_LAYERS_SCROLLBAR_GEOMETRY_STUB_H_ +#define CC_LAYERS_SCROLLBAR_GEOMETRY_STUB_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebScrollbarThemeGeometry.h" + +namespace cc { + +// This subclass wraps an existing scrollbar geometry class so that +// another class can derive from it and override specific functions, while +// passing through the remaining ones. +class CC_EXPORT ScrollbarGeometryStub : public NON_EXPORTED_BASE(WebKit::WebScrollbarThemeGeometry) { +public: + virtual ~ScrollbarGeometryStub(); + + // Allow derived classes to update themselves from a scrollbar. + void update(WebKit::WebScrollbar*) { } + + // WebScrollbarThemeGeometry interface + virtual WebKit::WebScrollbarThemeGeometry* clone() const OVERRIDE; + virtual int thumbPosition(WebKit::WebScrollbar*) OVERRIDE; + virtual int thumbLength(WebKit::WebScrollbar*) OVERRIDE; + virtual int trackPosition(WebKit::WebScrollbar*) OVERRIDE; + virtual int trackLength(WebKit::WebScrollbar*) OVERRIDE; + virtual bool hasButtons(WebKit::WebScrollbar*) OVERRIDE; + virtual bool hasThumb(WebKit::WebScrollbar*) OVERRIDE; + virtual WebKit::WebRect trackRect(WebKit::WebScrollbar*) OVERRIDE; + virtual WebKit::WebRect thumbRect(WebKit::WebScrollbar*) OVERRIDE; + virtual int minimumThumbLength(WebKit::WebScrollbar*) OVERRIDE; + virtual int scrollbarThickness(WebKit::WebScrollbar*) OVERRIDE; + virtual WebKit::WebRect backButtonStartRect(WebKit::WebScrollbar*) OVERRIDE; + virtual WebKit::WebRect backButtonEndRect(WebKit::WebScrollbar*) OVERRIDE; + virtual WebKit::WebRect forwardButtonStartRect(WebKit::WebScrollbar*) OVERRIDE; + virtual WebKit::WebRect forwardButtonEndRect(WebKit::WebScrollbar*) OVERRIDE; + virtual WebKit::WebRect constrainTrackRectToTrackPieces(WebKit::WebScrollbar*, const WebKit::WebRect&) OVERRIDE; + virtual void splitTrack(WebKit::WebScrollbar*, const WebKit::WebRect& track, WebKit::WebRect& startTrack, WebKit::WebRect& thumb, WebKit::WebRect& endTrack) OVERRIDE; + +protected: + explicit ScrollbarGeometryStub(scoped_ptr<WebKit::WebScrollbarThemeGeometry>); + +private: + scoped_ptr<WebKit::WebScrollbarThemeGeometry> m_geometry; + + DISALLOW_COPY_AND_ASSIGN(ScrollbarGeometryStub); +}; + +} + +#endif // CC_LAYERS_SCROLLBAR_GEOMETRY_STUB_H_ diff --git a/cc/layers/scrollbar_layer.cc b/cc/layers/scrollbar_layer.cc new file mode 100644 index 0000000..1550ad9 --- /dev/null +++ b/cc/layers/scrollbar_layer.cc @@ -0,0 +1,441 @@ +// Copyright 2012 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 "cc/layers/scrollbar_layer.h" + +#include "base/basictypes.h" +#include "base/debug/trace_event.h" +#include "cc/layers/scrollbar_layer_impl.h" +#include "cc/resources/caching_bitmap_content_layer_updater.h" +#include "cc/resources/layer_painter.h" +#include "cc/resources/prioritized_resource.h" +#include "cc/resources/resource_update_queue.h" +#include "cc/trees/layer_tree_host.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebRect.h" +#include "ui/gfx/rect_conversions.h" + +namespace cc { + +scoped_ptr<LayerImpl> ScrollbarLayer::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return ScrollbarLayerImpl::Create( + tree_impl, + id(), + ScrollbarGeometryFixedThumb::create(make_scoped_ptr(geometry_->clone()))) + .PassAs<LayerImpl>(); +} + +scoped_refptr<ScrollbarLayer> ScrollbarLayer::Create( + scoped_ptr<WebKit::WebScrollbar> scrollbar, + scoped_ptr<ScrollbarThemePainter> painter, + scoped_ptr<WebKit::WebScrollbarThemeGeometry> geometry, + int scrollLayerId) { + return make_scoped_refptr(new ScrollbarLayer(scrollbar.Pass(), + painter.Pass(), + geometry.Pass(), + scrollLayerId)); +} + +ScrollbarLayer::ScrollbarLayer( + scoped_ptr<WebKit::WebScrollbar> scrollbar, + scoped_ptr<ScrollbarThemePainter> painter, + scoped_ptr<WebKit::WebScrollbarThemeGeometry> geometry, + int scrollLayerId) + : scrollbar_(scrollbar.Pass()), + painter_(painter.Pass()), + geometry_(geometry.Pass()), + scroll_layer_id_(scrollLayerId), + texture_format_(GL_INVALID_ENUM) { + if (!scrollbar_->isOverlay()) + SetShouldScrollOnMainThread(true); +} + +ScrollbarLayer::~ScrollbarLayer() {} + +void ScrollbarLayer::SetScrollLayerId(int id) { + if (id == scroll_layer_id_) + return; + + scroll_layer_id_ = id; + SetNeedsFullTreeSync(); +} + +bool ScrollbarLayer::OpacityCanAnimateOnImplThread() const { + return scrollbar_->isOverlay(); +} + +WebKit::WebScrollbar::Orientation ScrollbarLayer::Orientation() const { + return scrollbar_->orientation(); +} + +int ScrollbarLayer::MaxTextureSize() { + DCHECK(layer_tree_host()); + return layer_tree_host()->GetRendererCapabilities().max_texture_size; +} + +float ScrollbarLayer::ClampScaleToMaxTextureSize(float scale) { + if (layer_tree_host()->settings().solidColorScrollbars) + return scale; + + // If the scaled content_bounds() is bigger than the max texture size of the + // device, we need to clamp it by rescaling, since content_bounds() is used + // below to set the texture size. + gfx::Size scaled_bounds = ComputeContentBoundsForScale(scale, scale); + if (scaled_bounds.width() > MaxTextureSize() || + scaled_bounds.height() > MaxTextureSize()) { + if (scaled_bounds.width() > scaled_bounds.height()) + return (MaxTextureSize() - 1) / static_cast<float>(bounds().width()); + else + return (MaxTextureSize() - 1) / static_cast<float>(bounds().height()); + } + return scale; +} + +void ScrollbarLayer::CalculateContentsScale(float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* contentBounds) { + ContentsScalingLayer::CalculateContentsScale( + ClampScaleToMaxTextureSize(ideal_contents_scale), + animating_transform_to_screen, + contents_scale_x, + contents_scale_y, + contentBounds); + DCHECK_LE(contentBounds->width(), MaxTextureSize()); + DCHECK_LE(contentBounds->height(), MaxTextureSize()); +} + +void ScrollbarLayer::PushPropertiesTo(LayerImpl* layer) { + ContentsScalingLayer::PushPropertiesTo(layer); + + ScrollbarLayerImpl* scrollbar_layer = static_cast<ScrollbarLayerImpl*>(layer); + + scrollbar_layer->SetScrollbarData(scrollbar_.get()); + scrollbar_layer->SetThumbSize(thumb_size_); + + if (back_track_ && back_track_->texture()->haveBackingTexture()) { + scrollbar_layer->set_back_track_resource_id( + back_track_->texture()->resourceId()); + } else { + scrollbar_layer->set_back_track_resource_id(0); + } + + if (fore_track_ && fore_track_->texture()->haveBackingTexture()) { + scrollbar_layer->set_fore_track_resource_id( + fore_track_->texture()->resourceId()); + } else { + scrollbar_layer->set_fore_track_resource_id(0); + } + + if (thumb_ && thumb_->texture()->haveBackingTexture()) + scrollbar_layer->set_thumb_resource_id(thumb_->texture()->resourceId()); + else + scrollbar_layer->set_thumb_resource_id(0); + + // Pinch zoom scrollbarLayerImpl does not get its scroll_layer_id_ + // set in LayerImpl, so we need to push it here. + if (scroll_layer_id_ == Layer::PINCH_ZOOM_ROOT_SCROLL_LAYER_ID) + scrollbar_layer->set_scroll_layer_id(scroll_layer_id_); +} + +ScrollbarLayer* ScrollbarLayer::ToScrollbarLayer() { + return this; +} + +class ScrollbarBackgroundPainter : public LayerPainter { + public: + static scoped_ptr<ScrollbarBackgroundPainter> Create( + WebKit::WebScrollbar* scrollbar, + ScrollbarThemePainter *painter, + WebKit::WebScrollbarThemeGeometry* geometry, + WebKit::WebScrollbar::ScrollbarPart trackPart) { + return make_scoped_ptr(new ScrollbarBackgroundPainter(scrollbar, + painter, + geometry, + trackPart)); + } + + virtual void Paint(SkCanvas* canvas, + gfx::Rect content_rect, + gfx::RectF* opaque) OVERRIDE { + // The following is a simplification of ScrollbarThemeComposite::paint. + painter_->PaintScrollbarBackground(canvas, content_rect); + + if (geometry_->hasButtons(scrollbar_)) { + gfx::Rect back_button_start_paint_rect = + geometry_->backButtonStartRect(scrollbar_); + painter_->PaintBackButtonStart(canvas, back_button_start_paint_rect); + + gfx::Rect back_button_end_paint_rect = + geometry_->backButtonEndRect(scrollbar_); + painter_->PaintBackButtonEnd(canvas, back_button_end_paint_rect); + + gfx::Rect forward_button_start_paint_rect = + geometry_->forwardButtonStartRect(scrollbar_); + painter_->PaintForwardButtonStart(canvas, + forward_button_start_paint_rect); + + gfx::Rect forward_button_end_paint_rect = + geometry_->forwardButtonEndRect(scrollbar_); + painter_->PaintForwardButtonEnd(canvas, forward_button_end_paint_rect); + } + + gfx::Rect track_paint_rect = geometry_->trackRect(scrollbar_); + painter_->PaintTrackBackground(canvas, track_paint_rect); + + bool thumb_present = geometry_->hasThumb(scrollbar_); + if (thumb_present) { + if (track_part_ == WebKit::WebScrollbar::ForwardTrackPart) + painter_->PaintForwardTrackPart(canvas, track_paint_rect); + else + painter_->PaintBackTrackPart(canvas, track_paint_rect); + } + + painter_->PaintTickmarks(canvas, track_paint_rect); + } + private: + ScrollbarBackgroundPainter(WebKit::WebScrollbar* scrollbar, + ScrollbarThemePainter *painter, + WebKit::WebScrollbarThemeGeometry* geometry, + WebKit::WebScrollbar::ScrollbarPart trackPart) + : scrollbar_(scrollbar), + painter_(painter), + geometry_(geometry), + track_part_(trackPart) {} + + WebKit::WebScrollbar* scrollbar_; + ScrollbarThemePainter* painter_; + WebKit::WebScrollbarThemeGeometry* geometry_; + WebKit::WebScrollbar::ScrollbarPart track_part_; + + DISALLOW_COPY_AND_ASSIGN(ScrollbarBackgroundPainter); +}; + +class ScrollbarThumbPainter : public LayerPainter { + public: + static scoped_ptr<ScrollbarThumbPainter> Create( + WebKit::WebScrollbar* scrollbar, + ScrollbarThemePainter* painter, + WebKit::WebScrollbarThemeGeometry* geometry) { + return make_scoped_ptr(new ScrollbarThumbPainter(scrollbar, + painter, + geometry)); + } + + virtual void Paint(SkCanvas* canvas, + gfx::Rect content_rect, + gfx::RectF* opaque) OVERRIDE { + // Consider the thumb to be at the origin when painting. + gfx::Rect thumb_rect = geometry_->thumbRect(scrollbar_); + painter_->PaintThumb(canvas, gfx::Rect(thumb_rect.size())); + } + + private: + ScrollbarThumbPainter(WebKit::WebScrollbar* scrollbar, + ScrollbarThemePainter* painter, + WebKit::WebScrollbarThemeGeometry* geometry) + : scrollbar_(scrollbar), + painter_(painter), + geometry_(geometry) {} + + WebKit::WebScrollbar* scrollbar_; + ScrollbarThemePainter* painter_; + WebKit::WebScrollbarThemeGeometry* geometry_; + + DISALLOW_COPY_AND_ASSIGN(ScrollbarThumbPainter); +}; + +void ScrollbarLayer::SetLayerTreeHost(LayerTreeHost* host) { + if (!host || host != layer_tree_host()) { + back_track_updater_ = NULL; + back_track_.reset(); + thumb_updater_ = NULL; + thumb_.reset(); + } + + ContentsScalingLayer::SetLayerTreeHost(host); +} + +void ScrollbarLayer::CreateUpdaterIfNeeded() { + if (layer_tree_host()->settings().solidColorScrollbars) + return; + + texture_format_ = + layer_tree_host()->GetRendererCapabilities().best_texture_format; + + if (!back_track_updater_) { + back_track_updater_ = CachingBitmapContentLayerUpdater::Create( + ScrollbarBackgroundPainter::Create( + scrollbar_.get(), + painter_.get(), + geometry_.get(), + WebKit::WebScrollbar::BackTrackPart).PassAs<LayerPainter>()); + } + if (!back_track_) { + back_track_ = back_track_updater_->CreateResource( + layer_tree_host()->contents_texture_manager()); + } + + // Only create two-part track if we think the two parts could be different in + // appearance. + if (scrollbar_->isCustomScrollbar()) { + if (!fore_track_updater_) { + fore_track_updater_ = CachingBitmapContentLayerUpdater::Create( + ScrollbarBackgroundPainter::Create( + scrollbar_.get(), + painter_.get(), + geometry_.get(), + WebKit::WebScrollbar::ForwardTrackPart).PassAs<LayerPainter>()); + } + if (!fore_track_) { + fore_track_ = fore_track_updater_->CreateResource( + layer_tree_host()->contents_texture_manager()); + } + } + + if (!thumb_updater_) { + thumb_updater_ = CachingBitmapContentLayerUpdater::Create( + ScrollbarThumbPainter::Create(scrollbar_.get(), + painter_.get(), + geometry_.get()).PassAs<LayerPainter>()); + } + if (!thumb_) { + thumb_ = thumb_updater_->CreateResource( + layer_tree_host()->contents_texture_manager()); + } +} + +void ScrollbarLayer::UpdatePart(CachingBitmapContentLayerUpdater* painter, + LayerUpdater::Resource* resource, + gfx::Rect rect, + ResourceUpdateQueue* queue, + RenderingStats* stats) { + if (layer_tree_host()->settings().solidColorScrollbars) + return; + + // Skip painting and uploading if there are no invalidations and + // we already have valid texture data. + if (resource->texture()->haveBackingTexture() && + resource->texture()->size() == rect.size() && + !is_dirty()) + return; + + // We should always have enough memory for UI. + DCHECK(resource->texture()->canAcquireBackingTexture()); + if (!resource->texture()->canAcquireBackingTexture()) + return; + + // Paint and upload the entire part. + gfx::Rect painted_opaque_rect; + painter->PrepareToUpdate(rect, + rect.size(), + contents_scale_x(), + contents_scale_y(), + &painted_opaque_rect, + stats); + if (!painter->pixels_did_change() && + resource->texture()->haveBackingTexture()) { + TRACE_EVENT_INSTANT0("cc", + "ScrollbarLayer::updatePart no texture upload needed"); + return; + } + + bool partial_updates_allowed = + layer_tree_host()->settings().maxPartialTextureUpdates > 0; + if (!partial_updates_allowed) + resource->texture()->returnBackingTexture(); + + gfx::Vector2d dest_offset(0, 0); + resource->Update(queue, rect, dest_offset, partial_updates_allowed, stats); +} + +gfx::Rect ScrollbarLayer::ScrollbarLayerRectToContentRect( + gfx::Rect layer_rect) const { + // Don't intersect with the bounds as in LayerRectToContentRect() because + // layer_rect here might be in coordinates of the containing layer. + gfx::RectF content_rect = gfx::ScaleRect(layer_rect, + contents_scale_y(), + contents_scale_y()); + return gfx::ToEnclosingRect(content_rect); +} + +void ScrollbarLayer::SetTexturePriorities( + const PriorityCalculator& priority_calc) { + if (layer_tree_host()->settings().solidColorScrollbars) + return; + + if (content_bounds().IsEmpty()) + return; + DCHECK_LE(content_bounds().width(), MaxTextureSize()); + DCHECK_LE(content_bounds().height(), MaxTextureSize()); + + CreateUpdaterIfNeeded(); + + bool draws_to_root = !render_target()->parent(); + if (back_track_) { + back_track_->texture()->setDimensions(content_bounds(), texture_format_); + back_track_->texture()->setRequestPriority( + PriorityCalculator::UIPriority(draws_to_root)); + } + if (fore_track_) { + fore_track_->texture()->setDimensions(content_bounds(), texture_format_); + fore_track_->texture()->setRequestPriority( + PriorityCalculator::UIPriority(draws_to_root)); + } + if (thumb_) { + gfx::Rect thumb_layer_rect = geometry_->thumbRect(scrollbar_.get()); + gfx::Size thumb_size = + ScrollbarLayerRectToContentRect(thumb_layer_rect).size(); + thumb_->texture()->setDimensions(thumb_size, texture_format_); + thumb_->texture()->setRequestPriority( + PriorityCalculator::UIPriority(draws_to_root)); + } +} + +void ScrollbarLayer::Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) { + ContentsScalingLayer::Update(queue, occlusion, stats); + + dirty_rect_.Union(update_rect_); + if (content_bounds().IsEmpty()) + return; + if (visible_content_rect().IsEmpty()) + return; + + CreateUpdaterIfNeeded(); + + gfx::Rect content_rect = ScrollbarLayerRectToContentRect( + gfx::Rect(scrollbar_->location(), bounds())); + UpdatePart(back_track_updater_.get(), + back_track_.get(), + content_rect, + queue, + stats); + if (fore_track_ && fore_track_updater_) { + UpdatePart(fore_track_updater_.get(), + fore_track_.get(), + content_rect, + queue, + stats); + } + + // Consider the thumb to be at the origin when painting. + gfx::Rect thumb_rect = geometry_->thumbRect(scrollbar_.get()); + thumb_size_ = thumb_rect.size(); + gfx::Rect origin_thumb_rect = + ScrollbarLayerRectToContentRect(gfx::Rect(thumb_rect.size())); + if (!origin_thumb_rect.IsEmpty()) { + UpdatePart(thumb_updater_.get(), + thumb_.get(), + origin_thumb_rect, + queue, + stats); + } + + dirty_rect_ = gfx::RectF(); +} + +} // namespace cc diff --git a/cc/layers/scrollbar_layer.h b/cc/layers/scrollbar_layer.h new file mode 100644 index 0000000..eafdde8 --- /dev/null +++ b/cc/layers/scrollbar_layer.h @@ -0,0 +1,99 @@ +// Copyright 2012 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 CC_LAYERS_SCROLLBAR_LAYER_H_ +#define CC_LAYERS_SCROLLBAR_LAYER_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/contents_scaling_layer.h" +#include "cc/layers/scrollbar_theme_painter.h" +#include "cc/resources/layer_updater.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebScrollbar.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebScrollbarThemeGeometry.h" + +namespace cc { +class CachingBitmapContentLayerUpdater; +class ResourceUpdateQueue; +class Scrollbar; +class ScrollbarThemeComposite; + +class CC_EXPORT ScrollbarLayer : public ContentsScalingLayer { + public: + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + + static scoped_refptr<ScrollbarLayer> Create( + scoped_ptr<WebKit::WebScrollbar>, + scoped_ptr<ScrollbarThemePainter>, + scoped_ptr<WebKit::WebScrollbarThemeGeometry>, + int scrollLayerId); + + int scroll_layer_id() const { return scroll_layer_id_; } + void SetScrollLayerId(int id); + + virtual bool OpacityCanAnimateOnImplThread() const OVERRIDE; + + WebKit::WebScrollbar::Orientation Orientation() const; + + // Layer interface + virtual void SetTexturePriorities(const PriorityCalculator& priority_calc) + OVERRIDE; + virtual void Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) OVERRIDE; + virtual void SetLayerTreeHost(LayerTreeHost* host) OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + virtual void CalculateContentsScale(float ideal_contents_scale, + bool animating_transform_to_screen, + float* contents_scale_x, + float* contents_scale_y, + gfx::Size* contentBounds) OVERRIDE; + + virtual ScrollbarLayer* ToScrollbarLayer() OVERRIDE; + + protected: + ScrollbarLayer( + scoped_ptr<WebKit::WebScrollbar>, + scoped_ptr<ScrollbarThemePainter>, + scoped_ptr<WebKit::WebScrollbarThemeGeometry>, + int scrollLayerId); + virtual ~ScrollbarLayer(); + + private: + void UpdatePart(CachingBitmapContentLayerUpdater* painter, + LayerUpdater::Resource* resource, + gfx::Rect rect, + ResourceUpdateQueue* queue, + RenderingStats* stats); + void CreateUpdaterIfNeeded(); + gfx::Rect ScrollbarLayerRectToContentRect(gfx::Rect layer_rect) const; + + bool is_dirty() const { return !dirty_rect_.IsEmpty(); } + + int MaxTextureSize(); + float ClampScaleToMaxTextureSize(float scale); + + scoped_ptr<WebKit::WebScrollbar> scrollbar_; + scoped_ptr<ScrollbarThemePainter> painter_; + scoped_ptr<WebKit::WebScrollbarThemeGeometry> geometry_; + gfx::Size thumb_size_; + int scroll_layer_id_; + + unsigned texture_format_; + + gfx::RectF dirty_rect_; + + scoped_refptr<CachingBitmapContentLayerUpdater> back_track_updater_; + scoped_refptr<CachingBitmapContentLayerUpdater> fore_track_updater_; + scoped_refptr<CachingBitmapContentLayerUpdater> thumb_updater_; + + // All the parts of the scrollbar except the thumb + scoped_ptr<LayerUpdater::Resource> back_track_; + scoped_ptr<LayerUpdater::Resource> fore_track_; + scoped_ptr<LayerUpdater::Resource> thumb_; +}; + +} // namespace cc + +#endif // CC_LAYERS_SCROLLBAR_LAYER_H_ diff --git a/cc/layers/scrollbar_layer_impl.cc b/cc/layers/scrollbar_layer_impl.cc new file mode 100644 index 0000000..63a17cc --- /dev/null +++ b/cc/layers/scrollbar_layer_impl.cc @@ -0,0 +1,342 @@ +// Copyright 2012 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 "cc/layers/scrollbar_layer_impl.h" + +#include "cc/animation/scrollbar_animation_controller.h" +#include "cc/layers/layer.h" +#include "cc/layers/quad_sink.h" +#include "cc/quads/solid_color_draw_quad.h" +#include "cc/quads/texture_draw_quad.h" +#include "cc/trees/layer_tree_impl.h" +#include "cc/trees/layer_tree_settings.h" +#include "ui/gfx/rect_conversions.h" + +using WebKit::WebRect; +using WebKit::WebScrollbar; + +namespace cc { + +scoped_ptr<ScrollbarLayerImpl> ScrollbarLayerImpl::Create( + LayerTreeImpl* tree_impl, + int id, + scoped_ptr<ScrollbarGeometryFixedThumb> geometry) { + return make_scoped_ptr(new ScrollbarLayerImpl(tree_impl, + id, + geometry.Pass())); +} + +ScrollbarLayerImpl::ScrollbarLayerImpl( + LayerTreeImpl* tree_impl, + int id, + scoped_ptr<ScrollbarGeometryFixedThumb> geometry) + : ScrollbarLayerImplBase(tree_impl, id), + scrollbar_(this), + back_track_resource_id_(0), + fore_track_resource_id_(0), + thumb_resource_id_(0), + geometry_(geometry.Pass()), + current_pos_(0), + total_size_(0), + maximum_(0), + vertical_adjust_(0.f), + scroll_layer_id_(Layer::INVALID_ID), + scrollbar_overlay_style_(WebScrollbar::ScrollbarOverlayStyleDefault), + orientation_(WebScrollbar::Horizontal), + control_size_(WebScrollbar::RegularScrollbar), + pressed_part_(WebScrollbar::NoPart), + hovered_part_(WebScrollbar::NoPart), + is_scrollable_area_active_(false), + is_scroll_view_scrollbar_(false), + enabled_(false), + is_custom_scrollbar_(false), + is_overlay_scrollbar_(false) {} + +ScrollbarLayerImpl::~ScrollbarLayerImpl() {} + +ScrollbarLayerImpl* ScrollbarLayerImpl::ToScrollbarLayer() { + return this; +} + +void ScrollbarLayerImpl::SetScrollbarData(WebScrollbar* scrollbar) { + scrollbar_overlay_style_ = scrollbar->scrollbarOverlayStyle(); + orientation_ = scrollbar->orientation(); + control_size_ = scrollbar->controlSize(); + pressed_part_ = scrollbar->pressedPart(); + hovered_part_ = scrollbar->hoveredPart(); + is_scrollable_area_active_ = scrollbar->isScrollableAreaActive(); + is_scroll_view_scrollbar_ = scrollbar->isScrollViewScrollbar(); + enabled_ = scrollbar->enabled(); + is_custom_scrollbar_ = scrollbar->isCustomScrollbar(); + is_overlay_scrollbar_ = scrollbar->isOverlay(); + + scrollbar->getTickmarks(tickmarks_); +} + +void ScrollbarLayerImpl::SetThumbSize(gfx::Size size) { + thumb_size_ = size; + if (!geometry_) { + // In impl-side painting, the ScrollbarLayerImpl in the pending tree + // simply holds properties that are later pushed to the active tree's + // layer, but it doesn't hold geometry or append quads. + DCHECK(layer_tree_impl()->IsPendingTree()); + return; + } + geometry_->setThumbSize(size); +} + +void ScrollbarLayerImpl::SetViewportWithinScrollableArea( + gfx::RectF scrollable_viewport, gfx::SizeF scrollable_area) { + normalized_viewport_ = gfx::RectF( + scrollable_viewport.x() / scrollable_area.width(), + scrollable_viewport.y() / scrollable_area.height(), + scrollable_viewport.width() / scrollable_area.width(), + scrollable_viewport.height() / scrollable_area.height()); +} + +float ScrollbarLayerImpl::CurrentPos() const { + return current_pos_; +} + +int ScrollbarLayerImpl::TotalSize() const { + return total_size_; +} + +int ScrollbarLayerImpl::Maximum() const { + return maximum_; +} + +WebKit::WebScrollbar::Orientation ScrollbarLayerImpl::Orientation() const { + return orientation_; +} + +static gfx::RectF ToUVRect(gfx::Rect r, gfx::Rect bounds) { + return gfx::ScaleRect(r, 1.f / bounds.width(), 1.f / bounds.height()); +} + +gfx::Rect ScrollbarLayerImpl::ScrollbarLayerRectToContentRect( + gfx::RectF layer_rect) const { + // Don't intersect with the bounds as in layerRectToContentRect() because + // layer_rect here might be in coordinates of the containing layer. + gfx::RectF content_rect = gfx::ScaleRect(layer_rect, + contents_scale_x(), + contents_scale_y()); + return gfx::ToEnclosingRect(content_rect); +} + +scoped_ptr<LayerImpl> ScrollbarLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return ScrollbarLayerImpl::Create(tree_impl, + id(), + geometry_.Pass()).PassAs<LayerImpl>(); +} + +void ScrollbarLayerImpl::PushPropertiesTo(LayerImpl* layer) { + LayerImpl::PushPropertiesTo(layer); + + ScrollbarLayerImpl* scrollbar_layer = static_cast<ScrollbarLayerImpl*>(layer); + + scrollbar_layer->SetScrollbarData(&scrollbar_); + scrollbar_layer->SetThumbSize(thumb_size_); + + scrollbar_layer->set_back_track_resource_id(back_track_resource_id_); + scrollbar_layer->set_fore_track_resource_id(fore_track_resource_id_); + scrollbar_layer->set_thumb_resource_id(thumb_resource_id_); +} + +void ScrollbarLayerImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + bool premultipled_alpha = true; + bool flipped = false; + gfx::PointF uv_top_left(0.f, 0.f); + gfx::PointF uv_bottom_right(1.f, 1.f); + gfx::Rect boundsRect(bounds()); + gfx::Rect contentBoundsRect(content_bounds()); + + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(CreateSharedQuadState()); + AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data); + + if (layer_tree_impl()->settings().solidColorScrollbars) { + gfx::Rect track_rect = geometry_->trackRect(&scrollbar_); + int thickness_override = + layer_tree_impl()->settings().solidColorScrollbarThicknessDIP; + gfx::RectF thumb_rect; + if (scrollbar_.orientation() == WebScrollbar::Horizontal) { + track_rect.set_y(track_rect.y() + vertical_adjust_); + thumb_rect = gfx::RectF(track_rect.x() + + (normalized_viewport_.x() * track_rect.width()), + track_rect.y(), + normalized_viewport_.width() * track_rect.width(), + track_rect.height()); + if (thickness_override != -1) + thumb_rect.set_height(thickness_override); + } else { + track_rect.set_height(track_rect.height() + vertical_adjust_); + thumb_rect = gfx::RectF( + track_rect.x(), + track_rect.y() + (normalized_viewport_.y() * track_rect.height()), + track_rect.width(), + normalized_viewport_.height() * track_rect.height()); + if (thickness_override != -1) + thumb_rect.set_width(thickness_override); + } + gfx::Rect quad_rect(ScrollbarLayerRectToContentRect(thumb_rect)); + scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create(); + quad->SetNew(shared_quad_state, + quad_rect, + layer_tree_impl()->settings().solidColorScrollbarColor); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + return; + } + + WebRect thumb_rect, back_track_rect, foreTrackRect; + geometry_->splitTrack(&scrollbar_, + geometry_->trackRect(&scrollbar_), + back_track_rect, + thumb_rect, + foreTrackRect); + if (!geometry_->hasThumb(&scrollbar_)) + thumb_rect = WebRect(); + + if (thumb_resource_id_ && !thumb_rect.isEmpty()) { + gfx::Rect quad_rect(ScrollbarLayerRectToContentRect(gfx::Rect(thumb_rect))); + gfx::Rect opaque_rect; + const float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f}; + scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + thumb_resource_id_, + premultipled_alpha, + uv_top_left, + uv_bottom_right, + opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + } + + if (!back_track_resource_id_) + return; + + // We only paint the track in two parts if we were given a texture for the + // forward track part. + if (fore_track_resource_id_ && !foreTrackRect.isEmpty()) { + gfx::Rect quad_rect(ScrollbarLayerRectToContentRect( + gfx::Rect(foreTrackRect))); + gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect()); + gfx::RectF uv_rect(ToUVRect(foreTrackRect, boundsRect)); + const float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f}; + scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + fore_track_resource_id_, + premultipled_alpha, + uv_rect.origin(), + uv_rect.bottom_right(), + opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + } + + // Order matters here: since the back track texture is being drawn to the + // entire contents rect, we must append it after the thumb and fore track + // quads. The back track texture contains (and displays) the buttons. + if (!contentBoundsRect.IsEmpty()) { + gfx::Rect quad_rect(contentBoundsRect); + gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect()); + const float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f}; + scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + back_track_resource_id_, + premultipled_alpha, + uv_top_left, + uv_bottom_right, + opacity, + flipped); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + } +} + +void ScrollbarLayerImpl::DidLoseOutputSurface() { + back_track_resource_id_ = 0; + fore_track_resource_id_ = 0; + thumb_resource_id_ = 0; +} + +bool ScrollbarLayerImpl::Scrollbar::isOverlay() const { + return owner_->is_overlay_scrollbar_; +} + +int ScrollbarLayerImpl::Scrollbar::value() const { + return owner_->CurrentPos(); +} + +WebKit::WebPoint ScrollbarLayerImpl::Scrollbar::location() const { + return WebKit::WebPoint(); +} + +WebKit::WebSize ScrollbarLayerImpl::Scrollbar::size() const { + return WebKit::WebSize(owner_->bounds().width(), owner_->bounds().height()); +} + +bool ScrollbarLayerImpl::Scrollbar::enabled() const { + return owner_->enabled_; +} + +int ScrollbarLayerImpl::Scrollbar::maximum() const { + return owner_->Maximum(); +} + +int ScrollbarLayerImpl::Scrollbar::totalSize() const { + return owner_->TotalSize(); +} + +bool ScrollbarLayerImpl::Scrollbar::isScrollViewScrollbar() const { + return owner_->is_scroll_view_scrollbar_; +} + +bool ScrollbarLayerImpl::Scrollbar::isScrollableAreaActive() const { + return owner_->is_scrollable_area_active_; +} + +void ScrollbarLayerImpl::Scrollbar::getTickmarks( + WebKit::WebVector<WebRect>& tickmarks) const { + tickmarks = owner_->tickmarks_; +} + +WebScrollbar::ScrollbarControlSize ScrollbarLayerImpl::Scrollbar::controlSize() + const { + return owner_->control_size_; +} + +WebScrollbar::ScrollbarPart ScrollbarLayerImpl::Scrollbar::pressedPart() const { + return owner_->pressed_part_; +} + +WebScrollbar::ScrollbarPart ScrollbarLayerImpl::Scrollbar::hoveredPart() const { + return owner_->hovered_part_; +} + +WebScrollbar::ScrollbarOverlayStyle +ScrollbarLayerImpl::Scrollbar::scrollbarOverlayStyle() const { + return owner_->scrollbar_overlay_style_; +} + +WebScrollbar::Orientation ScrollbarLayerImpl::Scrollbar::orientation() const { + return owner_->orientation_; +} + +bool ScrollbarLayerImpl::Scrollbar::isCustomScrollbar() const { + return owner_->is_custom_scrollbar_; +} + +const char* ScrollbarLayerImpl::LayerTypeAsString() const { + return "ScrollbarLayer"; +} + +} // namespace cc diff --git a/cc/layers/scrollbar_layer_impl.h b/cc/layers/scrollbar_layer_impl.h new file mode 100644 index 0000000..23422b3 --- /dev/null +++ b/cc/layers/scrollbar_layer_impl.h @@ -0,0 +1,150 @@ +// Copyright 2012 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 CC_LAYERS_SCROLLBAR_LAYER_IMPL_H_ +#define CC_LAYERS_SCROLLBAR_LAYER_IMPL_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/scrollbar_geometry_fixed_thumb.h" +#include "cc/layers/scrollbar_layer_impl_base.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebRect.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebVector.h" + +namespace cc { + +class ScrollView; + +class CC_EXPORT ScrollbarLayerImpl : public ScrollbarLayerImplBase { + public: + static scoped_ptr<ScrollbarLayerImpl> Create( + LayerTreeImpl* tree_impl, + int id, + scoped_ptr<ScrollbarGeometryFixedThumb> geometry); + virtual ~ScrollbarLayerImpl(); + + virtual ScrollbarLayerImpl* ToScrollbarLayer() OVERRIDE; + int scroll_layer_id() const { return scroll_layer_id_; } + void set_scroll_layer_id(int id) { scroll_layer_id_ = id; } + + void SetScrollbarData(WebKit::WebScrollbar* scrollbar); + void SetThumbSize(gfx::Size size); + + void set_vertical_adjust(float vertical_adjust) { + vertical_adjust_ = vertical_adjust; + } + void SetViewportWithinScrollableArea(gfx::RectF scrollable_viewport, + gfx::SizeF scrollable_area); + + void set_back_track_resource_id(ResourceProvider::ResourceId id) { + back_track_resource_id_ = id; + } + void set_fore_track_resource_id(ResourceProvider::ResourceId id) { + fore_track_resource_id_ = id; + } + void set_thumb_resource_id(ResourceProvider::ResourceId id) { + thumb_resource_id_ = id; + } + bool HasThumbTexture() { return thumb_resource_id_; } + + + // ScrollbarLayerImplBase implementation. + virtual float CurrentPos() const OVERRIDE; + virtual int TotalSize() const OVERRIDE; + virtual int Maximum() const OVERRIDE; + + void SetCurrentPos(float current_pos) { current_pos_ = current_pos; } + void SetTotalSize(int total_size) { total_size_ = total_size; } + void SetMaximum(int maximum) { maximum_ = maximum; } + + virtual WebKit::WebScrollbar::Orientation Orientation() const OVERRIDE; + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + + virtual void DidLoseOutputSurface() OVERRIDE; + + protected: + ScrollbarLayerImpl(LayerTreeImpl* tree_impl, + int id, + scoped_ptr<ScrollbarGeometryFixedThumb> geometry); + + private: + // nested class only to avoid namespace problem + class Scrollbar : public WebKit::WebScrollbar { + public: + explicit Scrollbar(ScrollbarLayerImpl* owner) : owner_(owner) {} + + // WebScrollbar implementation + virtual bool isOverlay() const; + virtual int value() const; + virtual WebKit::WebPoint location() const; + virtual WebKit::WebSize size() const; + virtual bool enabled() const; + virtual int maximum() const; + virtual int totalSize() const; + virtual bool isScrollViewScrollbar() const; + virtual bool isScrollableAreaActive() const; + virtual void getTickmarks(WebKit::WebVector<WebKit::WebRect>& tickmarks) + const; + virtual WebScrollbar::ScrollbarControlSize controlSize() const; + virtual WebScrollbar::ScrollbarPart pressedPart() const; + virtual WebScrollbar::ScrollbarPart hoveredPart() const; + virtual WebScrollbar::ScrollbarOverlayStyle scrollbarOverlayStyle() const; + virtual WebScrollbar::Orientation orientation() const; + virtual bool isCustomScrollbar() const; + + private: + ScrollbarLayerImpl* owner_; + + }; + + virtual const char* LayerTypeAsString() const OVERRIDE; + + gfx::Rect ScrollbarLayerRectToContentRect(gfx::RectF layer_rect) const; + + Scrollbar scrollbar_; + + ResourceProvider::ResourceId back_track_resource_id_; + ResourceProvider::ResourceId fore_track_resource_id_; + ResourceProvider::ResourceId thumb_resource_id_; + + scoped_ptr<ScrollbarGeometryFixedThumb> geometry_; + + float current_pos_; + int total_size_; + int maximum_; + gfx::Size thumb_size_; + + // Difference between the clip layer's height and the visible viewport + // height (which may differ in the presence of top-controls hiding). + float vertical_adjust_; + + // Specifies the position and size of the viewport within the scrollable + // area (normalized as if the scrollable area is a unit-sized box + // [0, 0, 1, 1]). + gfx::RectF normalized_viewport_; + + int scroll_layer_id_; + + // Data to implement Scrollbar + WebKit::WebScrollbar::ScrollbarOverlayStyle scrollbar_overlay_style_; + WebKit::WebVector<WebKit::WebRect> tickmarks_; + WebKit::WebScrollbar::Orientation orientation_; + WebKit::WebScrollbar::ScrollbarControlSize control_size_; + WebKit::WebScrollbar::ScrollbarPart pressed_part_; + WebKit::WebScrollbar::ScrollbarPart hovered_part_; + + bool is_scrollable_area_active_; + bool is_scroll_view_scrollbar_; + bool enabled_; + bool is_custom_scrollbar_; + bool is_overlay_scrollbar_; +}; + +} +#endif // CC_LAYERS_SCROLLBAR_LAYER_IMPL_H_ diff --git a/cc/layers/scrollbar_layer_impl_base.h b/cc/layers/scrollbar_layer_impl_base.h new file mode 100644 index 0000000..714c0d7 --- /dev/null +++ b/cc/layers/scrollbar_layer_impl_base.h @@ -0,0 +1,30 @@ +// Copyright 2012 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 CC_LAYERS_SCROLLBAR_LAYER_IMPL_BASE_H_ +#define CC_LAYERS_SCROLLBAR_LAYER_IMPL_BASE_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/layer_impl.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebScrollbar.h" + +namespace cc { + +class CC_EXPORT ScrollbarLayerImplBase : public LayerImpl { + public: + virtual ~ScrollbarLayerImplBase() {} + + virtual float CurrentPos() const = 0; + virtual int TotalSize() const = 0; + virtual int Maximum() const = 0; + virtual WebKit::WebScrollbar::Orientation Orientation() const = 0; + + protected: + ScrollbarLayerImplBase(LayerTreeImpl* tree_impl, int id) + : LayerImpl(tree_impl, id) {} +}; + +} // namespace cc + +#endif // CC_LAYERS_SCROLLBAR_LAYER_IMPL_BASE_H_ diff --git a/cc/layers/scrollbar_layer_unittest.cc b/cc/layers/scrollbar_layer_unittest.cc new file mode 100644 index 0000000..8c424a9 --- /dev/null +++ b/cc/layers/scrollbar_layer_unittest.cc @@ -0,0 +1,411 @@ +// Copyright 2012 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 "cc/layers/scrollbar_layer.h" + +#include "cc/animation/scrollbar_animation_controller.h" +#include "cc/layers/append_quads_data.h" +#include "cc/layers/scrollbar_layer_impl.h" +#include "cc/quads/solid_color_draw_quad.h" +#include "cc/resources/prioritized_resource_manager.h" +#include "cc/resources/priority_calculator.h" +#include "cc/resources/resource_update_queue.h" +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_client.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/fake_scrollbar_theme_painter.h" +#include "cc/test/fake_web_scrollbar.h" +#include "cc/test/fake_web_scrollbar_theme_geometry.h" +#include "cc/test/geometry_test_utils.h" +#include "cc/test/layer_tree_test_common.h" +#include "cc/test/mock_quad_culler.h" +#include "cc/test/test_web_graphics_context_3d.h" +#include "cc/trees/layer_tree_impl.h" +#include "cc/trees/single_thread_proxy.h" +#include "cc/trees/tree_synchronizer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebScrollbar.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebScrollbarThemeGeometry.h" + +namespace cc { +namespace { + +scoped_ptr<LayerImpl> layerImplForScrollAreaAndScrollbar( + FakeLayerTreeHostImpl* host_impl, + scoped_ptr<WebKit::WebScrollbar> scrollbar, + bool reverse_order) +{ + scoped_refptr<Layer> layerTreeRoot = Layer::Create(); + scoped_refptr<Layer> child1 = Layer::Create(); + scoped_refptr<Layer> child2 = ScrollbarLayer::Create(scrollbar.Pass(), FakeScrollbarThemePainter::Create(false).PassAs<ScrollbarThemePainter>(), FakeWebScrollbarThemeGeometry::create(true), child1->id()); + layerTreeRoot->AddChild(child1); + layerTreeRoot->InsertChild(child2, reverse_order ? 0 : 1); + scoped_ptr<LayerImpl> layerImpl = TreeSynchronizer::SynchronizeTrees(layerTreeRoot.get(), scoped_ptr<LayerImpl>(), host_impl->active_tree()); + TreeSynchronizer::PushProperties(layerTreeRoot.get(), layerImpl.get()); + return layerImpl.Pass(); +} + +TEST(ScrollbarLayerTest, resolveScrollLayerPointer) +{ + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + + { + scoped_ptr<WebKit::WebScrollbar> scrollbar(FakeWebScrollbar::Create()); + scoped_ptr<LayerImpl> layerImplTreeRoot = layerImplForScrollAreaAndScrollbar(&hostImpl, scrollbar.Pass(), false); + + LayerImpl* ccChild1 = layerImplTreeRoot->children()[0]; + ScrollbarLayerImpl* ccChild2 = static_cast<ScrollbarLayerImpl*>(layerImplTreeRoot->children()[1]); + + EXPECT_EQ(ccChild1->horizontal_scrollbar_layer(), ccChild2); + } + + { // another traverse order + scoped_ptr<WebKit::WebScrollbar> scrollbar(FakeWebScrollbar::Create()); + scoped_ptr<LayerImpl> layerImplTreeRoot = layerImplForScrollAreaAndScrollbar(&hostImpl, scrollbar.Pass(), true); + + ScrollbarLayerImpl* ccChild1 = static_cast<ScrollbarLayerImpl*>(layerImplTreeRoot->children()[0]); + LayerImpl* ccChild2 = layerImplTreeRoot->children()[1]; + + EXPECT_EQ(ccChild2->horizontal_scrollbar_layer(), ccChild1); + } +} + +TEST(ScrollbarLayerTest, shouldScrollNonOverlayOnMainThread) +{ + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + + // Create and attach a non-overlay scrollbar. + scoped_ptr<WebKit::WebScrollbar> scrollbar(FakeWebScrollbar::Create()); + static_cast<FakeWebScrollbar*>(scrollbar.get())->setOverlay(false); + scoped_ptr<LayerImpl> layerImplTreeRoot = layerImplForScrollAreaAndScrollbar(&hostImpl, scrollbar.Pass(), false); + ScrollbarLayerImpl* scrollbarLayerImpl = static_cast<ScrollbarLayerImpl*>(layerImplTreeRoot->children()[1]); + + // When the scrollbar is not an overlay scrollbar, the scroll should be + // responded to on the main thread as the compositor does not yet implement + // scrollbar scrolling. + EXPECT_EQ(InputHandlerClient::ScrollOnMainThread, scrollbarLayerImpl->TryScroll(gfx::Point(0, 0), InputHandlerClient::Gesture)); + + // Create and attach an overlay scrollbar. + scrollbar = FakeWebScrollbar::Create(); + static_cast<FakeWebScrollbar*>(scrollbar.get())->setOverlay(true); + + layerImplTreeRoot = layerImplForScrollAreaAndScrollbar(&hostImpl, scrollbar.Pass(), false); + scrollbarLayerImpl = static_cast<ScrollbarLayerImpl*>(layerImplTreeRoot->children()[1]); + + // The user shouldn't be able to drag an overlay scrollbar and the scroll + // may be handled in the compositor. + EXPECT_EQ(InputHandlerClient::ScrollIgnored, scrollbarLayerImpl->TryScroll(gfx::Point(0, 0), InputHandlerClient::Gesture)); +} + +TEST(ScrollbarLayerTest, scrollOffsetSynchronization) +{ + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + + scoped_ptr<WebKit::WebScrollbar> scrollbar(FakeWebScrollbar::Create()); + scoped_refptr<Layer> layerTreeRoot = Layer::Create(); + scoped_refptr<Layer> contentLayer = Layer::Create(); + scoped_refptr<Layer> scrollbarLayer = ScrollbarLayer::Create(scrollbar.Pass(), FakeScrollbarThemePainter::Create(false).PassAs<ScrollbarThemePainter>(), FakeWebScrollbarThemeGeometry::create(true), layerTreeRoot->id()); + layerTreeRoot->AddChild(contentLayer); + layerTreeRoot->AddChild(scrollbarLayer); + + layerTreeRoot->SetScrollOffset(gfx::Vector2d(10, 20)); + layerTreeRoot->SetMaxScrollOffset(gfx::Vector2d(30, 50)); + layerTreeRoot->SetBounds(gfx::Size(100, 200)); + contentLayer->SetBounds(gfx::Size(100, 200)); + + scoped_ptr<LayerImpl> layerImplTreeRoot = TreeSynchronizer::SynchronizeTrees(layerTreeRoot.get(), scoped_ptr<LayerImpl>(), hostImpl.active_tree()); + TreeSynchronizer::PushProperties(layerTreeRoot.get(), layerImplTreeRoot.get()); + + ScrollbarLayerImpl* ccScrollbarLayer = static_cast<ScrollbarLayerImpl*>(layerImplTreeRoot->children()[1]); + + EXPECT_EQ(10, ccScrollbarLayer->CurrentPos()); + EXPECT_EQ(100, ccScrollbarLayer->TotalSize()); + EXPECT_EQ(30, ccScrollbarLayer->Maximum()); + + layerTreeRoot->SetScrollOffset(gfx::Vector2d(100, 200)); + layerTreeRoot->SetMaxScrollOffset(gfx::Vector2d(300, 500)); + layerTreeRoot->SetBounds(gfx::Size(1000, 2000)); + contentLayer->SetBounds(gfx::Size(1000, 2000)); + + ScrollbarAnimationController* scrollbarController = layerImplTreeRoot->scrollbar_animation_controller(); + layerImplTreeRoot = TreeSynchronizer::SynchronizeTrees(layerTreeRoot.get(), layerImplTreeRoot.Pass(), hostImpl.active_tree()); + TreeSynchronizer::PushProperties(layerTreeRoot.get(), layerImplTreeRoot.get()); + EXPECT_EQ(scrollbarController, layerImplTreeRoot->scrollbar_animation_controller()); + + EXPECT_EQ(100, ccScrollbarLayer->CurrentPos()); + EXPECT_EQ(1000, ccScrollbarLayer->TotalSize()); + EXPECT_EQ(300, ccScrollbarLayer->Maximum()); + + layerImplTreeRoot->ScrollBy(gfx::Vector2d(12, 34)); + + EXPECT_EQ(112, ccScrollbarLayer->CurrentPos()); + EXPECT_EQ(1000, ccScrollbarLayer->TotalSize()); + EXPECT_EQ(300, ccScrollbarLayer->Maximum()); +} + +TEST(ScrollbarLayerTest, solidColorDrawQuads) +{ + LayerTreeSettings layerTreeSettings; + layerTreeSettings.solidColorScrollbars = true; + layerTreeSettings.solidColorScrollbarThicknessDIP = 3; + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(layerTreeSettings, &proxy); + + scoped_ptr<WebKit::WebScrollbar> scrollbar(FakeWebScrollbar::Create()); + static_cast<FakeWebScrollbar*>(scrollbar.get())->setOverlay(true); + scoped_ptr<LayerImpl> layerImplTreeRoot = layerImplForScrollAreaAndScrollbar(&hostImpl, scrollbar.Pass(), false); + ScrollbarLayerImpl* scrollbarLayerImpl = static_cast<ScrollbarLayerImpl*>(layerImplTreeRoot->children()[1]); + scrollbarLayerImpl->SetThumbSize(gfx::Size(4, 4)); + scrollbarLayerImpl->SetViewportWithinScrollableArea( + gfx::RectF(10.f, 0.f, 40.f, 0.f), gfx::SizeF(100.f, 100.f)); + + // Thickness should be overridden to 3. + { + MockQuadCuller quadCuller; + AppendQuadsData data; + scrollbarLayerImpl->AppendQuads(&quadCuller, &data); + + const QuadList& quads = quadCuller.quadList(); + ASSERT_EQ(1, quads.size()); + EXPECT_EQ(DrawQuad::SOLID_COLOR, quads[0]->material); + EXPECT_RECT_EQ(gfx::Rect(1, 0, 4, 3), quads[0]->rect); + } + + // Contents scale should scale the draw quad. + scrollbarLayerImpl->draw_properties().contents_scale_x = 2; + scrollbarLayerImpl->draw_properties().contents_scale_y = 2; + { + MockQuadCuller quadCuller; + AppendQuadsData data; + scrollbarLayerImpl->AppendQuads(&quadCuller, &data); + + const QuadList& quads = quadCuller.quadList(); + ASSERT_EQ(1, quads.size()); + EXPECT_EQ(DrawQuad::SOLID_COLOR, quads[0]->material); + EXPECT_RECT_EQ(gfx::Rect(2, 0, 8, 6), quads[0]->rect); + } + scrollbarLayerImpl->draw_properties().contents_scale_x = 1; + scrollbarLayerImpl->draw_properties().contents_scale_y = 1; + + // For solid color scrollbars, position and size should reflect the + // viewport, not the geometry object. + scrollbarLayerImpl->SetViewportWithinScrollableArea( + gfx::RectF(40.f, 0.f, 20.f, 0.f), gfx::SizeF(100.f, 100.f)); + { + MockQuadCuller quadCuller; + AppendQuadsData data; + scrollbarLayerImpl->AppendQuads(&quadCuller, &data); + + const QuadList& quads = quadCuller.quadList(); + ASSERT_EQ(1, quads.size()); + EXPECT_EQ(DrawQuad::SOLID_COLOR, quads[0]->material); + EXPECT_RECT_EQ(gfx::Rect(4, 0, 2, 3), quads[0]->rect); + } + +} + +class ScrollbarLayerTestMaxTextureSize : public ThreadedTest { +public: + ScrollbarLayerTestMaxTextureSize() {} + + void setScrollbarBounds(gfx::Size bounds) { + bounds_ = bounds; + } + + virtual void beginTest() OVERRIDE + { + m_layerTreeHost->InitializeRendererIfNeeded(); + + scoped_ptr<WebKit::WebScrollbar> scrollbar(FakeWebScrollbar::Create()); + m_scrollbarLayer = ScrollbarLayer::Create(scrollbar.Pass(), FakeScrollbarThemePainter::Create(false).PassAs<ScrollbarThemePainter>(), FakeWebScrollbarThemeGeometry::create(true), 1); + m_scrollbarLayer->SetLayerTreeHost(m_layerTreeHost.get()); + m_scrollbarLayer->SetBounds(bounds_); + m_layerTreeHost->root_layer()->AddChild(m_scrollbarLayer); + + m_scrollLayer = Layer::Create(); + m_scrollbarLayer->SetScrollLayerId(m_scrollLayer->id()); + m_layerTreeHost->root_layer()->AddChild(m_scrollLayer); + + postSetNeedsCommitToMainThread(); + } + + virtual void commitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE + { + const int kMaxTextureSize = impl->GetRendererCapabilities().max_texture_size; + + // Check first that we're actually testing something. + EXPECT_GT(m_scrollbarLayer->bounds().width(), kMaxTextureSize); + + EXPECT_EQ(m_scrollbarLayer->content_bounds().width(), kMaxTextureSize - 1); + EXPECT_EQ(m_scrollbarLayer->content_bounds().height(), kMaxTextureSize - 1); + + endTest(); + } + + virtual void afterTest() OVERRIDE + { + } + +private: + scoped_refptr<ScrollbarLayer> m_scrollbarLayer; + scoped_refptr<Layer> m_scrollLayer; + gfx::Size bounds_; +}; + +TEST_F(ScrollbarLayerTestMaxTextureSize, runTest) { + scoped_ptr<TestWebGraphicsContext3D> context = TestWebGraphicsContext3D::Create(); + int max_size = 0; + context->getIntegerv(GL_MAX_TEXTURE_SIZE, &max_size); + setScrollbarBounds(gfx::Size(max_size + 100, max_size + 100)); + runTest(true); +} + +class MockLayerTreeHost : public LayerTreeHost { +public: + MockLayerTreeHost(const LayerTreeSettings& settings) + : LayerTreeHost(&m_fakeClient, settings) + { + Initialize(scoped_ptr<Thread>(NULL)); + } + +private: + FakeLayerImplTreeHostClient m_fakeClient; +}; + + +class ScrollbarLayerTestResourceCreation : public testing::Test { +public: + ScrollbarLayerTestResourceCreation() + { + } + + void testResourceUpload(int expectedResources) + { + m_layerTreeHost.reset(new MockLayerTreeHost(m_layerTreeSettings)); + + scoped_ptr<WebKit::WebScrollbar> scrollbar(FakeWebScrollbar::Create()); + scoped_refptr<Layer> layerTreeRoot = Layer::Create(); + scoped_refptr<Layer> contentLayer = Layer::Create(); + scoped_refptr<Layer> scrollbarLayer = ScrollbarLayer::Create(scrollbar.Pass(), FakeScrollbarThemePainter::Create(false).PassAs<ScrollbarThemePainter>(), FakeWebScrollbarThemeGeometry::create(true), layerTreeRoot->id()); + layerTreeRoot->AddChild(contentLayer); + layerTreeRoot->AddChild(scrollbarLayer); + + m_layerTreeHost->InitializeRendererIfNeeded(); + m_layerTreeHost->contents_texture_manager()->setMaxMemoryLimitBytes(1024 * 1024); + m_layerTreeHost->SetRootLayer(layerTreeRoot); + + scrollbarLayer->SetIsDrawable(true); + scrollbarLayer->SetBounds(gfx::Size(100, 100)); + layerTreeRoot->SetScrollOffset(gfx::Vector2d(10, 20)); + layerTreeRoot->SetMaxScrollOffset(gfx::Vector2d(30, 50)); + layerTreeRoot->SetBounds(gfx::Size(100, 200)); + contentLayer->SetBounds(gfx::Size(100, 200)); + scrollbarLayer->draw_properties().content_bounds = gfx::Size(100, 200); + scrollbarLayer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + scrollbarLayer->CreateRenderSurface(); + scrollbarLayer->draw_properties().render_target = scrollbarLayer; + + testing::Mock::VerifyAndClearExpectations(m_layerTreeHost.get()); + EXPECT_EQ(scrollbarLayer->layer_tree_host(), m_layerTreeHost.get()); + + PriorityCalculator calculator; + ResourceUpdateQueue queue; + OcclusionTracker occlusionTracker(gfx::Rect(), false); + + scrollbarLayer->SetTexturePriorities(calculator); + m_layerTreeHost->contents_texture_manager()->prioritizeTextures(); + scrollbarLayer->Update(&queue, &occlusionTracker, NULL); + EXPECT_EQ(0, queue.fullUploadSize()); + EXPECT_EQ(expectedResources, queue.partialUploadSize()); + + testing::Mock::VerifyAndClearExpectations(m_layerTreeHost.get()); + } + +protected: + scoped_ptr<MockLayerTreeHost> m_layerTreeHost; + LayerTreeSettings m_layerTreeSettings; +}; + +TEST_F(ScrollbarLayerTestResourceCreation, resourceUpload) +{ + m_layerTreeSettings.solidColorScrollbars = false; + testResourceUpload(2); +} + +TEST_F(ScrollbarLayerTestResourceCreation, solidColorNoResourceUpload) +{ + m_layerTreeSettings.solidColorScrollbars = true; + testResourceUpload(0); +} + +TEST(ScrollbarLayerTest, pinchZoomScrollbarUpdates) +{ + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + + scoped_refptr<Layer> layerTreeRoot = Layer::Create(); + layerTreeRoot->SetScrollable(true); + + scoped_refptr<Layer> contentLayer = Layer::Create(); + scoped_ptr<WebKit::WebScrollbar> scrollbar1(FakeWebScrollbar::Create()); + scoped_refptr<Layer> scrollbarLayerHorizontal = + ScrollbarLayer::Create(scrollbar1.Pass(), + FakeScrollbarThemePainter::Create(false).PassAs<ScrollbarThemePainter>(), + FakeWebScrollbarThemeGeometry::create(true), + Layer::PINCH_ZOOM_ROOT_SCROLL_LAYER_ID); + scoped_ptr<WebKit::WebScrollbar> scrollbar2(FakeWebScrollbar::Create()); + scoped_refptr<Layer> scrollbarLayerVertical = + ScrollbarLayer::Create(scrollbar2.Pass(), + FakeScrollbarThemePainter::Create(false).PassAs<ScrollbarThemePainter>(), + FakeWebScrollbarThemeGeometry::create(true), + Layer::PINCH_ZOOM_ROOT_SCROLL_LAYER_ID); + + layerTreeRoot->AddChild(contentLayer); + layerTreeRoot->AddChild(scrollbarLayerHorizontal); + layerTreeRoot->AddChild(scrollbarLayerVertical); + + layerTreeRoot->SetScrollOffset(gfx::Vector2d(10, 20)); + layerTreeRoot->SetMaxScrollOffset(gfx::Vector2d(30, 50)); + layerTreeRoot->SetBounds(gfx::Size(100, 200)); + contentLayer->SetBounds(gfx::Size(100, 200)); + + scoped_ptr<LayerImpl> layerImplTreeRoot = + TreeSynchronizer::SynchronizeTrees(layerTreeRoot.get(), + scoped_ptr<LayerImpl>(), hostImpl.active_tree()); + TreeSynchronizer::PushProperties(layerTreeRoot.get(), + layerImplTreeRoot.get()); + + ScrollbarLayerImpl* pinchZoomHorizontal = static_cast<ScrollbarLayerImpl*>( + layerImplTreeRoot->children()[1]); + ScrollbarLayerImpl* pinchZoomVertical = static_cast<ScrollbarLayerImpl*>( + layerImplTreeRoot->children()[2]); + + // Need a root layer in the active tree in order for DidUpdateScroll() + // to work. + hostImpl.active_tree()->SetRootLayer(layerImplTreeRoot.Pass()); + hostImpl.active_tree()->FindRootScrollLayer(); + + // Manually set the pinch-zoom layers: normally this is done by + // LayerTreeHost. + hostImpl.active_tree()->SetPinchZoomHorizontalLayerId( + pinchZoomHorizontal->id()); + hostImpl.active_tree()->SetPinchZoomVerticalLayerId( + pinchZoomVertical->id()); + + hostImpl.active_tree()->DidUpdateScroll(); + + EXPECT_EQ(10, pinchZoomHorizontal->CurrentPos()); + EXPECT_EQ(100, pinchZoomHorizontal->TotalSize()); + EXPECT_EQ(30, pinchZoomHorizontal->Maximum()); + EXPECT_EQ(20, pinchZoomVertical->CurrentPos()); + EXPECT_EQ(200, pinchZoomVertical->TotalSize()); + EXPECT_EQ(50, pinchZoomVertical->Maximum()); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/scrollbar_theme_painter.h b/cc/layers/scrollbar_theme_painter.h new file mode 100644 index 0000000..50fec7e --- /dev/null +++ b/cc/layers/scrollbar_theme_painter.h @@ -0,0 +1,36 @@ +// Copyright 2012 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 CC_LAYERS_SCROLLBAR_THEME_PAINTER_H_ +#define CC_LAYERS_SCROLLBAR_THEME_PAINTER_H_ + +#include "cc/base/cc_export.h" + +class SkCanvas; + +namespace gfx { +class Rect; +} + +namespace cc { + +class CC_EXPORT ScrollbarThemePainter { + public: + virtual ~ScrollbarThemePainter() {} + + virtual void PaintScrollbarBackground(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintTrackBackground(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintBackTrackPart(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintForwardTrackPart(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintBackButtonStart(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintBackButtonEnd(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintForwardButtonStart(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintForwardButtonEnd(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintTickmarks(SkCanvas*, const gfx::Rect&) = 0; + virtual void PaintThumb(SkCanvas*, const gfx::Rect&) = 0; +}; + +} // namespace cc + +#endif // CC_LAYERS_SCROLLBAR_THEME_PAINTER_H_ diff --git a/cc/layers/solid_color_layer.cc b/cc/layers/solid_color_layer.cc new file mode 100644 index 0000000..5a1e26f --- /dev/null +++ b/cc/layers/solid_color_layer.cc @@ -0,0 +1,30 @@ +// Copyright 2012 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 "cc/layers/solid_color_layer.h" + +#include "cc/layers/solid_color_layer_impl.h" + +namespace cc { + +scoped_ptr<LayerImpl> SolidColorLayer::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return SolidColorLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +scoped_refptr<SolidColorLayer> SolidColorLayer::Create() { + return make_scoped_refptr(new SolidColorLayer()); +} + +SolidColorLayer::SolidColorLayer() + : Layer() {} + +SolidColorLayer::~SolidColorLayer() {} + +void SolidColorLayer::SetBackgroundColor(SkColor color) { + SetContentsOpaque(SkColorGetA(color) == 255); + Layer::SetBackgroundColor(color); +} + +} // namespace cc diff --git a/cc/layers/solid_color_layer.h b/cc/layers/solid_color_layer.h new file mode 100644 index 0000000..475f3ed --- /dev/null +++ b/cc/layers/solid_color_layer.h @@ -0,0 +1,33 @@ +// Copyright 2012 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 CC_LAYERS_SOLID_COLOR_LAYER_H_ +#define CC_LAYERS_SOLID_COLOR_LAYER_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/layer.h" + +namespace cc { + +// A Layer that renders a solid color. The color is specified by using +// SetBackgroundColor() on the base class. +class CC_EXPORT SolidColorLayer : public Layer { + public: + static scoped_refptr<SolidColorLayer> Create(); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + + virtual void SetBackgroundColor(SkColor color) OVERRIDE; + + protected: + SolidColorLayer(); + + private: + virtual ~SolidColorLayer(); +}; + +} +#endif // CC_LAYERS_SOLID_COLOR_LAYER_H_ diff --git a/cc/layers/solid_color_layer_impl.cc b/cc/layers/solid_color_layer_impl.cc new file mode 100644 index 0000000..114386e --- /dev/null +++ b/cc/layers/solid_color_layer_impl.cc @@ -0,0 +1,50 @@ +// Copyright 2012 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 "cc/layers/solid_color_layer_impl.h" + +#include "cc/layers/quad_sink.h" +#include "cc/quads/solid_color_draw_quad.h" + +namespace cc { + +SolidColorLayerImpl::SolidColorLayerImpl(LayerTreeImpl* tree_impl, int id) + : LayerImpl(tree_impl, id), + tile_size_(256) {} + +SolidColorLayerImpl::~SolidColorLayerImpl() {} + +scoped_ptr<LayerImpl> SolidColorLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return SolidColorLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void SolidColorLayerImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(CreateSharedQuadState()); + AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data); + + // We create a series of smaller quads instead of just one large one so that + // the culler can reduce the total pixels drawn. + int width = content_bounds().width(); + int height = content_bounds().height(); + for (int x = 0; x < width; x += tile_size_) { + for (int y = 0; y < height; y += tile_size_) { + gfx::Rect solidTileRect(x, + y, + std::min(width - x, tile_size_), + std::min(height - y, tile_size_)); + scoped_ptr<SolidColorDrawQuad> quad = SolidColorDrawQuad::Create(); + quad->SetNew(shared_quad_state, solidTileRect, background_color()); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + } + } +} + +const char* SolidColorLayerImpl::LayerTypeAsString() const { + return "SolidColorLayer"; +} + +} // namespace cc diff --git a/cc/layers/solid_color_layer_impl.h b/cc/layers/solid_color_layer_impl.h new file mode 100644 index 0000000..db99945 --- /dev/null +++ b/cc/layers/solid_color_layer_impl.h @@ -0,0 +1,39 @@ +// Copyright 2012 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 CC_LAYERS_SOLID_COLOR_LAYER_IMPL_H_ +#define CC_LAYERS_SOLID_COLOR_LAYER_IMPL_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/layers/layer_impl.h" + +namespace cc { + +class CC_EXPORT SolidColorLayerImpl : public LayerImpl { + public: + static scoped_ptr<SolidColorLayerImpl> Create(LayerTreeImpl* tree_impl, + int id) { + return make_scoped_ptr(new SolidColorLayerImpl(tree_impl, id)); + } + virtual ~SolidColorLayerImpl(); + + // LayerImpl overrides. + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + + protected: + SolidColorLayerImpl(LayerTreeImpl* tree_impl, int id); + + private: + virtual const char* LayerTypeAsString() const OVERRIDE; + + const int tile_size_; +}; + +} + +#endif // CC_LAYERS_SOLID_COLOR_LAYER_IMPL_H_ diff --git a/cc/layers/solid_color_layer_impl_unittest.cc b/cc/layers/solid_color_layer_impl_unittest.cc new file mode 100644 index 0000000..039732e --- /dev/null +++ b/cc/layers/solid_color_layer_impl_unittest.cc @@ -0,0 +1,163 @@ +// Copyright 2012 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 "cc/layers/solid_color_layer_impl.h" + +#include "cc/layers/append_quads_data.h" +#include "cc/layers/solid_color_layer.h" +#include "cc/quads/solid_color_draw_quad.h" +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/layer_test_common.h" +#include "cc/test/mock_quad_culler.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cc { +namespace { + +TEST(SolidColorLayerImplTest, verifyTilingCompleteAndNoOverlap) +{ + MockQuadCuller quadCuller; + gfx::Size layerSize = gfx::Size(800, 600); + gfx::Rect visibleContentRect = gfx::Rect(gfx::Point(), layerSize); + + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + scoped_ptr<SolidColorLayerImpl> layer = SolidColorLayerImpl::Create(hostImpl.active_tree(), 1); + layer->draw_properties().visible_content_rect = visibleContentRect; + layer->SetBounds(layerSize); + layer->SetContentBounds(layerSize); + layer->CreateRenderSurface(); + layer->draw_properties().render_target = layer.get(); + + AppendQuadsData data; + layer->AppendQuads(&quadCuller, &data); + + LayerTestCommon::verifyQuadsExactlyCoverRect(quadCuller.quadList(), visibleContentRect); +} + +TEST(SolidColorLayerImplTest, verifyCorrectBackgroundColorInQuad) +{ + SkColor testColor = 0xFFA55AFF; + + MockQuadCuller quadCuller; + gfx::Size layerSize = gfx::Size(100, 100); + gfx::Rect visibleContentRect = gfx::Rect(gfx::Point(), layerSize); + + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + scoped_ptr<SolidColorLayerImpl> layer = SolidColorLayerImpl::Create(hostImpl.active_tree(), 1); + layer->draw_properties().visible_content_rect = visibleContentRect; + layer->SetBounds(layerSize); + layer->SetContentBounds(layerSize); + layer->SetBackgroundColor(testColor); + layer->CreateRenderSurface(); + layer->draw_properties().render_target = layer.get(); + + AppendQuadsData data; + layer->AppendQuads(&quadCuller, &data); + + ASSERT_EQ(quadCuller.quadList().size(), 1U); + EXPECT_EQ(SolidColorDrawQuad::MaterialCast(quadCuller.quadList()[0])->color, testColor); +} + +TEST(SolidColorLayerImplTest, verifyCorrectOpacityInQuad) +{ + const float opacity = 0.5f; + + MockQuadCuller quadCuller; + gfx::Size layerSize = gfx::Size(100, 100); + gfx::Rect visibleContentRect = gfx::Rect(gfx::Point(), layerSize); + + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + scoped_ptr<SolidColorLayerImpl> layer = SolidColorLayerImpl::Create(hostImpl.active_tree(), 1); + layer->draw_properties().visible_content_rect = visibleContentRect; + layer->SetBounds(layerSize); + layer->SetContentBounds(layerSize); + layer->draw_properties().opacity = opacity; + layer->CreateRenderSurface(); + layer->draw_properties().render_target = layer.get(); + + AppendQuadsData data; + layer->AppendQuads(&quadCuller, &data); + + ASSERT_EQ(quadCuller.quadList().size(), 1U); + EXPECT_EQ(opacity, SolidColorDrawQuad::MaterialCast(quadCuller.quadList()[0])->opacity()); +} + +TEST(SolidColorLayerImplTest, verifyOpaqueRect) +{ + FakeImplProxy proxy; + FakeLayerTreeHostImpl hostImpl(&proxy); + + gfx::Size layerSize = gfx::Size(100, 100); + gfx::Rect visibleContentRect = gfx::Rect(gfx::Point(), layerSize); + + scoped_refptr<SolidColorLayer> layer = SolidColorLayer::Create(); + layer->SetBounds(layerSize); + layer->SetForceRenderSurface(true); + + scoped_refptr<Layer> root = Layer::Create(); + root->AddChild(layer); + + std::vector<scoped_refptr<Layer> > renderSurfaceLayerList; + LayerTreeHostCommon::calculateDrawProperties( + root, + gfx::Size(500, 500), + 1, + 1, + 1024, + false, + renderSurfaceLayerList); + + EXPECT_FALSE(layer->contents_opaque()); + layer->SetBackgroundColor(SkColorSetARGBInline(255, 10, 20, 30)); + EXPECT_TRUE(layer->contents_opaque()); + + { + scoped_ptr<SolidColorLayerImpl> layerImpl = SolidColorLayerImpl::Create(hostImpl.active_tree(), layer->id()); + layer->PushPropertiesTo(layerImpl.get()); + + // The impl layer should call itself opaque as well. + EXPECT_TRUE(layerImpl->contents_opaque()); + + // Impl layer has 1 opacity, and the color is opaque, so the opaqueRect should be the full tile. + layerImpl->draw_properties().opacity = 1; + + MockQuadCuller quadCuller; + AppendQuadsData data; + layerImpl->AppendQuads(&quadCuller, &data); + + ASSERT_EQ(quadCuller.quadList().size(), 1U); + EXPECT_EQ(visibleContentRect.ToString(), quadCuller.quadList()[0]->opaque_rect.ToString()); + } + + EXPECT_TRUE(layer->contents_opaque()); + layer->SetBackgroundColor(SkColorSetARGBInline(254, 10, 20, 30)); + EXPECT_FALSE(layer->contents_opaque()); + + { + scoped_ptr<SolidColorLayerImpl> layerImpl = SolidColorLayerImpl::Create(hostImpl.active_tree(), layer->id()); + layer->PushPropertiesTo(layerImpl.get()); + + // The impl layer should callnot itself opaque anymore. + EXPECT_FALSE(layerImpl->contents_opaque()); + + // Impl layer has 1 opacity, but the color is not opaque, so the opaque_rect should be empty. + layerImpl->draw_properties().opacity = 1; + + MockQuadCuller quadCuller; + AppendQuadsData data; + layerImpl->AppendQuads(&quadCuller, &data); + + ASSERT_EQ(quadCuller.quadList().size(), 1U); + EXPECT_EQ(gfx::Rect().ToString(), quadCuller.quadList()[0]->opaque_rect.ToString()); + } +} + +} // namespace +} // namespace cc diff --git a/cc/layers/texture_layer.cc b/cc/layers/texture_layer.cc new file mode 100644 index 0000000..2590470 --- /dev/null +++ b/cc/layers/texture_layer.cc @@ -0,0 +1,204 @@ +// Copyright 2010 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 "cc/layers/texture_layer.h" + +#include "cc/base/thread.h" +#include "cc/layers/texture_layer_client.h" +#include "cc/layers/texture_layer_impl.h" +#include "cc/trees/layer_tree_host.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebGraphicsContext3D.h" + +namespace cc { + +static void RunCallbackOnMainThread( + const TextureMailbox::ReleaseCallback& callback, + unsigned sync_point) { + callback.Run(sync_point); +} + +static void PostCallbackToMainThread( + Thread* main_thread, + const TextureMailbox::ReleaseCallback& callback, + unsigned sync_point) { + main_thread->PostTask( + base::Bind(&RunCallbackOnMainThread, callback, sync_point)); +} + +scoped_refptr<TextureLayer> TextureLayer::Create(TextureLayerClient* client) { + return scoped_refptr<TextureLayer>(new TextureLayer(client, false)); +} + +scoped_refptr<TextureLayer> TextureLayer::CreateForMailbox() { + return scoped_refptr<TextureLayer>(new TextureLayer(NULL, true)); +} + +TextureLayer::TextureLayer(TextureLayerClient* client, bool uses_mailbox) + : Layer(), + client_(client), + uses_mailbox_(uses_mailbox), + flipped_(true), + uv_top_left_(0.f, 0.f), + uv_bottom_right_(1.f, 1.f), + premultiplied_alpha_(true), + rate_limit_context_(false), + context_lost_(false), + texture_id_(0), + content_committed_(false), + own_mailbox_(false) { + vertex_opacity_[0] = 1.0f; + vertex_opacity_[1] = 1.0f; + vertex_opacity_[2] = 1.0f; + vertex_opacity_[3] = 1.0f; +} + +TextureLayer::~TextureLayer() { + if (layer_tree_host()) { + if (texture_id_) + layer_tree_host()->AcquireLayerTextures(); + if (rate_limit_context_ && client_) + layer_tree_host()->StopRateLimiter(client_->context()); + } + if (own_mailbox_) + texture_mailbox_.RunReleaseCallback(texture_mailbox_.sync_point()); +} + +scoped_ptr<LayerImpl> TextureLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) { + return TextureLayerImpl::Create(tree_impl, id(), uses_mailbox_). + PassAs<LayerImpl>(); +} + +void TextureLayer::SetFlipped(bool flipped) { + flipped_ = flipped; + SetNeedsCommit(); +} + +void TextureLayer::SetUV(gfx::PointF top_left, gfx::PointF bottom_right) { + uv_top_left_ = top_left; + uv_bottom_right_ = bottom_right; + SetNeedsCommit(); +} + +void TextureLayer::SetVertexOpacity(float bottom_left, + float top_left, + float top_right, + float bottom_right) { + // Indexing according to the quad vertex generation: + // 1--2 + // | | + // 0--3 + vertex_opacity_[0] = bottom_left; + vertex_opacity_[1] = top_left; + vertex_opacity_[2] = top_right; + vertex_opacity_[3] = bottom_right; + SetNeedsCommit(); +} + +void TextureLayer::SetPremultipliedAlpha(bool premultiplied_alpha) { + premultiplied_alpha_ = premultiplied_alpha; + SetNeedsCommit(); +} + +void TextureLayer::SetRateLimitContext(bool rate_limit) { + if (!rate_limit && rate_limit_context_ && client_ && layer_tree_host()) + layer_tree_host()->StopRateLimiter(client_->context()); + + rate_limit_context_ = rate_limit; +} + +void TextureLayer::SetTextureId(unsigned id) { + DCHECK(!uses_mailbox_); + if (texture_id_ == id) + return; + if (texture_id_ && layer_tree_host()) + layer_tree_host()->AcquireLayerTextures(); + texture_id_ = id; + SetNeedsCommit(); +} + +void TextureLayer::SetTextureMailbox(const TextureMailbox& mailbox) { + DCHECK(uses_mailbox_); + DCHECK(mailbox.IsEmpty() || !mailbox.Equals(texture_mailbox_)); + // If we never commited the mailbox, we need to release it here + if (own_mailbox_) + texture_mailbox_.RunReleaseCallback(texture_mailbox_.sync_point()); + texture_mailbox_ = mailbox; + own_mailbox_ = true; + + SetNeedsCommit(); +} + +void TextureLayer::WillModifyTexture() { + if (layer_tree_host() && (DrawsContent() || content_committed_)) { + layer_tree_host()->AcquireLayerTextures(); + content_committed_ = false; + } +} + +void TextureLayer::SetNeedsDisplayRect(const gfx::RectF& dirty_rect) { + Layer::SetNeedsDisplayRect(dirty_rect); + + if (rate_limit_context_ && client_ && layer_tree_host() && DrawsContent()) + layer_tree_host()->StartRateLimiter(client_->context()); +} + +void TextureLayer::SetLayerTreeHost(LayerTreeHost* host) { + if (texture_id_ && layer_tree_host() && host != layer_tree_host()) + layer_tree_host()->AcquireLayerTextures(); + Layer::SetLayerTreeHost(host); +} + +bool TextureLayer::DrawsContent() const { + return (client_ || texture_id_ || !texture_mailbox_.IsEmpty()) && + !context_lost_ && Layer::DrawsContent(); +} + +void TextureLayer::Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) { + if (client_) { + texture_id_ = client_->prepareTexture(*queue); + context_lost_ = + client_->context()->getGraphicsResetStatusARB() != GL_NO_ERROR; + } + + needs_display_ = false; +} + +void TextureLayer::PushPropertiesTo(LayerImpl* layer) { + Layer::PushPropertiesTo(layer); + + TextureLayerImpl* texture_layer = static_cast<TextureLayerImpl*>(layer); + texture_layer->set_flipped(flipped_); + texture_layer->set_uv_top_left(uv_top_left_); + texture_layer->set_uv_bottom_right(uv_bottom_right_); + texture_layer->set_vertex_opacity(vertex_opacity_); + texture_layer->set_premultiplied_alpha(premultiplied_alpha_); + if (uses_mailbox_ && own_mailbox_) { + Thread* main_thread = layer_tree_host()->proxy()->MainThread(); + TextureMailbox::ReleaseCallback callback; + if (!texture_mailbox_.IsEmpty()) + callback = base::Bind( + &PostCallbackToMainThread, main_thread, texture_mailbox_.callback()); + texture_layer->SetTextureMailbox(TextureMailbox( + texture_mailbox_.name(), callback, texture_mailbox_.sync_point())); + own_mailbox_ = false; + } else { + texture_layer->set_texture_id(texture_id_); + } + content_committed_ = DrawsContent(); +} + +bool TextureLayer::BlocksPendingCommit() const { + // Double-buffered texture layers need to be blocked until they can be made + // triple-buffered. Single-buffered layers already prevent draws, so + // can block too for simplicity. + return DrawsContent(); +} + +bool TextureLayer::CanClipSelf() const { + return true; +} + +} // namespace cc diff --git a/cc/layers/texture_layer.h b/cc/layers/texture_layer.h new file mode 100644 index 0000000..a957b4c --- /dev/null +++ b/cc/layers/texture_layer.h @@ -0,0 +1,106 @@ +// Copyright 2010 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 CC_LAYERS_TEXTURE_LAYER_H_ +#define CC_LAYERS_TEXTURE_LAYER_H_ + +#include <string> + +#include "base/callback.h" +#include "cc/base/cc_export.h" +#include "cc/layers/layer.h" +#include "cc/resources/texture_mailbox.h" + +namespace WebKit { class WebGraphicsContext3D; } + +namespace cc { + +class TextureLayerClient; + +// A Layer containing a the rendered output of a plugin instance. +class CC_EXPORT TextureLayer : public Layer { + public: + // If this texture layer requires special preparation logic for each frame + // driven by the compositor, pass in a non-nil client. Pass in a nil client + // pointer if texture updates are driven by an external process. + static scoped_refptr<TextureLayer> Create(TextureLayerClient* client); + + // Used when mailbox names are specified instead of texture IDs. + static scoped_refptr<TextureLayer> CreateForMailbox(); + + void ClearClient() { client_ = NULL; } + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + + // Sets whether this texture should be Y-flipped at draw time. Defaults to + // true. + void SetFlipped(bool flipped); + + // Sets a UV transform to be used at draw time. Defaults to (0, 0) and (1, 1). + void SetUV(gfx::PointF top_left, gfx::PointF bottom_right); + + // Sets an opacity value per vertex. It will be multiplied by the layer + // opacity value. + void SetVertexOpacity(float bottom_left, + float top_left, + float top_right, + float bottom_right); + + // Sets whether the alpha channel is premultiplied or unpremultiplied. + // Defaults to true. + void SetPremultipliedAlpha(bool premultiplied_alpha); + + // Sets whether this context should rate limit on damage to prevent too many + // frames from being queued up before the compositor gets a chance to run. + // Requires a non-nil client. Defaults to false. + void SetRateLimitContext(bool rate_limit); + + // Code path for plugins which supply their own texture ID. + void SetTextureId(unsigned texture_id); + + // Code path for plugins which supply their own mailbox. + void SetTextureMailbox(const TextureMailbox& mailbox); + + void WillModifyTexture(); + + virtual void SetNeedsDisplayRect(const gfx::RectF& dirty_rect) OVERRIDE; + + virtual void SetLayerTreeHost(LayerTreeHost* layer_tree_host) OVERRIDE; + virtual bool DrawsContent() const OVERRIDE; + virtual void Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + virtual bool BlocksPendingCommit() const OVERRIDE; + + virtual bool CanClipSelf() const OVERRIDE; + + protected: + TextureLayer(TextureLayerClient* client, bool uses_mailbox); + virtual ~TextureLayer(); + + private: + TextureLayerClient* client_; + bool uses_mailbox_; + + bool flipped_; + gfx::PointF uv_top_left_; + gfx::PointF uv_bottom_right_; + // [bottom left, top left, top right, bottom right] + float vertex_opacity_[4]; + bool premultiplied_alpha_; + bool rate_limit_context_; + bool context_lost_; + bool content_committed_; + + unsigned texture_id_; + TextureMailbox texture_mailbox_; + bool own_mailbox_; + + DISALLOW_COPY_AND_ASSIGN(TextureLayer); +}; + +} +#endif // CC_LAYERS_TEXTURE_LAYER_H_ diff --git a/cc/layers/texture_layer_client.h b/cc/layers/texture_layer_client.h new file mode 100644 index 0000000..f0a9ee0 --- /dev/null +++ b/cc/layers/texture_layer_client.h @@ -0,0 +1,31 @@ +// Copyright 2012 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 CC_LAYERS_TEXTURE_LAYER_CLIENT_H_ +#define CC_LAYERS_TEXTURE_LAYER_CLIENT_H_ + +namespace WebKit { +class WebGraphicsContext3D; +} + +namespace cc { +class ResourceUpdateQueue; + +class TextureLayerClient { +public: + // Called to prepare this layer's texture for compositing. The client may queue a texture + // upload or copy on the ResourceUpdateQueue. + // Returns the texture ID to be used for compositing. + virtual unsigned prepareTexture(ResourceUpdateQueue&) = 0; + + // Returns the context that is providing the texture. Used for rate limiting and detecting lost context. + virtual WebKit::WebGraphicsContext3D* context() = 0; + +protected: + virtual ~TextureLayerClient() { } +}; + +} + +#endif // CC_LAYERS_TEXTURE_LAYER_CLIENT_H_ diff --git a/cc/layers/texture_layer_impl.cc b/cc/layers/texture_layer_impl.cc new file mode 100644 index 0000000..ac366ad --- /dev/null +++ b/cc/layers/texture_layer_impl.cc @@ -0,0 +1,163 @@ +// Copyright 2011 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 "cc/layers/texture_layer_impl.h" + +#include "base/stringprintf.h" +#include "cc/layers/quad_sink.h" +#include "cc/output/renderer.h" +#include "cc/quads/texture_draw_quad.h" +#include "cc/trees/layer_tree_impl.h" + +namespace cc { + +TextureLayerImpl::TextureLayerImpl(LayerTreeImpl* tree_impl, + int id, + bool uses_mailbox) + : LayerImpl(tree_impl, id), + texture_id_(0), + external_texture_resource_(0), + premultiplied_alpha_(true), + flipped_(true), + uv_top_left_(0.f, 0.f), + uv_bottom_right_(1.f, 1.f), + uses_mailbox_(uses_mailbox), + own_mailbox_(false) { + vertex_opacity_[0] = 1.0f; + vertex_opacity_[1] = 1.0f; + vertex_opacity_[2] = 1.0f; + vertex_opacity_[3] = 1.0f; +} + +TextureLayerImpl::~TextureLayerImpl() { FreeTextureMailbox(); } + +void TextureLayerImpl::SetTextureMailbox(const TextureMailbox& mailbox) { + DCHECK(uses_mailbox_); + DCHECK(mailbox.IsEmpty() || !mailbox.Equals(texture_mailbox_)); + FreeTextureMailbox(); + texture_mailbox_ = mailbox; + own_mailbox_ = true; +} + +scoped_ptr<LayerImpl> TextureLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return TextureLayerImpl::Create(tree_impl, id(), uses_mailbox_). + PassAs<LayerImpl>(); +} + +void TextureLayerImpl::PushPropertiesTo(LayerImpl* layer) { + LayerImpl::PushPropertiesTo(layer); + + TextureLayerImpl* texture_layer = static_cast<TextureLayerImpl*>(layer); + texture_layer->set_flipped(flipped_); + texture_layer->set_uv_top_left(uv_top_left_); + texture_layer->set_uv_bottom_right(uv_bottom_right_); + texture_layer->set_vertex_opacity(vertex_opacity_); + texture_layer->set_premultiplied_alpha(premultiplied_alpha_); + if (uses_mailbox_ && own_mailbox_) { + texture_layer->SetTextureMailbox(texture_mailbox_); + own_mailbox_ = false; + } else { + texture_layer->set_texture_id(texture_id_); + } +} + +void TextureLayerImpl::WillDraw(ResourceProvider* resource_provider) { + if (uses_mailbox_ || !texture_id_) + return; + DCHECK(!external_texture_resource_); + external_texture_resource_ = + resource_provider->CreateResourceFromExternalTexture(texture_id_); +} + +void TextureLayerImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + if (!external_texture_resource_) + return; + + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(CreateSharedQuadState()); + AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data); + + gfx::Rect quad_rect(content_bounds()); + gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect()); + scoped_ptr<TextureDrawQuad> quad = TextureDrawQuad::Create(); + quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + external_texture_resource_, + premultiplied_alpha_, + uv_top_left_, + uv_bottom_right_, + vertex_opacity_, + flipped_); + + // Perform explicit clipping on a quad to avoid setting a scissor later. + if (shared_quad_state->is_clipped && quad->PerformClipping()) + shared_quad_state->is_clipped = false; + if (!quad->rect.IsEmpty()) + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); +} + +void TextureLayerImpl::DidDraw(ResourceProvider* resource_provider) { + if (uses_mailbox_ || !external_texture_resource_) + return; + // FIXME: the following assert will not be true when sending resources to a + // parent compositor. A synchronization scheme (double-buffering or + // pipelining of updates) for the client will need to exist to solve this. + DCHECK(!resource_provider->InUseByConsumer(external_texture_resource_)); + resource_provider->DeleteResource(external_texture_resource_); + external_texture_resource_ = 0; +} + +void TextureLayerImpl::DumpLayerProperties(std::string* str, int indent) const { + str->append(IndentString(indent)); + base::StringAppendF(str, + "texture layer texture id: %u premultiplied: %d\n", + texture_id_, + premultiplied_alpha_); + LayerImpl::DumpLayerProperties(str, indent); +} + +void TextureLayerImpl::DidLoseOutputSurface() { + texture_id_ = 0; + external_texture_resource_ = 0; +} + +const char* TextureLayerImpl::LayerTypeAsString() const { + return "TextureLayer"; +} + +bool TextureLayerImpl::CanClipSelf() const { + return true; +} + +void TextureLayerImpl::DidBecomeActive() { + if (!own_mailbox_) + return; + DCHECK(!external_texture_resource_); + ResourceProvider* resource_provider = layer_tree_impl()->resource_provider(); + if (!texture_mailbox_.IsEmpty()) { + external_texture_resource_ = + resource_provider->CreateResourceFromTextureMailbox(texture_mailbox_); + } + own_mailbox_ = false; +} + +void TextureLayerImpl::FreeTextureMailbox() { + if (!uses_mailbox_) + return; + if (own_mailbox_) { + DCHECK(!external_texture_resource_); + texture_mailbox_.RunReleaseCallback(texture_mailbox_.sync_point()); + } else if (external_texture_resource_) { + DCHECK(!own_mailbox_); + ResourceProvider* resource_provider = + layer_tree_impl()->resource_provider(); + resource_provider->DeleteResource(external_texture_resource_); + external_texture_resource_ = 0; + } +} + +} // namespace cc diff --git a/cc/layers/texture_layer_impl.h b/cc/layers/texture_layer_impl.h new file mode 100644 index 0000000..bee739a --- /dev/null +++ b/cc/layers/texture_layer_impl.h @@ -0,0 +1,87 @@ +// Copyright 2012 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 CC_LAYERS_TEXTURE_LAYER_IMPL_H_ +#define CC_LAYERS_TEXTURE_LAYER_IMPL_H_ + +#include <string> + +#include "base/callback.h" +#include "cc/base/cc_export.h" +#include "cc/layers/layer_impl.h" + +namespace cc { + +class CC_EXPORT TextureLayerImpl : public LayerImpl { + public: + static scoped_ptr<TextureLayerImpl> Create(LayerTreeImpl* tree_impl, + int id, + bool uses_mailbox) { + return make_scoped_ptr(new TextureLayerImpl(tree_impl, id, uses_mailbox)); + } + virtual ~TextureLayerImpl(); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* layer_tree_impl) + OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + + virtual void WillDraw(ResourceProvider* resource_provider) OVERRIDE; + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + virtual void DidDraw(ResourceProvider* resource_provider) OVERRIDE; + + virtual void DidLoseOutputSurface() OVERRIDE; + + virtual void DumpLayerProperties(std::string* str, int indent) const OVERRIDE; + virtual void DidBecomeActive() OVERRIDE; + + unsigned texture_id() const { return texture_id_; } + void set_texture_id(unsigned id) { texture_id_ = id; } + void set_premultiplied_alpha(bool premultiplied_alpha) { + premultiplied_alpha_ = premultiplied_alpha; + } + void set_flipped(bool flipped) { flipped_ = flipped; } + void set_uv_top_left(gfx::PointF top_left) { uv_top_left_ = top_left; } + void set_uv_bottom_right(gfx::PointF bottom_right) { + uv_bottom_right_ = bottom_right; + } + + // 1--2 + // | | + // 0--3 + void set_vertex_opacity(const float vertex_opacity[4]) { + vertex_opacity_[0] = vertex_opacity[0]; + vertex_opacity_[1] = vertex_opacity[1]; + vertex_opacity_[2] = vertex_opacity[2]; + vertex_opacity_[3] = vertex_opacity[3]; + } + + virtual bool CanClipSelf() const OVERRIDE; + + void SetTextureMailbox(const TextureMailbox& mailbox); + + private: + TextureLayerImpl(LayerTreeImpl* tree_impl, int id, bool uses_mailbox); + + virtual const char* LayerTypeAsString() const OVERRIDE; + void FreeTextureMailbox(); + + unsigned texture_id_; + ResourceProvider::ResourceId external_texture_resource_; + bool premultiplied_alpha_; + bool flipped_; + gfx::PointF uv_top_left_; + gfx::PointF uv_bottom_right_; + float vertex_opacity_[4]; + + TextureMailbox texture_mailbox_; + bool uses_mailbox_; + bool own_mailbox_; + + DISALLOW_COPY_AND_ASSIGN(TextureLayerImpl); +}; + +} // namespace cc + +#endif // CC_LAYERS_TEXTURE_LAYER_IMPL_H_ diff --git a/cc/layers/texture_layer_unittest.cc b/cc/layers/texture_layer_unittest.cc new file mode 100644 index 0000000..0e48b1b --- /dev/null +++ b/cc/layers/texture_layer_unittest.cc @@ -0,0 +1,471 @@ +// Copyright 2012 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 "cc/layers/texture_layer.h" + +#include <string> + +#include "base/callback.h" +#include "cc/base/thread.h" +#include "cc/layers/texture_layer_impl.h" +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_client.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/layer_tree_test_common.h" +#include "cc/trees/layer_tree_host.h" +#include "cc/trees/layer_tree_impl.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::Mock; +using ::testing::_; +using ::testing::AtLeast; +using ::testing::AnyNumber; + +namespace cc { +namespace { + +class MockLayerImplTreeHost : public LayerTreeHost { + public: + MockLayerImplTreeHost() : LayerTreeHost(&fake_client_, LayerTreeSettings()) { + Initialize(scoped_ptr<Thread>(NULL)); + } + + MOCK_METHOD0(AcquireLayerTextures, void()); + MOCK_METHOD0(SetNeedsCommit, void()); + + private: + FakeLayerImplTreeHostClient fake_client_; +}; + +class TextureLayerTest : public testing::Test { + public: + TextureLayerTest() : host_impl_(&proxy_) {} + + protected: + virtual void SetUp() { + layer_tree_host_.reset(new MockLayerImplTreeHost); + } + + virtual void TearDown() { + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber()); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber()); + + layer_tree_host_->SetRootLayer(NULL); + layer_tree_host_.reset(); + } + + scoped_ptr<MockLayerImplTreeHost> layer_tree_host_; + FakeImplProxy proxy_; + FakeLayerTreeHostImpl host_impl_; +}; + +TEST_F(TextureLayerTest, SyncImplWhenChangingTextureId) { + scoped_refptr<TextureLayer> test_layer = TextureLayer::Create(NULL); + ASSERT_TRUE(test_layer); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber()); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber()); + layer_tree_host_->SetRootLayer(test_layer); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + EXPECT_EQ(test_layer->layer_tree_host(), layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + test_layer->SetTextureId(1); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AtLeast(1)); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + test_layer->SetTextureId(2); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AtLeast(1)); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + test_layer->SetTextureId(0); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); +} + +TEST_F(TextureLayerTest, SyncImplWhenDrawing) { + gfx::RectF dirty_rect(0.f, 0.f, 1.f, 1.f); + + scoped_refptr<TextureLayer> test_layer = TextureLayer::Create(NULL); + ASSERT_TRUE(test_layer); + scoped_ptr<TextureLayerImpl> impl_layer; + impl_layer = TextureLayerImpl::Create(host_impl_.active_tree(), 1, false); + ASSERT_TRUE(impl_layer); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber()); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber()); + layer_tree_host_->SetRootLayer(test_layer); + test_layer->SetTextureId(1); + test_layer->SetIsDrawable(true); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + EXPECT_EQ(test_layer->layer_tree_host(), layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(1); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0); + test_layer->WillModifyTexture(); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1); + test_layer->SetNeedsDisplayRect(dirty_rect); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(1); + test_layer->PushPropertiesTo(impl_layer.get()); // fake commit + test_layer->SetIsDrawable(false); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + // Verify that non-drawable layers don't signal the compositor, + // except for the first draw after last commit, which must acquire + // the texture. + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(1); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0); + test_layer->WillModifyTexture(); + test_layer->SetNeedsDisplayRect(dirty_rect); + test_layer->PushPropertiesTo(impl_layer.get()); // fake commit + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + // Second draw with layer in non-drawable state: no texture + // acquisition. + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(0); + test_layer->WillModifyTexture(); + test_layer->SetNeedsDisplayRect(dirty_rect); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); +} + +TEST_F(TextureLayerTest, SyncImplWhenRemovingFromTree) { + scoped_refptr<Layer> root_layer = Layer::Create(); + ASSERT_TRUE(root_layer); + scoped_refptr<Layer> child_layer = Layer::Create(); + ASSERT_TRUE(child_layer); + root_layer->AddChild(child_layer); + scoped_refptr<TextureLayer> test_layer = TextureLayer::Create(NULL); + ASSERT_TRUE(test_layer); + test_layer->SetTextureId(0); + child_layer->AddChild(test_layer); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AnyNumber()); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber()); + layer_tree_host_->SetRootLayer(root_layer); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + test_layer->RemoveFromParent(); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + child_layer->AddChild(test_layer); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + test_layer->SetTextureId(1); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(AtLeast(1)); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + test_layer->RemoveFromParent(); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); +} + +class MockMailboxCallback { + public: + MOCK_METHOD2(Release, void(const std::string& mailbox, unsigned sync_point)); +}; + +struct CommonMailboxObjects { + CommonMailboxObjects() + : mailbox_name1_(64, '1'), + mailbox_name2_(64, '2'), + sync_point1_(1), + sync_point2_(2) { + release_mailbox1_ = base::Bind(&MockMailboxCallback::Release, + base::Unretained(&mock_callback_), + mailbox_name1_); + release_mailbox2_ = base::Bind(&MockMailboxCallback::Release, + base::Unretained(&mock_callback_), + mailbox_name2_); + gpu::Mailbox m1; + m1.SetName(reinterpret_cast<const int8*>(mailbox_name1_.data())); + mailbox1_ = TextureMailbox(m1, release_mailbox1_, sync_point1_); + gpu::Mailbox m2; + m2.SetName(reinterpret_cast<const int8*>(mailbox_name2_.data())); + mailbox2_ = TextureMailbox(m2, release_mailbox2_, sync_point2_); + } + + std::string mailbox_name1_; + std::string mailbox_name2_; + MockMailboxCallback mock_callback_; + TextureMailbox::ReleaseCallback release_mailbox1_; + TextureMailbox::ReleaseCallback release_mailbox2_; + TextureMailbox mailbox1_; + TextureMailbox mailbox2_; + unsigned sync_point1_; + unsigned sync_point2_; +}; + +class TextureLayerWithMailboxTest : public TextureLayerTest { + protected: + virtual void TearDown() { + Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + EXPECT_CALL(test_data_.mock_callback_, + Release(test_data_.mailbox_name1_, + test_data_.sync_point1_)).Times(1); + TextureLayerTest::TearDown(); + } + + CommonMailboxObjects test_data_; +}; + +TEST_F(TextureLayerWithMailboxTest, ReplaceMailboxOnMainThreadBeforeCommit) { + scoped_refptr<TextureLayer> test_layer = TextureLayer::CreateForMailbox(); + ASSERT_TRUE(test_layer); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AnyNumber()); + layer_tree_host_->SetRootLayer(test_layer); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + test_layer->SetTextureMailbox(test_data_.mailbox1_); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + EXPECT_CALL(test_data_.mock_callback_, + Release(test_data_.mailbox_name1_, test_data_.sync_point1_)) + .Times(1); + test_layer->SetTextureMailbox(test_data_.mailbox2_); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + + EXPECT_CALL(*layer_tree_host_, AcquireLayerTextures()).Times(0); + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + EXPECT_CALL(test_data_.mock_callback_, + Release(test_data_.mailbox_name2_, test_data_.sync_point2_)) + .Times(1); + test_layer->SetTextureMailbox(TextureMailbox()); + Mock::VerifyAndClearExpectations(layer_tree_host_.get()); + Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + + // Test destructor. + EXPECT_CALL(*layer_tree_host_, SetNeedsCommit()).Times(AtLeast(1)); + test_layer->SetTextureMailbox(test_data_.mailbox1_); +} + +class TextureLayerImplWithMailboxThreadedCallback : public ThreadedTest { + public: + TextureLayerImplWithMailboxThreadedCallback() + : callback_count_(0), + commit_count_(0) {} + + // Make sure callback is received on main and doesn't block the impl thread. + void ReleaseCallback(unsigned sync_point) { + EXPECT_EQ(true, proxy()->IsMainThread()); + ++callback_count_; + } + + void SetMailbox(char mailbox_char) { + TextureMailbox mailbox( + std::string(64, mailbox_char), + base::Bind( + &TextureLayerImplWithMailboxThreadedCallback::ReleaseCallback, + base::Unretained(this))); + layer_->SetTextureMailbox(mailbox); + } + + virtual void beginTest() OVERRIDE { + gfx::Size bounds(100, 100); + root_ = Layer::Create(); + root_->SetAnchorPoint(gfx::PointF()); + root_->SetBounds(bounds); + + layer_ = TextureLayer::CreateForMailbox(); + layer_->SetIsDrawable(true); + layer_->SetAnchorPoint(gfx::PointF()); + layer_->SetBounds(bounds); + + root_->AddChild(layer_); + m_layerTreeHost->SetRootLayer(root_); + m_layerTreeHost->SetViewportSize(bounds, bounds); + SetMailbox('1'); + EXPECT_EQ(0, callback_count_); + + // Case #1: change mailbox before the commit. The old mailbox should be + // released immediately. + SetMailbox('2'); + EXPECT_EQ(1, callback_count_); + postSetNeedsCommitToMainThread(); + } + + virtual void didCommit() OVERRIDE { + ++commit_count_; + switch (commit_count_) { + case 1: + // Case #2: change mailbox after the commit (and draw), where the + // layer draws. The old mailbox should be released during the next + // commit. + SetMailbox('3'); + EXPECT_EQ(1, callback_count_); + break; + case 2: + // Old mailbox was released, task was posted, but won't execute + // until this didCommit returns. + // TODO(piman): fix this. + EXPECT_EQ(1, callback_count_); + m_layerTreeHost->SetNeedsCommit(); + break; + case 3: + EXPECT_EQ(2, callback_count_); + // Case #3: change mailbox when the layer doesn't draw. The old + // mailbox should be released during the next commit. + layer_->SetBounds(gfx::Size()); + SetMailbox('4'); + break; + case 4: + // Old mailbox was released, task was posted, but won't execute + // until this didCommit returns. + // TODO(piman): fix this. + EXPECT_EQ(2, callback_count_); + m_layerTreeHost->SetNeedsCommit(); + break; + case 5: + EXPECT_EQ(3, callback_count_); + // Case #4: release mailbox that was committed but never drawn. The + // old mailbox should be released during the next commit. + layer_->SetTextureMailbox(TextureMailbox()); + break; + case 6: + // Old mailbox was released, task was posted, but won't execute + // until this didCommit returns. + // TODO(piman): fix this. + EXPECT_EQ(3, callback_count_); + m_layerTreeHost->SetNeedsCommit(); + break; + case 7: + EXPECT_EQ(4, callback_count_); + endTest(); + break; + default: + NOTREACHED(); + break; + } + } + + virtual void afterTest() OVERRIDE {} + + private: + int callback_count_; + int commit_count_; + scoped_refptr<Layer> root_; + scoped_refptr<TextureLayer> layer_; +}; + +SINGLE_AND_MULTI_THREAD_TEST_F(TextureLayerImplWithMailboxThreadedCallback); + +class TextureLayerImplWithMailboxTest : public TextureLayerTest { + protected: + virtual void SetUp() { + TextureLayerTest::SetUp(); + layer_tree_host_.reset(new MockLayerImplTreeHost); + EXPECT_TRUE(host_impl_.InitializeRenderer(createFakeOutputSurface())); + } + + CommonMailboxObjects test_data_; +}; + +TEST_F(TextureLayerImplWithMailboxTest, TestImplLayerCallbacks) { + host_impl_.CreatePendingTree(); + scoped_ptr<TextureLayerImpl> pending_layer; + pending_layer = TextureLayerImpl::Create(host_impl_.pending_tree(), 1, true); + ASSERT_TRUE(pending_layer); + + scoped_ptr<LayerImpl> activeLayer( + pending_layer->CreateLayerImpl(host_impl_.active_tree())); + ASSERT_TRUE(activeLayer); + + pending_layer->SetTextureMailbox(test_data_.mailbox1_); + + // Test multiple commits without an activation. + EXPECT_CALL(test_data_.mock_callback_, + Release(test_data_.mailbox_name1_, test_data_.sync_point1_)) + .Times(1); + pending_layer->SetTextureMailbox(test_data_.mailbox2_); + Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + + // Test callback after activation. + pending_layer->PushPropertiesTo(activeLayer.get()); + activeLayer->DidBecomeActive(); + + EXPECT_CALL(test_data_.mock_callback_, Release(_, _)).Times(0); + pending_layer->SetTextureMailbox(test_data_.mailbox1_); + Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + + EXPECT_CALL(test_data_.mock_callback_, Release(test_data_.mailbox_name2_, _)) + .Times(1); + pending_layer->PushPropertiesTo(activeLayer.get()); + activeLayer->DidBecomeActive(); + Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + + // Test resetting the mailbox. + EXPECT_CALL(test_data_.mock_callback_, Release(test_data_.mailbox_name1_, _)) + .Times(1); + pending_layer->SetTextureMailbox(TextureMailbox()); + pending_layer->PushPropertiesTo(activeLayer.get()); + activeLayer->DidBecomeActive(); + Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + + // Test destructor. + EXPECT_CALL(test_data_.mock_callback_, + Release(test_data_.mailbox_name1_, test_data_.sync_point1_)) + .Times(1); + pending_layer->SetTextureMailbox(test_data_.mailbox1_); +} + +TEST_F(TextureLayerImplWithMailboxTest, + TestDestructorCallbackOnCreatedResource) { + scoped_ptr<TextureLayerImpl> impl_layer; + impl_layer = TextureLayerImpl::Create(host_impl_.active_tree(), 1, true); + ASSERT_TRUE(impl_layer); + + EXPECT_CALL(test_data_.mock_callback_, Release(test_data_.mailbox_name1_, _)) + .Times(1); + impl_layer->SetTextureMailbox(test_data_.mailbox1_); + impl_layer->WillDraw(host_impl_.active_tree()->resource_provider()); + impl_layer->DidDraw(host_impl_.active_tree()->resource_provider()); + impl_layer->SetTextureMailbox(TextureMailbox()); +} + +TEST_F(TextureLayerImplWithMailboxTest, TestCallbackOnInUseResource) { + ResourceProvider* provider = host_impl_.active_tree()->resource_provider(); + ResourceProvider::ResourceId id = + provider->CreateResourceFromTextureMailbox(test_data_.mailbox1_); + provider->AllocateForTesting(id); + + // Transfer some resources to the parent. + ResourceProvider::ResourceIdArray resource_ids_to_transfer; + resource_ids_to_transfer.push_back(id); + TransferableResourceArray list; + provider->PrepareSendToParent(resource_ids_to_transfer, &list); + EXPECT_TRUE(provider->InUseByConsumer(id)); + EXPECT_CALL(test_data_.mock_callback_, Release(_, _)).Times(0); + provider->DeleteResource(id); + Mock::VerifyAndClearExpectations(&test_data_.mock_callback_); + EXPECT_CALL(test_data_.mock_callback_, Release(test_data_.mailbox_name1_, _)) + .Times(1); + provider->ReceiveFromParent(list); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/tiled_layer.cc b/cc/layers/tiled_layer.cc new file mode 100644 index 0000000..e64222b --- /dev/null +++ b/cc/layers/tiled_layer.cc @@ -0,0 +1,888 @@ +// Copyright 2011 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 "cc/layers/tiled_layer.h" + +#include "base/auto_reset.h" +#include "base/basictypes.h" +#include "build/build_config.h" +#include "cc/debug/overdraw_metrics.h" +#include "cc/layers/layer_impl.h" +#include "cc/layers/tiled_layer_impl.h" +#include "cc/resources/layer_updater.h" +#include "cc/resources/prioritized_resource.h" +#include "cc/resources/priority_calculator.h" +#include "cc/trees/layer_tree_host.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "ui/gfx/rect_conversions.h" + +namespace cc { + +// Maximum predictive expansion of the visible area. +static const int kMaxPredictiveTilesCount = 2; + +// Number of rows/columns of tiles to pre-paint. +// We should increase these further as all textures are +// prioritized and we insure performance doesn't suffer. +static const int kPrepaintRows = 4; +static const int kPrepaintColumns = 2; + +class UpdatableTile : public LayerTilingData::Tile { + public: + static scoped_ptr<UpdatableTile> Create( + scoped_ptr<LayerUpdater::Resource> updater_resource) { + return make_scoped_ptr(new UpdatableTile(updater_resource.Pass())); + } + + LayerUpdater::Resource* updater_resource() { return updater_resource_.get(); } + PrioritizedResource* managed_resource() { + return updater_resource_->texture(); + } + + bool is_dirty() const { return !dirty_rect.IsEmpty(); } + + // Reset update state for the current frame. This should occur before painting + // for all layers. Since painting one layer can invalidate another layer after + // it has already painted, mark all non-dirty tiles as valid before painting + // such that invalidations during painting won't prevent them from being + // pushed. + void ResetUpdateState() { + update_rect = gfx::Rect(); + occluded = false; + partial_update = false; + valid_for_frame = !is_dirty(); + } + + // This promises to update the tile and therefore also guarantees the tile + // will be valid for this frame. dirty_rect is copied into update_rect so we + // can continue to track re-entrant invalidations that occur during painting. + void MarkForUpdate() { + valid_for_frame = true; + update_rect = dirty_rect; + dirty_rect = gfx::Rect(); + } + + gfx::Rect dirty_rect; + gfx::Rect update_rect; + bool partial_update; + bool valid_for_frame; + bool occluded; + + private: + explicit UpdatableTile(scoped_ptr<LayerUpdater::Resource> updater_resource) + : partial_update(false), + valid_for_frame(false), + occluded(false), + updater_resource_(updater_resource.Pass()) {} + + scoped_ptr<LayerUpdater::Resource> updater_resource_; + + DISALLOW_COPY_AND_ASSIGN(UpdatableTile); +}; + +TiledLayer::TiledLayer() + : ContentsScalingLayer(), + texture_format_(GL_INVALID_ENUM), + skips_draw_(false), + failed_update_(false), + tiling_option_(AUTO_TILE) { + tiler_ = + LayerTilingData::Create(gfx::Size(), LayerTilingData::HAS_BORDER_TEXELS); +} + +TiledLayer::~TiledLayer() {} + +scoped_ptr<LayerImpl> TiledLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) { + return TiledLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void TiledLayer::UpdateTileSizeAndTilingOption() { + DCHECK(layer_tree_host()); + + gfx::Size default_tile_size = layer_tree_host()->settings().defaultTileSize; + gfx::Size max_untiled_layer_size = + layer_tree_host()->settings().maxUntiledLayerSize; + int layer_width = content_bounds().width(); + int layer_height = content_bounds().height(); + + gfx::Size tile_size(std::min(default_tile_size.width(), layer_width), + std::min(default_tile_size.height(), layer_height)); + + // Tile if both dimensions large, or any one dimension large and the other + // extends into a second tile but the total layer area isn't larger than that + // of the largest possible untiled layer. This heuristic allows for long + // skinny layers (e.g. scrollbars) that are Nx1 tiles to minimize wasted + // texture space but still avoids creating very large tiles. + bool any_dimension_large = layer_width > max_untiled_layer_size.width() || + layer_height > max_untiled_layer_size.height(); + bool any_dimension_one_tile = + (layer_width <= default_tile_size.width() || + layer_height <= default_tile_size.height()) && + (layer_width * layer_height) <= (max_untiled_layer_size.width() * + max_untiled_layer_size.height()); + bool auto_tiled = any_dimension_large && !any_dimension_one_tile; + + bool is_tiled; + if (tiling_option_ == ALWAYS_TILE) + is_tiled = true; + else if (tiling_option_ == NEVER_TILE) + is_tiled = false; + else + is_tiled = auto_tiled; + + gfx::Size requested_size = is_tiled ? tile_size : content_bounds(); + const int max_size = + layer_tree_host()->GetRendererCapabilities().max_texture_size; + requested_size.ClampToMax(gfx::Size(max_size, max_size)); + SetTileSize(requested_size); +} + +void TiledLayer::UpdateBounds() { + gfx::Size old_bounds = tiler_->bounds(); + gfx::Size new_bounds = content_bounds(); + if (old_bounds == new_bounds) + return; + tiler_->SetBounds(new_bounds); + + // Invalidate any areas that the new bounds exposes. + Region old_region = gfx::Rect(gfx::Point(), old_bounds); + Region new_region = gfx::Rect(gfx::Point(), new_bounds); + new_region.Subtract(old_region); + for (Region::Iterator new_rects(new_region); + new_rects.has_rect(); + new_rects.next()) + InvalidateContentRect(new_rects.rect()); +} + +void TiledLayer::SetTileSize(gfx::Size size) { tiler_->SetTileSize(size); } + +void TiledLayer::SetBorderTexelOption( + LayerTilingData::BorderTexelOption border_texel_option) { + tiler_->SetBorderTexelOption(border_texel_option); +} + +bool TiledLayer::DrawsContent() const { + if (!ContentsScalingLayer::DrawsContent()) + return false; + + bool has_more_than_one_tile = + tiler_->num_tiles_x() > 1 || tiler_->num_tiles_y() > 1; + if (tiling_option_ == NEVER_TILE && has_more_than_one_tile) + return false; + + return true; +} + +void TiledLayer::SetIsMask(bool is_mask) { + set_tiling_option(is_mask ? NEVER_TILE : AUTO_TILE); +} + +void TiledLayer::PushPropertiesTo(LayerImpl* layer) { + ContentsScalingLayer::PushPropertiesTo(layer); + + TiledLayerImpl* tiled_layer = static_cast<TiledLayerImpl*>(layer); + + tiled_layer->set_skips_draw(skips_draw_); + tiled_layer->SetTilingData(*tiler_); + std::vector<UpdatableTile*> invalid_tiles; + + for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin(); + iter != tiler_->tiles().end(); + ++iter) { + int i = iter->first.first; + int j = iter->first.second; + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second); + // FIXME: This should not ever be null. + if (!tile) + continue; + + if (!tile->managed_resource()->haveBackingTexture()) { + // Evicted tiles get deleted from both layers + invalid_tiles.push_back(tile); + continue; + } + + if (!tile->valid_for_frame) { + // Invalidated tiles are set so they can get different debug colors. + tiled_layer->PushInvalidTile(i, j); + continue; + } + + tiled_layer->PushTileProperties( + i, + j, + tile->managed_resource()->resourceId(), + tile->opaque_rect(), + tile->managed_resource()->contentsSwizzled()); + } + for (std::vector<UpdatableTile*>::const_iterator iter = invalid_tiles.begin(); + iter != invalid_tiles.end(); + ++iter) + tiler_->TakeTile((*iter)->i(), (*iter)->j()); +} + +bool TiledLayer::BlocksPendingCommit() const { return true; } + +PrioritizedResourceManager* TiledLayer::ResourceManager() const { + if (!layer_tree_host()) + return NULL; + return layer_tree_host()->contents_texture_manager(); +} + +const PrioritizedResource* TiledLayer::ResourceAtForTesting(int i, + int j) const { + UpdatableTile* tile = TileAt(i, j); + if (!tile) + return NULL; + return tile->managed_resource(); +} + +void TiledLayer::SetLayerTreeHost(LayerTreeHost* host) { + if (host && host != layer_tree_host()) { + for (LayerTilingData::TileMap::const_iterator + iter = tiler_->tiles().begin(); + iter != tiler_->tiles().end(); + ++iter) { + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second); + // FIXME: This should not ever be null. + if (!tile) + continue; + tile->managed_resource()->setTextureManager( + host->contents_texture_manager()); + } + } + ContentsScalingLayer::SetLayerTreeHost(host); +} + +UpdatableTile* TiledLayer::TileAt(int i, int j) const { + return static_cast<UpdatableTile*>(tiler_->TileAt(i, j)); +} + +UpdatableTile* TiledLayer::CreateTile(int i, int j) { + CreateUpdaterIfNeeded(); + + scoped_ptr<UpdatableTile> tile( + UpdatableTile::Create(Updater()->CreateResource(ResourceManager()))); + tile->managed_resource()->setDimensions(tiler_->tile_size(), texture_format_); + + UpdatableTile* added_tile = tile.get(); + tiler_->AddTile(tile.PassAs<LayerTilingData::Tile>(), i, j); + + added_tile->dirty_rect = tiler_->TileRect(added_tile); + + // Temporary diagnostic crash. + CHECK(added_tile); + CHECK(TileAt(i, j)); + + return added_tile; +} + +void TiledLayer::SetNeedsDisplayRect(const gfx::RectF& dirty_rect) { + InvalidateContentRect(LayerRectToContentRect(dirty_rect)); + ContentsScalingLayer::SetNeedsDisplayRect(dirty_rect); +} + +void TiledLayer::InvalidateContentRect(gfx::Rect content_rect) { + UpdateBounds(); + if (tiler_->is_empty() || content_rect.IsEmpty() || skips_draw_) + return; + + for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin(); + iter != tiler_->tiles().end(); + ++iter) { + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second); + DCHECK(tile); + // FIXME: This should not ever be null. + if (!tile) + continue; + gfx::Rect bound = tiler_->TileRect(tile); + bound.Intersect(content_rect); + tile->dirty_rect.Union(bound); + } +} + +// Returns true if tile is dirty and only part of it needs to be updated. +bool TiledLayer::TileOnlyNeedsPartialUpdate(UpdatableTile* tile) { + return !tile->dirty_rect.Contains(tiler_->TileRect(tile)) && + tile->managed_resource()->haveBackingTexture(); +} + +bool TiledLayer::UpdateTiles(int left, + int top, + int right, + int bottom, + ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats, + bool* did_paint) { + *did_paint = false; + CreateUpdaterIfNeeded(); + + bool ignore_occlusions = !occlusion; + if (!HaveTexturesForTiles(left, top, right, bottom, ignore_occlusions)) { + failed_update_ = true; + return false; + } + + gfx::Rect paint_rect = + MarkTilesForUpdate(left, top, right, bottom, ignore_occlusions); + + if (occlusion) + occlusion->overdraw_metrics()->DidPaint(paint_rect); + + if (paint_rect.IsEmpty()) + return true; + + *did_paint = true; + UpdateTileTextures( + paint_rect, left, top, right, bottom, queue, occlusion, stats); + return true; +} + +void TiledLayer::MarkOcclusionsAndRequestTextures( + int left, + int top, + int right, + int bottom, + const OcclusionTracker* occlusion) { + // There is some difficult dependancies between occlusions, recording + // occlusion metrics and requesting memory so those are encapsulated in this + // function: - We only want to call RequestLate on unoccluded textures (to + // preserve memory for other layers when near OOM). - We only want to record + // occlusion metrics if all memory requests succeed. + + int occluded_tile_count = 0; + bool succeeded = true; + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = TileAt(i, j); + DCHECK(tile); // Did SetTexturePriorities get skipped? + // FIXME: This should not ever be null. + if (!tile) + continue; + // Did ResetUpdateState get skipped? Are we doing more than one occlusion + // pass? + DCHECK(!tile->occluded); + gfx::Rect visible_tile_rect = gfx::IntersectRects( + tiler_->tile_bounds(i, j), visible_content_rect()); + if (occlusion && occlusion->Occluded(render_target(), + visible_tile_rect, + draw_transform(), + draw_transform_is_animating(), + is_clipped(), + clip_rect(), + NULL)) { + tile->occluded = true; + occluded_tile_count++; + } else { + succeeded &= tile->managed_resource()->requestLate(); + } + } + } + + if (!succeeded) + return; + if (occlusion) + occlusion->overdraw_metrics()->DidCullTilesForUpload(occluded_tile_count); +} + +bool TiledLayer::HaveTexturesForTiles(int left, + int top, + int right, + int bottom, + bool ignore_occlusions) { + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = TileAt(i, j); + DCHECK(tile); // Did SetTexturePriorites get skipped? + // FIXME: This should not ever be null. + if (!tile) + continue; + + // Ensure the entire tile is dirty if we don't have the texture. + if (!tile->managed_resource()->haveBackingTexture()) + tile->dirty_rect = tiler_->TileRect(tile); + + // If using occlusion and the visible region of the tile is occluded, + // don't reserve a texture or update the tile. + if (tile->occluded && !ignore_occlusions) + continue; + + if (!tile->managed_resource()->canAcquireBackingTexture()) + return false; + } + } + return true; +} + +gfx::Rect TiledLayer::MarkTilesForUpdate(int left, + int top, + int right, + int bottom, + bool ignore_occlusions) { + gfx::Rect paint_rect; + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = TileAt(i, j); + DCHECK(tile); // Did SetTexturePriorites get skipped? + // FIXME: This should not ever be null. + if (!tile) + continue; + if (tile->occluded && !ignore_occlusions) + continue; + // FIXME: Decide if partial update should be allowed based on cost + // of update. https://bugs.webkit.org/show_bug.cgi?id=77376 + if (tile->is_dirty() && layer_tree_host() && + layer_tree_host()->buffered_updates()) { + // If we get a partial update, we use the same texture, otherwise return + // the current texture backing, so we don't update visible textures + // non-atomically. If the current backing is in-use, it won't be + // deleted until after the commit as the texture manager will not allow + // deletion or recycling of in-use textures. + if (TileOnlyNeedsPartialUpdate(tile) && + layer_tree_host()->RequestPartialTextureUpdate()) { + tile->partial_update = true; + } else { + tile->dirty_rect = tiler_->TileRect(tile); + tile->managed_resource()->returnBackingTexture(); + } + } + + paint_rect.Union(tile->dirty_rect); + tile->MarkForUpdate(); + } + } + return paint_rect; +} + +void TiledLayer::UpdateTileTextures(gfx::Rect paint_rect, + int left, + int top, + int right, + int bottom, + ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) { + // The update_rect should be in layer space. So we have to convert the + // paint_rect from content space to layer space. + float width_scale = + bounds().width() / static_cast<float>(content_bounds().width()); + float height_scale = + bounds().height() / static_cast<float>(content_bounds().height()); + update_rect_ = gfx::ScaleRect(paint_rect, width_scale, height_scale); + + // Calling PrepareToUpdate() calls into WebKit to paint, which may have the + // side effect of disabling compositing, which causes our reference to the + // texture updater to be deleted. However, we can't free the memory backing + // the SkCanvas until the paint finishes, so we grab a local reference here to + // hold the updater alive until the paint completes. + scoped_refptr<LayerUpdater> protector(Updater()); + gfx::Rect painted_opaque_rect; + Updater()->PrepareToUpdate(paint_rect, + tiler_->tile_size(), + 1.f / width_scale, + 1.f / height_scale, + &painted_opaque_rect, + stats); + + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = TileAt(i, j); + DCHECK(tile); // Did SetTexturePriorites get skipped? + // FIXME: This should not ever be null. + if (!tile) + continue; + + gfx::Rect tile_rect = tiler_->tile_bounds(i, j); + + // Use update_rect as the above loop copied the dirty rect for this frame + // to update_rect. + gfx::Rect dirty_rect = tile->update_rect; + if (dirty_rect.IsEmpty()) + continue; + + // Save what was painted opaque in the tile. Keep the old area if the + // paint didn't touch it, and didn't paint some other part of the tile + // opaque. + gfx::Rect tile_painted_rect = gfx::IntersectRects(tile_rect, paint_rect); + gfx::Rect tile_painted_opaque_rect = + gfx::IntersectRects(tile_rect, painted_opaque_rect); + if (!tile_painted_rect.IsEmpty()) { + gfx::Rect paint_inside_tile_opaque_rect = + gfx::IntersectRects(tile->opaque_rect(), tile_painted_rect); + bool paint_inside_tile_opaque_rect_is_non_opaque = + !tile_painted_opaque_rect.Contains(paint_inside_tile_opaque_rect); + bool opaque_paint_not_inside_tile_opaque_rect = + !tile_painted_opaque_rect.IsEmpty() && + !tile->opaque_rect().Contains(tile_painted_opaque_rect); + + if (paint_inside_tile_opaque_rect_is_non_opaque || + opaque_paint_not_inside_tile_opaque_rect) + tile->set_opaque_rect(tile_painted_opaque_rect); + } + + // source_rect starts as a full-sized tile with border texels included. + gfx::Rect source_rect = tiler_->TileRect(tile); + source_rect.Intersect(dirty_rect); + // Paint rect not guaranteed to line up on tile boundaries, so + // make sure that source_rect doesn't extend outside of it. + source_rect.Intersect(paint_rect); + + tile->update_rect = source_rect; + + if (source_rect.IsEmpty()) + continue; + + const gfx::Point anchor = tiler_->TileRect(tile).origin(); + + // Calculate tile-space rectangle to upload into. + gfx::Vector2d dest_offset = source_rect.origin() - anchor; + CHECK_GE(dest_offset.x(), 0); + CHECK_GE(dest_offset.y(), 0); + + // Offset from paint rectangle to this tile's dirty rectangle. + gfx::Vector2d paint_offset = source_rect.origin() - paint_rect.origin(); + CHECK_GE(paint_offset.x(), 0); + CHECK_GE(paint_offset.y(), 0); + CHECK_LE(paint_offset.x() + source_rect.width(), paint_rect.width()); + CHECK_LE(paint_offset.y() + source_rect.height(), paint_rect.height()); + + tile->updater_resource()->Update( + queue, source_rect, dest_offset, tile->partial_update, stats); + if (occlusion) { + occlusion->overdraw_metrics()-> + DidUpload(gfx::Transform(), source_rect, tile->opaque_rect()); + } + + } + } +} + +// This picks a small animated layer to be anything less than one viewport. This +// is specifically for page transitions which are viewport-sized layers. The +// extra tile of padding is due to these layers being slightly larger than the +// viewport in some cases. +bool TiledLayer::IsSmallAnimatedLayer() const { + if (!draw_transform_is_animating() && !screen_space_transform_is_animating()) + return false; + gfx::Size viewport_size = + layer_tree_host() ? layer_tree_host()->device_viewport_size() + : gfx::Size(); + gfx::Rect content_rect(gfx::Point(), content_bounds()); + return content_rect.width() <= + viewport_size.width() + tiler_->tile_size().width() && + content_rect.height() <= + viewport_size.height() + tiler_->tile_size().height(); +} + +namespace { +// FIXME: Remove this and make this based on distance once distance can be +// calculated for offscreen layers. For now, prioritize all small animated +// layers after 512 pixels of pre-painting. +void SetPriorityForTexture(gfx::Rect visible_rect, + gfx::Rect tile_rect, + bool draws_to_root, + bool is_small_animated_layer, + PrioritizedResource* texture) { + int priority = PriorityCalculator::LowestPriority(); + if (!visible_rect.IsEmpty()) { + priority = PriorityCalculator::PriorityFromDistance( + visible_rect, tile_rect, draws_to_root); + } + + if (is_small_animated_layer) { + priority = PriorityCalculator::max_priority( + priority, PriorityCalculator::SmallAnimatedLayerMinPriority()); + } + + if (priority != PriorityCalculator::LowestPriority()) + texture->setRequestPriority(priority); +} +} // namespace + +void TiledLayer::SetTexturePriorities(const PriorityCalculator& priority_calc) { + UpdateBounds(); + ResetUpdateState(); + UpdateScrollPrediction(); + + if (tiler_->has_empty_bounds()) + return; + + bool draws_to_root = !render_target()->parent(); + bool small_animated_layer = IsSmallAnimatedLayer(); + + // Minimally create the tiles in the desired pre-paint rect. + gfx::Rect create_tiles_rect = IdlePaintRect(); + if (small_animated_layer) + create_tiles_rect = gfx::Rect(gfx::Point(), content_bounds()); + if (!create_tiles_rect.IsEmpty()) { + int left, top, right, bottom; + tiler_->ContentRectToTileIndices( + create_tiles_rect, &left, &top, &right, &bottom); + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + if (!TileAt(i, j)) + CreateTile(i, j); + } + } + } + + // Now update priorities on all tiles we have in the layer, no matter where + // they are. + for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin(); + iter != tiler_->tiles().end(); + ++iter) { + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second); + // FIXME: This should not ever be null. + if (!tile) + continue; + gfx::Rect tile_rect = tiler_->TileRect(tile); + SetPriorityForTexture(predicted_visible_rect_, + tile_rect, + draws_to_root, + small_animated_layer, + tile->managed_resource()); + } +} + +Region TiledLayer::VisibleContentOpaqueRegion() const { + if (skips_draw_) + return Region(); + if (contents_opaque()) + return visible_content_rect(); + return tiler_->OpaqueRegionInContentRect(visible_content_rect()); +} + +void TiledLayer::ResetUpdateState() { + skips_draw_ = false; + failed_update_ = false; + + LayerTilingData::TileMap::const_iterator end = tiler_->tiles().end(); + for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin(); + iter != end; + ++iter) { + UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second); + // FIXME: This should not ever be null. + if (!tile) + continue; + tile->ResetUpdateState(); + } +} + +namespace { +gfx::Rect ExpandRectByDelta(gfx::Rect rect, gfx::Vector2d delta) { + int width = rect.width() + std::abs(delta.x()); + int height = rect.height() + std::abs(delta.y()); + int x = rect.x() + ((delta.x() < 0) ? delta.x() : 0); + int y = rect.y() + ((delta.y() < 0) ? delta.y() : 0); + return gfx::Rect(x, y, width, height); +} +} + +void TiledLayer::UpdateScrollPrediction() { + // This scroll prediction is very primitive and should be replaced by a + // a recursive calculation on all layers which uses actual scroll/animation + // velocities. To insure this doesn't miss-predict, we only use it to predict + // the visible_rect if: + // - content_bounds() hasn't changed. + // - visible_rect.size() hasn't changed. + // These two conditions prevent rotations, scales, pinch-zooms etc. where + // the prediction would be incorrect. + gfx::Vector2d delta = visible_content_rect().CenterPoint() - + previous_visible_rect_.CenterPoint(); + predicted_scroll_ = -delta; + predicted_visible_rect_ = visible_content_rect(); + if (previous_content_bounds_ == content_bounds() && + previous_visible_rect_.size() == visible_content_rect().size()) { + // Only expand the visible rect in the major scroll direction, to prevent + // massive paints due to diagonal scrolls. + gfx::Vector2d major_scroll_delta = + (std::abs(delta.x()) > std::abs(delta.y())) ? + gfx::Vector2d(delta.x(), 0) : + gfx::Vector2d(0, delta.y()); + predicted_visible_rect_ = + ExpandRectByDelta(visible_content_rect(), major_scroll_delta); + + // Bound the prediction to prevent unbounded paints, and clamp to content + // bounds. + gfx::Rect bound = visible_content_rect(); + bound.Inset(-tiler_->tile_size().width() * kMaxPredictiveTilesCount, + -tiler_->tile_size().height() * kMaxPredictiveTilesCount); + bound.Intersect(gfx::Rect(gfx::Point(), content_bounds())); + predicted_visible_rect_.Intersect(bound); + } + previous_content_bounds_ = content_bounds(); + previous_visible_rect_ = visible_content_rect(); +} + +void TiledLayer::Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) { + DCHECK(!skips_draw_ && !failed_update_); // Did ResetUpdateState get skipped? + { + base::AutoReset<bool> ignore_set_needs_commit(&ignore_set_needs_commit_, + true); + + ContentsScalingLayer::Update(queue, occlusion, stats); + UpdateBounds(); + } + + if (tiler_->has_empty_bounds() || !DrawsContent()) + return; + + bool did_paint = false; + + // Animation pre-paint. If the layer is small, try to paint it all + // immediately whether or not it is occluded, to avoid paint/upload + // hiccups while it is animating. + if (IsSmallAnimatedLayer()) { + int left, top, right, bottom; + tiler_->ContentRectToTileIndices(gfx::Rect(gfx::Point(), content_bounds()), + &left, + &top, + &right, + &bottom); + UpdateTiles(left, top, right, bottom, queue, NULL, stats, &did_paint); + if (did_paint) + return; + // This was an attempt to paint the entire layer so if we fail it's okay, + // just fallback on painting visible etc. below. + failed_update_ = false; + } + + if (predicted_visible_rect_.IsEmpty()) + return; + + // Visible painting. First occlude visible tiles and paint the non-occluded + // tiles. + int left, top, right, bottom; + tiler_->ContentRectToTileIndices( + predicted_visible_rect_, &left, &top, &right, &bottom); + MarkOcclusionsAndRequestTextures(left, top, right, bottom, occlusion); + skips_draw_ = !UpdateTiles( + left, top, right, bottom, queue, occlusion, stats, &did_paint); + if (skips_draw_) + tiler_->reset(); + if (skips_draw_ || did_paint) + return; + + // If we have already painting everything visible. Do some pre-painting while + // idle. + gfx::Rect idle_paint_content_rect = IdlePaintRect(); + if (idle_paint_content_rect.IsEmpty()) + return; + + // Prepaint anything that was occluded but inside the layer's visible region. + if (!UpdateTiles(left, top, right, bottom, queue, NULL, stats, &did_paint) || + did_paint) + return; + + int prepaint_left, prepaint_top, prepaint_right, prepaint_bottom; + tiler_->ContentRectToTileIndices(idle_paint_content_rect, + &prepaint_left, + &prepaint_top, + &prepaint_right, + &prepaint_bottom); + + // Then expand outwards one row/column at a time until we find a dirty + // row/column to update. Increment along the major and minor scroll directions + // first. + gfx::Vector2d delta = -predicted_scroll_; + delta = gfx::Vector2d(delta.x() == 0 ? 1 : delta.x(), + delta.y() == 0 ? 1 : delta.y()); + gfx::Vector2d major_delta = + (abs(delta.x()) > abs(delta.y())) ? gfx::Vector2d(delta.x(), 0) + : gfx::Vector2d(0, delta.y()); + gfx::Vector2d minor_delta = + (abs(delta.x()) <= abs(delta.y())) ? gfx::Vector2d(delta.x(), 0) + : gfx::Vector2d(0, delta.y()); + gfx::Vector2d deltas[4] = { major_delta, minor_delta, -major_delta, + -minor_delta }; + for (int i = 0; i < 4; i++) { + if (deltas[i].y() > 0) { + while (bottom < prepaint_bottom) { + ++bottom; + if (!UpdateTiles( + left, bottom, right, bottom, queue, NULL, stats, &did_paint) || + did_paint) + return; + } + } + if (deltas[i].y() < 0) { + while (top > prepaint_top) { + --top; + if (!UpdateTiles( + left, top, right, top, queue, NULL, stats, &did_paint) || + did_paint) + return; + } + } + if (deltas[i].x() < 0) { + while (left > prepaint_left) { + --left; + if (!UpdateTiles( + left, top, left, bottom, queue, NULL, stats, &did_paint) || + did_paint) + return; + } + } + if (deltas[i].x() > 0) { + while (right < prepaint_right) { + ++right; + if (!UpdateTiles( + right, top, right, bottom, queue, NULL, stats, &did_paint) || + did_paint) + return; + } + } + } +} + +bool TiledLayer::NeedsIdlePaint() { + // Don't trigger more paints if we failed (as we'll just fail again). + if (failed_update_ || visible_content_rect().IsEmpty() || + tiler_->has_empty_bounds() || !DrawsContent()) + return false; + + gfx::Rect idle_paint_content_rect = IdlePaintRect(); + if (idle_paint_content_rect.IsEmpty()) + return false; + + int left, top, right, bottom; + tiler_->ContentRectToTileIndices( + idle_paint_content_rect, &left, &top, &right, &bottom); + + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + UpdatableTile* tile = TileAt(i, j); + DCHECK(tile); // Did SetTexturePriorities get skipped? + if (!tile) + continue; + + bool updated = !tile->update_rect.IsEmpty(); + bool can_acquire = tile->managed_resource()->canAcquireBackingTexture(); + bool dirty = + tile->is_dirty() || !tile->managed_resource()->haveBackingTexture(); + if (!updated && can_acquire && dirty) + return true; + } + } + return false; +} + +gfx::Rect TiledLayer::IdlePaintRect() { + // Don't inflate an empty rect. + if (visible_content_rect().IsEmpty()) + return gfx::Rect(); + + gfx::Rect prepaint_rect = visible_content_rect(); + prepaint_rect.Inset(-tiler_->tile_size().width() * kPrepaintColumns, + -tiler_->tile_size().height() * kPrepaintRows); + gfx::Rect content_rect(content_bounds()); + prepaint_rect.Intersect(content_rect); + + return prepaint_rect; +} + +} // namespace cc diff --git a/cc/layers/tiled_layer.h b/cc/layers/tiled_layer.h new file mode 100644 index 0000000..94ebada --- /dev/null +++ b/cc/layers/tiled_layer.h @@ -0,0 +1,143 @@ +// Copyright 2011 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 CC_LAYERS_TILED_LAYER_H_ +#define CC_LAYERS_TILED_LAYER_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/contents_scaling_layer.h" +#include "cc/resources/layer_tiling_data.h" + +namespace cc { +class LayerUpdater; +class PrioritizedResourceManager; +class PrioritizedResource; +class UpdatableTile; + +class CC_EXPORT TiledLayer : public ContentsScalingLayer { + public: + enum TilingOption { + ALWAYS_TILE, + NEVER_TILE, + AUTO_TILE, + }; + + // Layer implementation. + virtual void SetIsMask(bool is_mask) OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + virtual bool BlocksPendingCommit() const OVERRIDE; + virtual bool DrawsContent() const OVERRIDE; + virtual void SetNeedsDisplayRect(const gfx::RectF& dirty_rect) OVERRIDE; + virtual void SetLayerTreeHost(LayerTreeHost* layer_tree_host) OVERRIDE; + virtual void SetTexturePriorities(const PriorityCalculator& priority_calc) + OVERRIDE; + virtual Region VisibleContentOpaqueRegion() const OVERRIDE; + virtual void Update(ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats) OVERRIDE; + + protected: + TiledLayer(); + virtual ~TiledLayer(); + + void UpdateTileSizeAndTilingOption(); + void UpdateBounds(); + + // Exposed to subclasses for testing. + void SetTileSize(gfx::Size size); + void SetTextureFormat(unsigned texture_format) { + texture_format_ = texture_format; + } + void SetBorderTexelOption(LayerTilingData::BorderTexelOption option); + size_t NumPaintedTiles() { return tiler_->tiles().size(); } + + virtual LayerUpdater* Updater() const = 0; + virtual void CreateUpdaterIfNeeded() = 0; + + // Set invalidations to be potentially repainted during Update(). + void InvalidateContentRect(gfx::Rect content_rect); + + // Reset state on tiles that will be used for updating the layer. + void ResetUpdateState(); + + // After preparing an update, returns true if more painting is needed. + bool NeedsIdlePaint(); + gfx::Rect IdlePaintRect(); + + bool SkipsDraw() const { return skips_draw_; } + + // Virtual for testing + virtual PrioritizedResourceManager* ResourceManager() const; + const LayerTilingData* TilerForTesting() const { return tiler_.get(); } + const PrioritizedResource* ResourceAtForTesting(int i, int j) const; + + private: + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + + void CreateTilerIfNeeded(); + void set_tiling_option(TilingOption tiling_option) { + tiling_option_ = tiling_option; + } + + bool TileOnlyNeedsPartialUpdate(UpdatableTile* tile); + bool TileNeedsBufferedUpdate(UpdatableTile* tile); + + void MarkOcclusionsAndRequestTextures(int left, + int top, + int right, + int bottom, + const OcclusionTracker* occlusion); + + bool UpdateTiles(int left, + int top, + int right, + int bottom, + ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats, + bool* did_paint); + bool HaveTexturesForTiles(int left, + int top, + int right, + int bottom, + bool ignore_occlusions); + gfx::Rect MarkTilesForUpdate(int left, + int top, + int right, + int bottom, + bool ignore_occlusions); + void UpdateTileTextures(gfx::Rect paint_rect, + int left, + int top, + int right, + int bottom, + ResourceUpdateQueue* queue, + const OcclusionTracker* occlusion, + RenderingStats* stats); + void UpdateScrollPrediction(); + + UpdatableTile* TileAt(int i, int j) const; + UpdatableTile* CreateTile(int i, int j); + + bool IsSmallAnimatedLayer() const; + + unsigned texture_format_; + bool skips_draw_; + bool failed_update_; + + // Used for predictive painting. + gfx::Vector2d predicted_scroll_; + gfx::Rect predicted_visible_rect_; + gfx::Rect previous_visible_rect_; + gfx::Size previous_content_bounds_; + + TilingOption tiling_option_; + scoped_ptr<LayerTilingData> tiler_; + + DISALLOW_COPY_AND_ASSIGN(TiledLayer); +}; + +} +#endif // CC_LAYERS_TILED_LAYER_H_ diff --git a/cc/layers/tiled_layer_impl.cc b/cc/layers/tiled_layer_impl.cc new file mode 100644 index 0000000..2f098ce --- /dev/null +++ b/cc/layers/tiled_layer_impl.cc @@ -0,0 +1,307 @@ +// Copyright 2011 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 "cc/layers/tiled_layer_impl.h" + +#include "base/basictypes.h" +#include "base/stringprintf.h" +#include "cc/base/math_util.h" +#include "cc/debug/debug_colors.h" +#include "cc/layers/append_quads_data.h" +#include "cc/layers/quad_sink.h" +#include "cc/quads/checkerboard_draw_quad.h" +#include "cc/quads/debug_border_draw_quad.h" +#include "cc/quads/solid_color_draw_quad.h" +#include "cc/quads/tile_draw_quad.h" +#include "cc/resources/layer_tiling_data.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/quad_f.h" + +namespace cc { + +// Temporary diagnostic. +static bool s_safe_to_delete_drawable_tile = false; + +class DrawableTile : public LayerTilingData::Tile { + public: + static scoped_ptr<DrawableTile> Create() { + return make_scoped_ptr(new DrawableTile()); + } + + virtual ~DrawableTile() { CHECK(s_safe_to_delete_drawable_tile); } + + ResourceProvider::ResourceId resource_id() const { return resource_id_; } + void set_resource_id(ResourceProvider::ResourceId resource_id) { + resource_id_ = resource_id; + } + bool contents_swizzled() { return contents_swizzled_; } + void set_contents_swizzled(bool contents_swizzled) { + contents_swizzled_ = contents_swizzled; + } + + private: + DrawableTile() : resource_id_(0), contents_swizzled_(false) {} + + ResourceProvider::ResourceId resource_id_; + bool contents_swizzled_; + + DISALLOW_COPY_AND_ASSIGN(DrawableTile); +}; + +TiledLayerImpl::TiledLayerImpl(LayerTreeImpl* tree_impl, int id) + : LayerImpl(tree_impl, id), skips_draw_(true) {} + +TiledLayerImpl::~TiledLayerImpl() { + s_safe_to_delete_drawable_tile = true; + if (tiler_) + tiler_->reset(); + s_safe_to_delete_drawable_tile = false; +} + +ResourceProvider::ResourceId TiledLayerImpl::ContentsResourceId() const { + // This function is only valid for single texture layers, e.g. masks. + DCHECK(tiler_); + DCHECK_EQ(tiler_->num_tiles_x(), 1); + DCHECK_EQ(tiler_->num_tiles_y(), 1); + + DrawableTile* tile = TileAt(0, 0); + ResourceProvider::ResourceId resource_id = tile ? tile->resource_id() : 0; + return resource_id; +} + +void TiledLayerImpl::DumpLayerProperties(std::string* str, int indent) const { + str->append(IndentString(indent)); + base::StringAppendF(str, "skipsDraw: %d\n", (!tiler_ || skips_draw_)); + LayerImpl::DumpLayerProperties(str, indent); +} + +bool TiledLayerImpl::HasTileAt(int i, int j) const { + return tiler_->TileAt(i, j); +} + +bool TiledLayerImpl::HasResourceIdForTileAt(int i, int j) const { + return HasTileAt(i, j) && TileAt(i, j)->resource_id(); +} + +DrawableTile* TiledLayerImpl::TileAt(int i, int j) const { + return static_cast<DrawableTile*>(tiler_->TileAt(i, j)); +} + +DrawableTile* TiledLayerImpl::CreateTile(int i, int j) { + scoped_ptr<DrawableTile> tile(DrawableTile::Create()); + DrawableTile* added_tile = tile.get(); + tiler_->AddTile(tile.PassAs<LayerTilingData::Tile>(), i, j); + + // Temporary diagnostic checks. + CHECK(added_tile); + CHECK(TileAt(i, j)); + + return added_tile; +} + +void TiledLayerImpl::GetDebugBorderProperties(SkColor* color, + float* width) const { + *color = DebugColors::TiledContentLayerBorderColor(); + *width = DebugColors::TiledContentLayerBorderWidth(layer_tree_impl()); +} + +scoped_ptr<LayerImpl> TiledLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return TiledLayerImpl::Create(tree_impl, id()).PassAs<LayerImpl>(); +} + +void TiledLayerImpl::PushPropertiesTo(LayerImpl* layer) { + LayerImpl::PushPropertiesTo(layer); + + TiledLayerImpl* tiled_layer = static_cast<TiledLayerImpl*>(layer); + + tiled_layer->set_skips_draw(skips_draw_); + tiled_layer->SetTilingData(*tiler_); + + for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin(); + iter != tiler_->tiles().end(); + ++iter) { + int i = iter->first.first; + int j = iter->first.second; + DrawableTile* tile = static_cast<DrawableTile*>(iter->second); + + tiled_layer->PushTileProperties(i, + j, + tile->resource_id(), + tile->opaque_rect(), + tile->contents_swizzled()); + } +} + +void TiledLayerImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + gfx::Rect content_rect = visible_content_rect(); + + if (!tiler_ || tiler_->has_empty_bounds() || content_rect.IsEmpty()) + return; + + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(CreateSharedQuadState()); + AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data); + + int left, top, right, bottom; + tiler_->ContentRectToTileIndices(content_rect, &left, &top, &right, &bottom); + + if (ShowDebugBorders()) { + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + DrawableTile* tile = TileAt(i, j); + gfx::Rect tile_rect = tiler_->tile_bounds(i, j); + SkColor border_color; + float border_width; + + if (skips_draw_ || !tile || !tile->resource_id()) { + border_color = DebugColors::MissingTileBorderColor(); + border_width = DebugColors::MissingTileBorderWidth(layer_tree_impl()); + } else { + border_color = DebugColors::HighResTileBorderColor(); + border_width = DebugColors::HighResTileBorderWidth(layer_tree_impl()); + } + scoped_ptr<DebugBorderDrawQuad> debug_border_quad = + DebugBorderDrawQuad::Create(); + debug_border_quad->SetNew( + shared_quad_state, tile_rect, border_color, border_width); + quad_sink->Append(debug_border_quad.PassAs<DrawQuad>(), + append_quads_data); + } + } + } + + if (skips_draw_) + return; + + for (int j = top; j <= bottom; ++j) { + for (int i = left; i <= right; ++i) { + DrawableTile* tile = TileAt(i, j); + gfx::Rect tile_rect = tiler_->tile_bounds(i, j); + gfx::Rect display_rect = tile_rect; + tile_rect.Intersect(content_rect); + + // Skip empty tiles. + if (tile_rect.IsEmpty()) + continue; + + if (!tile || !tile->resource_id()) { + if (DrawCheckerboardForMissingTiles()) { + SkColor checker_color; + if (ShowDebugBorders()) { + checker_color = + tile ? DebugColors::InvalidatedTileCheckerboardColor() + : DebugColors::EvictedTileCheckerboardColor(); + } else { + checker_color = DebugColors::DefaultCheckerboardColor(); + } + + scoped_ptr<CheckerboardDrawQuad> checkerboard_quad = + CheckerboardDrawQuad::Create(); + checkerboard_quad->SetNew( + shared_quad_state, tile_rect, checker_color); + if (quad_sink->Append(checkerboard_quad.PassAs<DrawQuad>(), + append_quads_data)) + append_quads_data->numMissingTiles++; + } else { + scoped_ptr<SolidColorDrawQuad> solid_color_quad = + SolidColorDrawQuad::Create(); + solid_color_quad->SetNew( + shared_quad_state, tile_rect, background_color()); + if (quad_sink->Append(solid_color_quad.PassAs<DrawQuad>(), + append_quads_data)) + append_quads_data->numMissingTiles++; + } + continue; + } + + gfx::Rect tile_opaque_rect = contents_opaque() ? tile_rect : + gfx::IntersectRects(tile->opaque_rect(), content_rect); + + // Keep track of how the top left has moved, so the texture can be + // offset the same amount. + gfx::Vector2d display_offset = tile_rect.origin() - display_rect.origin(); + gfx::Vector2d texture_offset = + tiler_->texture_offset(i, j) + display_offset; + gfx::RectF tex_coord_rect = gfx::RectF(tile_rect.size()) + texture_offset; + + float tile_width = static_cast<float>(tiler_->tile_size().width()); + float tile_height = static_cast<float>(tiler_->tile_size().height()); + gfx::Size texture_size(tile_width, tile_height); + + scoped_ptr<TileDrawQuad> quad = TileDrawQuad::Create(); + quad->SetNew(shared_quad_state, + tile_rect, + tile_opaque_rect, + tile->resource_id(), + tex_coord_rect, + texture_size, + tile->contents_swizzled()); + quad_sink->Append(quad.PassAs<DrawQuad>(), append_quads_data); + } + } +} + +void TiledLayerImpl::SetTilingData(const LayerTilingData& tiler) { + s_safe_to_delete_drawable_tile = true; + + if (tiler_) { + tiler_->reset(); + } else { + tiler_ = LayerTilingData::Create(tiler.tile_size(), + tiler.has_border_texels() + ? LayerTilingData::HAS_BORDER_TEXELS + : LayerTilingData::NO_BORDER_TEXELS); + } + *tiler_ = tiler; + + s_safe_to_delete_drawable_tile = false; +} + +void TiledLayerImpl::PushTileProperties( + int i, + int j, + ResourceProvider::ResourceId resource_id, + gfx::Rect opaque_rect, + bool contents_swizzled) { + DrawableTile* tile = TileAt(i, j); + if (!tile) + tile = CreateTile(i, j); + tile->set_resource_id(resource_id); + tile->set_opaque_rect(opaque_rect); + tile->set_contents_swizzled(contents_swizzled); +} + +void TiledLayerImpl::PushInvalidTile(int i, int j) { + DrawableTile* tile = TileAt(i, j); + if (!tile) + tile = CreateTile(i, j); + tile->set_resource_id(0); + tile->set_opaque_rect(gfx::Rect()); + tile->set_contents_swizzled(false); +} + +Region TiledLayerImpl::VisibleContentOpaqueRegion() const { + if (skips_draw_) + return Region(); + if (contents_opaque()) + return visible_content_rect(); + return tiler_->OpaqueRegionInContentRect(visible_content_rect()); +} + +void TiledLayerImpl::DidLoseOutputSurface() { + s_safe_to_delete_drawable_tile = true; + // Temporary diagnostic check. + CHECK(tiler_); + tiler_->reset(); + s_safe_to_delete_drawable_tile = false; +} + +const char* TiledLayerImpl::LayerTypeAsString() const { + return "ContentLayer"; +} + +} // namespace cc diff --git a/cc/layers/tiled_layer_impl.h b/cc/layers/tiled_layer_impl.h new file mode 100644 index 0000000..ec0c484 --- /dev/null +++ b/cc/layers/tiled_layer_impl.h @@ -0,0 +1,70 @@ +// Copyright 2011 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 CC_LAYERS_TILED_LAYER_IMPL_H_ +#define CC_LAYERS_TILED_LAYER_IMPL_H_ + +#include "cc/base/cc_export.h" +#include "cc/layers/layer_impl.h" + +namespace cc { + +class LayerTilingData; +class DrawableTile; + +class CC_EXPORT TiledLayerImpl : public LayerImpl { + public: + static scoped_ptr<TiledLayerImpl> Create(LayerTreeImpl* tree_impl, int id) { + return make_scoped_ptr(new TiledLayerImpl(tree_impl, id)); + } + virtual ~TiledLayerImpl(); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + + virtual ResourceProvider::ResourceId ContentsResourceId() const OVERRIDE; + + virtual void DumpLayerProperties(std::string* str, int indent) const OVERRIDE; + + void set_skips_draw(bool skips_draw) { skips_draw_ = skips_draw; } + void SetTilingData(const LayerTilingData& tiler); + void PushTileProperties(int i, + int j, + ResourceProvider::ResourceId resource, + gfx::Rect opaque_rect, + bool contents_swizzled); + void PushInvalidTile(int i, int j); + + virtual Region VisibleContentOpaqueRegion() const OVERRIDE; + virtual void DidLoseOutputSurface() OVERRIDE; + + protected: + TiledLayerImpl(LayerTreeImpl* tree_impl, int id); + // Exposed for testing. + bool HasTileAt(int i, int j) const; + bool HasResourceIdForTileAt(int i, int j) const; + + virtual void GetDebugBorderProperties(SkColor* color, float* width) const + OVERRIDE; + + private: + virtual const char* LayerTypeAsString() const OVERRIDE; + + DrawableTile* TileAt(int i, int j) const; + DrawableTile* CreateTile(int i, int j); + + bool skips_draw_; + + scoped_ptr<LayerTilingData> tiler_; + + DISALLOW_COPY_AND_ASSIGN(TiledLayerImpl); +}; + +} // namespace cc + +#endif // CC_LAYERS_TILED_LAYER_IMPL_H_ diff --git a/cc/layers/tiled_layer_impl_unittest.cc b/cc/layers/tiled_layer_impl_unittest.cc new file mode 100644 index 0000000..994e8b7 --- /dev/null +++ b/cc/layers/tiled_layer_impl_unittest.cc @@ -0,0 +1,265 @@ +// Copyright 2012 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 "cc/layers/tiled_layer_impl.h" + +#include "cc/layers/append_quads_data.h" +#include "cc/quads/tile_draw_quad.h" +#include "cc/resources/layer_tiling_data.h" +#include "cc/test/fake_impl_proxy.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/layer_test_common.h" +#include "cc/test/mock_quad_culler.h" +#include "cc/trees/single_thread_proxy.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cc { +namespace { + +class TiledLayerImplTest : public testing::Test { + public: + TiledLayerImplTest() : host_impl_(&proxy_) {} + + // Create a default tiled layer with textures for all tiles and a default + // visibility of the entire layer size. + scoped_ptr<TiledLayerImpl> CreateLayer( + gfx::Size tile_size, + gfx::Size layer_size, + LayerTilingData::BorderTexelOption border_texels) { + scoped_ptr<TiledLayerImpl> layer = + TiledLayerImpl::Create(host_impl_.active_tree(), 1); + scoped_ptr<LayerTilingData> tiler = + LayerTilingData::Create(tile_size, border_texels); + tiler->SetBounds(layer_size); + layer->SetTilingData(*tiler); + layer->set_skips_draw(false); + layer->draw_properties().visible_content_rect = + gfx::Rect(gfx::Point(), layer_size); + layer->draw_properties().opacity = 1; + layer->SetBounds(layer_size); + layer->SetContentBounds(layer_size); + layer->CreateRenderSurface(); + layer->draw_properties().render_target = layer.get(); + + ResourceProvider::ResourceId resource_id = 1; + for (int i = 0; i < tiler->num_tiles_x(); ++i) { + for (int j = 0; j < tiler->num_tiles_y(); ++j) { + layer->PushTileProperties( + i, j, resource_id++, gfx::Rect(0, 0, 1, 1), false); + } + } + + return layer.Pass(); + } + + void GetQuads(QuadList* quads, + SharedQuadStateList* shared_states, + gfx::Size tile_size, + gfx::Size layer_size, + LayerTilingData::BorderTexelOption border_texel_option, + gfx::Rect visible_content_rect) { + scoped_ptr<TiledLayerImpl> layer = + CreateLayer(tile_size, layer_size, border_texel_option); + layer->draw_properties().visible_content_rect = visible_content_rect; + layer->SetBounds(layer_size); + + MockQuadCuller quad_culler(*quads, *shared_states); + AppendQuadsData data; + layer->AppendQuads(&quad_culler, &data); + } + + protected: + FakeImplProxy proxy_; + FakeLayerTreeHostImpl host_impl_; +}; + +TEST_F(TiledLayerImplTest, EmptyQuadList) { + gfx::Size tile_size(90, 90); + int num_tiles_x = 8; + int num_tiles_y = 4; + gfx::Size layer_size(tile_size.width() * num_tiles_x, + tile_size.height() * num_tiles_y); + + // Verify default layer does creates quads + { + scoped_ptr<TiledLayerImpl> layer = + CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS); + MockQuadCuller quad_culler; + AppendQuadsData data; + layer->AppendQuads(&quad_culler, &data); + unsigned num_tiles = num_tiles_x * num_tiles_y; + EXPECT_EQ(quad_culler.quadList().size(), num_tiles); + } + + // Layer with empty visible layer rect produces no quads + { + scoped_ptr<TiledLayerImpl> layer = + CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS); + layer->draw_properties().visible_content_rect = gfx::Rect(); + + MockQuadCuller quad_culler; + AppendQuadsData data; + layer->AppendQuads(&quad_culler, &data); + EXPECT_EQ(quad_culler.quadList().size(), 0u); + } + + // Layer with non-intersecting visible layer rect produces no quads + { + scoped_ptr<TiledLayerImpl> layer = + CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS); + + gfx::Rect outsideBounds(gfx::Point(-100, -100), gfx::Size(50, 50)); + layer->draw_properties().visible_content_rect = outsideBounds; + + MockQuadCuller quad_culler; + AppendQuadsData data; + layer->AppendQuads(&quad_culler, &data); + EXPECT_EQ(quad_culler.quadList().size(), 0u); + } + + // Layer with skips draw produces no quads + { + scoped_ptr<TiledLayerImpl> layer = + CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS); + layer->set_skips_draw(true); + + MockQuadCuller quad_culler; + AppendQuadsData data; + layer->AppendQuads(&quad_culler, &data); + EXPECT_EQ(quad_culler.quadList().size(), 0u); + } +} + +TEST_F(TiledLayerImplTest, Checkerboarding) { + gfx::Size tile_size(10, 10); + int num_tiles_x = 2; + int num_tiles_y = 2; + gfx::Size layer_size(tile_size.width() * num_tiles_x, + tile_size.height() * num_tiles_y); + + scoped_ptr<TiledLayerImpl> layer = + CreateLayer(tile_size, layer_size, LayerTilingData::NO_BORDER_TEXELS); + + // No checkerboarding + { + MockQuadCuller quad_culler; + AppendQuadsData data; + layer->AppendQuads(&quad_culler, &data); + EXPECT_EQ(quad_culler.quadList().size(), 4u); + EXPECT_EQ(0u, data.numMissingTiles); + + for (size_t i = 0; i < quad_culler.quadList().size(); ++i) + EXPECT_EQ(quad_culler.quadList()[i]->material, DrawQuad::TILED_CONTENT); + } + + for (int i = 0; i < num_tiles_x; ++i) + for (int j = 0; j < num_tiles_y; ++j) + layer->PushTileProperties(i, j, 0, gfx::Rect(), false); + + // All checkerboarding + { + MockQuadCuller quad_culler; + AppendQuadsData data; + layer->AppendQuads(&quad_culler, &data); + EXPECT_LT(0u, data.numMissingTiles); + EXPECT_EQ(quad_culler.quadList().size(), 4u); + for (size_t i = 0; i < quad_culler.quadList().size(); ++i) + EXPECT_NE(quad_culler.quadList()[i]->material, DrawQuad::TILED_CONTENT); + } +} + +// Test with both border texels and without. +#define WITH_AND_WITHOUT_BORDER_TEST(text_fixture_name) \ + TEST_F(TiledLayerImplBorderTest, text_fixture_name##NoBorders) { \ + text_fixture_name(LayerTilingData::NO_BORDER_TEXELS); \ + } \ + TEST_F(TiledLayerImplBorderTest, text_fixture_name##HasBorders) { \ + text_fixture_name(LayerTilingData::HAS_BORDER_TEXELS); \ + } + +class TiledLayerImplBorderTest : public TiledLayerImplTest { + public: + void CoverageVisibleRectOnTileBoundaries( + LayerTilingData::BorderTexelOption borders) { + gfx::Size layer_size(1000, 1000); + QuadList quads; + SharedQuadStateList shared_states; + GetQuads(&quads, + &shared_states, + gfx::Size(100, 100), + layer_size, + borders, + gfx::Rect(gfx::Point(), layer_size)); + LayerTestCommon::verifyQuadsExactlyCoverRect( + quads, gfx::Rect(gfx::Point(), layer_size)); + } + + void CoverageVisibleRectIntersectsTiles( + LayerTilingData::BorderTexelOption borders) { + // This rect intersects the middle 3x3 of the 5x5 tiles. + gfx::Point top_left(65, 73); + gfx::Point bottom_right(182, 198); + gfx::Rect visible_content_rect = gfx::BoundingRect(top_left, bottom_right); + + gfx::Size layer_size(250, 250); + QuadList quads; + SharedQuadStateList shared_states; + GetQuads(&quads, + &shared_states, + gfx::Size(50, 50), + gfx::Size(250, 250), + LayerTilingData::NO_BORDER_TEXELS, + visible_content_rect); + LayerTestCommon::verifyQuadsExactlyCoverRect(quads, visible_content_rect); + } + + void CoverageVisibleRectIntersectsBounds( + LayerTilingData::BorderTexelOption borders) { + gfx::Size layer_size(220, 210); + gfx::Rect visible_content_rect(layer_size); + QuadList quads; + SharedQuadStateList shared_states; + GetQuads(&quads, + &shared_states, + gfx::Size(100, 100), + layer_size, + LayerTilingData::NO_BORDER_TEXELS, + visible_content_rect); + LayerTestCommon::verifyQuadsExactlyCoverRect(quads, visible_content_rect); + } +}; +WITH_AND_WITHOUT_BORDER_TEST(CoverageVisibleRectOnTileBoundaries); + +WITH_AND_WITHOUT_BORDER_TEST(CoverageVisibleRectIntersectsTiles); + +WITH_AND_WITHOUT_BORDER_TEST(CoverageVisibleRectIntersectsBounds); + +TEST_F(TiledLayerImplTest, TextureInfoForLayerNoBorders) { + gfx::Size tile_size(50, 50); + gfx::Size layer_size(250, 250); + QuadList quads; + SharedQuadStateList shared_states; + GetQuads(&quads, + &shared_states, + tile_size, + layer_size, + LayerTilingData::NO_BORDER_TEXELS, + gfx::Rect(gfx::Point(), layer_size)); + + for (size_t i = 0; i < quads.size(); ++i) { + const TileDrawQuad* quad = TileDrawQuad::MaterialCast(quads[i]); + + EXPECT_NE(0u, quad->resource_id) << LayerTestCommon::quadString << i; + EXPECT_EQ(gfx::RectF(gfx::PointF(), tile_size), quad->tex_coord_rect) + << LayerTestCommon::quadString << i; + EXPECT_EQ(tile_size, quad->texture_size) << LayerTestCommon::quadString + << i; + EXPECT_EQ(gfx::Rect(0, 0, 1, 1), quad->opaque_rect) + << LayerTestCommon::quadString << i; + } +} + +} // namespace +} // namespace cc diff --git a/cc/layers/tiled_layer_unittest.cc b/cc/layers/tiled_layer_unittest.cc new file mode 100644 index 0000000..671fd0d --- /dev/null +++ b/cc/layers/tiled_layer_unittest.cc @@ -0,0 +1,1672 @@ +// Copyright 2011 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 "cc/layers/tiled_layer.h" + +#include "cc/debug/overdraw_metrics.h" +#include "cc/resources/bitmap_content_layer_updater.h" +#include "cc/resources/layer_painter.h" +#include "cc/resources/prioritized_resource_manager.h" +#include "cc/resources/resource_update_controller.h" +#include "cc/test/animation_test_common.h" +#include "cc/test/fake_layer_tree_host_client.h" +#include "cc/test/fake_layer_tree_host_impl.h" +#include "cc/test/fake_output_surface.h" +#include "cc/test/fake_proxy.h" +#include "cc/test/geometry_test_utils.h" +#include "cc/test/tiled_layer_test_common.h" +#include "cc/trees/single_thread_proxy.h" // For DebugScopedSetImplThread +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect_conversions.h" +#include "ui/gfx/transform.h" + +namespace cc { +namespace { + +class TestOcclusionTracker : public OcclusionTracker { +public: + TestOcclusionTracker() + : OcclusionTracker(gfx::Rect(0, 0, 1000, 1000), true) + { + stack_.push_back(StackObject()); + } + + void setRenderTarget(Layer* renderTarget) + { + stack_.back().target = renderTarget; + } + + void setOcclusion(const Region& occlusion) + { + stack_.back().occlusion_from_inside_target = occlusion; + } +}; + +class TiledLayerTest : public testing::Test { +public: + TiledLayerTest() + : m_proxy(NULL) + , m_outputSurface(createFakeOutputSurface()) + , m_queue(make_scoped_ptr(new ResourceUpdateQueue)) + , m_occlusion(0) + { + m_settings.maxPartialTextureUpdates = std::numeric_limits<size_t>::max(); + } + + virtual void SetUp() + { + layer_tree_host_ = LayerTreeHost::Create(&m_fakeLayerImplTreeHostClient, m_settings, scoped_ptr<Thread>(NULL)); + m_proxy = layer_tree_host_->proxy(); + m_resourceManager = PrioritizedResourceManager::create(m_proxy); + layer_tree_host_->InitializeRendererIfNeeded(); + layer_tree_host_->SetRootLayer(Layer::Create()); + + DebugScopedSetImplThreadAndMainThreadBlocked implThreadAndMainThreadBlocked(m_proxy); + m_resourceProvider = ResourceProvider::Create(m_outputSurface.get()); + m_hostImpl = make_scoped_ptr(new FakeLayerTreeHostImpl(m_proxy)); + } + + virtual ~TiledLayerTest() + { + resourceManagerClearAllMemory(m_resourceManager.get(), m_resourceProvider.get()); + + DebugScopedSetImplThreadAndMainThreadBlocked implThreadAndMainThreadBlocked(m_proxy); + m_resourceProvider.reset(); + m_hostImpl.reset(); + } + + void resourceManagerClearAllMemory(PrioritizedResourceManager* resourceManager, ResourceProvider* resourceProvider) + { + { + DebugScopedSetImplThreadAndMainThreadBlocked implThreadAndMainThreadBlocked(m_proxy); + resourceManager->clearAllMemory(resourceProvider); + resourceManager->reduceMemory(resourceProvider); + } + resourceManager->unlinkAndClearEvictedBackings(); + } + void updateTextures() + { + DebugScopedSetImplThreadAndMainThreadBlocked implThreadAndMainThreadBlocked(m_proxy); + DCHECK(m_queue); + scoped_ptr<ResourceUpdateController> updateController = + ResourceUpdateController::Create( + NULL, + m_proxy->ImplThread(), + m_queue.Pass(), + m_resourceProvider.get()); + updateController->Finalize(); + m_queue = make_scoped_ptr(new ResourceUpdateQueue); + } + void layerPushPropertiesTo(FakeTiledLayer* layer, FakeTiledLayerImpl* layerImpl) + { + DebugScopedSetImplThreadAndMainThreadBlocked implThreadAndMainThreadBlocked(m_proxy); + layer->PushPropertiesTo(layerImpl); + } + void layerUpdate(FakeTiledLayer* layer, TestOcclusionTracker* occluded) + { + DebugScopedSetMainThread mainThread(m_proxy); + layer->Update(m_queue.get(), occluded, NULL); + } + + void calcDrawProps(const scoped_refptr<FakeTiledLayer>& layer1) + { + scoped_refptr<FakeTiledLayer> layer2; + calcDrawProps(layer1, layer2); + } + + void calcDrawProps(const scoped_refptr<FakeTiledLayer>& layer1, + const scoped_refptr<FakeTiledLayer>& layer2) + { + if (layer1 && !layer1->parent()) + layer_tree_host_->root_layer()->AddChild(layer1); + if (layer2 && !layer2->parent()) + layer_tree_host_->root_layer()->AddChild(layer2); + if (m_occlusion) + m_occlusion->setRenderTarget(layer_tree_host_->root_layer()); + + std::vector<scoped_refptr<Layer> > renderSurfaceLayerList; + LayerTreeHostCommon::calculateDrawProperties( + layer_tree_host_->root_layer(), + layer_tree_host_->device_viewport_size(), + layer_tree_host_->device_scale_factor(), + 1, // page_scale_factor + layer_tree_host_->GetRendererCapabilities().max_texture_size, + false, // can_use_lcd_text + renderSurfaceLayerList); + } + + bool updateAndPush(const scoped_refptr<FakeTiledLayer>& layer1, + const scoped_ptr<FakeTiledLayerImpl>& layerImpl1) + { + scoped_refptr<FakeTiledLayer> layer2; + scoped_ptr<FakeTiledLayerImpl> layerImpl2; + return updateAndPush(layer1, layerImpl1, layer2, layerImpl2); + } + + bool updateAndPush(const scoped_refptr<FakeTiledLayer>& layer1, + const scoped_ptr<FakeTiledLayerImpl>& layerImpl1, + const scoped_refptr<FakeTiledLayer>& layer2, + const scoped_ptr<FakeTiledLayerImpl>& layerImpl2) + { + // Get textures + m_resourceManager->clearPriorities(); + if (layer1) + layer1->SetTexturePriorities(m_priorityCalculator); + if (layer2) + layer2->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + + // Update content + if (layer1) + layer1->Update(m_queue.get(), m_occlusion, NULL); + if (layer2) + layer2->Update(m_queue.get(), m_occlusion, NULL); + + bool needsUpdate = false; + if (layer1) + needsUpdate |= layer1->NeedsIdlePaint(); + if (layer2) + needsUpdate |= layer2->NeedsIdlePaint(); + + // Update textures and push. + updateTextures(); + if (layer1) + layerPushPropertiesTo(layer1.get(), layerImpl1.get()); + if (layer2) + layerPushPropertiesTo(layer2.get(), layerImpl2.get()); + + return needsUpdate; + } + +public: + Proxy* m_proxy; + LayerTreeSettings m_settings; + scoped_ptr<OutputSurface> m_outputSurface; + scoped_ptr<ResourceProvider> m_resourceProvider; + scoped_ptr<ResourceUpdateQueue> m_queue; + PriorityCalculator m_priorityCalculator; + FakeLayerImplTreeHostClient m_fakeLayerImplTreeHostClient; + scoped_ptr<LayerTreeHost> layer_tree_host_; + scoped_ptr<FakeLayerTreeHostImpl> m_hostImpl; + scoped_ptr<PrioritizedResourceManager> m_resourceManager; + TestOcclusionTracker* m_occlusion; +}; + +TEST_F(TiledLayerTest, pushDirtyTiles) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + calcDrawProps(layer); + updateAndPush(layer, layerImpl); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 1)); + + // Invalidates both tiles, but then only update one of them. + layer->InvalidateContentRect(gfx::Rect(0, 0, 100, 200)); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100); + updateAndPush(layer, layerImpl); + + // We should only have the first tile since the other tile was invalidated but not painted. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, pushOccludedDirtyTiles) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000), gfx::Size(1000, 1000)); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + calcDrawProps(layer); + updateAndPush(layer, layerImpl); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 1)); + + // Invalidates part of the top tile... + layer->InvalidateContentRect(gfx::Rect(0, 0, 50, 50)); + // ....but the area is occluded. + occluded.setOcclusion(gfx::Rect(0, 0, 50, 50)); + calcDrawProps(layer); + updateAndPush(layer, layerImpl); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000 + 2500, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + // We should still have both tiles, as part of the top tile is still unoccluded. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, pushDeletedTiles) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + calcDrawProps(layer); + updateAndPush(layer, layerImpl); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 1)); + + m_resourceManager->clearPriorities(); + resourceManagerClearAllMemory(m_resourceManager.get(), m_resourceProvider.get()); + m_resourceManager->setMaxMemoryLimitBytes(4*1024*1024); + + // This should drop the tiles on the impl thread. + layerPushPropertiesTo(layer.get(), layerImpl.get()); + + // We should now have no textures on the impl thread. + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(0, 1)); + + // This should recreate and update one of the deleted textures. + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100); + updateAndPush(layer, layerImpl); + + // We should have one tiles on the impl side. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, pushIdlePaintTiles) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // The tile size is 100x100. Setup 5x5 tiles with one visible tile in the center. + // This paints 1 visible of the 25 invalid tiles. + layer->SetBounds(gfx::Size(500, 500)); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = gfx::Rect(200, 200, 100, 100); + bool needsUpdate = updateAndPush(layer, layerImpl); + // We should need idle-painting for surrounding tiles. + EXPECT_TRUE(needsUpdate); + + // We should have one tile on the impl side. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(2, 2)); + + // For the next four updates, we should detect we still need idle painting. + for (int i = 0; i < 4; i++) { + needsUpdate = updateAndPush(layer, layerImpl); + EXPECT_TRUE(needsUpdate); + } + + // We should always finish painting eventually. + for (int i = 0; i < 20; i++) + needsUpdate = updateAndPush(layer, layerImpl); + + // We should have pre-painted all of the surrounding tiles. + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(i, j)); + } + + EXPECT_FALSE(needsUpdate); +} + +TEST_F(TiledLayerTest, predictivePainting) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // Prepainting should occur in the scroll direction first, and the + // visible rect should be extruded only along the dominant axis. + gfx::Vector2d directions[6] = { gfx::Vector2d(-10, 0), + gfx::Vector2d(10, 0), + gfx::Vector2d(0, -10), + gfx::Vector2d(0, 10), + gfx::Vector2d(10, 20), + gfx::Vector2d(-20, 10) }; + // We should push all tiles that touch the extruded visible rect. + gfx::Rect pushedVisibleTiles[6] = { gfx::Rect(2, 2, 2, 1), + gfx::Rect(1, 2, 2, 1), + gfx::Rect(2, 2, 1, 2), + gfx::Rect(2, 1, 1, 2), + gfx::Rect(2, 1, 1, 2), + gfx::Rect(2, 2, 2, 1) }; + // The first pre-paint should also paint first in the scroll + // direction so we should find one additional tile in the scroll direction. + gfx::Rect pushedPrepaintTiles[6] = { gfx::Rect(2, 2, 3, 1), + gfx::Rect(0, 2, 3, 1), + gfx::Rect(2, 2, 1, 3), + gfx::Rect(2, 0, 1, 3), + gfx::Rect(2, 0, 1, 3), + gfx::Rect(2, 2, 3, 1) }; + for(int k = 0; k < 6; k++) { + // The tile size is 100x100. Setup 5x5 tiles with one visible tile + // in the center. + gfx::Size bounds = gfx::Size(500, 500); + gfx::Rect visibleRect = gfx::Rect(200, 200, 100, 100); + gfx::Rect previousVisibleRect = gfx::Rect(visibleRect.origin() + directions[k], visibleRect.size()); + gfx::Rect nextVisibleRect = gfx::Rect(visibleRect.origin() - directions[k], visibleRect.size()); + + // Setup. Use the previousVisibleRect to setup the prediction for next frame. + layer->SetBounds(bounds); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = previousVisibleRect; + bool needsUpdate = updateAndPush(layer, layerImpl); + + // Invalidate and move the visibleRect in the scroll direction. + // Check that the correct tiles have been painted in the visible pass. + layer->SetNeedsDisplay(); + layer->draw_properties().visible_content_rect = visibleRect; + needsUpdate = updateAndPush(layer, layerImpl); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) + EXPECT_EQ(layerImpl->HasResourceIdForTileAt(i, j), pushedVisibleTiles[k].Contains(i, j)); + } + + // Move the transform in the same direction without invalidating. + // Check that non-visible pre-painting occured in the correct direction. + // Ignore diagonal scrolls here (k > 3) as these have new visible content now. + if (k <= 3) { + layer->draw_properties().visible_content_rect = nextVisibleRect; + needsUpdate = updateAndPush(layer, layerImpl); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) + EXPECT_EQ(layerImpl->HasResourceIdForTileAt(i, j), pushedPrepaintTiles[k].Contains(i, j)); + } + } + + // We should always finish painting eventually. + for (int i = 0; i < 20; i++) + needsUpdate = updateAndPush(layer, layerImpl); + EXPECT_FALSE(needsUpdate); + } +} + +TEST_F(TiledLayerTest, pushTilesAfterIdlePaintFailed) +{ + // Start with 2mb of memory, but the test is going to try to use just more than 1mb, so we reduce to 1mb later. + m_resourceManager->setMaxMemoryLimitBytes(2 * 1024 * 1024); + scoped_refptr<FakeTiledLayer> layer1 = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl1 = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + scoped_refptr<FakeTiledLayer> layer2 = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl2 = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 2)); + + // For this test we have two layers. layer1 exhausts most texture memory, leaving room for 2 more tiles from + // layer2, but not all three tiles. First we paint layer1, and one tile from layer2. Then when we idle paint + // layer2, we will fail on the third tile of layer2, and this should not leave the second tile in a bad state. + + // This uses 960000 bytes, leaving 88576 bytes of memory left, which is enough for 2 tiles only in the other layer. + gfx::Rect layer1Rect(0, 0, 100, 2400); + + // This requires 4*30000 bytes of memory. + gfx::Rect layer2Rect(0, 0, 100, 300); + + // Paint a single tile in layer2 so that it will idle paint. + layer1->SetBounds(layer1Rect.size()); + layer2->SetBounds(layer2Rect.size()); + calcDrawProps(layer1, layer2); + layer1->draw_properties().visible_content_rect = layer1Rect; + layer2->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100); + bool needsUpdate = updateAndPush(layer1, layerImpl1, + layer2, layerImpl2); + // We should need idle-painting for both remaining tiles in layer2. + EXPECT_TRUE(needsUpdate); + + // Reduce our memory limits to 1mb. + m_resourceManager->setMaxMemoryLimitBytes(1024 * 1024); + + // Now idle paint layer2. We are going to run out of memory though! + // Oh well, commit the frame and push. + for (int i = 0; i < 4; i++) { + needsUpdate = updateAndPush(layer1, layerImpl1, + layer2, layerImpl2); + } + + // Sanity check, we should have textures for the big layer. + EXPECT_TRUE(layerImpl1->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl1->HasResourceIdForTileAt(0, 23)); + + // We should only have the first two tiles from layer2 since + // it failed to idle update the last tile. + EXPECT_TRUE(layerImpl2->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl2->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl2->HasResourceIdForTileAt(0, 1)); + EXPECT_TRUE(layerImpl2->HasResourceIdForTileAt(0, 1)); + + EXPECT_FALSE(needsUpdate); + EXPECT_FALSE(layerImpl2->HasResourceIdForTileAt(0, 2)); +} + +TEST_F(TiledLayerTest, pushIdlePaintedOccludedTiles) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + + // The tile size is 100x100, so this invalidates one occluded tile, culls it during paint, but prepaints it. + occluded.setOcclusion(gfx::Rect(0, 0, 100, 100)); + + layer->SetBounds(gfx::Size(100, 100)); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100); + updateAndPush(layer, layerImpl); + + // We should have the prepainted tile on the impl side, but culled it during paint. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_EQ(1, occluded.overdraw_metrics()->tiles_culled_for_upload()); +} + +TEST_F(TiledLayerTest, pushTilesMarkedDirtyDuringPaint) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + // However, during the paint, we invalidate one of the tiles. This should + // not prevent the tile from being pushed. + layer->fakeLayerUpdater()->setRectToInvalidate(gfx::Rect(0, 50, 100, 50), layer.get()); + layer->SetBounds(gfx::Size(100, 200)); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + updateAndPush(layer, layerImpl); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, pushTilesLayerMarkedDirtyDuringPaintOnNextLayer) +{ + scoped_refptr<FakeTiledLayer> layer1 = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_refptr<FakeTiledLayer> layer2 = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layer1Impl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + scoped_ptr<FakeTiledLayerImpl> layer2Impl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 2)); + + // Invalidate a tile on layer1, during update of layer 2. + layer2->fakeLayerUpdater()->setRectToInvalidate(gfx::Rect(0, 50, 100, 50), layer1.get()); + layer1->SetBounds(gfx::Size(100, 200)); + layer2->SetBounds(gfx::Size(100, 200)); + calcDrawProps(layer1, layer2); + layer1->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + layer2->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + updateAndPush(layer1, layer1Impl, + layer2, layer2Impl); + + // We should have both tiles on the impl side for all layers. + EXPECT_TRUE(layer1Impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer1Impl->HasResourceIdForTileAt(0, 1)); + EXPECT_TRUE(layer2Impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer2Impl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, pushTilesLayerMarkedDirtyDuringPaintOnPreviousLayer) +{ + scoped_refptr<FakeTiledLayer> layer1 = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_refptr<FakeTiledLayer> layer2 = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layer1Impl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + scoped_ptr<FakeTiledLayerImpl> layer2Impl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 2)); + + layer1->fakeLayerUpdater()->setRectToInvalidate(gfx::Rect(0, 50, 100, 50), layer2.get()); + layer1->SetBounds(gfx::Size(100, 200)); + layer2->SetBounds(gfx::Size(100, 200)); + calcDrawProps(layer1, layer2); + layer1->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + layer2->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + updateAndPush(layer1, layer1Impl, + layer2, layer2Impl); + + // We should have both tiles on the impl side for all layers. + EXPECT_TRUE(layer1Impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer1Impl->HasResourceIdForTileAt(0, 1)); + EXPECT_TRUE(layer2Impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer2Impl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, paintSmallAnimatedLayersImmediately) +{ + // Create a LayerTreeHost that has the right viewportsize, + // so the layer is considered small enough. + FakeLayerImplTreeHostClient fakeLayerImplTreeHostClient; + + bool runOutOfMemory[2] = {false, true}; + for (int i = 0; i < 2; i++) { + // Create a layer with 5x5 tiles, with 4x4 size viewport. + int viewportWidth = 4 * FakeTiledLayer::tileSize().width(); + int viewportHeight = 4 * FakeTiledLayer::tileSize().width(); + int layerWidth = 5 * FakeTiledLayer::tileSize().width(); + int layerHeight = 5 * FakeTiledLayer::tileSize().height(); + int memoryForLayer = layerWidth * layerHeight * 4; + layer_tree_host_->SetViewportSize(gfx::Size(layerWidth, layerHeight), gfx::Size(layerWidth, layerHeight)); + + // Use 10x5 tiles to run out of memory. + if (runOutOfMemory[i]) + layerWidth *= 2; + + m_resourceManager->setMaxMemoryLimitBytes(memoryForLayer); + + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // Full size layer with half being visible. + layer->SetBounds(gfx::Size(layerWidth, layerHeight)); + gfx::Rect visibleRect(gfx::Point(), gfx::Size(layerWidth / 2, layerHeight)); + calcDrawProps(layer); + + // Pretend the layer is animating. + layer->draw_properties().target_space_transform_is_animating = true; + layer->draw_properties().visible_content_rect = visibleRect; + layer->SetLayerTreeHost(layer_tree_host_.get()); + + // The layer should paint its entire contents on the first paint + // if it is close to the viewport size and has the available memory. + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), 0, NULL); + updateTextures(); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + + // We should have all the tiles for the small animated layer. + // We should still have the visible tiles when we didn't + // have enough memory for all the tiles. + if (!runOutOfMemory[i]) { + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 5; ++j) + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(i, j)); + } + } else { + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 5; ++j) + EXPECT_EQ(layerImpl->HasResourceIdForTileAt(i, j), i < 5); + } + } + } +} + +TEST_F(TiledLayerTest, idlePaintOutOfMemory) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // We have enough memory for only the visible rect, so we will run out of memory in first idle paint. + int memoryLimit = 4 * 100 * 100; // 1 tiles, 4 bytes per pixel. + m_resourceManager->setMaxMemoryLimitBytes(memoryLimit); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + bool needsUpdate = false; + layer->SetBounds(gfx::Size(300, 300)); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = gfx::Rect(100, 100, 100, 100); + for (int i = 0; i < 2; i++) + needsUpdate = updateAndPush(layer, layerImpl); + + // Idle-painting should see no more priority tiles for painting. + EXPECT_FALSE(needsUpdate); + + // We should have one tile on the impl side. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(1, 1)); +} + +TEST_F(TiledLayerTest, idlePaintZeroSizedLayer) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + bool animating[2] = {false, true}; + for (int i = 0; i < 2; i++) { + // Pretend the layer is animating. + layer->draw_properties().target_space_transform_is_animating = animating[i]; + + // The layer's bounds are empty. + // Empty layers don't paint or idle-paint. + layer->SetBounds(gfx::Size()); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = gfx::Rect(); + bool needsUpdate = updateAndPush(layer, layerImpl); + + // Empty layers don't have tiles. + EXPECT_EQ(0u, layer->NumPaintedTiles()); + + // Empty layers don't need prepaint. + EXPECT_FALSE(needsUpdate); + + // Empty layers don't have tiles. + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(0, 0)); + } +} + +TEST_F(TiledLayerTest, idlePaintNonVisibleLayers) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // Alternate between not visible and visible. + gfx::Rect v(0, 0, 100, 100); + gfx::Rect nv(0, 0, 0, 0); + gfx::Rect visibleRect[10] = {nv, nv, v, v, nv, nv, v, v, nv, nv}; + bool invalidate[10] = {true, true, true, true, true, true, true, true, false, false }; + + // We should not have any tiles except for when the layer was visible + // or after the layer was visible and we didn't invalidate. + bool haveTile[10] = { false, false, true, true, false, false, true, true, true, true }; + + for (int i = 0; i < 10; i++) { + layer->SetBounds(gfx::Size(100, 100)); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = visibleRect[i]; + + if (invalidate[i]) + layer->InvalidateContentRect(gfx::Rect(0, 0, 100, 100)); + bool needsUpdate = updateAndPush(layer, layerImpl); + + // We should never signal idle paint, as we painted the entire layer + // or the layer was not visible. + EXPECT_FALSE(needsUpdate); + EXPECT_EQ(layerImpl->HasResourceIdForTileAt(0, 0), haveTile[i]); + } +} + +TEST_F(TiledLayerTest, invalidateFromPrepare) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + updateAndPush(layer, layerImpl); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 1)); + + layer->fakeLayerUpdater()->clearPrepareCount(); + // Invoke update again. As the layer is valid update shouldn't be invoked on + // the LayerUpdater. + updateAndPush(layer, layerImpl); + EXPECT_EQ(0, layer->fakeLayerUpdater()->prepareCount()); + + // setRectToInvalidate triggers InvalidateContentRect() being invoked from update. + layer->fakeLayerUpdater()->setRectToInvalidate(gfx::Rect(25, 25, 50, 50), layer.get()); + layer->fakeLayerUpdater()->clearPrepareCount(); + layer->InvalidateContentRect(gfx::Rect(0, 0, 50, 50)); + updateAndPush(layer, layerImpl); + EXPECT_EQ(1, layer->fakeLayerUpdater()->prepareCount()); + layer->fakeLayerUpdater()->clearPrepareCount(); + + // The layer should still be invalid as update invoked invalidate. + updateAndPush(layer, layerImpl); // visible + EXPECT_EQ(1, layer->fakeLayerUpdater()->prepareCount()); +} + +TEST_F(TiledLayerTest, verifyUpdateRectWhenContentBoundsAreScaled) +{ + // The updateRect (that indicates what was actually painted) should be in + // layer space, not the content space. + scoped_refptr<FakeTiledLayerWithScaledBounds> layer = make_scoped_refptr(new FakeTiledLayerWithScaledBounds(m_resourceManager.get())); + + gfx::Rect layerBounds(0, 0, 300, 200); + gfx::Rect contentBounds(0, 0, 200, 250); + + layer->SetBounds(layerBounds.size()); + layer->setContentBounds(contentBounds.size()); + layer->draw_properties().visible_content_rect = contentBounds; + + // On first update, the updateRect includes all tiles, even beyond the boundaries of the layer. + // However, it should still be in layer space, not content space. + layer->InvalidateContentRect(contentBounds); + + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), 0, NULL); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 300, 300 * 0.8), layer->updateRect()); + updateTextures(); + + // After the tiles are updated once, another invalidate only needs to update the bounds of the layer. + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->InvalidateContentRect(contentBounds); + layer->Update(m_queue.get(), 0, NULL); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(layerBounds), layer->updateRect()); + updateTextures(); + + // Partial re-paint should also be represented by the updateRect in layer space, not content space. + gfx::Rect partialDamage(30, 100, 10, 10); + layer->InvalidateContentRect(partialDamage); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), 0, NULL); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(45, 80, 15, 8), layer->updateRect()); +} + +TEST_F(TiledLayerTest, verifyInvalidationWhenContentsScaleChanges) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + + // Create a layer with one tile. + layer->SetBounds(gfx::Size(100, 100)); + calcDrawProps(layer); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 100, 100), layer->lastNeedsDisplayRect()); + + // Push the tiles to the impl side and check that there is exactly one. + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), 0, NULL); + updateTextures(); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(0, 1)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(1, 0)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(1, 1)); + + layer->SetNeedsDisplayRect(gfx::Rect()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(), layer->lastNeedsDisplayRect()); + + // Change the contents scale. + layer->updateContentsScale(2); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 200, 200); + + // The impl side should get 2x2 tiles now. + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), 0, NULL); + updateTextures(); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(0, 1)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(1, 0)); + EXPECT_TRUE(layerImpl->HasResourceIdForTileAt(1, 1)); + + // Verify that changing the contents scale caused invalidation, and + // that the layer-space rectangle requiring painting is not scaled. + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 100, 100), layer->lastNeedsDisplayRect()); + + // Invalidate the entire layer again, but do not paint. All tiles should be gone now from the + // impl side. + layer->SetNeedsDisplay(); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + + layerPushPropertiesTo(layer.get(), layerImpl.get()); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(0, 1)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(1, 0)); + EXPECT_FALSE(layerImpl->HasResourceIdForTileAt(1, 1)); +} + +TEST_F(TiledLayerTest, skipsDrawGetsReset) +{ + // Create two 300 x 300 tiled layers. + gfx::Size contentBounds(300, 300); + gfx::Rect contentRect(gfx::Point(), contentBounds); + + // We have enough memory for only one of the two layers. + int memoryLimit = 4 * 300 * 300; // 4 bytes per pixel. + + scoped_refptr<FakeTiledLayer> root_layer = make_scoped_refptr(new FakeTiledLayer(layer_tree_host_->contents_texture_manager())); + scoped_refptr<FakeTiledLayer> childLayer = make_scoped_refptr(new FakeTiledLayer(layer_tree_host_->contents_texture_manager())); + root_layer->AddChild(childLayer); + + root_layer->SetBounds(contentBounds); + root_layer->draw_properties().visible_content_rect = contentRect; + root_layer->SetPosition(gfx::PointF(0, 0)); + childLayer->SetBounds(contentBounds); + childLayer->draw_properties().visible_content_rect = contentRect; + childLayer->SetPosition(gfx::PointF(0, 0)); + root_layer->InvalidateContentRect(contentRect); + childLayer->InvalidateContentRect(contentRect); + + layer_tree_host_->SetRootLayer(root_layer); + layer_tree_host_->SetViewportSize(gfx::Size(300, 300), gfx::Size(300, 300)); + + layer_tree_host_->UpdateLayers(m_queue.get(), memoryLimit); + + // We'll skip the root layer. + EXPECT_TRUE(root_layer->SkipsDraw()); + EXPECT_FALSE(childLayer->SkipsDraw()); + + layer_tree_host_->CommitComplete(); + + // Remove the child layer. + root_layer->RemoveAllChildren(); + + layer_tree_host_->UpdateLayers(m_queue.get(), memoryLimit); + EXPECT_FALSE(root_layer->SkipsDraw()); + + resourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(), m_resourceProvider.get()); + layer_tree_host_->SetRootLayer(NULL); +} + +TEST_F(TiledLayerTest, resizeToSmaller) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + + layer->SetBounds(gfx::Size(700, 700)); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 700, 700); + layer->InvalidateContentRect(gfx::Rect(0, 0, 700, 700)); + + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), 0, NULL); + + layer->SetBounds(gfx::Size(200, 200)); + layer->InvalidateContentRect(gfx::Rect(0, 0, 200, 200)); +} + +TEST_F(TiledLayerTest, hugeLayerUpdateCrash) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + + int size = 1 << 30; + layer->SetBounds(gfx::Size(size, size)); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 700, 700); + layer->InvalidateContentRect(gfx::Rect(0, 0, size, size)); + + // Ensure no crash for bounds where size * size would overflow an int. + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), 0, NULL); +} + +class TiledLayerPartialUpdateTest : public TiledLayerTest { +public: + TiledLayerPartialUpdateTest() + { + m_settings.maxPartialTextureUpdates = 4; + } +}; + +TEST_F(TiledLayerPartialUpdateTest, partialUpdates) +{ + // Create one 300 x 200 tiled layer with 3 x 2 tiles. + gfx::Size contentBounds(300, 200); + gfx::Rect contentRect(gfx::Point(), contentBounds); + + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(layer_tree_host_->contents_texture_manager())); + layer->SetBounds(contentBounds); + layer->SetPosition(gfx::PointF(0, 0)); + layer->draw_properties().visible_content_rect = contentRect; + layer->InvalidateContentRect(contentRect); + + layer_tree_host_->SetRootLayer(layer); + layer_tree_host_->SetViewportSize(gfx::Size(300, 200), gfx::Size(300, 200)); + + // Full update of all 6 tiles. + layer_tree_host_->UpdateLayers(m_queue.get(), + std::numeric_limits<size_t>::max()); + { + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + EXPECT_EQ(6, m_queue->fullUploadSize()); + EXPECT_EQ(0, m_queue->partialUploadSize()); + updateTextures(); + EXPECT_EQ(6, layer->fakeLayerUpdater()->updateCount()); + EXPECT_FALSE(m_queue->hasMoreUpdates()); + layer->fakeLayerUpdater()->clearUpdateCount(); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + } + layer_tree_host_->CommitComplete(); + + // Full update of 3 tiles and partial update of 3 tiles. + layer->InvalidateContentRect(gfx::Rect(0, 0, 300, 150)); + layer_tree_host_->UpdateLayers(m_queue.get(), std::numeric_limits<size_t>::max()); + { + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + EXPECT_EQ(3, m_queue->fullUploadSize()); + EXPECT_EQ(3, m_queue->partialUploadSize()); + updateTextures(); + EXPECT_EQ(6, layer->fakeLayerUpdater()->updateCount()); + EXPECT_FALSE(m_queue->hasMoreUpdates()); + layer->fakeLayerUpdater()->clearUpdateCount(); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + } + layer_tree_host_->CommitComplete(); + + // Partial update of 6 tiles. + layer->InvalidateContentRect(gfx::Rect(50, 50, 200, 100)); + { + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + layer_tree_host_->UpdateLayers(m_queue.get(), std::numeric_limits<size_t>::max()); + EXPECT_EQ(2, m_queue->fullUploadSize()); + EXPECT_EQ(4, m_queue->partialUploadSize()); + updateTextures(); + EXPECT_EQ(6, layer->fakeLayerUpdater()->updateCount()); + EXPECT_FALSE(m_queue->hasMoreUpdates()); + layer->fakeLayerUpdater()->clearUpdateCount(); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + } + layer_tree_host_->CommitComplete(); + + // Checkerboard all tiles. + layer->InvalidateContentRect(gfx::Rect(0, 0, 300, 200)); + { + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + } + layer_tree_host_->CommitComplete(); + + // Partial update of 6 checkerboard tiles. + layer->InvalidateContentRect(gfx::Rect(50, 50, 200, 100)); + { + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + layer_tree_host_->UpdateLayers(m_queue.get(), std::numeric_limits<size_t>::max()); + EXPECT_EQ(6, m_queue->fullUploadSize()); + EXPECT_EQ(0, m_queue->partialUploadSize()); + updateTextures(); + EXPECT_EQ(6, layer->fakeLayerUpdater()->updateCount()); + EXPECT_FALSE(m_queue->hasMoreUpdates()); + layer->fakeLayerUpdater()->clearUpdateCount(); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + } + layer_tree_host_->CommitComplete(); + + // Partial update of 4 tiles. + layer->InvalidateContentRect(gfx::Rect(50, 50, 100, 100)); + { + scoped_ptr<FakeTiledLayerImpl> layerImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), 1)); + layer_tree_host_->UpdateLayers(m_queue.get(), std::numeric_limits<size_t>::max()); + EXPECT_EQ(0, m_queue->fullUploadSize()); + EXPECT_EQ(4, m_queue->partialUploadSize()); + updateTextures(); + EXPECT_EQ(4, layer->fakeLayerUpdater()->updateCount()); + EXPECT_FALSE(m_queue->hasMoreUpdates()); + layer->fakeLayerUpdater()->clearUpdateCount(); + layerPushPropertiesTo(layer.get(), layerImpl.get()); + } + layer_tree_host_->CommitComplete(); + + resourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(), m_resourceProvider.get()); + layer_tree_host_->SetRootLayer(NULL); +} + +TEST_F(TiledLayerTest, tilesPaintedWithoutOcclusion) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + calcDrawProps(layer); + + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), 0, NULL); + EXPECT_EQ(2, layer->fakeLayerUpdater()->updateCount()); +} + +TEST_F(TiledLayerTest, tilesPaintedWithOcclusion) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + + // The tile size is 100x100. + + layer_tree_host_->SetViewportSize(gfx::Size(600, 600), gfx::Size(600, 600)); + layer->SetBounds(gfx::Size(600, 600)); + calcDrawProps(layer); + + occluded.setOcclusion(gfx::Rect(200, 200, 300, 100)); + layer->draw_properties().drawable_content_rect = gfx::Rect(gfx::Point(), layer->content_bounds()); + layer->draw_properties().visible_content_rect = gfx::Rect(gfx::Point(), layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(36-3, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000, 1); + EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fakeLayerUpdater()->clearUpdateCount(); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + + occluded.setOcclusion(gfx::Rect(250, 200, 300, 100)); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(36-2, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000 + 340000, 1); + EXPECT_EQ(3 + 2, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fakeLayerUpdater()->clearUpdateCount(); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + + occluded.setOcclusion(gfx::Rect(250, 250, 300, 100)); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(36, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000 + 340000 + 360000, 1); + EXPECT_EQ(3 + 2, occluded.overdraw_metrics()->tiles_culled_for_upload()); +} + +TEST_F(TiledLayerTest, tilesPaintedWithOcclusionAndVisiblityConstraints) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + + // The tile size is 100x100. + + layer_tree_host_->SetViewportSize(gfx::Size(600, 600), gfx::Size(600, 600)); + layer->SetBounds(gfx::Size(600, 600)); + calcDrawProps(layer); + + // The partially occluded tiles (by the 150 occlusion height) are visible beyond the occlusion, so not culled. + occluded.setOcclusion(gfx::Rect(200, 200, 300, 150)); + layer->draw_properties().drawable_content_rect = gfx::Rect(0, 0, 600, 360); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 600, 360); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(24-3, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 210000, 1); + EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fakeLayerUpdater()->clearUpdateCount(); + + // Now the visible region stops at the edge of the occlusion so the partly visible tiles become fully occluded. + occluded.setOcclusion(gfx::Rect(200, 200, 300, 150)); + layer->draw_properties().drawable_content_rect = gfx::Rect(0, 0, 600, 350); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 600, 350); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(24-6, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 210000 + 180000, 1); + EXPECT_EQ(3 + 6, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fakeLayerUpdater()->clearUpdateCount(); + + // Now the visible region is even smaller than the occlusion, it should have the same result. + occluded.setOcclusion(gfx::Rect(200, 200, 300, 150)); + layer->draw_properties().drawable_content_rect = gfx::Rect(0, 0, 600, 340); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 600, 340); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(24-6, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 210000 + 180000 + 180000, 1); + EXPECT_EQ(3 + 6 + 6, occluded.overdraw_metrics()->tiles_culled_for_upload()); + +} + +TEST_F(TiledLayerTest, tilesNotPaintedWithoutInvalidation) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + + // The tile size is 100x100. + + layer_tree_host_->SetViewportSize(gfx::Size(600, 600), gfx::Size(600, 600)); + layer->SetBounds(gfx::Size(600, 600)); + calcDrawProps(layer); + + occluded.setOcclusion(gfx::Rect(200, 200, 300, 100)); + layer->draw_properties().drawable_content_rect = gfx::Rect(0, 0, 600, 600); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 600, 600); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(36-3, layer->fakeLayerUpdater()->updateCount()); + { + updateTextures(); + } + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000, 1); + EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fakeLayerUpdater()->clearUpdateCount(); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + + // Repaint without marking it dirty. The 3 culled tiles will be pre-painted now. + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(3, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000, 1); + EXPECT_EQ(6, occluded.overdraw_metrics()->tiles_culled_for_upload()); +} + +TEST_F(TiledLayerTest, tilesPaintedWithOcclusionAndTransforms) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + + // The tile size is 100x100. + + // This makes sure the painting works when the occluded region (in screen space) + // is transformed differently than the layer. + layer_tree_host_->SetViewportSize(gfx::Size(600, 600), gfx::Size(600, 600)); + layer->SetBounds(gfx::Size(600, 600)); + calcDrawProps(layer); + gfx::Transform screenTransform; + screenTransform.Scale(0.5, 0.5); + layer->draw_properties().screen_space_transform = screenTransform; + layer->draw_properties().target_space_transform = screenTransform; + + occluded.setOcclusion(gfx::Rect(100, 100, 150, 50)); + layer->draw_properties().drawable_content_rect = gfx::Rect(gfx::Point(), layer->content_bounds()); + layer->draw_properties().visible_content_rect = gfx::Rect(gfx::Point(), layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(36-3, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 330000, 1); + EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload()); +} + +TEST_F(TiledLayerTest, tilesPaintedWithOcclusionAndScaling) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + + // The tile size is 100x100. + + // This makes sure the painting works when the content space is scaled to + // a different layer space. In this case tiles are scaled to be 200x200 + // pixels, which means none should be occluded. + layer_tree_host_->SetViewportSize(gfx::Size(600, 600), gfx::Size(600, 600)); + layer->SetBounds(gfx::Size(600, 600)); + layer->SetRasterScale(0.5); + calcDrawProps(layer); + EXPECT_FLOAT_EQ(layer->contents_scale_x(), layer->contents_scale_y()); + gfx::Transform drawTransform; + double invScaleFactor = 1 / layer->contents_scale_x(); + drawTransform.Scale(invScaleFactor, invScaleFactor); + layer->draw_properties().target_space_transform = drawTransform; + layer->draw_properties().screen_space_transform = drawTransform; + + occluded.setOcclusion(gfx::Rect(200, 200, 300, 100)); + layer->draw_properties().drawable_content_rect = gfx::Rect(gfx::Point(), layer->bounds()); + layer->draw_properties().visible_content_rect = gfx::Rect(gfx::Point(), layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + // The content is half the size of the layer (so the number of tiles is fewer). + // In this case, the content is 300x300, and since the tile size is 100, the + // number of tiles 3x3. + EXPECT_EQ(9, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 90000, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fakeLayerUpdater()->clearUpdateCount(); + + // This makes sure the painting works when the content space is scaled to + // a different layer space. In this case the occluded region catches the + // blown up tiles. + occluded.setOcclusion(gfx::Rect(200, 200, 300, 200)); + layer->draw_properties().drawable_content_rect = gfx::Rect(gfx::Point(), layer->bounds()); + layer->draw_properties().visible_content_rect = gfx::Rect(gfx::Point(), layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(9-1, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 90000 + 80000, 1); + EXPECT_EQ(1, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fakeLayerUpdater()->clearUpdateCount(); + + // This makes sure content scaling and transforms work together. + gfx::Transform screenTransform; + screenTransform.Scale(0.5, 0.5); + layer->draw_properties().screen_space_transform = screenTransform; + layer->draw_properties().target_space_transform = screenTransform; + + occluded.setOcclusion(gfx::Rect(100, 100, 150, 100)); + + gfx::Rect layerBoundsRect(gfx::Point(), layer->bounds()); + layer->draw_properties().drawable_content_rect = gfx::ToEnclosingRect(gfx::ScaleRect(layerBoundsRect, 0.5)); + layer->draw_properties().visible_content_rect = gfx::Rect(gfx::Point(), layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + EXPECT_EQ(9-1, layer->fakeLayerUpdater()->updateCount()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 90000 + 80000 + 80000, 1); + EXPECT_EQ(1 + 1, occluded.overdraw_metrics()->tiles_culled_for_upload()); +} + +TEST_F(TiledLayerTest, visibleContentOpaqueRegion) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000), gfx::Size(1000, 1000)); + + // The tile size is 100x100, so this invalidates and then paints two tiles in various ways. + + gfx::Rect opaquePaintRect; + Region opaqueContents; + + gfx::Rect contentBounds = gfx::Rect(0, 0, 100, 200); + gfx::Rect visibleBounds = gfx::Rect(0, 0, 100, 150); + + layer->SetBounds(contentBounds.size()); + calcDrawProps(layer); + layer->draw_properties().drawable_content_rect = visibleBounds; + layer->draw_properties().visible_content_rect = visibleBounds; + + // If the layer doesn't paint opaque content, then the visibleContentOpaqueRegion should be empty. + layer->fakeLayerUpdater()->setOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(contentBounds); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + opaqueContents = layer->VisibleContentOpaqueRegion(); + EXPECT_TRUE(opaqueContents.IsEmpty()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + // visibleContentOpaqueRegion should match the visible part of what is painted opaque. + opaquePaintRect = gfx::Rect(10, 10, 90, 190); + layer->fakeLayerUpdater()->setOpaquePaintRect(opaquePaintRect); + layer->InvalidateContentRect(contentBounds); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + updateTextures(); + opaqueContents = layer->VisibleContentOpaqueRegion(); + EXPECT_EQ(gfx::IntersectRects(opaquePaintRect, visibleBounds).ToString(), opaqueContents.ToString()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000 * 2, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 17100, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000 + 20000 - 17100, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + // If we paint again without invalidating, the same stuff should be opaque. + layer->fakeLayerUpdater()->setOpaquePaintRect(gfx::Rect()); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + updateTextures(); + opaqueContents = layer->VisibleContentOpaqueRegion(); + EXPECT_EQ(gfx::IntersectRects(opaquePaintRect, visibleBounds).ToString(), opaqueContents.ToString()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000 * 2, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 17100, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000 + 20000 - 17100, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + // If we repaint a non-opaque part of the tile, then it shouldn't lose its opaque-ness. And other tiles should + // not be affected. + layer->fakeLayerUpdater()->setOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 1, 1)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + updateTextures(); + opaqueContents = layer->VisibleContentOpaqueRegion(); + EXPECT_EQ(gfx::IntersectRects(opaquePaintRect, visibleBounds).ToString(), opaqueContents.ToString()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000 * 2 + 1, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 17100, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000 + 20000 - 17100 + 1, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + // If we repaint an opaque part of the tile, then it should lose its opaque-ness. But other tiles should still + // not be affected. + layer->fakeLayerUpdater()->setOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(gfx::Rect(10, 10, 1, 1)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + updateTextures(); + opaqueContents = layer->VisibleContentOpaqueRegion(); + EXPECT_EQ(gfx::IntersectRects(gfx::Rect(10, 100, 90, 100), visibleBounds).ToString(), opaqueContents.ToString()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 20000 * 2 + 1 + 1, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 17100, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 20000 + 20000 - 17100 + 1 + 1, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); +} + +TEST_F(TiledLayerTest, pixels_paintedMetrics) +{ + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr(new FakeTiledLayer(m_resourceManager.get())); + TestOcclusionTracker occluded; + m_occlusion = &occluded; + layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000), gfx::Size(1000, 1000)); + + // The tile size is 100x100, so this invalidates and then paints two tiles in various ways. + + gfx::Rect opaquePaintRect; + Region opaqueContents; + + gfx::Rect contentBounds = gfx::Rect(0, 0, 100, 300); + layer->SetBounds(contentBounds.size()); + calcDrawProps(layer); + + // Invalidates and paints the whole layer. + layer->fakeLayerUpdater()->setOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(contentBounds); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + updateTextures(); + opaqueContents = layer->VisibleContentOpaqueRegion(); + EXPECT_TRUE(opaqueContents.IsEmpty()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 30000, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 30000, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + // Invalidates an area on the top and bottom tile, which will cause us to paint the tile in the middle, + // even though it is not dirty and will not be uploaded. + layer->fakeLayerUpdater()->setOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 1, 1)); + layer->InvalidateContentRect(gfx::Rect(50, 200, 10, 10)); + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + layer->Update(m_queue.get(), &occluded, NULL); + updateTextures(); + opaqueContents = layer->VisibleContentOpaqueRegion(); + EXPECT_TRUE(opaqueContents.IsEmpty()); + + // The middle tile was painted even though not invalidated. + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_painted(), 30000 + 60 * 210, 1); + // The pixels uploaded will not include the non-invalidated tile in the middle. + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), 30000 + 1 + 100, 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); +} + +TEST_F(TiledLayerTest, dontAllocateContentsWhenTargetSurfaceCantBeAllocated) +{ + // Tile size is 100x100. + gfx::Rect rootRect(0, 0, 300, 200); + gfx::Rect childRect(0, 0, 300, 100); + gfx::Rect child2Rect(0, 100, 300, 100); + + scoped_refptr<FakeTiledLayer> root = make_scoped_refptr(new FakeTiledLayer(layer_tree_host_->contents_texture_manager())); + scoped_refptr<Layer> surface = Layer::Create(); + scoped_refptr<FakeTiledLayer> child = make_scoped_refptr(new FakeTiledLayer(layer_tree_host_->contents_texture_manager())); + scoped_refptr<FakeTiledLayer> child2 = make_scoped_refptr(new FakeTiledLayer(layer_tree_host_->contents_texture_manager())); + + root->SetBounds(rootRect.size()); + root->SetAnchorPoint(gfx::PointF()); + root->draw_properties().drawable_content_rect = rootRect; + root->draw_properties().visible_content_rect = rootRect; + root->AddChild(surface); + + surface->SetForceRenderSurface(true); + surface->SetAnchorPoint(gfx::PointF()); + surface->SetOpacity(0.5); + surface->AddChild(child); + surface->AddChild(child2); + + child->SetBounds(childRect.size()); + child->SetAnchorPoint(gfx::PointF()); + child->SetPosition(childRect.origin()); + child->draw_properties().visible_content_rect = childRect; + child->draw_properties().drawable_content_rect = rootRect; + + child2->SetBounds(child2Rect.size()); + child2->SetAnchorPoint(gfx::PointF()); + child2->SetPosition(child2Rect.origin()); + child2->draw_properties().visible_content_rect = child2Rect; + child2->draw_properties().drawable_content_rect = rootRect; + + layer_tree_host_->SetRootLayer(root); + layer_tree_host_->SetViewportSize(rootRect.size(), rootRect.size()); + + // With a huge memory limit, all layers should update and push their textures. + root->InvalidateContentRect(rootRect); + child->InvalidateContentRect(childRect); + child2->InvalidateContentRect(child2Rect); + layer_tree_host_->UpdateLayers(m_queue.get(), std::numeric_limits<size_t>::max()); + { + updateTextures(); + EXPECT_EQ(6, root->fakeLayerUpdater()->updateCount()); + EXPECT_EQ(3, child->fakeLayerUpdater()->updateCount()); + EXPECT_EQ(3, child2->fakeLayerUpdater()->updateCount()); + EXPECT_FALSE(m_queue->hasMoreUpdates()); + + root->fakeLayerUpdater()->clearUpdateCount(); + child->fakeLayerUpdater()->clearUpdateCount(); + child2->fakeLayerUpdater()->clearUpdateCount(); + + scoped_ptr<FakeTiledLayerImpl> rootImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), root->id())); + scoped_ptr<FakeTiledLayerImpl> childImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), child->id())); + scoped_ptr<FakeTiledLayerImpl> child2Impl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), child2->id())); + layerPushPropertiesTo(root.get(), rootImpl.get()); + layerPushPropertiesTo(child.get(), childImpl.get()); + layerPushPropertiesTo(child2.get(), child2Impl.get()); + + for (unsigned i = 0; i < 3; ++i) { + for (unsigned j = 0; j < 2; ++j) + EXPECT_TRUE(rootImpl->HasResourceIdForTileAt(i, j)); + EXPECT_TRUE(childImpl->HasResourceIdForTileAt(i, 0)); + EXPECT_TRUE(child2Impl->HasResourceIdForTileAt(i, 0)); + } + } + layer_tree_host_->CommitComplete(); + + // With a memory limit that includes only the root layer (3x2 tiles) and half the surface that + // the child layers draw into, the child layers will not be allocated. If the surface isn't + // accounted for, then one of the children would fit within the memory limit. + root->InvalidateContentRect(rootRect); + child->InvalidateContentRect(childRect); + child2->InvalidateContentRect(child2Rect); + layer_tree_host_->UpdateLayers(m_queue.get(), (3 * 2 + 3 * 1) * (100 * 100) * 4); + { + updateTextures(); + EXPECT_EQ(6, root->fakeLayerUpdater()->updateCount()); + EXPECT_EQ(0, child->fakeLayerUpdater()->updateCount()); + EXPECT_EQ(0, child2->fakeLayerUpdater()->updateCount()); + EXPECT_FALSE(m_queue->hasMoreUpdates()); + + root->fakeLayerUpdater()->clearUpdateCount(); + child->fakeLayerUpdater()->clearUpdateCount(); + child2->fakeLayerUpdater()->clearUpdateCount(); + + scoped_ptr<FakeTiledLayerImpl> rootImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), root->id())); + scoped_ptr<FakeTiledLayerImpl> childImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), child->id())); + scoped_ptr<FakeTiledLayerImpl> child2Impl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), child2->id())); + layerPushPropertiesTo(root.get(), rootImpl.get()); + layerPushPropertiesTo(child.get(), childImpl.get()); + layerPushPropertiesTo(child2.get(), child2Impl.get()); + + for (unsigned i = 0; i < 3; ++i) { + for (unsigned j = 0; j < 2; ++j) + EXPECT_TRUE(rootImpl->HasResourceIdForTileAt(i, j)); + EXPECT_FALSE(childImpl->HasResourceIdForTileAt(i, 0)); + EXPECT_FALSE(child2Impl->HasResourceIdForTileAt(i, 0)); + } + } + layer_tree_host_->CommitComplete(); + + // With a memory limit that includes only half the root layer, no contents will be + // allocated. If render surface memory wasn't accounted for, there is enough space + // for one of the children layers, but they draw into a surface that can't be + // allocated. + root->InvalidateContentRect(rootRect); + child->InvalidateContentRect(childRect); + child2->InvalidateContentRect(child2Rect); + layer_tree_host_->UpdateLayers(m_queue.get(), (3 * 1) * (100 * 100) * 4); + { + updateTextures(); + EXPECT_EQ(0, root->fakeLayerUpdater()->updateCount()); + EXPECT_EQ(0, child->fakeLayerUpdater()->updateCount()); + EXPECT_EQ(0, child2->fakeLayerUpdater()->updateCount()); + EXPECT_FALSE(m_queue->hasMoreUpdates()); + + root->fakeLayerUpdater()->clearUpdateCount(); + child->fakeLayerUpdater()->clearUpdateCount(); + child2->fakeLayerUpdater()->clearUpdateCount(); + + scoped_ptr<FakeTiledLayerImpl> rootImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), root->id())); + scoped_ptr<FakeTiledLayerImpl> childImpl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), child->id())); + scoped_ptr<FakeTiledLayerImpl> child2Impl = make_scoped_ptr(new FakeTiledLayerImpl(m_hostImpl->active_tree(), child2->id())); + layerPushPropertiesTo(root.get(), rootImpl.get()); + layerPushPropertiesTo(child.get(), childImpl.get()); + layerPushPropertiesTo(child2.get(), child2Impl.get()); + + for (unsigned i = 0; i < 3; ++i) { + for (unsigned j = 0; j < 2; ++j) + EXPECT_FALSE(rootImpl->HasResourceIdForTileAt(i, j)); + EXPECT_FALSE(childImpl->HasResourceIdForTileAt(i, 0)); + EXPECT_FALSE(child2Impl->HasResourceIdForTileAt(i, 0)); + } + } + layer_tree_host_->CommitComplete(); + + resourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(), m_resourceProvider.get()); + layer_tree_host_->SetRootLayer(NULL); +} + +class TrackingLayerPainter : public LayerPainter { +public: + static scoped_ptr<TrackingLayerPainter> Create() { return make_scoped_ptr(new TrackingLayerPainter()); } + + virtual void Paint(SkCanvas* canvas, gfx::Rect content_rect, gfx::RectF* opaque) OVERRIDE + { + m_paintedRect = content_rect; + } + + const gfx::Rect& paintedRect() const { return m_paintedRect; } + void resetPaintedRect() { m_paintedRect = gfx::Rect(); } + +private: + TrackingLayerPainter() { } + + gfx::Rect m_paintedRect; +}; + +class UpdateTrackingTiledLayer : public FakeTiledLayer { +public: + explicit UpdateTrackingTiledLayer(PrioritizedResourceManager* manager) + : FakeTiledLayer(manager) + { + scoped_ptr<TrackingLayerPainter> trackingLayerPainter(TrackingLayerPainter::Create()); + m_trackingLayerPainter = trackingLayerPainter.get(); + m_layerUpdater = BitmapContentLayerUpdater::Create(trackingLayerPainter.PassAs<LayerPainter>()); + } + + TrackingLayerPainter* trackingLayerPainter() const { return m_trackingLayerPainter; } + +protected: + virtual ~UpdateTrackingTiledLayer() { } + + virtual LayerUpdater* Updater() const OVERRIDE { return m_layerUpdater.get(); } + +private: + TrackingLayerPainter* m_trackingLayerPainter; + scoped_refptr<BitmapContentLayerUpdater> m_layerUpdater; +}; + +TEST_F(TiledLayerTest, nonIntegerContentsScaleIsNotDistortedDuringPaint) +{ + scoped_refptr<UpdateTrackingTiledLayer> layer = make_scoped_refptr(new UpdateTrackingTiledLayer(m_resourceManager.get())); + + gfx::Rect layerRect(0, 0, 30, 31); + layer->SetPosition(layerRect.origin()); + layer->SetBounds(layerRect.size()); + layer->updateContentsScale(1.5); + + gfx::Rect contentRect(0, 0, 45, 47); + EXPECT_EQ(contentRect.size(), layer->content_bounds()); + layer->draw_properties().visible_content_rect = contentRect; + layer->draw_properties().drawable_content_rect = contentRect; + + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + + // Update the whole tile. + layer->Update(m_queue.get(), 0, NULL); + layer->trackingLayerPainter()->resetPaintedRect(); + + EXPECT_RECT_EQ(gfx::Rect(), layer->trackingLayerPainter()->paintedRect()); + updateTextures(); + + // Invalidate the entire layer in content space. When painting, the rect given to webkit should match the layer's bounds. + layer->InvalidateContentRect(contentRect); + layer->Update(m_queue.get(), 0, NULL); + + EXPECT_RECT_EQ(layerRect, layer->trackingLayerPainter()->paintedRect()); +} + +TEST_F(TiledLayerTest, nonIntegerContentsScaleIsNotDistortedDuringInvalidation) +{ + scoped_refptr<UpdateTrackingTiledLayer> layer = make_scoped_refptr(new UpdateTrackingTiledLayer(m_resourceManager.get())); + + gfx::Rect layerRect(0, 0, 30, 31); + layer->SetPosition(layerRect.origin()); + layer->SetBounds(layerRect.size()); + layer->updateContentsScale(1.3f); + + gfx::Rect contentRect(gfx::Point(), layer->content_bounds()); + layer->draw_properties().visible_content_rect = contentRect; + layer->draw_properties().drawable_content_rect = contentRect; + + layer->SetTexturePriorities(m_priorityCalculator); + m_resourceManager->prioritizeTextures(); + + // Update the whole tile. + layer->Update(m_queue.get(), 0, NULL); + layer->trackingLayerPainter()->resetPaintedRect(); + + EXPECT_RECT_EQ(gfx::Rect(), layer->trackingLayerPainter()->paintedRect()); + updateTextures(); + + // Invalidate the entire layer in layer space. When painting, the rect given to webkit should match the layer's bounds. + layer->SetNeedsDisplayRect(layerRect); + layer->Update(m_queue.get(), 0, NULL); + + EXPECT_RECT_EQ(layerRect, layer->trackingLayerPainter()->paintedRect()); +} + +} // namespace +} // namespace cc diff --git a/cc/layers/video_frame_provider.h b/cc/layers/video_frame_provider.h new file mode 100644 index 0000000..ecf0024 --- /dev/null +++ b/cc/layers/video_frame_provider.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 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 CC_LAYERS_VIDEO_FRAME_PROVIDER_H_ +#define CC_LAYERS_VIDEO_FRAME_PROVIDER_H_ + +#include "base/memory/ref_counted.h" + +namespace media { +class VideoFrame; +} + +namespace cc { + +// Threading notes: This class may be used in a multi threaded manner. +// Specifically, the implementation may call getCurrentFrame() or +// putCurrentFrame() from the compositor thread. If so, the caller is +// responsible for making sure Client::didReceiveFrame and +// Client::didUpdateMatrix are only called from this same thread. +class VideoFrameProvider { + public: + virtual ~VideoFrameProvider() {} + + class Client { + public: + // Provider will call this method to tell the client to stop using it. + // StopUsingProvider() may be called from any thread. The client should + // block until it has PutCurrentFrame() any outstanding frames. + virtual void StopUsingProvider() = 0; + + // Notifies the provider's client that a call to GetCurrentFrame() will + // return new data. + virtual void DidReceiveFrame() = 0; + + // Notifies the provider's client of a new UV transform matrix to be used. + virtual void DidUpdateMatrix(const float* matrix) = 0; + }; + + // May be called from any thread, but there must be some external guarantee + // that the provider is not destroyed before this call returns. + virtual void SetVideoFrameProviderClient(Client* client) = 0; + + // This function places a lock on the current frame and returns a pointer to + // it. Calls to this method should always be followed with a call to + // PutCurrentFrame(). + // Only the current provider client should call this function. + virtual scoped_refptr<media::VideoFrame> GetCurrentFrame() = 0; + + // This function releases the lock on the video frame. It should always be + // called after GetCurrentFrame(). Frames passed into this method + // should no longer be referenced after the call is made. Only the current + // provider client should call this function. + virtual void PutCurrentFrame( + const scoped_refptr<media::VideoFrame>& frame) = 0; +}; + +} // namespace cc + +#endif // CC_LAYERS_VIDEO_FRAME_PROVIDER_H_ diff --git a/cc/layers/video_frame_provider_client_impl.cc b/cc/layers/video_frame_provider_client_impl.cc new file mode 100644 index 0000000..2f318bf --- /dev/null +++ b/cc/layers/video_frame_provider_client_impl.cc @@ -0,0 +1,87 @@ +// Copyright 2013 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 "cc/layers/video_frame_provider_client_impl.h" + +#include "cc/base/math_util.h" +#include "cc/layers/video_layer_impl.h" + +namespace cc { + +// static +scoped_refptr<VideoFrameProviderClientImpl> + VideoFrameProviderClientImpl::Create( + VideoFrameProvider* provider) { + return make_scoped_refptr( + new VideoFrameProviderClientImpl(provider)); +} + +VideoFrameProviderClientImpl::~VideoFrameProviderClientImpl() {} + +VideoFrameProviderClientImpl::VideoFrameProviderClientImpl( + VideoFrameProvider* provider) + : provider_(provider) { + // This only happens during a commit on the compositor thread while the main + // thread is blocked. That makes this a thread-safe call to set the video + // frame provider client that does not require a lock. The same is true of + // the call to Stop(). + provider_->SetVideoFrameProviderClient(this); + + // This matrix is the default transformation for stream textures, and flips + // on the Y axis. + stream_texture_matrix_ = gfx::Transform( + 1.0, 0.0, 0.0, 0.0, + 0.0, -1.0, 0.0, 1.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +void VideoFrameProviderClientImpl::Stop() { + if (!provider_) + return; + provider_->SetVideoFrameProviderClient(NULL); + provider_ = NULL; +} + +media::VideoFrame* VideoFrameProviderClientImpl::AcquireLockAndCurrentFrame() { + provider_lock_.Acquire(); // Balanced by call to ReleaseLock(). + if (!provider_) + return NULL; + + return provider_->GetCurrentFrame(); +} + +void VideoFrameProviderClientImpl::PutCurrentFrame(media::VideoFrame* frame) { + provider_lock_.AssertAcquired(); + provider_->PutCurrentFrame(frame); +} + +void VideoFrameProviderClientImpl::ReleaseLock() { + provider_lock_.AssertAcquired(); + provider_lock_.Release(); +} + +void VideoFrameProviderClientImpl::StopUsingProvider() { + // Block the provider from shutting down until this client is done + // using the frame. + base::AutoLock locker(provider_lock_); + provider_ = 0; +} + +void VideoFrameProviderClientImpl::DidReceiveFrame() { + if (active_video_layer_) + active_video_layer_->SetNeedsRedraw(); +} + +void VideoFrameProviderClientImpl::DidUpdateMatrix(const float* matrix) { + stream_texture_matrix_ = gfx::Transform( + matrix[0], matrix[4], matrix[8], matrix[12], + matrix[1], matrix[5], matrix[9], matrix[13], + matrix[2], matrix[6], matrix[10], matrix[14], + matrix[3], matrix[7], matrix[11], matrix[15]); + if (active_video_layer_) + active_video_layer_->SetNeedsRedraw(); +} + +} // namespace cc diff --git a/cc/layers/video_frame_provider_client_impl.h b/cc/layers/video_frame_provider_client_impl.h new file mode 100644 index 0000000..c7f332e --- /dev/null +++ b/cc/layers/video_frame_provider_client_impl.h @@ -0,0 +1,63 @@ +// Copyright 2013 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 CC_LAYERS_VIDEO_FRAME_PROVIDER_CLIENT_IMPL_H_ +#define CC_LAYERS_VIDEO_FRAME_PROVIDER_CLIENT_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "cc/layers/video_frame_provider.h" +#include "ui/gfx/transform.h" + +namespace media { class VideoFrame; } + +namespace cc { +class VideoLayerImpl; + +class VideoFrameProviderClientImpl + : public VideoFrameProvider::Client, + public base::RefCounted<VideoFrameProviderClientImpl> { + public: + static scoped_refptr<VideoFrameProviderClientImpl> Create( + VideoFrameProvider* provider); + + void set_active_video_layer(VideoLayerImpl* video_layer) { + active_video_layer_ = video_layer; + } + + void Stop(); + bool Stopped() const { return !provider_; } + + media::VideoFrame* AcquireLockAndCurrentFrame(); + void PutCurrentFrame(media::VideoFrame* frame); + void ReleaseLock(); + const gfx::Transform& stream_texture_matrix() const { + return stream_texture_matrix_; + } + + // VideoFrameProvider::Client implementation. These methods are all callable + // on any thread. + virtual void StopUsingProvider() OVERRIDE; + virtual void DidReceiveFrame() OVERRIDE; + virtual void DidUpdateMatrix(const float* matrix) OVERRIDE; + + private: + explicit VideoFrameProviderClientImpl(VideoFrameProvider* provider); + friend class base::RefCounted<VideoFrameProviderClientImpl>; + virtual ~VideoFrameProviderClientImpl(); + + VideoLayerImpl* active_video_layer_; + + // Guards the destruction of provider_ and the frame that it provides + base::Lock provider_lock_; + VideoFrameProvider* provider_; + + gfx::Transform stream_texture_matrix_; + + DISALLOW_COPY_AND_ASSIGN(VideoFrameProviderClientImpl); +}; + +} // namespace cc + +#endif // CC_LAYERS_VIDEO_FRAME_PROVIDER_CLIENT_IMPL_H_ diff --git a/cc/layers/video_layer.cc b/cc/layers/video_layer.cc new file mode 100644 index 0000000..5066835 --- /dev/null +++ b/cc/layers/video_layer.cc @@ -0,0 +1,25 @@ +// Copyright 2010 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 "cc/layers/video_layer.h" + +#include "cc/layers/video_layer_impl.h" + +namespace cc { + +scoped_refptr<VideoLayer> VideoLayer::Create(VideoFrameProvider* provider) { + return make_scoped_refptr(new VideoLayer(provider)); +} + +VideoLayer::VideoLayer(VideoFrameProvider* provider) : provider_(provider) { + DCHECK(provider_); +} + +VideoLayer::~VideoLayer() {} + +scoped_ptr<LayerImpl> VideoLayer::CreateLayerImpl(LayerTreeImpl* tree_impl) { + return VideoLayerImpl::Create(tree_impl, id(), provider_).PassAs<LayerImpl>(); +} + +} // namespace cc diff --git a/cc/layers/video_layer.h b/cc/layers/video_layer.h new file mode 100644 index 0000000..c9705cd --- /dev/null +++ b/cc/layers/video_layer.h @@ -0,0 +1,38 @@ +// Copyright 2010 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 CC_LAYERS_VIDEO_LAYER_H_ +#define CC_LAYERS_VIDEO_LAYER_H_ + +#include "base/callback.h" +#include "cc/base/cc_export.h" +#include "cc/layers/layer.h" + +namespace media { class VideoFrame; } + +namespace cc { + +class VideoFrameProvider; +class VideoLayerImpl; + +// A Layer that contains a Video element. +class CC_EXPORT VideoLayer : public Layer { + public: + static scoped_refptr<VideoLayer> Create(VideoFrameProvider* provider); + + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + + private: + explicit VideoLayer(VideoFrameProvider* provider); + virtual ~VideoLayer(); + + // This pointer is only for passing to VideoLayerImpl's constructor. It should + // never be dereferenced by this class. + VideoFrameProvider* provider_; +}; + +} // namespace cc + +#endif // CC_LAYERS_VIDEO_LAYER_H_ diff --git a/cc/layers/video_layer_impl.cc b/cc/layers/video_layer_impl.cc new file mode 100644 index 0000000..42d5036 --- /dev/null +++ b/cc/layers/video_layer_impl.cc @@ -0,0 +1,482 @@ +// Copyright 2011 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 "cc/layers/video_layer_impl.h" + +#include "base/logging.h" +#include "cc/base/math_util.h" +#include "cc/layers/quad_sink.h" +#include "cc/layers/video_frame_provider_client_impl.h" +#include "cc/output/renderer.h" +#include "cc/quads/io_surface_draw_quad.h" +#include "cc/quads/stream_video_draw_quad.h" +#include "cc/quads/texture_draw_quad.h" +#include "cc/quads/yuv_video_draw_quad.h" +#include "cc/resources/resource_provider.h" +#include "cc/trees/layer_tree_impl.h" +#include "gpu/GLES2/gl2extchromium.h" +#include "media/filters/skcanvas_video_renderer.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "third_party/khronos/GLES2/gl2ext.h" + +#if defined(GOOGLE_TV) +#include "cc/quads/solid_color_draw_quad.h" +#endif + +namespace cc { + +// static +scoped_ptr<VideoLayerImpl> VideoLayerImpl::Create( + LayerTreeImpl* tree_impl, + int id, + VideoFrameProvider* provider) { + scoped_ptr<VideoLayerImpl> layer(new VideoLayerImpl(tree_impl, id)); + layer->SetProviderClientImpl(VideoFrameProviderClientImpl::Create(provider)); + DCHECK(tree_impl->proxy()->IsImplThread()); + DCHECK(tree_impl->proxy()->IsMainThreadBlocked()); + return layer.Pass(); +} + +VideoLayerImpl::VideoLayerImpl(LayerTreeImpl* tree_impl, int id) + : LayerImpl(tree_impl, id), + frame_(NULL), + format_(GL_INVALID_VALUE), + convert_yuv_(false), + external_texture_resource_(0) {} + +VideoLayerImpl::~VideoLayerImpl() { + if (!provider_client_impl_->Stopped()) { + // In impl side painting, we may have a pending and active layer + // associated with the video provider at the same time. Both have a ref + // on the VideoFrameProviderClientImpl, but we stop when the first + // LayerImpl (the one on the pending tree) is destroyed since we know + // the main thread is blocked for this commit. + DCHECK(layer_tree_impl()->proxy()->IsImplThread()); + DCHECK(layer_tree_impl()->proxy()->IsMainThreadBlocked()); + provider_client_impl_->Stop(); + } + FreePlaneData(layer_tree_impl()->resource_provider()); + +#ifndef NDEBUG + for (size_t i = 0; i < media::VideoFrame::kMaxPlanes; ++i) + DCHECK(!frame_planes_[i].resource_id); + DCHECK(!external_texture_resource_); +#endif +} + +scoped_ptr<LayerImpl> VideoLayerImpl::CreateLayerImpl( + LayerTreeImpl* tree_impl) { + return scoped_ptr<LayerImpl>(new VideoLayerImpl(tree_impl, id())); +} + +void VideoLayerImpl::PushPropertiesTo(LayerImpl* layer) { + LayerImpl::PushPropertiesTo(layer); + + VideoLayerImpl* other = static_cast<VideoLayerImpl*>(layer); + other->SetProviderClientImpl(provider_client_impl_); +} + +void VideoLayerImpl::DidBecomeActive() { + provider_client_impl_->set_active_video_layer(this); +} + +// Convert media::VideoFrame::Format to OpenGL enum values. +static GLenum ConvertVFCFormatToGLenum(const media::VideoFrame& frame) { + switch (frame.format()) { + case media::VideoFrame::YV12: + case media::VideoFrame::YV16: + return GL_LUMINANCE; + case media::VideoFrame::NATIVE_TEXTURE: + return frame.texture_target(); +#if defined(GOOGLE_TV) + case media::VideoFrame::HOLE: + return GL_INVALID_VALUE; +#endif + case media::VideoFrame::INVALID: + case media::VideoFrame::RGB32: + case media::VideoFrame::EMPTY: + case media::VideoFrame::I420: + NOTREACHED(); + break; + } + return GL_INVALID_VALUE; +} + +size_t VideoLayerImpl::NumPlanes() const { + if (!frame_) + return 0; + + if (convert_yuv_) + return 1; + + return media::VideoFrame::NumPlanes(frame_->format()); +} + +void VideoLayerImpl::WillDraw(ResourceProvider* resource_provider) { + LayerImpl::WillDraw(resource_provider); + + + // Explicitly acquire and release the provider mutex so it can be held from + // willDraw to didDraw. Since the compositor thread is in the middle of + // drawing, the layer will not be destroyed before didDraw is called. + // Therefore, the only thing that will prevent this lock from being released + // is the GPU process locking it. As the GPU process can't cause the + // destruction of the provider (calling stopUsingProvider), holding this + // lock should not cause a deadlock. + frame_ = provider_client_impl_->AcquireLockAndCurrentFrame(); + + WillDrawInternal(resource_provider); + FreeUnusedPlaneData(resource_provider); + + if (!frame_) + provider_client_impl_->ReleaseLock(); +} + +void VideoLayerImpl::WillDrawInternal(ResourceProvider* resource_provider) { + DCHECK(!external_texture_resource_); + + if (!frame_) + return; + +#if defined(GOOGLE_TV) + if (frame_->format() == media::VideoFrame::HOLE) + return; +#endif + + format_ = ConvertVFCFormatToGLenum(*frame_); + + // If these fail, we'll have to add draw logic that handles offset bitmap/ + // texture UVs. For now, just expect (0, 0) offset, since all our decoders + // so far don't offset. + DCHECK_EQ(frame_->visible_rect().x(), 0); + DCHECK_EQ(frame_->visible_rect().y(), 0); + + if (format_ == GL_INVALID_VALUE) { + provider_client_impl_->PutCurrentFrame(frame_); + frame_ = NULL; + return; + } + + // TODO: If we're in software compositing mode, we do the YUV -> RGB + // conversion here. That involves an extra copy of each frame to a bitmap. + // Obviously, this is suboptimal and should be addressed once ubercompositor + // starts shaping up. + convert_yuv_ = + resource_provider->default_resource_type() == ResourceProvider::Bitmap && + (frame_->format() == media::VideoFrame::YV12 || + frame_->format() == media::VideoFrame::YV16); + + if (convert_yuv_) + format_ = GL_RGBA; + + if (!AllocatePlaneData(resource_provider)) { + provider_client_impl_->PutCurrentFrame(frame_); + frame_ = NULL; + return; + } + + if (!CopyPlaneData(resource_provider)) { + provider_client_impl_->PutCurrentFrame(frame_); + frame_ = NULL; + return; + } + + if (format_ == GL_TEXTURE_2D) { + external_texture_resource_ = + resource_provider->CreateResourceFromExternalTexture( + frame_->texture_id()); + } +} + +void VideoLayerImpl::AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) { + if (!frame_) + return; + + SharedQuadState* shared_quad_state = + quad_sink->UseSharedQuadState(CreateSharedQuadState()); + AppendDebugBorderQuad(quad_sink, shared_quad_state, append_quads_data); + + // TODO: When we pass quads out of process, we need to double-buffer, or + // otherwise synchonize use of all textures in the quad. + + gfx::Rect quad_rect(content_bounds()); + gfx::Rect opaque_rect(contents_opaque() ? quad_rect : gfx::Rect()); + gfx::Rect visible_rect = frame_->visible_rect(); + gfx::Size coded_size = frame_->coded_size(); + + // pixels for macroblocked formats. + float tex_width_scale = + static_cast<float>(visible_rect.width()) / coded_size.width(); + float tex_height_scale = + static_cast<float>(visible_rect.height()) / coded_size.height(); + +#if defined(GOOGLE_TV) + // This block and other blocks wrapped around #if defined(GOOGLE_TV) is not + // maintained by the general compositor team. Please contact the following + // people instead: + // + // wonsik@chromium.org + // ycheo@chromium.org + + if (frame_->format() == media::VideoFrame::HOLE) { + scoped_ptr<SolidColorDrawQuad> solid_color_draw_quad = + SolidColorDrawQuad::Create(); + // Create a solid color quad with transparent black and force no + // blending. + solid_color_draw_quad->SetAll( + shared_quad_state, quad_rect, quad_rect, quad_rect, false, + SK_ColorTRANSPARENT); + quad_sink->Append(solid_color_draw_quad.PassAs<DrawQuad>(), + append_quads_data); + return; + } +#endif + + switch (format_) { + case GL_LUMINANCE: { + // YUV software decoder. + const FramePlane& y_plane = frame_planes_[media::VideoFrame::kYPlane]; + const FramePlane& u_plane = frame_planes_[media::VideoFrame::kUPlane]; + const FramePlane& v_plane = frame_planes_[media::VideoFrame::kVPlane]; + gfx::SizeF tex_scale(tex_width_scale, tex_height_scale); + scoped_ptr<YUVVideoDrawQuad> yuv_video_quad = YUVVideoDrawQuad::Create(); + yuv_video_quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + tex_scale, + y_plane, + u_plane, + v_plane); + quad_sink->Append(yuv_video_quad.PassAs<DrawQuad>(), append_quads_data); + break; + } + case GL_RGBA: { + // RGBA software decoder. + const FramePlane& plane = frame_planes_[media::VideoFrame::kRGBPlane]; + bool premultiplied_alpha = true; + gfx::PointF uv_top_left(0.f, 0.f); + gfx::PointF uv_bottom_right(tex_width_scale, tex_height_scale); + float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f}; + bool flipped = false; + scoped_ptr<TextureDrawQuad> texture_quad = TextureDrawQuad::Create(); + texture_quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + plane.resource_id, + premultiplied_alpha, + uv_top_left, + uv_bottom_right, + opacity, + flipped); + quad_sink->Append(texture_quad.PassAs<DrawQuad>(), append_quads_data); + break; + } + case GL_TEXTURE_2D: { + // NativeTexture hardware decoder. + bool premultiplied_alpha = true; + gfx::PointF uv_top_left(0.f, 0.f); + gfx::PointF uv_bottom_right(tex_width_scale, tex_height_scale); + float opacity[] = {1.0f, 1.0f, 1.0f, 1.0f}; + bool flipped = false; + scoped_ptr<TextureDrawQuad> texture_quad = TextureDrawQuad::Create(); + texture_quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + external_texture_resource_, + premultiplied_alpha, + uv_top_left, + uv_bottom_right, + opacity, + flipped); + quad_sink->Append(texture_quad.PassAs<DrawQuad>(), append_quads_data); + break; + } + case GL_TEXTURE_RECTANGLE_ARB: { + gfx::Size visible_size(visible_rect.width(), visible_rect.height()); + scoped_ptr<IOSurfaceDrawQuad> io_surface_quad = + IOSurfaceDrawQuad::Create(); + io_surface_quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + visible_size, + frame_->texture_id(), + IOSurfaceDrawQuad::UNFLIPPED); + quad_sink->Append(io_surface_quad.PassAs<DrawQuad>(), append_quads_data); + break; + } + case GL_TEXTURE_EXTERNAL_OES: { + // StreamTexture hardware decoder. + gfx::Transform transform(provider_client_impl_->stream_texture_matrix()); + transform.Scale(tex_width_scale, tex_height_scale); + scoped_ptr<StreamVideoDrawQuad> stream_video_quad = + StreamVideoDrawQuad::Create(); + stream_video_quad->SetNew(shared_quad_state, + quad_rect, + opaque_rect, + frame_->texture_id(), + transform); + quad_sink->Append(stream_video_quad.PassAs<DrawQuad>(), + append_quads_data); + break; + } + default: + // Someone updated ConvertVFCFormatToGLenum() above but update this! + NOTREACHED(); + break; + } +} + +void VideoLayerImpl::DidDraw(ResourceProvider* resource_provider) { + LayerImpl::DidDraw(resource_provider); + + if (!frame_) + return; + + if (format_ == GL_TEXTURE_2D) { + DCHECK(external_texture_resource_); + // TODO: the following assert will not be true when sending resources to a + // parent compositor. We will probably need to hold on to frame_ for + // longer, and have several "current frames" in the pipeline. + DCHECK(!resource_provider->InUseByConsumer(external_texture_resource_)); + resource_provider->DeleteResource(external_texture_resource_); + external_texture_resource_ = 0; + } + + provider_client_impl_->PutCurrentFrame(frame_); + frame_ = NULL; + + provider_client_impl_->ReleaseLock(); +} + +static gfx::Size VideoFrameDimension(media::VideoFrame* frame, int plane) { + gfx::Size dimensions = frame->coded_size(); + switch (frame->format()) { + case media::VideoFrame::YV12: + if (plane != media::VideoFrame::kYPlane) { + dimensions.set_width(dimensions.width() / 2); + dimensions.set_height(dimensions.height() / 2); + } + break; + case media::VideoFrame::YV16: + if (plane != media::VideoFrame::kYPlane) + dimensions.set_width(dimensions.width() / 2); + break; + default: + break; + } + return dimensions; +} + +bool VideoLayerImpl::FramePlane::AllocateData( + ResourceProvider* resource_provider) { + if (resource_id) + return true; + + resource_id = resource_provider->CreateResource( + size, format, ResourceProvider::TextureUsageAny); + return resource_id; +} + +void VideoLayerImpl::FramePlane::FreeData(ResourceProvider* resource_provider) { + if (!resource_id) + return; + + resource_provider->DeleteResource(resource_id); + resource_id = 0; +} + +bool VideoLayerImpl::AllocatePlaneData(ResourceProvider* resource_provider) { + int max_texture_size = resource_provider->max_texture_size(); + size_t plane_count = NumPlanes(); + for (size_t plane_index = 0; plane_index < plane_count; ++plane_index) { + VideoLayerImpl::FramePlane* plane = &frame_planes_[plane_index]; + + gfx::Size required_texture_size = VideoFrameDimension(frame_, plane_index); + // TODO: Remove the test against max_texture_size when tiled layers are + // implemented. + if (required_texture_size.IsEmpty() || + required_texture_size.width() > max_texture_size || + required_texture_size.height() > max_texture_size) + return false; + + if (plane->size != required_texture_size || plane->format != format_) { + plane->FreeData(resource_provider); + plane->size = required_texture_size; + plane->format = format_; + } + + if (!plane->AllocateData(resource_provider)) + return false; + } + return true; +} + +bool VideoLayerImpl::CopyPlaneData(ResourceProvider* resource_provider) { + size_t plane_count = NumPlanes(); + if (!plane_count) + return true; + + if (convert_yuv_) { + if (!video_renderer_) + video_renderer_.reset(new media::SkCanvasVideoRenderer); + const VideoLayerImpl::FramePlane& plane = + frame_planes_[media::VideoFrame::kRGBPlane]; + ResourceProvider::ScopedWriteLockSoftware lock(resource_provider, + plane.resource_id); + video_renderer_->Paint(frame_, + lock.sk_canvas(), + frame_->visible_rect(), + 0xff); + return true; + } + + for (size_t plane_index = 0; plane_index < plane_count; ++plane_index) { + const VideoLayerImpl::FramePlane& plane = frame_planes_[plane_index]; + // Only non-FormatNativeTexture planes should need upload. + DCHECK_EQ(plane.format, GL_LUMINANCE); + const uint8_t* software_plane_pixels = frame_->data(plane_index); + gfx::Rect image_rect(0, + 0, + frame_->stride(plane_index), + plane.size.height()); + gfx::Rect source_rect(plane.size); + resource_provider->SetPixels(plane.resource_id, + software_plane_pixels, + image_rect, + source_rect, + gfx::Vector2d()); + } + return true; +} + +void VideoLayerImpl::FreePlaneData(ResourceProvider* resource_provider) { + for (size_t i = 0; i < media::VideoFrame::kMaxPlanes; ++i) + frame_planes_[i].FreeData(resource_provider); +} + +void VideoLayerImpl::FreeUnusedPlaneData(ResourceProvider* resource_provider) { + size_t first_unused_plane = NumPlanes(); + for (size_t i = first_unused_plane; i < media::VideoFrame::kMaxPlanes; ++i) + frame_planes_[i].FreeData(resource_provider); +} + +void VideoLayerImpl::DidLoseOutputSurface() { + FreePlaneData(layer_tree_impl()->resource_provider()); +} + +void VideoLayerImpl::SetNeedsRedraw() { + layer_tree_impl()->SetNeedsRedraw(); +} + +void VideoLayerImpl::SetProviderClientImpl( + scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl) { + provider_client_impl_ = provider_client_impl; +} + +const char* VideoLayerImpl::LayerTypeAsString() const { + return "VideoLayer"; +} + +} // namespace cc diff --git a/cc/layers/video_layer_impl.h b/cc/layers/video_layer_impl.h new file mode 100644 index 0000000..b7969aa --- /dev/null +++ b/cc/layers/video_layer_impl.h @@ -0,0 +1,88 @@ +// Copyright 2012 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 CC_LAYERS_VIDEO_LAYER_IMPL_H_ +#define CC_LAYERS_VIDEO_LAYER_IMPL_H_ + +#include "base/callback.h" +#include "base/synchronization/lock.h" +#include "cc/base/cc_export.h" +#include "cc/layers/layer_impl.h" +#include "cc/layers/video_frame_provider.h" +#include "media/base/video_frame.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "ui/gfx/size.h" +#include "ui/gfx/transform.h" + +namespace media { +class SkCanvasVideoRenderer; +} + +namespace cc { +class LayerTreeHostImpl; +class VideoFrameProviderClientImpl; + +class CC_EXPORT VideoLayerImpl : public LayerImpl { + public: + static scoped_ptr<VideoLayerImpl> Create(LayerTreeImpl* tree_impl, + int id, + VideoFrameProvider* provider); + virtual ~VideoLayerImpl(); + + // LayerImpl implementation. + virtual scoped_ptr<LayerImpl> CreateLayerImpl(LayerTreeImpl* tree_impl) + OVERRIDE; + virtual void PushPropertiesTo(LayerImpl* layer) OVERRIDE; + virtual void WillDraw(ResourceProvider* resource_provider) OVERRIDE; + virtual void AppendQuads(QuadSink* quad_sink, + AppendQuadsData* append_quads_data) OVERRIDE; + virtual void DidDraw(ResourceProvider* resource_provider) OVERRIDE; + virtual void DidBecomeActive() OVERRIDE; + virtual void DidLoseOutputSurface() OVERRIDE; + + void SetNeedsRedraw(); + + void SetProviderClientImpl( + scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl); + + struct FramePlane { + ResourceProvider::ResourceId resource_id; + gfx::Size size; + GLenum format; + + FramePlane() : resource_id(0), format(GL_LUMINANCE) {} + + bool AllocateData(ResourceProvider* resource_provider); + void FreeData(ResourceProvider* resource_provider); + }; + + private: + VideoLayerImpl(LayerTreeImpl* tree_impl, int id); + + virtual const char* LayerTypeAsString() const OVERRIDE; + + void WillDrawInternal(ResourceProvider* resource_provider); + bool AllocatePlaneData(ResourceProvider* resource_provider); + bool CopyPlaneData(ResourceProvider* resource_provider); + void FreePlaneData(ResourceProvider* resource_provider); + void FreeUnusedPlaneData(ResourceProvider* resource_provider); + size_t NumPlanes() const; + + scoped_refptr<VideoFrameProviderClientImpl> provider_client_impl_; + + media::VideoFrame* frame_; + GLenum format_; + bool convert_yuv_; + ResourceProvider::ResourceId external_texture_resource_; + scoped_ptr<media::SkCanvasVideoRenderer> video_renderer_; + + // Each index in this array corresponds to a plane in media::VideoFrame. + FramePlane frame_planes_[media::VideoFrame::kMaxPlanes]; + + DISALLOW_COPY_AND_ASSIGN(VideoLayerImpl); +}; + +} // namespace cc + +#endif // CC_LAYERS_VIDEO_LAYER_IMPL_H_ |