diff --git a/BUILD.gn b/BUILD.gn index 84ac5f072ceb8c4f416574a4b0eda05b4525f09e..936c8cb4079cbe7ed1213ed92153bddbc2c7941b 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -25,6 +25,7 @@ group("flutter") { deps += [ "//flutter/shell/platform/darwin:flutter_channels_unittests" ] } deps += [ + "//flutter/flow:flow_unittests", "//flutter/fml:fml_unittests", "//flutter/sky/engine/wtf:wtf_unittests", "//flutter/synchronization:synchronization_unittests", diff --git a/flow/BUILD.gn b/flow/BUILD.gn index 3293c75f27a66ebb3d9b4cb4227a6a8966ed9407..112ec6389c44a3c130c107aea3a5483e8d904492 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -6,6 +6,8 @@ source_set("flow") { sources = [ "compositor_context.cc", "compositor_context.h", + "debug_print.cc", + "debug_print.h", "instrumentation.cc", "instrumentation.h", "layers/backdrop_filter_layer.cc", @@ -36,11 +38,15 @@ source_set("flow") { "layers/shader_mask_layer.h", "layers/transform_layer.cc", "layers/transform_layer.h", + "matrix_decomposition.cc", + "matrix_decomposition.h", "paint_utils.cc", "paint_utils.h", "process_info.h", "raster_cache.cc", "raster_cache.h", + "raster_cache_key.cc", + "raster_cache_key.h", "scene_update_context.cc", "scene_update_context.h", ] @@ -65,3 +71,19 @@ source_set("flow") { ] } } + +executable("flow_unittests") { + testonly = true + + sources = [ + "matrix_decomposition_unittests.cc", + "raster_cache_unittests.cc", + ] + + deps = [ + ":flow", + "//dart/runtime:libdart_jit", # for tracing + "//flutter/testing", + "//third_party/skia", + ] +} diff --git a/flow/debug_print.cc b/flow/debug_print.cc new file mode 100644 index 0000000000000000000000000000000000000000..368194b86649e6b4ea6a7583b8f7fad6bb3fd745 --- /dev/null +++ b/flow/debug_print.cc @@ -0,0 +1,60 @@ +// Copyright 2017 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/debug_print.h" + +#include + +#include "third_party/skia/include/core/SkString.h" + +std::ostream& operator<<(std::ostream& os, const flow::MatrixDecomposition& m) { + if (!m.IsValid()) { + os << "Invalid Matrix!" << std::endl; + return os; + } + + os << "Translation (x, y, z): " << m.translation() << std::endl; + os << "Scale (z, y, z): " << m.scale() << std::endl; + os << "Shear (zy, yz, zx): " << m.shear() << std::endl; + os << "Perspective (x, y, z, w): " << m.perspective() << std::endl; + os << "Rotation (x, y, z, w): " << m.rotation() << std::endl; + + return os; +} + +std::ostream& operator<<(std::ostream& os, const SkMatrix& m) { + SkString string; + m.toString(&string); + os << string.c_str(); + return os; +} + +std::ostream& operator<<(std::ostream& os, const SkMatrix44& m) { + os << m.get(0, 0) << ", " << m.get(0, 1) << ", " << m.get(0, 2) << ", " + << m.get(0, 3) << std::endl; + os << m.get(1, 0) << ", " << m.get(1, 1) << ", " << m.get(1, 2) << ", " + << m.get(1, 3) << std::endl; + os << m.get(2, 0) << ", " << m.get(2, 1) << ", " << m.get(2, 2) << ", " + << m.get(2, 3) << std::endl; + os << m.get(3, 0) << ", " << m.get(3, 1) << ", " << m.get(3, 2) << ", " + << m.get(3, 3); + return os; +} + +std::ostream& operator<<(std::ostream& os, const SkVector3& v) { + os << v.x() << ", " << v.y() << ", " << v.z(); + return os; +} + +std::ostream& operator<<(std::ostream& os, const SkVector4& v) { + os << v.fData[0] << ", " << v.fData[1] << ", " << v.fData[2] << ", " + << v.fData[3]; + return os; +} + +std::ostream& operator<<(std::ostream& os, const flow::RasterCacheKey& k) { + os << "Picture: " << k.picture_id() << " Scale: " << k.scale_key().width() + << ", " << k.scale_key().height(); + return os; +} diff --git a/flow/debug_print.h b/flow/debug_print.h new file mode 100644 index 0000000000000000000000000000000000000000..32d073806047ebeb136f6092af5ed2a0e99a2317 --- /dev/null +++ b/flow/debug_print.h @@ -0,0 +1,24 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FLOW_DEBUG_PRINT_H_ +#define FLUTTER_FLOW_DEBUG_PRINT_H_ + +#include "flutter/flow/matrix_decomposition.h" +#include "flutter/flow/raster_cache_key.h" +#include "lib/ftl/macros.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkMatrix44.h" +#include "third_party/skia/include/core/SkPoint3.h" + +#define DEF_PRINTER(x) std::ostream& operator<<(std::ostream&, const x&); + +DEF_PRINTER(flow::RasterCacheKey); +DEF_PRINTER(flow::MatrixDecomposition); +DEF_PRINTER(SkMatrix); +DEF_PRINTER(SkMatrix44); +DEF_PRINTER(SkVector3); +DEF_PRINTER(SkVector4); + +#endif // FLUTTER_FLOW_DEBUG_PRINT_H_ diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index 2f53e771cf71b51679ecd56dfb5f5e1a2ed474ab..cc6a4a86c52dbdaba828bcc4766942ce9cb6096a 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -5,7 +5,6 @@ #include "flutter/flow/layers/picture_layer.h" #include "flutter/common/threads.h" -#include "flutter/flow/raster_cache.h" #include "lib/ftl/logging.h" namespace flow { @@ -23,8 +22,8 @@ PictureLayer::~PictureLayer() { void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { if (auto cache = context->raster_cache) { - image_ = cache->GetPrerolledImage(context->gr_context, picture_.get(), - matrix, is_complex_, will_change_); + raster_cache_result_ = cache->GetPrerolledImage( + context->gr_context, picture_.get(), matrix, is_complex_, will_change_); } SkRect bounds = picture_->cullRect().makeOffset(offset_.x(), offset_.y()); @@ -35,15 +34,19 @@ void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { void PictureLayer::Paint(PaintContext& context) { FTL_DCHECK(picture_); - TRACE_EVENT1("flutter", "PictureLayer::Paint", "image", - image_ ? "prerolled" : "normal"); + TRACE_EVENT0("flutter", "PictureLayer::Paint"); SkAutoCanvasRestore save(&context.canvas, true); context.canvas.translate(offset_.x(), offset_.y()); - if (image_) { - context.canvas.drawImageRect(image_.get(), picture_->cullRect(), nullptr, - SkCanvas::kFast_SrcRectConstraint); + if (raster_cache_result_.is_valid()) { + context.canvas.drawImageRect( + raster_cache_result_.image(), // image + raster_cache_result_.source_rect(), // source + raster_cache_result_.destination_rect(), // destination + nullptr, // paint + SkCanvas::kStrict_SrcRectConstraint // source constraint + ); } else { context.canvas.drawPicture(picture_.get()); } diff --git a/flow/layers/picture_layer.h b/flow/layers/picture_layer.h index c0305784548157469e7639662d59db113f99a8bb..516bcb2c09cde7a4f157e3807d0b45b01e09b055 100644 --- a/flow/layers/picture_layer.h +++ b/flow/layers/picture_layer.h @@ -6,6 +6,7 @@ #define FLUTTER_FLOW_LAYERS_PICTURE_LAYER_H_ #include "flutter/flow/layers/layer.h" +#include "flutter/flow/raster_cache.h" namespace flow { @@ -30,9 +31,7 @@ class PictureLayer : public Layer { sk_sp picture_; bool is_complex_ = false; bool will_change_ = false; - - // If we rasterized the picture separately, image_ holds the pixels. - sk_sp image_; + RasterCacheResult raster_cache_result_; FTL_DISALLOW_COPY_AND_ASSIGN(PictureLayer); }; diff --git a/flow/matrix_decomposition.cc b/flow/matrix_decomposition.cc new file mode 100644 index 0000000000000000000000000000000000000000..b1e12b044d201b5c67c0ceeea3023c68e4072d17 --- /dev/null +++ b/flow/matrix_decomposition.cc @@ -0,0 +1,141 @@ +// Copyright 2017 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/matrix_decomposition.h" + +namespace flow { + +static inline SkVector3 SkVector3Combine(const SkVector3& a, + float a_scale, + const SkVector3& b, + float b_scale) { + return { + a_scale * a.fX + b_scale * b.fX, // + a_scale * a.fY + b_scale * b.fY, // + a_scale * a.fZ + b_scale * b.fZ, // + }; +} + +static inline SkVector3 SkVector3Cross(const SkVector3& a, const SkVector3& b) { + return { + (a.fY * b.fZ) - (a.fZ * b.fY), // + (a.fZ * b.fX) - (a.fX * b.fZ), // + (a.fX * b.fY) - (a.fY * b.fX) // + }; +} + +MatrixDecomposition::MatrixDecomposition(const SkMatrix& matrix) + : MatrixDecomposition(SkMatrix44{matrix}) {} + +MatrixDecomposition::MatrixDecomposition(SkMatrix44 matrix) : valid_(false) { + if (matrix.get(3, 3) == 0) { + return; + } + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + matrix.set(j, i, matrix.get(j, i) / matrix.get(3, 3)); + } + } + + SkMatrix44 perpective_matrix = matrix; + for (int i = 0; i < 3; i++) { + perpective_matrix.set(3, i, 0.0); + } + + perpective_matrix.set(3, 3, 1.0); + + if (perpective_matrix.determinant() == 0.0) { + return; + } + + if (matrix.get(3, 0) != 0.0 || matrix.get(3, 1) != 0.0 || + matrix.get(3, 2) != 0.0) { + const SkVector4 right_hand_side(matrix.get(3, 0), matrix.get(3, 1), + matrix.get(3, 2), matrix.get(3, 3)); + + SkMatrix44 inverted_transposed( + SkMatrix44::Uninitialized_Constructor::kUninitialized_Constructor); + if (!perpective_matrix.invert(&inverted_transposed)) { + return; + } + inverted_transposed.transpose(); + + perspective_ = inverted_transposed * right_hand_side; + + matrix.set(3, 0, 0); + matrix.set(3, 1, 0); + matrix.set(3, 2, 0); + matrix.set(3, 3, 1); + } + + translation_ = {matrix.get(0, 3), matrix.get(1, 3), matrix.get(2, 3)}; + + matrix.set(0, 3, 0.0); + matrix.set(1, 3, 0.0); + matrix.set(2, 3, 0.0); + + SkVector3 row[3]; + for (int i = 0; i < 3; i++) { + row[i].set(matrix.get(0, i), matrix.get(1, i), matrix.get(2, i)); + } + + scale_.fX = row[0].length(); + row[0].normalize(); + + shear_.fX = row[0].dot(row[1]); + row[1] = SkVector3Combine(row[1], 1.0, row[0], -shear_.fX); + + scale_.fY = row[1].length(); + row[1].normalize(); + shear_.fX /= scale_.fY; + + shear_.fY = row[0].dot(row[2]); + row[2] = SkVector3Combine(row[2], 1.0, row[0], -shear_.fY); + shear_.fZ = row[1].dot(row[2]); + row[2] = SkVector3Combine(row[2], 1.0, row[1], -shear_.fZ); + + scale_.fZ = row[2].length(); + row[2].normalize(); + + shear_.fY /= scale_.fZ; + shear_.fZ /= scale_.fZ; + + if (row[0].dot(SkVector3Cross(row[1], row[2])) < 0) { + scale_.fX *= -1; + scale_.fY *= -1; + scale_.fZ *= -1; + + for (int i = 0; i < 3; i++) { + row[i].fX *= -1; + row[i].fY *= -1; + row[i].fZ *= -1; + } + } + + rotation_.set(0.5 * sqrt(fmax(1.0 + row[0].fX - row[1].fY - row[2].fZ, 0.0)), + 0.5 * sqrt(fmax(1.0 - row[0].fX + row[1].fY - row[2].fZ, 0.0)), + 0.5 * sqrt(fmax(1.0 - row[0].fX - row[1].fY + row[2].fZ, 0.0)), + 0.5 * sqrt(fmax(1.0 + row[0].fX + row[1].fY + row[2].fZ, 0.0))); + + if (row[2].fY > row[1].fZ) { + rotation_.fData[0] = -rotation_.fData[0]; + } + if (row[0].fZ > row[2].fX) { + rotation_.fData[1] = -rotation_.fData[1]; + } + if (row[1].fX > row[0].fY) { + rotation_.fData[2] = -rotation_.fData[2]; + } + + valid_ = true; +} + +MatrixDecomposition::~MatrixDecomposition() = default; + +bool MatrixDecomposition::IsValid() const { + return valid_; +} + +} // namespace flow diff --git a/flow/matrix_decomposition.h b/flow/matrix_decomposition.h new file mode 100644 index 0000000000000000000000000000000000000000..d0819a13e4cf08a9198ceb472195017d81b87f3e --- /dev/null +++ b/flow/matrix_decomposition.h @@ -0,0 +1,51 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FLOW_MATRIX_DECOMPOSITION_H_ +#define FLUTTER_FLOW_MATRIX_DECOMPOSITION_H_ + +#include "lib/ftl/macros.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkMatrix44.h" +#include "third_party/skia/include/core/SkPoint3.h" + +namespace flow { + +/// Decomposes a given non-degenerate transformation matrix into a sequence of +/// operations that produced it. The validity of the decomposition must always +/// be checked before attempting to access any of the decomposed elements. +class MatrixDecomposition { + public: + MatrixDecomposition(const SkMatrix& matrix); + + MatrixDecomposition(SkMatrix44 matrix); + + ~MatrixDecomposition(); + + bool IsValid() const; + + const SkVector3& translation() const { return translation_; } + + const SkVector3& scale() const { return scale_; } + + const SkVector3& shear() const { return shear_; } + + const SkVector4& perspective() const { return perspective_; } + + const SkVector4& rotation() const { return rotation_; } + + private: + bool valid_; + SkVector3 translation_; + SkVector3 scale_; + SkVector3 shear_; + SkVector4 perspective_; + SkVector4 rotation_; + + FTL_DISALLOW_COPY_AND_ASSIGN(MatrixDecomposition); +}; + +} // namespace flow + +#endif // FLUTTER_FLOW_MATRIX_DECOMPOSITION_H_ diff --git a/flow/matrix_decomposition_unittests.cc b/flow/matrix_decomposition_unittests.cc new file mode 100644 index 0000000000000000000000000000000000000000..9d24168d7951468229b03155fcf5af67f29d0a2d --- /dev/null +++ b/flow/matrix_decomposition_unittests.cc @@ -0,0 +1,88 @@ +// Copyright 2017 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/matrix_decomposition.h" +#include "third_party/gtest/include/gtest/gtest.h" + +TEST(MatrixDecomposition, Rotation) { + SkMatrix44 matrix = SkMatrix44::I(); + + const auto angle = M_PI_4; + matrix.setRotateAbout(0.0, 0.0, 1.0, angle); + + flow::MatrixDecomposition decomposition(matrix); + ASSERT_TRUE(decomposition.IsValid()); + + const auto sine = sin(angle * 0.5); + + ASSERT_FLOAT_EQ(0, decomposition.rotation().fData[0]); + ASSERT_FLOAT_EQ(0, decomposition.rotation().fData[1]); + ASSERT_FLOAT_EQ(sine, decomposition.rotation().fData[2]); + ASSERT_FLOAT_EQ(cos(angle * 0.5), decomposition.rotation().fData[3]); +} + +TEST(MatrixDecomposition, Scale) { + SkMatrix44 matrix = SkMatrix44::I(); + + const auto scale = 5.0; + matrix.setScale(scale + 0, scale + 1, scale + 2); + + flow::MatrixDecomposition decomposition(matrix); + ASSERT_TRUE(decomposition.IsValid()); + + ASSERT_FLOAT_EQ(scale + 0, decomposition.scale().fX); + ASSERT_FLOAT_EQ(scale + 1, decomposition.scale().fY); + ASSERT_FLOAT_EQ(scale + 2, decomposition.scale().fZ); +} + +TEST(MatrixDecomposition, Translate) { + SkMatrix44 matrix = SkMatrix44::I(); + + const auto translate = 125.0; + matrix.setTranslate(translate + 0, translate + 1, translate + 2); + + flow::MatrixDecomposition decomposition(matrix); + ASSERT_TRUE(decomposition.IsValid()); + + ASSERT_FLOAT_EQ(translate + 0, decomposition.translation().fX); + ASSERT_FLOAT_EQ(translate + 1, decomposition.translation().fY); + ASSERT_FLOAT_EQ(translate + 2, decomposition.translation().fZ); +} + +TEST(MatrixDecomposition, Combination) { + SkMatrix44 matrix = SkMatrix44::I(); + + const auto rotation = M_PI_4; + const auto scale = 5; + const auto translate = 125.0; + + SkMatrix44 m1 = SkMatrix44::I(); + m1.setRotateAbout(0, 0, 1, rotation); + + SkMatrix44 m2 = SkMatrix44::I(); + m2.setScale(scale); + + SkMatrix44 m3 = SkMatrix44::I(); + m3.setTranslate(translate, translate, translate); + + SkMatrix44 combined = m3 * m2 * m1; + + flow::MatrixDecomposition decomposition(combined); + ASSERT_TRUE(decomposition.IsValid()); + + ASSERT_FLOAT_EQ(translate, decomposition.translation().fX); + ASSERT_FLOAT_EQ(translate, decomposition.translation().fY); + ASSERT_FLOAT_EQ(translate, decomposition.translation().fZ); + + ASSERT_FLOAT_EQ(scale, decomposition.scale().fX); + ASSERT_FLOAT_EQ(scale, decomposition.scale().fY); + ASSERT_FLOAT_EQ(scale, decomposition.scale().fZ); + + const auto sine = sin(rotation * 0.5); + + ASSERT_FLOAT_EQ(0, decomposition.rotation().fData[0]); + ASSERT_FLOAT_EQ(0, decomposition.rotation().fData[1]); + ASSERT_FLOAT_EQ(sine, decomposition.rotation().fData[2]); + ASSERT_FLOAT_EQ(cos(rotation * 0.5), decomposition.rotation().fData[3]); +} diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index ef7d0ec60af689293dc9fa10011ffcea73aaaf9a..eb11e1cdb5ecf7302be7a0275421c9856eed8033 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -17,96 +17,172 @@ namespace flow { -static const int kRasterThreshold = 3; +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; + } -static bool isWorthRasterizing(SkPicture* picture) { // 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; } -RasterCache::RasterCache() : checkerboard_images_(false), weak_factory_(this) {} +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 surface = + context + ? SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, image_info) + : SkSurface::MakeRaster(image_info); + + if (!surface) { + FTL_DCHECK(false); + return {}; + } + + SkCanvas* canvas = surface->getCanvas(); -RasterCache::~RasterCache() {} + 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); + } -RasterCache::Entry::Entry() { - physical_size.setEmpty(); + 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 + }; } -RasterCache::Entry::~Entry() {} +static inline size_t ClampSize(size_t value, size_t min, size_t max) { + if (value > max) { + return max; + } -sk_sp RasterCache::GetPrerolledImage(GrContext* context, - SkPicture* picture, - const SkMatrix& ctm, - bool is_complex, - bool will_change) { - SkScalar scaleX = ctm.getScaleX(); - SkScalar scaleY = ctm.getScaleY(); + if (value < min) { + return min; + } - SkRect rect = picture->cullRect(); + return value; +} - SkISize physical_size = - SkISize::Make(rect.width() * scaleX, rect.height() * scaleY); +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 {}; + } - if (physical_size.isEmpty()) - return nullptr; + // 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); - Entry& entry = cache_[picture->uniqueID()]; + if (!matrix.IsValid()) { + // The matrix was singular. No point in going further. + return {}; + } - const bool size_matched = entry.physical_size == physical_size; + 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; - entry.physical_size = physical_size; - - if (!size_matched) { - entry.access_count = 1; - entry.image = nullptr; - return nullptr; - } - - entry.access_count++; - - if (entry.access_count >= kRasterThreshold) { - // Saturate at the threshhold. - entry.access_count = kRasterThreshold; - - if (!entry.image && !will_change && - (is_complex || isWorthRasterizing(picture))) { - TRACE_EVENT2("flutter", "Rasterize picture layer", "width", - std::to_string(physical_size.width()).c_str(), "height", - std::to_string(physical_size.height()).c_str()); - SkImageInfo info = SkImageInfo::MakeN32Premul(physical_size); - sk_sp surface = - SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info); - if (surface) { - SkCanvas* canvas = surface->getCanvas(); - canvas->clear(SK_ColorTRANSPARENT); - canvas->scale(scaleX, scaleY); - canvas->translate(-rect.left(), -rect.top()); - canvas->drawPicture(picture); - if (checkerboard_images_) { - DrawCheckerboard(canvas, rect); - } - entry.image = surface->makeImageSnapshot(); - } - } + + 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 dead; + std::vector::iterator> dead; for (auto it = cache_.begin(); it != cache_.end(); ++it) { Entry& entry = it->second; - if (!entry.used_this_frame) + if (!entry.used_this_frame) { dead.push_back(it); + } entry.used_this_frame = false; } - for (auto it : dead) + for (auto it : dead) { cache_.erase(it); + } } void RasterCache::Clear() { diff --git a/flow/raster_cache.h b/flow/raster_cache.h index 13b08e852f3c740281c04e3888a43cfc7cc85ffc..9601082cbc7226d3df805337c5aae72914022936 100644 --- a/flow/raster_cache.h +++ b/flow/raster_cache.h @@ -9,6 +9,7 @@ #include #include "flutter/flow/instrumentation.h" +#include "flutter/flow/raster_cache_key.h" #include "lib/ftl/macros.h" #include "lib/ftl/memory/weak_ptr.h" #include "third_party/skia/include/core/SkImage.h" @@ -16,16 +17,44 @@ namespace flow { +class RasterCacheResult { + public: + RasterCacheResult() + : source_rect_(SkRect::MakeEmpty()), + destination_rect_(SkRect::MakeEmpty()) {} + + RasterCacheResult(sk_sp image, SkRect source, SkRect destination) + : image_(std::move(image)), + source_rect_(source), + destination_rect_(destination) {} + + operator bool() const { return static_cast(image_); } + + bool is_valid() const { return static_cast(image_); }; + + sk_sp image() const { return image_; } + + const SkRect& source_rect() const { return source_rect_; } + + const SkRect& destination_rect() const { return destination_rect_; } + + private: + sk_sp image_; + SkRect source_rect_; + SkRect destination_rect_; +}; + class RasterCache { public: - RasterCache(); + explicit RasterCache(size_t threshold = 3); + ~RasterCache(); - sk_sp GetPrerolledImage(GrContext* context, - SkPicture* picture, - const SkMatrix& ctm, - bool is_complex, - bool will_change); + RasterCacheResult GetPrerolledImage(GrContext* context, + SkPicture* picture, + const SkMatrix& transformation_matrix, + bool is_complex, + bool will_change); void SweepAfterFrame(); void Clear(); @@ -34,18 +63,13 @@ class RasterCache { private: struct Entry { - Entry(); - ~Entry(); - bool used_this_frame = false; - int access_count = 0; - SkISize physical_size; - sk_sp image; + size_t access_count = 0; + RasterCacheResult image; }; - using Cache = std::unordered_map; - - Cache cache_; + const size_t threshold_; + RasterCacheKey::Map cache_; bool checkerboard_images_; ftl::WeakPtrFactory weak_factory_; diff --git a/flow/raster_cache_key.cc b/flow/raster_cache_key.cc new file mode 100644 index 0000000000000000000000000000000000000000..a75bfecfee8b2c1bdd19438b49faf960bd99852b --- /dev/null +++ b/flow/raster_cache_key.cc @@ -0,0 +1,11 @@ +// Copyright 2017 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_key.h" + +namespace flow { + +// + +} // namespace flow diff --git a/flow/raster_cache_key.h b/flow/raster_cache_key.h new file mode 100644 index 0000000000000000000000000000000000000000..f7ccb231d2a7ea3df9a951bfa2a15c40b002f547 --- /dev/null +++ b/flow/raster_cache_key.h @@ -0,0 +1,51 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FLOW_RASTER_CACHE_KEY_H_ +#define FLUTTER_FLOW_RASTER_CACHE_KEY_H_ + +#include +#include "flutter/flow/matrix_decomposition.h" +#include "lib/ftl/macros.h" +#include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/core/SkPicture.h" + +namespace flow { + +class RasterCacheKey { + public: + RasterCacheKey(const SkPicture& picture, const MatrixDecomposition& matrix) + : picture_id_(picture.uniqueID()), + scale_key_(SkISize::Make(matrix.scale().x() * 1e3, + matrix.scale().y() * 1e3)) {} + + uint32_t picture_id() const { return picture_id_; } + + const SkISize& scale_key() const { return scale_key_; } + + struct Hash { + std::size_t operator()(RasterCacheKey const& key) const { + return key.picture_id_; + } + }; + + struct Equal { + constexpr bool operator()(const RasterCacheKey& lhs, + const RasterCacheKey& rhs) const { + return lhs.picture_id_ == rhs.picture_id_ && + lhs.scale_key_ == rhs.scale_key_; + } + }; + + template + using Map = std::unordered_map; + + private: + uint32_t picture_id_; + SkISize scale_key_; +}; + +} // namespace flow + +#endif // FLUTTER_FLOW_RASTER_CACHE_KEY_H_ diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc new file mode 100644 index 0000000000000000000000000000000000000000..361a06c34e914af02a4c5c207d2b83b284b57a40 --- /dev/null +++ b/flow/raster_cache_unittests.cc @@ -0,0 +1,92 @@ +// Copyright 2017 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 "third_party/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" + +sk_sp GetSamplePicture() { + SkPictureRecorder recorder; + recorder.beginRecording(SkRect::MakeWH(150, 100)); + SkPaint paint; + paint.setColor(SK_ColorRED); + recorder.getRecordingCanvas()->drawRect(SkRect::MakeXYWH(10, 10, 80, 80), + paint); + return recorder.finishRecordingAsPicture(); +} + +TEST(RasterCache, SimpleInitialization) { + flow::RasterCache cache; + ASSERT_TRUE(true); +} + +TEST(RasterCache, ThresholdIsRespected) { + size_t threshold = 3; + flow::RasterCache cache(threshold); + + SkMatrix matrix = SkMatrix::I(); + + auto picture = GetSamplePicture(); + + sk_sp image; + + ASSERT_FALSE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 1 + cache.SweepAfterFrame(); + ASSERT_FALSE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 2 + cache.SweepAfterFrame(); + ASSERT_TRUE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 3 + cache.SweepAfterFrame(); +} + +TEST(RasterCache, ThresholdIsRespectedWhenZero) { + size_t threshold = 0; + flow::RasterCache cache(threshold); + + SkMatrix matrix = SkMatrix::I(); + + auto picture = GetSamplePicture(); + + sk_sp image; + + ASSERT_FALSE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 1 + cache.SweepAfterFrame(); + ASSERT_FALSE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 2 + cache.SweepAfterFrame(); + ASSERT_FALSE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 3 + cache.SweepAfterFrame(); +} + +TEST(RasterCache, SweepsRemoveUnusedFrames) { + size_t threshold = 3; + flow::RasterCache cache(threshold); + + SkMatrix matrix = SkMatrix::I(); + + auto picture = GetSamplePicture(); + + sk_sp image; + + ASSERT_FALSE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 1 + cache.SweepAfterFrame(); + ASSERT_FALSE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 2 + cache.SweepAfterFrame(); + ASSERT_TRUE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 3 + cache.SweepAfterFrame(); + ASSERT_TRUE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 4 + cache.SweepAfterFrame(); + cache.SweepAfterFrame(); // Extra frame without a preroll image access. + ASSERT_FALSE( + cache.GetPrerolledImage(NULL, picture.get(), matrix, true, false)); // 5 +} diff --git a/travis/licenses_golden/licenses_flutter b/travis/licenses_golden/licenses_flutter index f0001906c3dfe0b63b54def1c8c3b7abd467d5d3..fd9b1aec7963866d1fbf43ff6e83f4ad7183b274 100644 --- a/travis/licenses_golden/licenses_flutter +++ b/travis/licenses_golden/licenses_flutter @@ -1365,10 +1365,18 @@ FILE: ../../../flutter/content_handler/vulkan_native_rasterizer.cc FILE: ../../../flutter/content_handler/vulkan_native_rasterizer.h FILE: ../../../flutter/content_handler/vulkan_rasterizer.cc FILE: ../../../flutter/content_handler/vulkan_rasterizer.h +FILE: ../../../flutter/flow/debug_print.cc +FILE: ../../../flutter/flow/debug_print.h FILE: ../../../flutter/flow/layers/physical_model_layer.cc FILE: ../../../flutter/flow/layers/physical_model_layer.h +FILE: ../../../flutter/flow/matrix_decomposition.cc +FILE: ../../../flutter/flow/matrix_decomposition.h +FILE: ../../../flutter/flow/matrix_decomposition_unittests.cc FILE: ../../../flutter/flow/paint_utils.cc FILE: ../../../flutter/flow/paint_utils.h +FILE: ../../../flutter/flow/raster_cache_key.cc +FILE: ../../../flutter/flow/raster_cache_key.h +FILE: ../../../flutter/flow/raster_cache_unittests.cc FILE: ../../../flutter/fml/icu_util.cc FILE: ../../../flutter/fml/icu_util.h FILE: ../../../flutter/fml/mapping.cc