flutter_flutter/flow/raster_cache.cc
Chinmay Garde 1c6a531e89 Rework raster cache to fix numerous issues. (#3717)
* Fix pixel rounding error in the picture layer by first ensuring that
  the texture for the image is at least as big as the next integer size
  along each dimension and using kStrict_SrcRectConstraint while
  drawing the same image. We already select the source subset by
  looking at the cull rect of the picture.
* Decompose the transformation matrix into a series of operations that
  generated the same to calculate the scale at which to rasterize the
  picture. This make the rasterization scale resilient to
  transformations that introduce a perspective component to the
  resultant matrix.
* The scale in the decomposed matrix is now part of the key in the
  cache.
* Raster cache images that could never be rasterized were still taking
  part in the cache. Now, those entries are rejected early on. This
  leads to the sweep after the frame iterating over fewer items.
* Added a unit test target.
2017-06-05 15:57:05 -07:00

205 lines
5.6 KiB
C++

// Copyright 2016 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 "flutter/flow/raster_cache.h"
#include <vector>
#include "flutter/common/threads.h"
#include "flutter/flow/paint_utils.h"
#include "flutter/glue/trace_event.h"
#include "lib/ftl/logging.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkPicture.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace flow {
RasterCache::RasterCache(size_t threshold)
: threshold_(threshold), checkerboard_images_(false), weak_factory_(this) {}
RasterCache::~RasterCache() = default;
static bool CanRasterizePicture(SkPicture* picture) {
if (picture == nullptr) {
return false;
}
const SkRect cull_rect = picture->cullRect();
if (cull_rect.isEmpty()) {
// No point in ever rasterizing an empty picture.
return false;
}
if (!cull_rect.isFinite()) {
// Cannot attempt to rasterize into an infinitely large surface.
return false;
}
return true;
}
static bool IsPictureWorthRasterizing(SkPicture* picture,
bool will_change,
bool is_complex) {
if (will_change) {
// If the picture is going to change in the future, there is no point in
// doing to extra work to rasterize.
return false;
}
if (!CanRasterizePicture(picture)) {
// No point in deciding whether the picture is worth rasterizing if it
// cannot be rasterized at all.
return false;
}
if (is_complex) {
// The caller seems to have extra information about the picture and thinks
// the picture is always worth rasterizing.
return true;
}
// TODO(abarth): We should find a better heuristic here that lets us avoid
// wasting memory on trivial layers that are easy to re-rasterize every frame.
return picture->approximateOpCount() > 10;
}
RasterCacheResult RasterizePicture(SkPicture* picture,
GrContext* context,
const MatrixDecomposition& matrix,
bool checkerboard) {
TRACE_EVENT0("flutter", "RasterCachePopulate");
const SkVector3& scale = matrix.scale();
SkRect logical_rect = picture->cullRect();
const SkImageInfo image_info = SkImageInfo::MakeN32Premul(
std::ceil(logical_rect.width() * std::abs(scale.x())), // physical width
std::ceil(logical_rect.height() *
std::abs(scale.y())), // physical height
nullptr // colorspace
);
sk_sp<SkSurface> surface =
context
? SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, image_info)
: SkSurface::MakeRaster(image_info);
if (!surface) {
FTL_DCHECK(false);
return {};
}
SkCanvas* canvas = surface->getCanvas();
canvas->clear(SK_ColorTRANSPARENT);
canvas->scale(std::abs(scale.x()), std::abs(scale.y()));
canvas->translate(-logical_rect.left(), -logical_rect.top());
canvas->drawPicture(picture);
if (checkerboard) {
DrawCheckerboard(canvas, logical_rect);
}
return {
surface->makeImageSnapshot(), // image
SkRect::MakeWH(
logical_rect.width() * std::abs(scale.x()),
logical_rect.height() * std::abs(scale.y())), // source rect
logical_rect // destination rect
};
}
static inline size_t ClampSize(size_t value, size_t min, size_t max) {
if (value > max) {
return max;
}
if (value < min) {
return min;
}
return value;
}
RasterCacheResult RasterCache::GetPrerolledImage(
GrContext* context,
SkPicture* picture,
const SkMatrix& transformation_matrix,
bool is_complex,
bool will_change) {
if (!IsPictureWorthRasterizing(picture, will_change, is_complex)) {
// We only deal with pictures that are worthy of rasterization.
return {};
}
// Decompose the matrix (once) for all subsequent operations. We want to make
// sure to avoid volumetric distortions while accounting for scaling.
const MatrixDecomposition matrix(transformation_matrix);
if (!matrix.IsValid()) {
// The matrix was singular. No point in going further.
return {};
}
RasterCacheKey cache_key(*picture, matrix);
Entry& entry = cache_[cache_key];
entry.access_count = ClampSize(entry.access_count + 1, 0, threshold_);
entry.used_this_frame = true;
if (entry.access_count < threshold_ || threshold_ == 0) {
// Frame threshold has not yet been reached.
return {};
}
if (!entry.image.is_valid()) {
entry.image =
RasterizePicture(picture, context, matrix, checkerboard_images_);
}
// We are not considering unrasterizable images. So if we don't have an image
// by now, we know that rasterization itself failed.
FTL_DCHECK(entry.image.is_valid());
return entry.image;
}
void RasterCache::SweepAfterFrame() {
std::vector<RasterCacheKey::Map<Entry>::iterator> dead;
for (auto it = cache_.begin(); it != cache_.end(); ++it) {
Entry& entry = it->second;
if (!entry.used_this_frame) {
dead.push_back(it);
}
entry.used_this_frame = false;
}
for (auto it : dead) {
cache_.erase(it);
}
}
void RasterCache::Clear() {
cache_.clear();
}
void RasterCache::SetCheckboardCacheImages(bool checkerboard) {
if (checkerboard_images_ == checkerboard) {
return;
}
checkerboard_images_ = checkerboard;
// Clear all existing entries so previously rasterized items (with or without
// a checkerboard) will be refreshed in subsequent passes.
Clear();
}
} // namespace flow