diff options
Diffstat (limited to 'chromium/cc/layers/tiled_layer_unittest.cc')
-rw-r--r-- | chromium/cc/layers/tiled_layer_unittest.cc | 1942 |
1 files changed, 1942 insertions, 0 deletions
diff --git a/chromium/cc/layers/tiled_layer_unittest.cc b/chromium/cc/layers/tiled_layer_unittest.cc new file mode 100644 index 00000000000..a2f0ca7a64d --- /dev/null +++ b/chromium/cc/layers/tiled_layer_unittest.cc @@ -0,0 +1,1942 @@ +// 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 <limits> +#include <vector> + +#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/fake_rendering_stats_instrumentation.h" +#include "cc/test/geometry_test_utils.h" +#include "cc/test/tiled_layer_test_common.h" +#include "cc/trees/single_thread_proxy.h" +#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* render_target) { + stack_.back().target = render_target; + } + + void SetOcclusion(const Region& occlusion) { + stack_.back().occlusion_from_inside_target = occlusion; + } +}; + +class TiledLayerTest : public testing::Test { + public: + TiledLayerTest() + : proxy_(NULL), + output_surface_(CreateFakeOutputSurface()), + queue_(make_scoped_ptr(new ResourceUpdateQueue)), + fake_layer_impl_tree_host_client_(FakeLayerTreeHostClient::DIRECT_3D), + occlusion_(NULL) { + settings_.max_partial_texture_updates = std::numeric_limits<size_t>::max(); + settings_.layer_transforms_should_scale_layer_contents = true; + } + + virtual void SetUp() { + layer_tree_host_ = LayerTreeHost::Create(&fake_layer_impl_tree_host_client_, + settings_, + NULL); + proxy_ = layer_tree_host_->proxy(); + resource_manager_ = PrioritizedResourceManager::Create(proxy_); + layer_tree_host_->InitializeOutputSurfaceIfNeeded(); + layer_tree_host_->SetRootLayer(Layer::Create()); + + DebugScopedSetImplThreadAndMainThreadBlocked + impl_thread_and_main_thread_blocked(proxy_); + resource_provider_ = ResourceProvider::Create(output_surface_.get(), 0); + host_impl_ = make_scoped_ptr(new FakeLayerTreeHostImpl(proxy_)); + } + + virtual ~TiledLayerTest() { + ResourceManagerClearAllMemory(resource_manager_.get(), + resource_provider_.get()); + + DebugScopedSetImplThreadAndMainThreadBlocked + impl_thread_and_main_thread_blocked(proxy_); + resource_provider_.reset(); + host_impl_.reset(); + } + + void ResourceManagerClearAllMemory( + PrioritizedResourceManager* resource_manager, + ResourceProvider* resource_provider) { + { + DebugScopedSetImplThreadAndMainThreadBlocked + impl_thread_and_main_thread_blocked(proxy_); + resource_manager->ClearAllMemory(resource_provider); + resource_manager->ReduceMemory(resource_provider); + } + resource_manager->UnlinkAndClearEvictedBackings(); + } + + void UpdateTextures() { + DebugScopedSetImplThreadAndMainThreadBlocked + impl_thread_and_main_thread_blocked(proxy_); + DCHECK(queue_); + scoped_ptr<ResourceUpdateController> update_controller = + ResourceUpdateController::Create(NULL, + proxy_->ImplThreadTaskRunner(), + queue_.Pass(), + resource_provider_.get()); + update_controller->Finalize(); + queue_ = make_scoped_ptr(new ResourceUpdateQueue); + } + + void LayerPushPropertiesTo(FakeTiledLayer* layer, + FakeTiledLayerImpl* layer_impl) { + DebugScopedSetImplThreadAndMainThreadBlocked + impl_thread_and_main_thread_blocked(proxy_); + layer->PushPropertiesTo(layer_impl); + layer->ResetNumDependentsNeedPushProperties(); + } + + void LayerUpdate(FakeTiledLayer* layer, TestOcclusionTracker* occluded) { + DebugScopedSetMainThread main_thread(proxy_); + layer->Update(queue_.get(), occluded); + } + + void CalcDrawProps(RenderSurfaceLayerList* render_surface_layer_list) { + if (occlusion_) + occlusion_->SetRenderTarget(layer_tree_host_->root_layer()); + + LayerTreeHostCommon::CalcDrawPropsMainInputsForTesting inputs( + layer_tree_host_->root_layer(), + layer_tree_host_->device_viewport_size(), + render_surface_layer_list); + inputs.device_scale_factor = layer_tree_host_->device_scale_factor(); + inputs.max_texture_size = + layer_tree_host_->GetRendererCapabilities().max_texture_size; + inputs.can_adjust_raster_scales = true; + LayerTreeHostCommon::CalculateDrawProperties(&inputs); + } + + bool UpdateAndPush(const scoped_refptr<FakeTiledLayer>& layer1, + const scoped_ptr<FakeTiledLayerImpl>& layer_impl1) { + scoped_refptr<FakeTiledLayer> layer2; + scoped_ptr<FakeTiledLayerImpl> layer_impl2; + return UpdateAndPush(layer1, layer_impl1, layer2, layer_impl2); + } + + bool UpdateAndPush(const scoped_refptr<FakeTiledLayer>& layer1, + const scoped_ptr<FakeTiledLayerImpl>& layer_impl1, + const scoped_refptr<FakeTiledLayer>& layer2, + const scoped_ptr<FakeTiledLayerImpl>& layer_impl2) { + // Get textures + resource_manager_->ClearPriorities(); + if (layer1.get()) + layer1->SetTexturePriorities(priority_calculator_); + if (layer2.get()) + layer2->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + + // Save paint properties + if (layer1.get()) + layer1->SavePaintProperties(); + if (layer2.get()) + layer2->SavePaintProperties(); + + // Update content + if (layer1.get()) + layer1->Update(queue_.get(), occlusion_); + if (layer2.get()) + layer2->Update(queue_.get(), occlusion_); + + bool needs_update = false; + if (layer1.get()) + needs_update |= layer1->NeedsIdlePaint(); + if (layer2.get()) + needs_update |= layer2->NeedsIdlePaint(); + + // Update textures and push. + UpdateTextures(); + if (layer1.get()) + LayerPushPropertiesTo(layer1.get(), layer_impl1.get()); + if (layer2.get()) + LayerPushPropertiesTo(layer2.get(), layer_impl2.get()); + + return needs_update; + } + + public: + Proxy* proxy_; + LayerTreeSettings settings_; + scoped_ptr<OutputSurface> output_surface_; + scoped_ptr<ResourceProvider> resource_provider_; + scoped_ptr<ResourceUpdateQueue> queue_; + PriorityCalculator priority_calculator_; + FakeLayerTreeHostClient fake_layer_impl_tree_host_client_; + scoped_ptr<LayerTreeHost> layer_tree_host_; + scoped_ptr<FakeLayerTreeHostImpl> host_impl_; + scoped_ptr<PrioritizedResourceManager> resource_manager_; + TestOcclusionTracker* occlusion_; +}; + +TEST_F(TiledLayerTest, PushDirtyTiles) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + CalcDrawProps(&render_surface_layer_list); + UpdateAndPush(layer, layer_impl); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl->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, layer_impl); + + // We should only have the first tile since the other tile was invalidated but + // not painted. + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, PushOccludedDirtyTiles) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + TestOcclusionTracker occluded; + occlusion_ = &occluded; + layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000)); + + layer_tree_host_->root_layer()->AddChild(layer); + + { + RenderSurfaceLayerList render_surface_layer_list; + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + CalcDrawProps(&render_surface_layer_list); + UpdateAndPush(layer, layer_impl); + + 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(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1)); + } + + { + RenderSurfaceLayerList render_surface_layer_list; + + // 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(&render_surface_layer_list); + UpdateAndPush(layer, layer_impl); + + 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(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1)); + } +} + +TEST_F(TiledLayerTest, PushDeletedTiles) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + CalcDrawProps(&render_surface_layer_list); + UpdateAndPush(layer, layer_impl); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1)); + + resource_manager_->ClearPriorities(); + ResourceManagerClearAllMemory(resource_manager_.get(), + resource_provider_.get()); + resource_manager_->SetMaxMemoryLimitBytes(4 * 1024 * 1024); + + // This should drop the tiles on the impl thread. + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + + // We should now have no textures on the impl thread. + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layer_impl->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, layer_impl); + + // We should have one tiles on the impl side. + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, PushIdlePaintTiles) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // 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(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = gfx::Rect(200, 200, 100, 100); + bool needs_update = UpdateAndPush(layer, layer_impl); + // We should need idle-painting for surrounding tiles. + EXPECT_TRUE(needs_update); + + // We should have one tile on the impl side. + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(2, 2)); + + // For the next four updates, we should detect we still need idle painting. + for (int i = 0; i < 4; i++) { + needs_update = UpdateAndPush(layer, layer_impl); + EXPECT_TRUE(needs_update); + } + + // We should always finish painting eventually. + for (int i = 0; i < 20; i++) + needs_update = UpdateAndPush(layer, layer_impl); + + // 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(layer_impl->HasResourceIdForTileAt(i, j)); + } + + EXPECT_FALSE(needs_update); +} + +TEST_F(TiledLayerTest, PredictivePainting) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + + layer_tree_host_->root_layer()->AddChild(layer); + + // 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 pushed_visible_tiles[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 pushed_prepaint_tiles[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 visible_rect = gfx::Rect(200, 200, 100, 100); + gfx::Rect previous_visible_rect = + gfx::Rect(visible_rect.origin() + directions[k], visible_rect.size()); + gfx::Rect next_visible_rect = + gfx::Rect(visible_rect.origin() - directions[k], visible_rect.size()); + + // Setup. Use the previous_visible_rect to setup the prediction for next + // frame. + layer->SetBounds(bounds); + + RenderSurfaceLayerList render_surface_layer_list; + CalcDrawProps(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = previous_visible_rect; + bool needs_update = UpdateAndPush(layer, layer_impl); + + // Invalidate and move the visible_rect in the scroll direction. + // Check that the correct tiles have been painted in the visible pass. + layer->SetNeedsDisplay(); + layer->draw_properties().visible_content_rect = visible_rect; + needs_update = UpdateAndPush(layer, layer_impl); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) + EXPECT_EQ(layer_impl->HasResourceIdForTileAt(i, j), + pushed_visible_tiles[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 = next_visible_rect; + needs_update = UpdateAndPush(layer, layer_impl); + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) + EXPECT_EQ(layer_impl->HasResourceIdForTileAt(i, j), + pushed_prepaint_tiles[k].Contains(i, j)); + } + } + + // We should always finish painting eventually. + for (int i = 0; i < 20; i++) + needs_update = UpdateAndPush(layer, layer_impl); + EXPECT_FALSE(needs_update); + } +} + +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. + resource_manager_->SetMaxMemoryLimitBytes(2 * 1024 * 1024); + scoped_refptr<FakeTiledLayer> layer1 = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl1 = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + scoped_refptr<FakeTiledLayer> layer2 = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl2 = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 2)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer1); + layer_tree_host_->root_layer()->AddChild(layer2); + + // 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 layer1_rect(0, 0, 100, 2400); + + // This requires 4*30000 bytes of memory. + gfx::Rect layer2_rect(0, 0, 100, 300); + + // Paint a single tile in layer2 so that it will idle paint. + layer1->SetBounds(layer1_rect.size()); + layer2->SetBounds(layer2_rect.size()); + CalcDrawProps(&render_surface_layer_list); + layer1->draw_properties().visible_content_rect = layer1_rect; + layer2->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100); + bool needs_update = UpdateAndPush(layer1, layer_impl1, layer2, layer_impl2); + // We should need idle-painting for both remaining tiles in layer2. + EXPECT_TRUE(needs_update); + + // Reduce our memory limits to 1mb. + resource_manager_->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++) { + needs_update = UpdateAndPush(layer1, layer_impl1, layer2, layer_impl2); + } + + // Sanity check, we should have textures for the big layer. + EXPECT_TRUE(layer_impl1->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl1->HasResourceIdForTileAt(0, 23)); + + // We should only have the first two tiles from layer2 since + // it failed to idle update the last tile. + EXPECT_TRUE(layer_impl2->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl2->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl2->HasResourceIdForTileAt(0, 1)); + EXPECT_TRUE(layer_impl2->HasResourceIdForTileAt(0, 1)); + + EXPECT_FALSE(needs_update); + EXPECT_FALSE(layer_impl2->HasResourceIdForTileAt(0, 2)); +} + +TEST_F(TiledLayerTest, PushIdlePaintedOccludedTiles) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + TestOcclusionTracker occluded; + occlusion_ = &occluded; + + layer_tree_host_->root_layer()->AddChild(layer); + + // 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(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100); + UpdateAndPush(layer, layer_impl); + + // We should have the prepainted tile on the impl side, but culled it during + // paint. + EXPECT_TRUE(layer_impl->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(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // 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->fake_layer_updater()->SetRectToInvalidate( + gfx::Rect(0, 50, 100, 50), layer.get()); + layer->SetBounds(gfx::Size(100, 200)); + CalcDrawProps(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + UpdateAndPush(layer, layer_impl); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, PushTilesLayerMarkedDirtyDuringPaintOnNextLayer) { + scoped_refptr<FakeTiledLayer> layer1 = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_refptr<FakeTiledLayer> layer2 = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer1_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + scoped_ptr<FakeTiledLayerImpl> layer2_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 2)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer1); + layer_tree_host_->root_layer()->AddChild(layer2); + + // Invalidate a tile on layer1, during update of layer 2. + layer2->fake_layer_updater()->SetRectToInvalidate( + gfx::Rect(0, 50, 100, 50), layer1.get()); + layer1->SetBounds(gfx::Size(100, 200)); + layer2->SetBounds(gfx::Size(100, 200)); + CalcDrawProps(&render_surface_layer_list); + 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, layer1_impl, layer2, layer2_impl); + + // We should have both tiles on the impl side for all layers. + EXPECT_TRUE(layer1_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer1_impl->HasResourceIdForTileAt(0, 1)); + EXPECT_TRUE(layer2_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer2_impl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, PushTilesLayerMarkedDirtyDuringPaintOnPreviousLayer) { + scoped_refptr<FakeTiledLayer> layer1 = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_refptr<FakeTiledLayer> layer2 = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer1_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + scoped_ptr<FakeTiledLayerImpl> layer2_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 2)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer1); + layer_tree_host_->root_layer()->AddChild(layer2); + + layer1->fake_layer_updater()->SetRectToInvalidate( + gfx::Rect(0, 50, 100, 50), layer2.get()); + layer1->SetBounds(gfx::Size(100, 200)); + layer2->SetBounds(gfx::Size(100, 200)); + CalcDrawProps(&render_surface_layer_list); + 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, layer1_impl, layer2, layer2_impl); + + // We should have both tiles on the impl side for all layers. + EXPECT_TRUE(layer1_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer1_impl->HasResourceIdForTileAt(0, 1)); + EXPECT_TRUE(layer2_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer2_impl->HasResourceIdForTileAt(0, 1)); +} + +TEST_F(TiledLayerTest, PaintSmallAnimatedLayersImmediately) { + // Create a LayerTreeHost that has the right viewportsize, + // so the layer is considered small enough. + bool run_out_of_memory[2] = { false, true }; + for (int i = 0; i < 2; i++) { + // Create a layer with 5x5 tiles, with 4x4 size viewport. + int viewport_width = 4 * FakeTiledLayer::tile_size().width(); + int viewport_height = 4 * FakeTiledLayer::tile_size().width(); + int layer_width = 5 * FakeTiledLayer::tile_size().width(); + int layer_height = 5 * FakeTiledLayer::tile_size().height(); + int memory_for_layer = layer_width * layer_height * 4; + layer_tree_host_->SetViewportSize( + gfx::Size(viewport_width, viewport_height)); + + // Use 10x5 tiles to run out of memory. + if (run_out_of_memory[i]) + layer_width *= 2; + + resource_manager_->SetMaxMemoryLimitBytes(memory_for_layer); + + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // Full size layer with half being visible. + layer->SetBounds(gfx::Size(layer_width, layer_height)); + gfx::Rect visible_rect(0, 0, layer_width / 2, layer_height); + CalcDrawProps(&render_surface_layer_list); + + // Pretend the layer is animating. + layer->draw_properties().target_space_transform_is_animating = true; + layer->draw_properties().visible_content_rect = visible_rect; + 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(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), NULL); + UpdateTextures(); + LayerPushPropertiesTo(layer.get(), layer_impl.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 (!run_out_of_memory[i]) { + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 5; ++j) + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(i, j)); + } + } else { + for (int i = 0; i < 10; ++i) { + for (int j = 0; j < 5; ++j) + EXPECT_EQ(layer_impl->HasResourceIdForTileAt(i, j), i < 5); + } + } + + layer->RemoveFromParent(); + } +} + +TEST_F(TiledLayerTest, IdlePaintOutOfMemory) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // We have enough memory for only the visible rect, so we will run out of + // memory in first idle paint. + int memory_limit = 4 * 100 * 100; // 1 tiles, 4 bytes per pixel. + resource_manager_->SetMaxMemoryLimitBytes(memory_limit); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + bool needs_update = false; + layer->SetBounds(gfx::Size(300, 300)); + CalcDrawProps(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = gfx::Rect(100, 100, 100, 100); + for (int i = 0; i < 2; i++) + needs_update = UpdateAndPush(layer, layer_impl); + + // Idle-painting should see no more priority tiles for painting. + EXPECT_FALSE(needs_update); + + // We should have one tile on the impl side. + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(1, 1)); +} + +TEST_F(TiledLayerTest, IdlePaintZeroSizedLayer) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + + layer_tree_host_->root_layer()->AddChild(layer); + + 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()); + + RenderSurfaceLayerList render_surface_layer_list; + CalcDrawProps(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = gfx::Rect(); + bool needs_update = UpdateAndPush(layer, layer_impl); + + // Empty layers don't have tiles. + EXPECT_EQ(0u, layer->NumPaintedTiles()); + + // Empty layers don't need prepaint. + EXPECT_FALSE(needs_update); + + // Empty layers don't have tiles. + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 0)); + } +} + +TEST_F(TiledLayerTest, IdlePaintNonVisibleLayers) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->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 visible_rect[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 have_tile[10] = { false, false, true, true, false, false, true, true, + true, true }; + + layer_tree_host_->root_layer()->AddChild(layer); + + for (int i = 0; i < 10; i++) { + layer->SetBounds(gfx::Size(100, 100)); + + RenderSurfaceLayerList render_surface_layer_list; + CalcDrawProps(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = visible_rect[i]; + + if (invalidate[i]) + layer->InvalidateContentRect(gfx::Rect(0, 0, 100, 100)); + bool needs_update = UpdateAndPush(layer, layer_impl); + + // We should never signal idle paint, as we painted the entire layer + // or the layer was not visible. + EXPECT_FALSE(needs_update); + EXPECT_EQ(layer_impl->HasResourceIdForTileAt(0, 0), have_tile[i]); + } +} + +TEST_F(TiledLayerTest, InvalidateFromPrepare) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + CalcDrawProps(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 200); + UpdateAndPush(layer, layer_impl); + + // We should have both tiles on the impl side. + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1)); + + layer->fake_layer_updater()->ClearPrepareCount(); + // Invoke update again. As the layer is valid update shouldn't be invoked on + // the LayerUpdater. + UpdateAndPush(layer, layer_impl); + EXPECT_EQ(0, layer->fake_layer_updater()->prepare_count()); + + // SetRectToInvalidate triggers InvalidateContentRect() being invoked from + // update. + layer->fake_layer_updater()->SetRectToInvalidate( + gfx::Rect(25, 25, 50, 50), layer.get()); + layer->fake_layer_updater()->ClearPrepareCount(); + layer->InvalidateContentRect(gfx::Rect(0, 0, 50, 50)); + UpdateAndPush(layer, layer_impl); + EXPECT_EQ(1, layer->fake_layer_updater()->prepare_count()); + layer->fake_layer_updater()->ClearPrepareCount(); + + // The layer should still be invalid as update invoked invalidate. + UpdateAndPush(layer, layer_impl); // visible + EXPECT_EQ(1, layer->fake_layer_updater()->prepare_count()); +} + +TEST_F(TiledLayerTest, VerifyUpdateRectWhenContentBoundsAreScaled) { + // The update rect (that indicates what was actually painted) should be in + // layer space, not the content space. + scoped_refptr<FakeTiledLayerWithScaledBounds> layer = make_scoped_refptr( + new FakeTiledLayerWithScaledBounds(resource_manager_.get())); + + layer_tree_host_->root_layer()->AddChild(layer); + + gfx::Rect layer_bounds(0, 0, 300, 200); + gfx::Rect content_bounds(0, 0, 200, 250); + + layer->SetBounds(layer_bounds.size()); + layer->SetContentBounds(content_bounds.size()); + layer->draw_properties().visible_content_rect = content_bounds; + + // On first update, the update_rect includes all tiles, even beyond the + // boundaries of the layer. + // However, it should still be in layer space, not content space. + layer->InvalidateContentRect(content_bounds); + + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), NULL); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 300, 300 * 0.8), layer->update_rect()); + UpdateTextures(); + + // After the tiles are updated once, another invalidate only needs to update + // the bounds of the layer. + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->InvalidateContentRect(content_bounds); + layer->SavePaintProperties(); + layer->Update(queue_.get(), NULL); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(layer_bounds), layer->update_rect()); + UpdateTextures(); + + // Partial re-paint should also be represented by the update rect in layer + // space, not content space. + gfx::Rect partial_damage(30, 100, 10, 10); + layer->InvalidateContentRect(partial_damage); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), NULL); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(45, 80, 15, 8), layer->update_rect()); +} + +TEST_F(TiledLayerTest, VerifyInvalidationWhenContentsScaleChanges) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // Create a layer with one tile. + layer->SetBounds(gfx::Size(100, 100)); + CalcDrawProps(&render_surface_layer_list); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 100, 100); + layer->Update(queue_.get(), NULL); + UpdateTextures(); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(0, 0, 100, 100), + layer->last_needs_display_rect()); + + // Push the tiles to the impl side and check that there is exactly one. + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), NULL); + UpdateTextures(); + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1)); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(1, 0)); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(1, 1)); + + layer->SetNeedsDisplayRect(gfx::Rect()); + EXPECT_FLOAT_RECT_EQ(gfx::RectF(), layer->last_needs_display_rect()); + + // Change the contents scale. + layer->UpdateContentsScale(2.f); + layer->draw_properties().visible_content_rect = gfx::Rect(0, 0, 200, 200); + + // The impl side should get 2x2 tiles now. + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), NULL); + UpdateTextures(); + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(0, 1)); + EXPECT_TRUE(layer_impl->HasResourceIdForTileAt(1, 0)); + EXPECT_TRUE(layer_impl->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->last_needs_display_rect()); + + // Invalidate the entire layer again, but do not paint. All tiles should be + // gone now from the impl side. + layer->SetNeedsDisplay(); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 0)); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(0, 1)); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(1, 0)); + EXPECT_FALSE(layer_impl->HasResourceIdForTileAt(1, 1)); +} + +TEST_F(TiledLayerTest, SkipsDrawGetsReset) { + // Create two 300 x 300 tiled layers. + gfx::Size content_bounds(300, 300); + gfx::Rect content_rect(content_bounds); + + // We have enough memory for only one of the two layers. + int memory_limit = 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> child_layer = make_scoped_refptr( + new FakeTiledLayer(layer_tree_host_->contents_texture_manager())); + root_layer->AddChild(child_layer); + + root_layer->SetBounds(content_bounds); + root_layer->draw_properties().visible_content_rect = content_rect; + root_layer->SetPosition(gfx::PointF(0, 0)); + child_layer->SetBounds(content_bounds); + child_layer->draw_properties().visible_content_rect = content_rect; + child_layer->SetPosition(gfx::PointF(0, 0)); + root_layer->InvalidateContentRect(content_rect); + child_layer->InvalidateContentRect(content_rect); + + layer_tree_host_->SetRootLayer(root_layer); + layer_tree_host_->SetViewportSize(gfx::Size(300, 300)); + + layer_tree_host_->UpdateLayers(queue_.get(), memory_limit); + + // We'll skip the root layer. + EXPECT_TRUE(root_layer->SkipsDraw()); + EXPECT_FALSE(child_layer->SkipsDraw()); + + layer_tree_host_->CommitComplete(); + + // Remove the child layer. + root_layer->RemoveAllChildren(); + + layer_tree_host_->UpdateLayers(queue_.get(), memory_limit); + EXPECT_FALSE(root_layer->SkipsDraw()); + + ResourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(), + resource_provider_.get()); + layer_tree_host_->SetRootLayer(NULL); +} + +TEST_F(TiledLayerTest, ResizeToSmaller) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + + layer_tree_host_->root_layer()->AddChild(layer); + + 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(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), 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(resource_manager_.get())); + + layer_tree_host_->root_layer()->AddChild(layer); + + 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(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), NULL); +} + +class TiledLayerPartialUpdateTest : public TiledLayerTest { + public: + TiledLayerPartialUpdateTest() { settings_.max_partial_texture_updates = 4; } +}; + +TEST_F(TiledLayerPartialUpdateTest, PartialUpdates) { + // Create one 300 x 200 tiled layer with 3 x 2 tiles. + gfx::Size content_bounds(300, 200); + gfx::Rect content_rect(content_bounds); + + scoped_refptr<FakeTiledLayer> layer = make_scoped_refptr( + new FakeTiledLayer(layer_tree_host_->contents_texture_manager())); + layer->SetBounds(content_bounds); + layer->SetPosition(gfx::PointF(0, 0)); + layer->draw_properties().visible_content_rect = content_rect; + layer->InvalidateContentRect(content_rect); + + layer_tree_host_->SetRootLayer(layer); + layer_tree_host_->SetViewportSize(gfx::Size(300, 200)); + + // Full update of all 6 tiles. + layer_tree_host_->UpdateLayers(queue_.get(), + std::numeric_limits<size_t>::max()); + { + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + EXPECT_EQ(6u, queue_->FullUploadSize()); + EXPECT_EQ(0u, queue_->PartialUploadSize()); + UpdateTextures(); + EXPECT_EQ(6, layer->fake_layer_updater()->update_count()); + EXPECT_FALSE(queue_->HasMoreUpdates()); + layer->fake_layer_updater()->ClearUpdateCount(); + LayerPushPropertiesTo(layer.get(), layer_impl.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(queue_.get(), + std::numeric_limits<size_t>::max()); + { + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + EXPECT_EQ(3u, queue_->FullUploadSize()); + EXPECT_EQ(3u, queue_->PartialUploadSize()); + UpdateTextures(); + EXPECT_EQ(6, layer->fake_layer_updater()->update_count()); + EXPECT_FALSE(queue_->HasMoreUpdates()); + layer->fake_layer_updater()->ClearUpdateCount(); + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + } + layer_tree_host_->CommitComplete(); + + // Partial update of 6 tiles. + layer->InvalidateContentRect(gfx::Rect(50, 50, 200, 100)); + { + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + layer_tree_host_->UpdateLayers(queue_.get(), + std::numeric_limits<size_t>::max()); + EXPECT_EQ(2u, queue_->FullUploadSize()); + EXPECT_EQ(4u, queue_->PartialUploadSize()); + UpdateTextures(); + EXPECT_EQ(6, layer->fake_layer_updater()->update_count()); + EXPECT_FALSE(queue_->HasMoreUpdates()); + layer->fake_layer_updater()->ClearUpdateCount(); + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + } + layer_tree_host_->CommitComplete(); + + // Checkerboard all tiles. + layer->InvalidateContentRect(gfx::Rect(0, 0, 300, 200)); + { + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + } + layer_tree_host_->CommitComplete(); + + // Partial update of 6 checkerboard tiles. + layer->InvalidateContentRect(gfx::Rect(50, 50, 200, 100)); + { + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + layer_tree_host_->UpdateLayers(queue_.get(), + std::numeric_limits<size_t>::max()); + EXPECT_EQ(6u, queue_->FullUploadSize()); + EXPECT_EQ(0u, queue_->PartialUploadSize()); + UpdateTextures(); + EXPECT_EQ(6, layer->fake_layer_updater()->update_count()); + EXPECT_FALSE(queue_->HasMoreUpdates()); + layer->fake_layer_updater()->ClearUpdateCount(); + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + } + layer_tree_host_->CommitComplete(); + + // Partial update of 4 tiles. + layer->InvalidateContentRect(gfx::Rect(50, 50, 100, 100)); + { + scoped_ptr<FakeTiledLayerImpl> layer_impl = + make_scoped_ptr(new FakeTiledLayerImpl(host_impl_->active_tree(), 1)); + layer_tree_host_->UpdateLayers(queue_.get(), + std::numeric_limits<size_t>::max()); + EXPECT_EQ(0u, queue_->FullUploadSize()); + EXPECT_EQ(4u, queue_->PartialUploadSize()); + UpdateTextures(); + EXPECT_EQ(4, layer->fake_layer_updater()->update_count()); + EXPECT_FALSE(queue_->HasMoreUpdates()); + layer->fake_layer_updater()->ClearUpdateCount(); + LayerPushPropertiesTo(layer.get(), layer_impl.get()); + } + layer_tree_host_->CommitComplete(); + + ResourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(), + resource_provider_.get()); + layer_tree_host_->SetRootLayer(NULL); +} + +TEST_F(TiledLayerTest, TilesPaintedWithoutOcclusion) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + RenderSurfaceLayerList render_surface_layer_list; + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100, so this invalidates and then paints two tiles. + layer->SetBounds(gfx::Size(100, 200)); + CalcDrawProps(&render_surface_layer_list); + + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), NULL); + EXPECT_EQ(2, layer->fake_layer_updater()->update_count()); +} + +TEST_F(TiledLayerTest, TilesPaintedWithOcclusion) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + RenderSurfaceLayerList render_surface_layer_list; + TestOcclusionTracker occluded; + occlusion_ = &occluded; + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100. + + layer_tree_host_->SetViewportSize(gfx::Size(600, 600)); + layer->SetBounds(gfx::Size(600, 600)); + CalcDrawProps(&render_surface_layer_list); + + occluded.SetOcclusion(gfx::Rect(200, 200, 300, 100)); + layer->draw_properties().drawable_content_rect = + gfx::Rect(layer->content_bounds()); + layer->draw_properties().visible_content_rect = + gfx::Rect(layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(36 - 3, layer->fake_layer_updater()->update_count()); + + 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->fake_layer_updater()->ClearUpdateCount(); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + + occluded.SetOcclusion(gfx::Rect(250, 200, 300, 100)); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(36 - 2, layer->fake_layer_updater()->update_count()); + + 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->fake_layer_updater()->ClearUpdateCount(); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + + occluded.SetOcclusion(gfx::Rect(250, 250, 300, 100)); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(36, layer->fake_layer_updater()->update_count()); + + 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(resource_manager_.get())); + RenderSurfaceLayerList render_surface_layer_list; + TestOcclusionTracker occluded; + occlusion_ = &occluded; + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100. + + layer_tree_host_->SetViewportSize(gfx::Size(600, 600)); + layer->SetBounds(gfx::Size(600, 600)); + CalcDrawProps(&render_surface_layer_list); + + // 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(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(24 - 3, layer->fake_layer_updater()->update_count()); + + 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->fake_layer_updater()->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(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(24 - 6, layer->fake_layer_updater()->update_count()); + + 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->fake_layer_updater()->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(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(24 - 6, layer->fake_layer_updater()->update_count()); + + 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(resource_manager_.get())); + RenderSurfaceLayerList render_surface_layer_list; + TestOcclusionTracker occluded; + occlusion_ = &occluded; + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100. + + layer_tree_host_->SetViewportSize(gfx::Size(600, 600)); + layer->SetBounds(gfx::Size(600, 600)); + CalcDrawProps(&render_surface_layer_list); + + 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(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(36 - 3, layer->fake_layer_updater()->update_count()); + { 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->fake_layer_updater()->ClearUpdateCount(); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + + // Repaint without marking it dirty. The 3 culled tiles will be pre-painted + // now. + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(3, layer->fake_layer_updater()->update_count()); + + 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(resource_manager_.get())); + RenderSurfaceLayerList render_surface_layer_list; + TestOcclusionTracker occluded; + occlusion_ = &occluded; + + layer_tree_host_->root_layer()->AddChild(layer); + + // 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)); + layer->SetBounds(gfx::Size(600, 600)); + CalcDrawProps(&render_surface_layer_list); + gfx::Transform screen_transform; + screen_transform.Scale(0.5, 0.5); + layer->draw_properties().screen_space_transform = screen_transform; + layer->draw_properties().target_space_transform = screen_transform; + + occluded.SetOcclusion(gfx::Rect(100, 100, 150, 50)); + layer->draw_properties().drawable_content_rect = + gfx::Rect(layer->content_bounds()); + layer->draw_properties().visible_content_rect = + gfx::Rect(layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + EXPECT_EQ(36 - 3, layer->fake_layer_updater()->update_count()); + + 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 = + new FakeTiledLayer(resource_manager_.get()); + RenderSurfaceLayerList render_surface_layer_list; + TestOcclusionTracker occluded; + occlusion_ = &occluded; + + scoped_refptr<FakeTiledLayer> scale_layer = + new FakeTiledLayer(resource_manager_.get()); + gfx::Transform scale_transform; + scale_transform.Scale(2.0, 2.0); + scale_layer->SetTransform(scale_transform); + + layer_tree_host_->root_layer()->AddChild(scale_layer); + + // The tile size is 100x100. + + // This makes sure the painting works when the content space is scaled to + // a different layer space. + layer_tree_host_->SetViewportSize(gfx::Size(600, 600)); + layer->SetAnchorPoint(gfx::PointF()); + layer->SetBounds(gfx::Size(300, 300)); + scale_layer->AddChild(layer); + CalcDrawProps(&render_surface_layer_list); + EXPECT_FLOAT_EQ(2.f, layer->contents_scale_x()); + EXPECT_FLOAT_EQ(2.f, layer->contents_scale_y()); + EXPECT_EQ(gfx::Size(600, 600).ToString(), + layer->content_bounds().ToString()); + + // No tiles are covered by the 300x50 occlusion. + occluded.SetOcclusion(gfx::Rect(200, 200, 300, 50)); + layer->draw_properties().drawable_content_rect = + gfx::Rect(layer->bounds()); + layer->draw_properties().visible_content_rect = + gfx::Rect(layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + int visible_tiles1 = 6 * 6; + EXPECT_EQ(visible_tiles1, layer->fake_layer_updater()->update_count()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), + visible_tiles1 * 100 * 100, + 1); + EXPECT_EQ(0, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fake_layer_updater()->ClearUpdateCount(); + + // The occlusion of 300x100 will be cover 3 tiles as tiles are 100x100 still. + occluded.SetOcclusion(gfx::Rect(200, 200, 300, 100)); + layer->draw_properties().drawable_content_rect = + gfx::Rect(layer->bounds()); + layer->draw_properties().visible_content_rect = + gfx::Rect(layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + int visible_tiles2 = 6 * 6 - 3; + EXPECT_EQ(visible_tiles2, layer->fake_layer_updater()->update_count()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), + visible_tiles2 * 100 * 100 + + visible_tiles1 * 100 * 100, + 1); + EXPECT_EQ(3, occluded.overdraw_metrics()->tiles_culled_for_upload()); + + layer->fake_layer_updater()->ClearUpdateCount(); + + // This makes sure content scaling and transforms work together. + // When the tiles are scaled down by half, they are 50x50 each in the + // screen. + gfx::Transform screen_transform; + screen_transform.Scale(0.5, 0.5); + layer->draw_properties().screen_space_transform = screen_transform; + layer->draw_properties().target_space_transform = screen_transform; + + // An occlusion of 150x100 will cover 3*2 = 6 tiles. + occluded.SetOcclusion(gfx::Rect(100, 100, 150, 100)); + + gfx::Rect layer_bounds_rect(layer->bounds()); + layer->draw_properties().drawable_content_rect = + gfx::ScaleToEnclosingRect(layer_bounds_rect, 0.5f); + layer->draw_properties().visible_content_rect = + gfx::Rect(layer->content_bounds()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 600, 600)); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + int visible_tiles3 = 6 * 6 - 6; + EXPECT_EQ(visible_tiles3, layer->fake_layer_updater()->update_count()); + + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_opaque(), 0, 1); + EXPECT_NEAR(occluded.overdraw_metrics()->pixels_uploaded_translucent(), + visible_tiles3 * 100 * 100 + + visible_tiles2 * 100 * 100 + + visible_tiles1 * 100 * 100, + 1); + EXPECT_EQ(6 + 3, occluded.overdraw_metrics()->tiles_culled_for_upload()); +} + +TEST_F(TiledLayerTest, VisibleContentOpaqueRegion) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + RenderSurfaceLayerList render_surface_layer_list; + TestOcclusionTracker occluded; + occlusion_ = &occluded; + layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000)); + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100, so this invalidates and then paints two tiles in + // various ways. + + gfx::Rect opaque_paint_rect; + Region opaque_contents; + + gfx::Rect content_bounds = gfx::Rect(0, 0, 100, 200); + gfx::Rect visible_bounds = gfx::Rect(0, 0, 100, 150); + + layer->SetBounds(content_bounds.size()); + CalcDrawProps(&render_surface_layer_list); + layer->draw_properties().drawable_content_rect = visible_bounds; + layer->draw_properties().visible_content_rect = visible_bounds; + + // If the layer doesn't paint opaque content, then the + // VisibleContentOpaqueRegion should be empty. + layer->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(content_bounds); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + opaque_contents = layer->VisibleContentOpaqueRegion(); + EXPECT_TRUE(opaque_contents.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. + opaque_paint_rect = gfx::Rect(10, 10, 90, 190); + layer->fake_layer_updater()->SetOpaquePaintRect(opaque_paint_rect); + layer->InvalidateContentRect(content_bounds); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + UpdateTextures(); + opaque_contents = layer->VisibleContentOpaqueRegion(); + EXPECT_EQ(gfx::IntersectRects(opaque_paint_rect, visible_bounds).ToString(), + opaque_contents.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->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect()); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + UpdateTextures(); + opaque_contents = layer->VisibleContentOpaqueRegion(); + EXPECT_EQ(gfx::IntersectRects(opaque_paint_rect, visible_bounds).ToString(), + opaque_contents.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->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 1, 1)); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + UpdateTextures(); + opaque_contents = layer->VisibleContentOpaqueRegion(); + EXPECT_EQ(gfx::IntersectRects(opaque_paint_rect, visible_bounds).ToString(), + opaque_contents.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->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(gfx::Rect(10, 10, 1, 1)); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + UpdateTextures(); + opaque_contents = layer->VisibleContentOpaqueRegion(); + EXPECT_EQ(gfx::IntersectRects(gfx::Rect(10, 100, 90, 100), + visible_bounds).ToString(), + opaque_contents.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, PixelsPaintedMetrics) { + scoped_refptr<FakeTiledLayer> layer = + make_scoped_refptr(new FakeTiledLayer(resource_manager_.get())); + RenderSurfaceLayerList render_surface_layer_list; + TestOcclusionTracker occluded; + occlusion_ = &occluded; + layer_tree_host_->SetViewportSize(gfx::Size(1000, 1000)); + + layer_tree_host_->root_layer()->AddChild(layer); + + // The tile size is 100x100, so this invalidates and then paints two tiles in + // various ways. + + gfx::Rect opaque_paint_rect; + Region opaque_contents; + + gfx::Rect content_bounds = gfx::Rect(0, 0, 100, 300); + layer->SetBounds(content_bounds.size()); + CalcDrawProps(&render_surface_layer_list); + + // Invalidates and paints the whole layer. + layer->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(content_bounds); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + UpdateTextures(); + opaque_contents = layer->VisibleContentOpaqueRegion(); + EXPECT_TRUE(opaque_contents.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->fake_layer_updater()->SetOpaquePaintRect(gfx::Rect()); + layer->InvalidateContentRect(gfx::Rect(0, 0, 1, 1)); + layer->InvalidateContentRect(gfx::Rect(50, 200, 10, 10)); + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + layer->Update(queue_.get(), &occluded); + UpdateTextures(); + opaque_contents = layer->VisibleContentOpaqueRegion(); + EXPECT_TRUE(opaque_contents.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 root_rect(0, 0, 300, 200); + gfx::Rect child_rect(0, 0, 300, 100); + gfx::Rect child2_rect(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(root_rect.size()); + root->SetAnchorPoint(gfx::PointF()); + root->draw_properties().drawable_content_rect = root_rect; + root->draw_properties().visible_content_rect = root_rect; + root->AddChild(surface); + + surface->SetForceRenderSurface(true); + surface->SetAnchorPoint(gfx::PointF()); + surface->SetOpacity(0.5); + surface->AddChild(child); + surface->AddChild(child2); + + child->SetBounds(child_rect.size()); + child->SetAnchorPoint(gfx::PointF()); + child->SetPosition(child_rect.origin()); + child->draw_properties().visible_content_rect = child_rect; + child->draw_properties().drawable_content_rect = root_rect; + + child2->SetBounds(child2_rect.size()); + child2->SetAnchorPoint(gfx::PointF()); + child2->SetPosition(child2_rect.origin()); + child2->draw_properties().visible_content_rect = child2_rect; + child2->draw_properties().drawable_content_rect = root_rect; + + layer_tree_host_->SetRootLayer(root); + layer_tree_host_->SetViewportSize(root_rect.size()); + + // With a huge memory limit, all layers should update and push their textures. + root->InvalidateContentRect(root_rect); + child->InvalidateContentRect(child_rect); + child2->InvalidateContentRect(child2_rect); + layer_tree_host_->UpdateLayers(queue_.get(), + std::numeric_limits<size_t>::max()); + { + UpdateTextures(); + EXPECT_EQ(6, root->fake_layer_updater()->update_count()); + EXPECT_EQ(3, child->fake_layer_updater()->update_count()); + EXPECT_EQ(3, child2->fake_layer_updater()->update_count()); + EXPECT_FALSE(queue_->HasMoreUpdates()); + + root->fake_layer_updater()->ClearUpdateCount(); + child->fake_layer_updater()->ClearUpdateCount(); + child2->fake_layer_updater()->ClearUpdateCount(); + + scoped_ptr<FakeTiledLayerImpl> root_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), root->id())); + scoped_ptr<FakeTiledLayerImpl> child_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), child->id())); + scoped_ptr<FakeTiledLayerImpl> child2_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), child2->id())); + LayerPushPropertiesTo(child2.get(), child2_impl.get()); + LayerPushPropertiesTo(child.get(), child_impl.get()); + LayerPushPropertiesTo(root.get(), root_impl.get()); + + for (unsigned i = 0; i < 3; ++i) { + for (unsigned j = 0; j < 2; ++j) + EXPECT_TRUE(root_impl->HasResourceIdForTileAt(i, j)); + EXPECT_TRUE(child_impl->HasResourceIdForTileAt(i, 0)); + EXPECT_TRUE(child2_impl->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(root_rect); + child->InvalidateContentRect(child_rect); + child2->InvalidateContentRect(child2_rect); + layer_tree_host_->UpdateLayers(queue_.get(), + (3 * 2 + 3 * 1) * (100 * 100) * 4); + { + UpdateTextures(); + EXPECT_EQ(6, root->fake_layer_updater()->update_count()); + EXPECT_EQ(0, child->fake_layer_updater()->update_count()); + EXPECT_EQ(0, child2->fake_layer_updater()->update_count()); + EXPECT_FALSE(queue_->HasMoreUpdates()); + + root->fake_layer_updater()->ClearUpdateCount(); + child->fake_layer_updater()->ClearUpdateCount(); + child2->fake_layer_updater()->ClearUpdateCount(); + + scoped_ptr<FakeTiledLayerImpl> root_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), root->id())); + scoped_ptr<FakeTiledLayerImpl> child_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), child->id())); + scoped_ptr<FakeTiledLayerImpl> child2_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), child2->id())); + LayerPushPropertiesTo(child2.get(), child2_impl.get()); + LayerPushPropertiesTo(child.get(), child_impl.get()); + LayerPushPropertiesTo(root.get(), root_impl.get()); + + for (unsigned i = 0; i < 3; ++i) { + for (unsigned j = 0; j < 2; ++j) + EXPECT_TRUE(root_impl->HasResourceIdForTileAt(i, j)); + EXPECT_FALSE(child_impl->HasResourceIdForTileAt(i, 0)); + EXPECT_FALSE(child2_impl->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(root_rect); + child->InvalidateContentRect(child_rect); + child2->InvalidateContentRect(child2_rect); + layer_tree_host_->UpdateLayers(queue_.get(), (3 * 1) * (100 * 100) * 4); + { + UpdateTextures(); + EXPECT_EQ(0, root->fake_layer_updater()->update_count()); + EXPECT_EQ(0, child->fake_layer_updater()->update_count()); + EXPECT_EQ(0, child2->fake_layer_updater()->update_count()); + EXPECT_FALSE(queue_->HasMoreUpdates()); + + root->fake_layer_updater()->ClearUpdateCount(); + child->fake_layer_updater()->ClearUpdateCount(); + child2->fake_layer_updater()->ClearUpdateCount(); + + scoped_ptr<FakeTiledLayerImpl> root_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), root->id())); + scoped_ptr<FakeTiledLayerImpl> child_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), child->id())); + scoped_ptr<FakeTiledLayerImpl> child2_impl = make_scoped_ptr( + new FakeTiledLayerImpl(host_impl_->active_tree(), child2->id())); + LayerPushPropertiesTo(child2.get(), child2_impl.get()); + LayerPushPropertiesTo(child.get(), child_impl.get()); + LayerPushPropertiesTo(root.get(), root_impl.get()); + + for (unsigned i = 0; i < 3; ++i) { + for (unsigned j = 0; j < 2; ++j) + EXPECT_FALSE(root_impl->HasResourceIdForTileAt(i, j)); + EXPECT_FALSE(child_impl->HasResourceIdForTileAt(i, 0)); + EXPECT_FALSE(child2_impl->HasResourceIdForTileAt(i, 0)); + } + } + layer_tree_host_->CommitComplete(); + + ResourceManagerClearAllMemory(layer_tree_host_->contents_texture_manager(), + resource_provider_.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 { + painted_rect_ = content_rect; + } + + gfx::Rect PaintedRect() const { return painted_rect_; } + void ResetPaintedRect() { painted_rect_ = gfx::Rect(); } + + private: + gfx::Rect painted_rect_; +}; + +class UpdateTrackingTiledLayer : public FakeTiledLayer { + public: + explicit UpdateTrackingTiledLayer(PrioritizedResourceManager* manager) + : FakeTiledLayer(manager) { + scoped_ptr<TrackingLayerPainter> painter(TrackingLayerPainter::Create()); + tracking_layer_painter_ = painter.get(); + layer_updater_ = + BitmapContentLayerUpdater::Create(painter.PassAs<LayerPainter>(), + &stats_instrumentation_, + 0); + } + + TrackingLayerPainter* tracking_layer_painter() const { + return tracking_layer_painter_; + } + + private: + virtual LayerUpdater* Updater() const OVERRIDE { + return layer_updater_.get(); + } + virtual ~UpdateTrackingTiledLayer() {} + + TrackingLayerPainter* tracking_layer_painter_; + scoped_refptr<BitmapContentLayerUpdater> layer_updater_; + FakeRenderingStatsInstrumentation stats_instrumentation_; +}; + +TEST_F(TiledLayerTest, NonIntegerContentsScaleIsNotDistortedDuringPaint) { + scoped_refptr<UpdateTrackingTiledLayer> layer = + make_scoped_refptr(new UpdateTrackingTiledLayer(resource_manager_.get())); + + layer_tree_host_->root_layer()->AddChild(layer); + + gfx::Rect layer_rect(0, 0, 30, 31); + layer->SetPosition(layer_rect.origin()); + layer->SetBounds(layer_rect.size()); + layer->UpdateContentsScale(1.5f); + + gfx::Rect content_rect(0, 0, 45, 47); + EXPECT_EQ(content_rect.size(), layer->content_bounds()); + layer->draw_properties().visible_content_rect = content_rect; + layer->draw_properties().drawable_content_rect = content_rect; + + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + + // Update the whole tile. + layer->Update(queue_.get(), NULL); + layer->tracking_layer_painter()->ResetPaintedRect(); + + EXPECT_RECT_EQ(gfx::Rect(), layer->tracking_layer_painter()->PaintedRect()); + UpdateTextures(); + + // Invalidate the entire layer in content space. When painting, the rect given + // to webkit should match the layer's bounds. + layer->InvalidateContentRect(content_rect); + layer->Update(queue_.get(), NULL); + + EXPECT_RECT_EQ(layer_rect, layer->tracking_layer_painter()->PaintedRect()); +} + +TEST_F(TiledLayerTest, + NonIntegerContentsScaleIsNotDistortedDuringInvalidation) { + scoped_refptr<UpdateTrackingTiledLayer> layer = + make_scoped_refptr(new UpdateTrackingTiledLayer(resource_manager_.get())); + + layer_tree_host_->root_layer()->AddChild(layer); + + gfx::Rect layer_rect(0, 0, 30, 31); + layer->SetPosition(layer_rect.origin()); + layer->SetBounds(layer_rect.size()); + layer->UpdateContentsScale(1.3f); + + gfx::Rect content_rect(layer->content_bounds()); + layer->draw_properties().visible_content_rect = content_rect; + layer->draw_properties().drawable_content_rect = content_rect; + + layer->SetTexturePriorities(priority_calculator_); + resource_manager_->PrioritizeTextures(); + layer->SavePaintProperties(); + + // Update the whole tile. + layer->Update(queue_.get(), NULL); + layer->tracking_layer_painter()->ResetPaintedRect(); + + EXPECT_RECT_EQ(gfx::Rect(), layer->tracking_layer_painter()->PaintedRect()); + UpdateTextures(); + + // Invalidate the entire layer in layer space. When painting, the rect given + // to webkit should match the layer's bounds. + layer->SetNeedsDisplayRect(layer_rect); + layer->Update(queue_.get(), NULL); + + EXPECT_RECT_EQ(layer_rect, layer->tracking_layer_painter()->PaintedRect()); +} + +} // namespace +} // namespace cc |