// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <algorithm> #include <limits> #include "base/debug/trace_event.h" #include "cc/base/region.h" #include "cc/debug/debug_colors.h" #include "cc/resources/picture_pile_impl.h" #include "skia/ext/analysis_canvas.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkSize.h" #include "ui/gfx/rect_conversions.h" #include "ui/gfx/size_conversions.h" #include "ui/gfx/skia_util.h" namespace cc { PicturePileImpl::ClonesForDrawing::ClonesForDrawing( const PicturePileImpl* pile, int num_threads) { for (int i = 0; i < num_threads; i++) { scoped_refptr<PicturePileImpl> clone = PicturePileImpl::CreateCloneForDrawing(pile, i); clones_.push_back(clone); } } PicturePileImpl::ClonesForDrawing::~ClonesForDrawing() { } scoped_refptr<PicturePileImpl> PicturePileImpl::Create() { return make_scoped_refptr(new PicturePileImpl); } scoped_refptr<PicturePileImpl> PicturePileImpl::CreateFromOther( const PicturePileBase* other) { return make_scoped_refptr(new PicturePileImpl(other)); } scoped_refptr<PicturePileImpl> PicturePileImpl::CreateCloneForDrawing( const PicturePileImpl* other, unsigned thread_index) { return make_scoped_refptr(new PicturePileImpl(other, thread_index)); } PicturePileImpl::PicturePileImpl() : clones_for_drawing_(ClonesForDrawing(this, 0)) { } PicturePileImpl::PicturePileImpl(const PicturePileBase* other) : PicturePileBase(other), clones_for_drawing_(ClonesForDrawing(this, num_raster_threads())) { } PicturePileImpl::PicturePileImpl( const PicturePileImpl* other, unsigned thread_index) : PicturePileBase(other, thread_index), clones_for_drawing_(ClonesForDrawing(this, 0)) { } PicturePileImpl::~PicturePileImpl() { } PicturePileImpl* PicturePileImpl::GetCloneForDrawingOnThread( unsigned thread_index) const { CHECK_GT(clones_for_drawing_.clones_.size(), thread_index); return clones_for_drawing_.clones_[thread_index].get(); } void PicturePileImpl::RasterDirect( SkCanvas* canvas, gfx::Rect canvas_rect, float contents_scale, RenderingStatsInstrumentation* rendering_stats_instrumentation) { RasterCommon(canvas, NULL, canvas_rect, contents_scale, rendering_stats_instrumentation, false); } void PicturePileImpl::RasterForAnalysis( skia::AnalysisCanvas* canvas, gfx::Rect canvas_rect, float contents_scale, RenderingStatsInstrumentation* stats_instrumentation) { RasterCommon( canvas, canvas, canvas_rect, contents_scale, stats_instrumentation, true); } void PicturePileImpl::RasterToBitmap( SkCanvas* canvas, gfx::Rect canvas_rect, float contents_scale, RenderingStatsInstrumentation* rendering_stats_instrumentation) { if (clear_canvas_with_debug_color_) { // Any non-painted areas will be left in this color. canvas->clear(DebugColors::NonPaintedFillColor()); } // If this picture has opaque contents, it is guaranteeing that it will // draw an opaque rect the size of the layer. If it is not, then we must // clear this canvas ourselves. if (!contents_opaque_) { // Clearing is about ~4x faster than drawing a rect even if the content // isn't covering a majority of the canvas. canvas->clear(SK_ColorTRANSPARENT); } else { // Even if it is opaque, on any rasterizations that touch the edge of the // layer, we also need to raster the background color underneath the last // texel (since the recording won't cover it) and outside the last texel // (due to linear filtering when using this texture). gfx::SizeF total_content_size = gfx::ScaleSize(tiling_.total_size(), contents_scale); gfx::Rect content_rect(gfx::ToCeiledSize(total_content_size)); // The final texel of content may only be partially covered by a // rasterization; this rect represents the content rect that is fully // covered by content. gfx::Rect deflated_content_rect = content_rect; deflated_content_rect.Inset(0, 0, 1, 1); if (!deflated_content_rect.Contains(canvas_rect)) { // Drawing at most 2 x 2 x (canvas width + canvas height) texels is 2-3X // faster than clearing, so special case this. canvas->save(); canvas->translate(-canvas_rect.x(), -canvas_rect.y()); gfx::Rect inflated_content_rect = content_rect; inflated_content_rect.Inset(0, 0, -1, -1); canvas->clipRect(gfx::RectToSkRect(inflated_content_rect), SkRegion::kReplace_Op); canvas->clipRect(gfx::RectToSkRect(deflated_content_rect), SkRegion::kDifference_Op); canvas->drawColor(background_color_, SkXfermode::kSrc_Mode); canvas->restore(); } } RasterCommon(canvas, NULL, canvas_rect, contents_scale, rendering_stats_instrumentation, false); } void PicturePileImpl::CoalesceRasters(gfx::Rect canvas_rect, gfx::Rect content_rect, float contents_scale, PictureRegionMap* results) { DCHECK(results); // Rasterize the collection of relevant picture piles. gfx::Rect layer_rect = gfx::ScaleToEnclosingRect( content_rect, 1.f / contents_scale); // Coalesce rasters of the same picture into different rects: // - Compute the clip of each of the pile chunks, // - Subtract it from the canvas rect to get difference region // - Later, use the difference region to subtract each of the comprising // rects from the canvas. // Note that in essence, we're trying to mimic clipRegion with intersect op // that also respects the current canvas transform and clip. In order to use // the canvas transform, we must stick to clipRect operations (clipRegion // ignores the transform). Intersect then can be written as subtracting the // negation of the region we're trying to intersect. Luckily, we know that all // of the rects will have to fit into |content_rect|, so we can start with // that and subtract chunk rects to get the region that we need to subtract // from the canvas. Then, we can use clipRect with difference op to subtract // each rect in the region. bool include_borders = true; for (TilingData::Iterator tile_iter(&tiling_, layer_rect, include_borders); tile_iter; ++tile_iter) { PictureMap::iterator map_iter = picture_map_.find(tile_iter.index()); if (map_iter == picture_map_.end()) continue; PictureInfo& info = map_iter->second; Picture* picture = info.GetPicture(); if (!picture) continue; // This is intentionally *enclosed* rect, so that the clip is aligned on // integral post-scale content pixels and does not extend past the edges // of the picture chunk's layer rect. The min_contents_scale enforces that // enough buffer pixels have been added such that the enclosed rect // encompasses all invalidated pixels at any larger scale level. gfx::Rect chunk_rect = PaddedRect(tile_iter.index()); gfx::Rect content_clip = gfx::ScaleToEnclosedRect(chunk_rect, contents_scale); DCHECK(!content_clip.IsEmpty()) << "Layer rect: " << picture->LayerRect().ToString() << "Contents scale: " << contents_scale; content_clip.Intersect(canvas_rect); PictureRegionMap::iterator it = results->find(picture); if (it == results->end()) { Region& region = (*results)[picture]; region = content_rect; region.Subtract(content_clip); continue; } Region& region = it->second; region.Subtract(content_clip); } } void PicturePileImpl::RasterCommon( SkCanvas* canvas, SkDrawPictureCallback* callback, gfx::Rect canvas_rect, float contents_scale, RenderingStatsInstrumentation* rendering_stats_instrumentation, bool is_analysis) { DCHECK(contents_scale >= min_contents_scale_); canvas->translate(-canvas_rect.x(), -canvas_rect.y()); gfx::SizeF total_content_size = gfx::ScaleSize(tiling_.total_size(), contents_scale); gfx::Rect total_content_rect(gfx::ToCeiledSize(total_content_size)); gfx::Rect content_rect = total_content_rect; content_rect.Intersect(canvas_rect); canvas->clipRect(gfx::RectToSkRect(content_rect), SkRegion::kIntersect_Op); PictureRegionMap picture_region_map; CoalesceRasters( canvas_rect, content_rect, contents_scale, &picture_region_map); #ifndef NDEBUG Region total_clip; #endif // NDEBUG // Iterate the coalesced map and use each picture's region // to clip the canvas. for (PictureRegionMap::iterator it = picture_region_map.begin(); it != picture_region_map.end(); ++it) { Picture* picture = it->first; Region negated_clip_region = it->second; #ifndef NDEBUG Region positive_clip = content_rect; positive_clip.Subtract(negated_clip_region); total_clip.Union(positive_clip); #endif // NDEBUG base::TimeDelta best_duration = base::TimeDelta::FromInternalValue(std::numeric_limits<int64>::max()); int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_); int rasterized_pixel_count = 0; for (int j = 0; j < repeat_count; ++j) { base::TimeTicks start_time; if (rendering_stats_instrumentation) start_time = rendering_stats_instrumentation->StartRecording(); rasterized_pixel_count = picture->Raster( canvas, callback, negated_clip_region, contents_scale); if (rendering_stats_instrumentation) { base::TimeDelta duration = rendering_stats_instrumentation->EndRecording(start_time); best_duration = std::min(best_duration, duration); } } if (rendering_stats_instrumentation) { if (is_analysis) { rendering_stats_instrumentation->AddAnalysis(best_duration, rasterized_pixel_count); } else { rendering_stats_instrumentation->AddRaster(best_duration, rasterized_pixel_count); } } } #ifndef NDEBUG // Fill the clip with debug color. This allows us to // distinguish between non painted areas and problems with missing // pictures. SkPaint paint; for (Region::Iterator it(total_clip); it.has_rect(); it.next()) canvas->clipRect(gfx::RectToSkRect(it.rect()), SkRegion::kDifference_Op); paint.setColor(DebugColors::MissingPictureFillColor()); paint.setXfermodeMode(SkXfermode::kSrc_Mode); canvas->drawPaint(paint); #endif // NDEBUG } skia::RefPtr<SkPicture> PicturePileImpl::GetFlattenedPicture() { TRACE_EVENT0("cc", "PicturePileImpl::GetFlattenedPicture"); gfx::Rect layer_rect(tiling_.total_size()); skia::RefPtr<SkPicture> picture = skia::AdoptRef(new SkPicture); if (layer_rect.IsEmpty()) return picture; SkCanvas* canvas = picture->beginRecording( layer_rect.width(), layer_rect.height(), SkPicture::kUsePathBoundsForClip_RecordingFlag); RasterToBitmap(canvas, layer_rect, 1.0, NULL); picture->endRecording(); return picture; } void PicturePileImpl::AnalyzeInRect( gfx::Rect content_rect, float contents_scale, PicturePileImpl::Analysis* analysis) { AnalyzeInRect(content_rect, contents_scale, analysis, NULL); } void PicturePileImpl::AnalyzeInRect( gfx::Rect content_rect, float contents_scale, PicturePileImpl::Analysis* analysis, RenderingStatsInstrumentation* stats_instrumentation) { DCHECK(analysis); TRACE_EVENT0("cc", "PicturePileImpl::AnalyzeInRect"); gfx::Rect layer_rect = gfx::ScaleToEnclosingRect( content_rect, 1.0f / contents_scale); layer_rect.Intersect(gfx::Rect(tiling_.total_size())); SkBitmap empty_bitmap; empty_bitmap.setConfig(SkBitmap::kNo_Config, layer_rect.width(), layer_rect.height()); skia::AnalysisDevice device(empty_bitmap); skia::AnalysisCanvas canvas(&device); RasterForAnalysis(&canvas, layer_rect, 1.0f, stats_instrumentation); analysis->is_solid_color = canvas.GetColorIfSolid(&analysis->solid_color); analysis->has_text = canvas.HasText(); } PicturePileImpl::Analysis::Analysis() : is_solid_color(false), has_text(false) { } PicturePileImpl::Analysis::~Analysis() { } PicturePileImpl::PixelRefIterator::PixelRefIterator( gfx::Rect content_rect, float contents_scale, const PicturePileImpl* picture_pile) : picture_pile_(picture_pile), layer_rect_( gfx::ScaleToEnclosingRect(content_rect, 1.f / contents_scale)), tile_iterator_(&picture_pile_->tiling_, layer_rect_, true) { // TODO(enne): tile iterator should not include borders // Early out if there isn't a single tile. if (!tile_iterator_) return; AdvanceToTilePictureWithPixelRefs(); } PicturePileImpl::PixelRefIterator::~PixelRefIterator() { } PicturePileImpl::PixelRefIterator& PicturePileImpl::PixelRefIterator::operator++() { ++pixel_ref_iterator_; if (pixel_ref_iterator_) return *this; ++tile_iterator_; AdvanceToTilePictureWithPixelRefs(); return *this; } void PicturePileImpl::PixelRefIterator::AdvanceToTilePictureWithPixelRefs() { for (; tile_iterator_; ++tile_iterator_) { PictureMap::const_iterator it = picture_pile_->picture_map_.find(tile_iterator_.index()); if (it == picture_pile_->picture_map_.end()) continue; const Picture* picture = it->second.GetPicture(); if (!picture || (processed_pictures_.count(picture) != 0) || !picture->WillPlayBackBitmaps()) continue; processed_pictures_.insert(picture); pixel_ref_iterator_ = Picture::PixelRefIterator(layer_rect_, picture); if (pixel_ref_iterator_) break; } } void PicturePileImpl::DidBeginTracing() { gfx::Rect layer_rect(tiling_.total_size()); std::set<void*> processed_pictures; for (PictureMap::iterator it = picture_map_.begin(); it != picture_map_.end(); ++it) { Picture* picture = it->second.GetPicture(); if (picture && (processed_pictures.count(picture) == 0)) { picture->EmitTraceSnapshot(); processed_pictures.insert(picture); } } } } // namespace cc