text: // 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 <algorithm>
  #include <vector>
  
  #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_(RGBA_8888),
        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().default_tile_size;
    gfx::Size max_untiled_layer_size =
        layer_tree_host()->settings().max_untiled_layer_size;
    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.SetToMin(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(old_bounds);
    Region new_region = gfx::Rect(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(const 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::ReduceMemoryUsage() {
    if (Updater())
      Updater()->ReduceMemoryUsage();
  }
  
  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);
      // TODO(enne): This should not ever be null.
      if (!tile)
        continue;
  
      if (!tile->managed_resource()->have_backing_texture()) {
        // 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()->resource_id(),
          tile->opaque_rect(),
          tile->managed_resource()->contents_swizzled());
    }
    for (std::vector<UpdatableTile*>::const_iterator iter = invalid_tiles.begin();
         iter != invalid_tiles.end();
         ++iter)
      tiler_->TakeTile((*iter)->i(), (*iter)->j());
  
    // TiledLayer must push properties every frame, since viewport state and
    // occlusion from anywhere in the tree can change what the layer decides to
    // push to the impl tree.
    needs_push_properties_ = true;
  }
  
  PrioritizedResourceManager* TiledLayer::ResourceManager() {
    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);
        // TODO(enne): 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(const 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);
      // TODO(enne): 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()->have_backing_texture();
  }
  
  bool TiledLayer::UpdateTiles(int left,
                               int top,
                               int right,
                               int bottom,
                               ResourceUpdateQueue* queue,
                               const OcclusionTracker* occlusion,
                               bool* updated) {
    CreateUpdaterIfNeeded();
  
    bool ignore_occlusions = !occlusion;
    if (!HaveTexturesForTiles(left, top, right, bottom, ignore_occlusions)) {
      failed_update_ = true;
      return false;
    }
  
    gfx::Rect update_rect;
    gfx::Rect paint_rect;
    MarkTilesForUpdate(
      &update_rect, &paint_rect, left, top, right, bottom, ignore_occlusions);
  
    if (occlusion)
      occlusion->overdraw_metrics()->DidPaint(paint_rect);
  
    if (paint_rect.IsEmpty())
      return true;
  
    *updated = true;
    UpdateTileTextures(
        update_rect, paint_rect, left, top, right, bottom, queue, occlusion);
    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?
        // TODO(enne): 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())) {
          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?
                       // TODO(enne): 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()->have_backing_texture())
          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()->can_acquire_backing_texture())
          return false;
      }
    }
    return true;
  }
  
  void TiledLayer::MarkTilesForUpdate(gfx::Rect* update_rect,
                                      gfx::Rect* paint_rect,
                                      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?
                       // TODO(enne): This should not ever be null.
        if (!tile)
          continue;
        if (tile->occluded && !ignore_occlusions)
          continue;
  
        // Prepare update rect from original dirty rects.
        update_rect->Union(tile->dirty_rect);
  
        // TODO(reveman): 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()->AlwaysUsePartialTextureUpdates()) {
          // 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();
      }
    }
  }
  
  void TiledLayer::UpdateTileTextures(const gfx::Rect& update_rect,
                                      const gfx::Rect& paint_rect,
                                      int left,
                                      int top,
                                      int right,
                                      int bottom,
                                      ResourceUpdateQueue* queue,
                                      const OcclusionTracker* occlusion) {
    // 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 =
        paint_properties().bounds.width() /
        static_cast<float>(content_bounds().width());
    float height_scale =
        paint_properties().bounds.height() /
        static_cast<float>(content_bounds().height());
    update_rect_ = gfx::ScaleRect(update_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);
  
    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?
                       // TODO(enne): 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 =
              !paint_inside_tile_opaque_rect.IsEmpty() &&
              !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);
        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(content_bounds());
    return content_rect.width() <=
           viewport_size.width() + tiler_->tile_size().width() &&
           content_rect.height() <=
           viewport_size.height() + tiler_->tile_size().height();
  }
  
  namespace {
  // TODO(epenner): 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(const gfx::Rect& visible_rect,
                             const 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->set_request_priority(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(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);
      // TODO(enne): 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);
      // TODO(enne): This should not ever be null.
      if (!tile)
        continue;
      tile->ResetUpdateState();
    }
  }
  
  namespace {
  gfx::Rect ExpandRectByDelta(const gfx::Rect& rect, const 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(content_bounds()));
      predicted_visible_rect_.Intersect(bound);
    }
    previous_content_bounds_ = content_bounds();
    previous_visible_rect_ = visible_content_rect();
  }
  
  bool TiledLayer::Update(ResourceUpdateQueue* queue,
                          const OcclusionTracker* occlusion) {
    DCHECK(!skips_draw_ && !failed_update_);  // Did ResetUpdateState get skipped?
  
    // Tiled layer always causes commits to wait for activation, as it does
    // not support pending trees.
    SetNextCommitWaitsForActivation();
  
    bool updated = false;
  
    {
      base::AutoReset<bool> ignore_set_needs_commit(&ignore_set_needs_commit_,
                                                    true);
  
      updated |= ContentsScalingLayer::Update(queue, occlusion);
      UpdateBounds();
    }
  
    if (tiler_->has_empty_bounds() || !DrawsContent())
      return 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(content_bounds()),
                                       &left,
                                       &top,
                                       &right,
                                       &bottom);
      UpdateTiles(left, top, right, bottom, queue, NULL, &updated);
      if (updated)
        return updated;
      // 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 updated;
  
    // 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, &updated);
    if (skips_draw_)
      tiler_->reset();
    if (skips_draw_ || updated)
      return true;
  
    // 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 updated;
  
    // Prepaint anything that was occluded but inside the layer's visible region.
    if (!UpdateTiles(left, top, right, bottom, queue, NULL, &updated) ||
        updated)
      return updated;
  
    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 =
        (std::abs(delta.x()) > std::abs(delta.y())) ? gfx::Vector2d(delta.x(), 0)
                                          : gfx::Vector2d(0, delta.y());
    gfx::Vector2d minor_delta =
        (std::abs(delta.x()) <= std::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, &updated) ||
              updated)
            return updated;
        }
      }
      if (deltas[i].y() < 0) {
        while (top > prepaint_top) {
          --top;
          if (!UpdateTiles(
                  left, top, right, top, queue, NULL, &updated) ||
              updated)
            return updated;
        }
      }
      if (deltas[i].x() < 0) {
        while (left > prepaint_left) {
          --left;
          if (!UpdateTiles(
                  left, top, left, bottom, queue, NULL, &updated) ||
              updated)
            return updated;
        }
      }
      if (deltas[i].x() > 0) {
        while (right < prepaint_right) {
          ++right;
          if (!UpdateTiles(
                  right, top, right, bottom, queue, NULL, &updated) ||
              updated)
            return updated;
        }
      }
    }
    return updated;
  }
  
  void TiledLayer::OnOutputSurfaceCreated() {
    // Ensure that all textures are of the right format.
    for (LayerTilingData::TileMap::const_iterator iter = tiler_->tiles().begin();
         iter != tiler_->tiles().end();
         ++iter) {
      UpdatableTile* tile = static_cast<UpdatableTile*>(iter->second);
      if (!tile)
        continue;
      PrioritizedResource* resource = tile->managed_resource();
      resource->SetDimensions(resource->size(), texture_format_);
    }
  }
  
  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()->can_acquire_backing_texture();
        bool dirty =
            tile->is_dirty() || !tile->managed_resource()->have_backing_texture();
        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
  