Commit 0c568dc7 authored by Khushal's avatar Khushal Committed by Commit Bot

cc: Support animated images in PaintFilters.

Since we don't analyze or decode images in PaintFilters in cc, they
completely skip our image decode and animation stack. This change
ensures that we analyze images in these cases to track their
invalidation rects and register them with the animation system, and
creates a snaphot of the filter, if it has an animated image, during
raster. This ensures we use the correct frame for this image.

Since its not performant to re-create the filter graph for every
raster, the change is restricted to filters with animated images. In
addition, since its not easy to figure out the final scale for these
images, we decode them at the original size.

This also (hopefully) ties the last loose end where an image in a
paint recording could be skipped in cc.

R=chrishtr@chromium.org, enne@chromium.org

Bug: 821031
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.android:android_optional_gpu_tests_rel
Change-Id: I4a0ecb1c6dff8b816861ffbcf68bdd2cd5aa5973
Reviewed-on: https://chromium-review.googlesource.com/965442Reviewed-by: default avatarenne <enne@chromium.org>
Commit-Queue: Khushal <khushalsagar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#544614}
parent 82d8058f
...@@ -616,6 +616,7 @@ cc_test("cc_unittests") { ...@@ -616,6 +616,7 @@ cc_test("cc_unittests") {
"paint/display_item_list_unittest.cc", "paint/display_item_list_unittest.cc",
"paint/filter_operations_unittest.cc", "paint/filter_operations_unittest.cc",
"paint/oop_pixeltest.cc", "paint/oop_pixeltest.cc",
"paint/paint_filter_unittest.cc",
"paint/paint_image_unittest.cc", "paint/paint_image_unittest.cc",
"paint/paint_op_buffer_unittest.cc", "paint/paint_op_buffer_unittest.cc",
"paint/paint_op_helper_unittest.cc", "paint/paint_op_helper_unittest.cc",
......
...@@ -24,6 +24,7 @@ cc_component("paint") { ...@@ -24,6 +24,7 @@ cc_component("paint") {
"filter_operation.h", "filter_operation.h",
"filter_operations.cc", "filter_operations.cc",
"filter_operations.h", "filter_operations.h",
"image_analysis_state.h",
"image_animation_count.h", "image_animation_count.h",
"image_id.h", "image_id.h",
"image_provider.cc", "image_provider.cc",
......
...@@ -15,8 +15,8 @@ DecodeStashingImageProvider::~DecodeStashingImageProvider() = default; ...@@ -15,8 +15,8 @@ DecodeStashingImageProvider::~DecodeStashingImageProvider() = default;
ImageProvider::ScopedDecodedDrawImage ImageProvider::ScopedDecodedDrawImage
DecodeStashingImageProvider::GetDecodedDrawImage(const DrawImage& draw_image) { DecodeStashingImageProvider::GetDecodedDrawImage(const DrawImage& draw_image) {
auto decode = source_provider_->GetDecodedDrawImage(draw_image); auto decode = source_provider_->GetDecodedDrawImage(draw_image);
if (!decode) if (!decode.needs_unlock())
return ScopedDecodedDrawImage(); return decode;
// No need to add any destruction callback to the returned image. The images // No need to add any destruction callback to the returned image. The images
// decoded here match the lifetime of this provider. // decoded here match the lifetime of this provider.
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "cc/paint/paint_filter.h"
#include "cc/paint/paint_op_buffer.h" #include "cc/paint/paint_op_buffer.h"
#include "third_party/skia/include/utils/SkNoDrawCanvas.h" #include "third_party/skia/include/utils/SkNoDrawCanvas.h"
#include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/rect_conversions.h"
...@@ -124,6 +125,26 @@ class DiscardableImageGenerator { ...@@ -124,6 +125,26 @@ class DiscardableImageGenerator {
} }
private: private:
class ImageGatheringProvider : public ImageProvider {
public:
ImageGatheringProvider(DiscardableImageGenerator* generator,
const gfx::Rect& op_rect)
: generator_(generator), op_rect_(op_rect) {}
~ImageGatheringProvider() override = default;
ScopedDecodedDrawImage GetDecodedDrawImage(
const DrawImage& draw_image) override {
generator_->AddImage(draw_image.paint_image(),
SkRect::Make(draw_image.src_rect()), op_rect_,
SkMatrix::I(), draw_image.filter_quality());
return ScopedDecodedDrawImage();
}
private:
DiscardableImageGenerator* generator_;
gfx::Rect op_rect_;
};
// Adds discardable images from |buffer| to the set of images tracked by // Adds discardable images from |buffer| to the set of images tracked by
// this generator. If |buffer| is being used in a DrawOp that requires // this generator. If |buffer| is being used in a DrawOp that requires
// rasterization of the buffer as a pre-processing step for execution of the // rasterization of the buffer as a pre-processing step for execution of the
...@@ -144,12 +165,13 @@ class DiscardableImageGenerator { ...@@ -144,12 +165,13 @@ class DiscardableImageGenerator {
// TODO(khushalsagar): Optimize out save/restore blocks if there are no // TODO(khushalsagar): Optimize out save/restore blocks if there are no
// images in the draw ops between them. // images in the draw ops between them.
for (auto* op : PaintOpBuffer::Iterator(buffer)) { for (auto* op : PaintOpBuffer::Iterator(buffer)) {
if (!op->IsDrawOp()) { // We need to play non-draw ops on the SkCanvas since they can affect the
// transform/clip state.
if (!op->IsDrawOp())
op->Raster(canvas, params); op->Raster(canvas, params);
if (!PaintOp::OpHasDiscardableImages(op))
continue; continue;
} else if (!PaintOp::OpHasDiscardableImages(op)) {
continue;
}
gfx::Rect op_rect; gfx::Rect op_rect;
base::Optional<gfx::Rect> local_op_rect; base::Optional<gfx::Rect> local_op_rect;
...@@ -202,9 +224,10 @@ class DiscardableImageGenerator { ...@@ -202,9 +224,10 @@ class DiscardableImageGenerator {
gfx::Rect transformed_rect; gfx::Rect transformed_rect;
SkRect op_rect; SkRect op_rect;
if (!PaintOp::GetBounds(op, &op_rect)) { if (!op->IsDrawOp() || !PaintOp::GetBounds(op, &op_rect)) {
// If we can't provide a conservative bounding rect for the op, assume it // If we can't provide a conservative bounding rect for the op, assume it
// covers the complete current clip. // covers the complete current clip.
// TODO(khushalsagar): See if we can do something better for non-draw ops.
transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(clip_rect)); transformed_rect = gfx::ToEnclosingRect(gfx::SkRectToRectF(clip_rect));
} else { } else {
const PaintFlags* flags = const PaintFlags* flags =
...@@ -247,34 +270,48 @@ class DiscardableImageGenerator { ...@@ -247,34 +270,48 @@ class DiscardableImageGenerator {
void AddImageFromFlags(const gfx::Rect& op_rect, void AddImageFromFlags(const gfx::Rect& op_rect,
const PaintFlags& flags, const PaintFlags& flags,
const SkMatrix& ctm) { const SkMatrix& ctm) {
if (!flags.getShader()) AddImageFromShader(op_rect, flags.getShader(), ctm,
flags.getFilterQuality());
AddImageFromFilter(op_rect, flags.getImageFilter().get());
}
void AddImageFromShader(const gfx::Rect& op_rect,
const PaintShader* shader,
const SkMatrix& ctm,
SkFilterQuality filter_quality) {
if (!shader || !shader->has_discardable_images())
return; return;
if (flags.getShader()->shader_type() == PaintShader::Type::kImage) { if (shader->shader_type() == PaintShader::Type::kImage) {
const PaintImage& paint_image = flags.getShader()->paint_image(); const PaintImage& paint_image = shader->paint_image();
SkMatrix matrix = ctm; SkMatrix matrix = ctm;
matrix.postConcat(flags.getShader()->GetLocalMatrix()); matrix.postConcat(shader->GetLocalMatrix());
AddImage(paint_image, AddImage(paint_image,
SkRect::MakeWH(paint_image.width(), paint_image.height()), SkRect::MakeWH(paint_image.width(), paint_image.height()),
op_rect, matrix, flags.getFilterQuality()); op_rect, matrix, filter_quality);
} else if (flags.getShader()->shader_type() == return;
PaintShader::Type::kPaintRecord && }
flags.getShader()->paint_record()->HasDiscardableImages()) {
if (shader->shader_type() == PaintShader::Type::kPaintRecord) {
// For record backed shaders, only analyze them if they have animated
// images.
if (shader->image_analysis_state() ==
ImageAnalysisState::kNoAnimatedImages) {
return;
}
SkRect scaled_tile_rect; SkRect scaled_tile_rect;
if (!flags.getShader()->GetRasterizationTileRect(ctm, if (!shader->GetRasterizationTileRect(ctm, &scaled_tile_rect)) {
&scaled_tile_rect)) {
return; return;
} }
PaintTrackingCanvas canvas(scaled_tile_rect.width(), PaintTrackingCanvas canvas(scaled_tile_rect.width(),
scaled_tile_rect.height()); scaled_tile_rect.height());
canvas.setMatrix(SkMatrix::MakeRectToRect(flags.getShader()->tile(), canvas.setMatrix(SkMatrix::MakeRectToRect(
scaled_tile_rect, shader->tile(), scaled_tile_rect, SkMatrix::kFill_ScaleToFit));
SkMatrix::kFill_ScaleToFit)); base::AutoReset<bool> auto_reset(&only_gather_animated_images_, true);
base::AutoReset<bool> auto_reset(&iterating_record_shaders_, true);
size_t prev_image_set_size = image_set_.size(); size_t prev_image_set_size = image_set_.size();
GatherDiscardableImages(flags.getShader()->paint_record().get(), &op_rect, GatherDiscardableImages(shader->paint_record().get(), &op_rect, &canvas);
&canvas);
// We only track animated images for PaintShaders. If we added any entry // We only track animated images for PaintShaders. If we added any entry
// to the |image_set_|, this shader any has animated images. // to the |image_set_|, this shader any has animated images.
...@@ -282,9 +319,29 @@ class DiscardableImageGenerator { ...@@ -282,9 +319,29 @@ class DiscardableImageGenerator {
// PaintShader here since the analysis is done on the main thread, before // PaintShader here since the analysis is done on the main thread, before
// the PaintOpBuffer is used for rasterization. // the PaintOpBuffer is used for rasterization.
DCHECK_GE(image_set_.size(), prev_image_set_size); DCHECK_GE(image_set_.size(), prev_image_set_size);
if (image_set_.size() > prev_image_set_size) const bool has_animated_images = image_set_.size() > prev_image_set_size;
const_cast<PaintShader*>(flags.getShader())->set_has_animated_images(); const_cast<PaintShader*>(shader)->set_has_animated_images(
has_animated_images);
}
}
void AddImageFromFilter(const gfx::Rect& op_rect, const PaintFilter* filter) {
// Only analyze filters if they have animated images.
if (!filter || !filter->has_discardable_images() ||
filter->image_analysis_state() ==
ImageAnalysisState::kNoAnimatedImages) {
return;
} }
base::AutoReset<bool> auto_reset(&only_gather_animated_images_, true);
size_t prev_image_set_size = image_set_.size();
ImageGatheringProvider image_provider(this, op_rect);
filter->SnapshotWithImages(&image_provider);
DCHECK_GE(image_set_.size(), prev_image_set_size);
const bool has_animated_images = image_set_.size() > prev_image_set_size;
const_cast<PaintFilter*>(filter)->set_has_animated_images(
has_animated_images);
} }
void AddImage(PaintImage paint_image, void AddImage(PaintImage paint_image,
...@@ -335,7 +392,8 @@ class DiscardableImageGenerator { ...@@ -335,7 +392,8 @@ class DiscardableImageGenerator {
// are animated. We defer decoding of images in record shaders to skia, but // are animated. We defer decoding of images in record shaders to skia, but
// we still need to track animated images to invalidate and advance the // we still need to track animated images to invalidate and advance the
// animation in cc. // animation in cc.
bool add_image = !iterating_record_shaders_ || paint_image.ShouldAnimate(); bool add_image =
!only_gather_animated_images_ || paint_image.ShouldAnimate();
if (add_image) { if (add_image) {
image_set_.emplace_back( image_set_.emplace_back(
DrawImage(std::move(paint_image), src_irect, filter_quality, matrix), DrawImage(std::move(paint_image), src_irect, filter_quality, matrix),
...@@ -348,7 +406,7 @@ class DiscardableImageGenerator { ...@@ -348,7 +406,7 @@ class DiscardableImageGenerator {
std::vector<DiscardableImageMap::AnimatedImageMetadata> std::vector<DiscardableImageMap::AnimatedImageMetadata>
animated_images_metadata_; animated_images_metadata_;
base::flat_map<PaintImage::Id, PaintImage::DecodingMode> decoding_mode_map_; base::flat_map<PaintImage::Id, PaintImage::DecodingMode> decoding_mode_map_;
bool iterating_record_shaders_ = false; bool only_gather_animated_images_ = false;
// Statistics about the number of images and pixels that will require color // Statistics about the number of images and pixels that will require color
// conversion if the target color space is not sRGB. // conversion if the target color space is not sRGB.
......
...@@ -756,9 +756,11 @@ TEST_F(DiscardableImageMapTest, CapturesImagesInPaintRecordShaders) { ...@@ -756,9 +756,11 @@ TEST_F(DiscardableImageMapTest, CapturesImagesInPaintRecordShaders) {
display_list->EndPaintOfUnpaired(visible_rect); display_list->EndPaintOfUnpaired(visible_rect);
display_list->Finalize(); display_list->Finalize();
EXPECT_FALSE(flags.getShader()->has_animated_images()); EXPECT_EQ(flags.getShader()->image_analysis_state(),
ImageAnalysisState::kNoAnalysis);
display_list->GenerateDiscardableImagesMetadata(); display_list->GenerateDiscardableImagesMetadata();
EXPECT_TRUE(flags.getShader()->has_animated_images()); EXPECT_EQ(flags.getShader()->image_analysis_state(),
ImageAnalysisState::kAnimatedImages);
const auto& image_map = display_list->discardable_image_map(); const auto& image_map = display_list->discardable_image_map();
// The image rect is set to the rect for the DrawRectOp, and only animated // The image rect is set to the rect for the DrawRectOp, and only animated
...@@ -776,6 +778,75 @@ TEST_F(DiscardableImageMapTest, CapturesImagesInPaintRecordShaders) { ...@@ -776,6 +778,75 @@ TEST_F(DiscardableImageMapTest, CapturesImagesInPaintRecordShaders) {
EXPECT_EQ(SkSize::Make(4.f, 4.f), draw_images[0].scale); EXPECT_EQ(SkSize::Make(4.f, 4.f), draw_images[0].scale);
} }
TEST_F(DiscardableImageMapTest, CapturesImagesInPaintFilters) {
// Create the record to use in the filter.
auto filter_record = sk_make_sp<PaintOpBuffer>();
PaintImage static_image = CreateDiscardablePaintImage(gfx::Size(100, 100));
filter_record->push<DrawImageOp>(static_image, 0.f, 0.f, nullptr);
std::vector<FrameMetadata> frames = {
FrameMetadata(true, base::TimeDelta::FromMilliseconds(1)),
FrameMetadata(true, base::TimeDelta::FromMilliseconds(1))};
PaintImage animated_image = CreateAnimatedImage(gfx::Size(100, 100), frames);
filter_record->push<DrawImageOp>(animated_image, 0.f, 0.f, nullptr);
gfx::Rect visible_rect(500, 500);
scoped_refptr<DisplayItemList> display_list = new DisplayItemList();
display_list->StartPaint();
PaintFlags flags;
flags.setImageFilter(sk_make_sp<RecordPaintFilter>(
filter_record, SkRect::MakeWH(100.f, 100.f)));
display_list->push<DrawRectOp>(SkRect::MakeWH(200, 200), flags);
display_list->EndPaintOfUnpaired(visible_rect);
display_list->Finalize();
EXPECT_EQ(flags.getImageFilter()->image_analysis_state(),
ImageAnalysisState::kNoAnalysis);
display_list->GenerateDiscardableImagesMetadata();
EXPECT_EQ(flags.getImageFilter()->image_analysis_state(),
ImageAnalysisState::kAnimatedImages);
const auto& image_map = display_list->discardable_image_map();
// The image rect is set to the rect for the DrawRectOp, and only animated
// images in a filter are tracked.
std::vector<PositionScaleDrawImage> draw_images =
GetDiscardableImagesInRect(image_map, visible_rect);
std::vector<gfx::Rect> inset_rects = InsetImageRects(draw_images);
ASSERT_EQ(draw_images.size(), 1u);
EXPECT_EQ(draw_images[0].image, animated_image);
// The position of the image is the position of the DrawRectOp that uses the
// filter.
EXPECT_EQ(gfx::Rect(200, 200), inset_rects[0]);
// Images in a filter are decoded at the original size.
EXPECT_EQ(SkSize::Make(1.f, 1.f), draw_images[0].scale);
}
TEST_F(DiscardableImageMapTest, CapturesImagesInSaveLayers) {
PaintFlags flags;
PaintImage image = CreateDiscardablePaintImage(gfx::Size(100, 100));
flags.setShader(PaintShader::MakeImage(image, SkShader::kClamp_TileMode,
SkShader::kClamp_TileMode, nullptr));
gfx::Rect visible_rect(500, 500);
scoped_refptr<DisplayItemList> display_list = new DisplayItemList();
display_list->StartPaint();
display_list->push<SaveLayerOp>(nullptr, &flags);
display_list->push<DrawColorOp>(SK_ColorBLUE, SkBlendMode::kSrc);
display_list->EndPaintOfUnpaired(visible_rect);
display_list->Finalize();
display_list->GenerateDiscardableImagesMetadata();
const auto& image_map = display_list->discardable_image_map();
std::vector<PositionScaleDrawImage> draw_images =
GetDiscardableImagesInRect(image_map, visible_rect);
std::vector<gfx::Rect> inset_rects = InsetImageRects(draw_images);
ASSERT_EQ(draw_images.size(), 1u);
EXPECT_EQ(draw_images[0].image, image);
EXPECT_EQ(gfx::Rect(500, 500), inset_rects[0]);
EXPECT_EQ(SkSize::Make(1.f, 1.f), draw_images[0].scale);
}
TEST_F(DiscardableImageMapTest, EmbeddedShaderWithAnimatedImages) { TEST_F(DiscardableImageMapTest, EmbeddedShaderWithAnimatedImages) {
// Create the record with animated image to use in the shader. // Create the record with animated image to use in the shader.
SkRect tile = SkRect::MakeWH(100, 100); SkRect tile = SkRect::MakeWH(100, 100);
...@@ -806,8 +877,10 @@ TEST_F(DiscardableImageMapTest, EmbeddedShaderWithAnimatedImages) { ...@@ -806,8 +877,10 @@ TEST_F(DiscardableImageMapTest, EmbeddedShaderWithAnimatedImages) {
display_list->EndPaintOfUnpaired(visible_rect); display_list->EndPaintOfUnpaired(visible_rect);
display_list->Finalize(); display_list->Finalize();
display_list->GenerateDiscardableImagesMetadata(); display_list->GenerateDiscardableImagesMetadata();
EXPECT_TRUE(shader_with_image->has_animated_images()); EXPECT_EQ(shader_with_image->image_analysis_state(),
EXPECT_TRUE(shader_with_shader_with_image->has_animated_images()); ImageAnalysisState::kAnimatedImages);
EXPECT_EQ(shader_with_shader_with_image->image_analysis_state(),
ImageAnalysisState::kAnimatedImages);
} }
TEST_F(DiscardableImageMapTest, DecodingModeHintsBasic) { TEST_F(DiscardableImageMapTest, DecodingModeHintsBasic) {
......
// 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 CC_PAINT_IMAGE_ANALYSIS_STATE_H_
#define CC_PAINT_IMAGE_ANALYSIS_STATE_H_
namespace cc {
enum class ImageAnalysisState {
kNoAnalysis,
kAnimatedImages,
kNoAnimatedImages,
};
} // namespace cc
#endif // CC_PAINT_IMAGE_ANALYSIS_STATE_H_
This diff is collapsed.
This diff is collapsed.
// Copyright 2018 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/paint/paint_filter.h"
#include "cc/paint/paint_op_buffer.h"
#include "cc/test/skia_common.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/effects/SkLumaColorFilter.h"
namespace cc {
namespace {
class MockImageProvider : public ImageProvider {
public:
MockImageProvider() = default;
~MockImageProvider() override = default;
ScopedDecodedDrawImage GetDecodedDrawImage(
const DrawImage& draw_image) override {
image_count_++;
return ScopedDecodedDrawImage(DecodedDrawImage(
CreateBitmapImage(gfx::Size(10, 10)).GetSkImage(), SkSize::MakeEmpty(),
SkSize::Make(1.0f, 1.0f), draw_image.filter_quality(), true));
}
int image_count_ = 0;
};
sk_sp<PaintFilter> CreateTestFilter(PaintFilter::Type filter_type,
bool has_discardable_images) {
PaintImage image;
if (has_discardable_images)
image = CreateDiscardablePaintImage(gfx::Size(100, 100));
else
image = CreateBitmapImage(gfx::Size(100, 100));
auto image_filter = sk_make_sp<ImagePaintFilter>(
image, SkRect::MakeWH(100.f, 100.f), SkRect::MakeWH(100.f, 100.f),
kNone_SkFilterQuality);
auto record = sk_make_sp<PaintOpBuffer>();
record->push<DrawImageOp>(image, 0.f, 0.f, nullptr);
auto record_filter =
sk_make_sp<RecordPaintFilter>(record, SkRect::MakeWH(100.f, 100.f));
SkImageFilter::CropRect crop_rect(SkRect::MakeWH(100.f, 100.f));
switch (filter_type) {
case PaintFilter::Type::kNullFilter:
NOTREACHED();
return nullptr;
case PaintFilter::Type::kColorFilter:
return sk_make_sp<ColorFilterPaintFilter>(SkLumaColorFilter::Make(),
image_filter, &crop_rect);
case PaintFilter::Type::kBlur:
return sk_make_sp<BlurPaintFilter>(0.1f, 0.2f,
SkBlurImageFilter::kClamp_TileMode,
record_filter, &crop_rect);
case PaintFilter::Type::kDropShadow:
return sk_make_sp<DropShadowPaintFilter>(
0.1, 0.2f, 0.3f, 0.4f, SK_ColorWHITE,
SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode, image_filter,
&crop_rect);
case PaintFilter::Type::kMagnifier:
return sk_make_sp<MagnifierPaintFilter>(SkRect::MakeWH(100.f, 100.f),
0.1f, record_filter, &crop_rect);
case PaintFilter::Type::kCompose:
return sk_make_sp<ComposePaintFilter>(image_filter, record_filter);
case PaintFilter::Type::kAlphaThreshold:
return sk_make_sp<AlphaThresholdPaintFilter>(
SkRegion(SkIRect::MakeWH(100, 100)), 0.1f, 0.2f, image_filter,
&crop_rect);
case PaintFilter::Type::kXfermode:
return sk_make_sp<XfermodePaintFilter>(SkBlendMode::kSrc, image_filter,
record_filter, &crop_rect);
case PaintFilter::Type::kArithmetic:
return sk_make_sp<ArithmeticPaintFilter>(0.1f, 0.2f, 0.3f, 0.4f, true,
image_filter, record_filter,
&crop_rect);
case PaintFilter::Type::kMatrixConvolution: {
SkScalar scalars[9] = {1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f};
return sk_make_sp<MatrixConvolutionPaintFilter>(
SkISize::Make(3, 3), scalars, 0.1f, 0.2f, SkIPoint::Make(2, 2),
SkMatrixConvolutionImageFilter::TileMode::kRepeat_TileMode, false,
image_filter, &crop_rect);
}
case PaintFilter::Type::kDisplacementMapEffect:
return sk_make_sp<DisplacementMapEffectPaintFilter>(
SkDisplacementMapEffect::ChannelSelectorType::kR_ChannelSelectorType,
SkDisplacementMapEffect::ChannelSelectorType::kR_ChannelSelectorType,
0.1f, image_filter, record_filter, &crop_rect);
case PaintFilter::Type::kImage:
return image_filter;
case PaintFilter::Type::kPaintRecord:
return record_filter;
case PaintFilter::Type::kMerge: {
sk_sp<PaintFilter> filters[2] = {image_filter, record_filter};
return sk_make_sp<MergePaintFilter>(filters, 2, &crop_rect);
}
case PaintFilter::Type::kMorphology:
return sk_make_sp<MorphologyPaintFilter>(
MorphologyPaintFilter::MorphType::kDilate, 1, 2, image_filter,
&crop_rect);
case PaintFilter::Type::kOffset:
return sk_make_sp<OffsetPaintFilter>(0.1f, 0.2f, image_filter,
&crop_rect);
case PaintFilter::Type::kTile:
return sk_make_sp<TilePaintFilter>(SkRect::MakeWH(100.f, 100.f),
SkRect::MakeWH(200.f, 200.f),
record_filter);
case PaintFilter::Type::kTurbulence:
return sk_make_sp<TurbulencePaintFilter>(
TurbulencePaintFilter::TurbulenceType::kTurbulence, 0.1f, 0.2f, 2,
0.3f, nullptr, &crop_rect);
case PaintFilter::Type::kPaintFlags: {
PaintFlags flags;
flags.setShader(PaintShader::MakeImage(image, SkShader::kClamp_TileMode,
SkShader::kClamp_TileMode,
nullptr));
return sk_make_sp<PaintFlagsPaintFilter>(flags, &crop_rect);
}
case PaintFilter::Type::kMatrix:
return sk_make_sp<MatrixPaintFilter>(SkMatrix::I(), kNone_SkFilterQuality,
record_filter);
case PaintFilter::Type::kLightingDistant:
return sk_make_sp<LightingDistantPaintFilter>(
PaintFilter::LightingType::kDiffuse, SkPoint3::Make(0.1f, 0.2f, 0.3f),
SK_ColorWHITE, 0.1f, 0.2f, 0.3f, image_filter, &crop_rect);
case PaintFilter::Type::kLightingPoint:
return sk_make_sp<LightingPointPaintFilter>(
PaintFilter::LightingType::kDiffuse, SkPoint3::Make(0.1f, 0.2f, 0.3f),
SK_ColorWHITE, 0.1f, 0.2f, 0.3f, record_filter, &crop_rect);
case PaintFilter::Type::kLightingSpot:
return sk_make_sp<LightingSpotPaintFilter>(
PaintFilter::LightingType::kDiffuse, SkPoint3::Make(0.1f, 0.2f, 0.3f),
SkPoint3::Make(0.4f, 0.5f, 0.6f), 0.1f, 0.2f, SK_ColorWHITE, 0.4f,
0.5f, 0.6f, image_filter, &crop_rect);
}
NOTREACHED();
return nullptr;
}
} // namespace
class PaintFilterTest : public ::testing::TestWithParam<uint8_t> {
public:
PaintFilter::Type GetParamType() const {
return static_cast<PaintFilter::Type>(GetParam());
}
};
INSTANTIATE_TEST_CASE_P(
P,
PaintFilterTest,
::testing::Range(static_cast<uint8_t>(PaintFilter::Type::kColorFilter),
static_cast<uint8_t>(PaintFilter::Type::kMaxFilterType)));
TEST_P(PaintFilterTest, HasDiscardableImagesYes) {
// TurbulencePaintFilter can not embed images.
if (GetParamType() == PaintFilter::Type::kTurbulence)
return;
EXPECT_TRUE(CreateTestFilter(GetParamType(), true)->has_discardable_images())
<< PaintFilter::TypeToString(GetParamType());
}
TEST_P(PaintFilterTest, HasDiscardableImagesNo) {
EXPECT_FALSE(
CreateTestFilter(GetParamType(), false)->has_discardable_images())
<< PaintFilter::TypeToString(GetParamType());
}
TEST_P(PaintFilterTest, SnapshotWithImages) {
auto filter = CreateTestFilter(GetParamType(), true);
MockImageProvider image_provider;
auto snapshot_filter = filter->SnapshotWithImages(&image_provider);
if (GetParamType() != PaintFilter::Type::kTurbulence) {
// TurbulencePaintFilter can not embed images.
EXPECT_GT(image_provider.image_count_, 0)
<< PaintFilter::TypeToString(GetParamType());
}
EXPECT_EQ(*filter, *snapshot_filter)
<< PaintFilter::TypeToString(GetParamType());
}
TEST(PaintFilterTest, ImageAnalysisState) {
auto filter = CreateTestFilter(PaintFilter::Type::kImage, true);
EXPECT_EQ(filter->image_analysis_state(), ImageAnalysisState::kNoAnalysis);
filter->set_has_animated_images(true);
EXPECT_EQ(filter->image_analysis_state(),
ImageAnalysisState::kAnimatedImages);
filter->set_has_animated_images(false);
EXPECT_EQ(filter->image_analysis_state(),
ImageAnalysisState::kNoAnimatedImages);
}
} // namespace cc
...@@ -199,13 +199,8 @@ bool PaintFlags::operator==(const PaintFlags& other) const { ...@@ -199,13 +199,8 @@ bool PaintFlags::operator==(const PaintFlags& other) const {
} }
bool PaintFlags::HasDiscardableImages() const { bool PaintFlags::HasDiscardableImages() const {
if (!shader_) return (shader_ && shader_->has_discardable_images()) ||
return false; (image_filter_ && image_filter_->has_discardable_images());
else if (shader_->shader_type() == PaintShader::Type::kImage)
return shader_->paint_image().IsLazyGenerated();
else if (shader_->shader_type() == PaintShader::Type::kPaintRecord)
return shader_->paint_record()->HasDiscardableImages();
return false;
} }
size_t PaintFlags::GetSerializedSize() const { size_t PaintFlags::GetSerializedSize() const {
......
...@@ -1893,7 +1893,8 @@ bool PaintOp::GetBounds(const PaintOp* op, SkRect* rect) { ...@@ -1893,7 +1893,8 @@ bool PaintOp::GetBounds(const PaintOp* op, SkRect* rect) {
// static // static
bool PaintOp::QuickRejectDraw(const PaintOp* op, const SkCanvas* canvas) { bool PaintOp::QuickRejectDraw(const PaintOp* op, const SkCanvas* canvas) {
DCHECK(op->IsDrawOp()); if (!op->IsDrawOp())
return false;
SkRect rect; SkRect rect;
if (!PaintOp::GetBounds(op, &rect)) if (!PaintOp::GetBounds(op, &rect))
...@@ -1937,7 +1938,7 @@ void PaintOp::DestroyThis() { ...@@ -1937,7 +1938,7 @@ void PaintOp::DestroyThis() {
} }
bool PaintOpWithFlags::HasDiscardableImagesFromFlags() const { bool PaintOpWithFlags::HasDiscardableImagesFromFlags() const {
return IsDrawOp() && flags.HasDiscardableImages(); return flags.HasDiscardableImages();
} }
void PaintOpWithFlags::RasterWithFlags(SkCanvas* canvas, void PaintOpWithFlags::RasterWithFlags(SkCanvas* canvas,
......
...@@ -3101,7 +3101,7 @@ TEST(PaintOpBufferTest, RecordShadersSerializeScaledImages) { ...@@ -3101,7 +3101,7 @@ TEST(PaintOpBufferTest, RecordShadersSerializeScaledImages) {
record_buffer, SkRect::MakeWH(10.f, 10.f), record_buffer, SkRect::MakeWH(10.f, 10.f),
SkShader::TileMode::kRepeat_TileMode, SkShader::TileMode::kRepeat_TileMode,
SkShader::TileMode::kRepeat_TileMode, nullptr); SkShader::TileMode::kRepeat_TileMode, nullptr);
shader->set_has_animated_images(); shader->set_has_animated_images(true);
auto buffer = sk_make_sp<PaintOpBuffer>(); auto buffer = sk_make_sp<PaintOpBuffer>();
buffer->push<ScaleOp>(0.5f, 0.8f); buffer->push<ScaleOp>(0.5f, 0.8f);
PaintFlags flags; PaintFlags flags;
......
...@@ -9,18 +9,23 @@ ...@@ -9,18 +9,23 @@
namespace cc { namespace cc {
sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record, const SkRect& bounds) { sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record,
const SkRect& bounds,
ImageProvider* image_provider) {
SkPictureRecorder recorder; SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(bounds); SkCanvas* canvas = recorder.beginRecording(bounds);
record->Playback(canvas); PlaybackParams params(image_provider);
record->Playback(canvas, params);
return recorder.finishRecordingAsPicture(); return recorder.finishRecordingAsPicture();
} }
sk_sp<const SkPicture> ToSkPicture(sk_sp<const PaintRecord> record, sk_sp<const SkPicture> ToSkPicture(sk_sp<const PaintRecord> record,
const SkRect& bounds) { const SkRect& bounds,
ImageProvider* image_provider) {
SkPictureRecorder recorder; SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(bounds); SkCanvas* canvas = recorder.beginRecording(bounds);
record->Playback(canvas); PlaybackParams params(image_provider);
record->Playback(canvas, params);
return recorder.finishRecordingAsPicture(); return recorder.finishRecordingAsPicture();
} }
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkPicture.h"
namespace cc { namespace cc {
class ImageProvider;
// TODO(enne): Don't want to rename the world for this. Using these as the // TODO(enne): Don't want to rename the world for this. Using these as the
// same types for now prevents an extra allocation. Probably PaintRecord // same types for now prevents an extra allocation. Probably PaintRecord
...@@ -17,12 +18,15 @@ namespace cc { ...@@ -17,12 +18,15 @@ namespace cc {
using PaintRecord = PaintOpBuffer; using PaintRecord = PaintOpBuffer;
// TODO(enne): Remove these if possible, they are really expensive. // TODO(enne): Remove these if possible, they are really expensive.
CC_PAINT_EXPORT sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record, CC_PAINT_EXPORT sk_sp<SkPicture> ToSkPicture(
const SkRect& bounds); sk_sp<PaintRecord> record,
const SkRect& bounds,
ImageProvider* image_provider = nullptr);
CC_PAINT_EXPORT sk_sp<const SkPicture> ToSkPicture( CC_PAINT_EXPORT sk_sp<const SkPicture> ToSkPicture(
sk_sp<const PaintRecord> record, sk_sp<const PaintRecord> record,
const SkRect& bounds); const SkRect& bounds,
ImageProvider* image_provider = nullptr);
} // namespace cc } // namespace cc
......
...@@ -186,6 +186,11 @@ size_t PaintShader::GetSerializedSize(const PaintShader* shader) { ...@@ -186,6 +186,11 @@ size_t PaintShader::GetSerializedSize(const PaintShader* shader) {
PaintShader::PaintShader(Type type) : shader_type_(type) {} PaintShader::PaintShader(Type type) : shader_type_(type) {}
PaintShader::~PaintShader() = default; PaintShader::~PaintShader() = default;
bool PaintShader::has_discardable_images() const {
return (image_ && image_.IsLazyGenerated()) ||
(record_ && record_->HasDiscardableImages());
}
bool PaintShader::GetRasterizationTileRect(const SkMatrix& ctm, bool PaintShader::GetRasterizationTileRect(const SkMatrix& ctm,
SkRect* tile_rect) const { SkRect* tile_rect) const {
DCHECK_EQ(shader_type_, Type::kPaintRecord); DCHECK_EQ(shader_type_, Type::kPaintRecord);
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/optional.h" #include "base/optional.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "cc/paint/image_analysis_state.h"
#include "cc/paint/paint_export.h" #include "cc/paint/paint_export.h"
#include "cc/paint/paint_image.h" #include "cc/paint/paint_image.h"
#include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkImage.h"
...@@ -106,8 +107,16 @@ class CC_PAINT_EXPORT PaintShader : public SkRefCnt { ...@@ -106,8 +107,16 @@ class CC_PAINT_EXPORT PaintShader : public SkRefCnt {
~PaintShader() override; ~PaintShader() override;
void set_has_animated_images() { has_animated_images_ = true; } void set_has_animated_images(bool has_animated_images) {
bool has_animated_images() const { return has_animated_images_; } image_analysis_state_ = has_animated_images
? ImageAnalysisState::kAnimatedImages
: ImageAnalysisState::kNoAnimatedImages;
}
ImageAnalysisState image_analysis_state() const {
return image_analysis_state_;
}
bool has_discardable_images() const;
SkMatrix GetLocalMatrix() const { SkMatrix GetLocalMatrix() const {
return local_matrix_ ? *local_matrix_ : SkMatrix::I(); return local_matrix_ ? *local_matrix_ : SkMatrix::I();
...@@ -200,7 +209,7 @@ class CC_PAINT_EXPORT PaintShader : public SkRefCnt { ...@@ -200,7 +209,7 @@ class CC_PAINT_EXPORT PaintShader : public SkRefCnt {
// accesses to it are thread-safe. // accesses to it are thread-safe.
sk_sp<SkShader> cached_shader_; sk_sp<SkShader> cached_shader_;
bool has_animated_images_ = false; ImageAnalysisState image_analysis_state_ = ImageAnalysisState::kNoAnalysis;
DISALLOW_COPY_AND_ASSIGN(PaintShader); DISALLOW_COPY_AND_ASSIGN(PaintShader);
}; };
......
...@@ -84,7 +84,7 @@ TEST(PaintShaderTest, DecodePaintRecord) { ...@@ -84,7 +84,7 @@ TEST(PaintShaderTest, DecodePaintRecord) {
auto record_shader = PaintShader::MakePaintRecord( auto record_shader = PaintShader::MakePaintRecord(
record, SkRect::MakeWH(100, 100), SkShader::TileMode::kClamp_TileMode, record, SkRect::MakeWH(100, 100), SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode, &local_matrix); SkShader::TileMode::kClamp_TileMode, &local_matrix);
record_shader->set_has_animated_images(); record_shader->set_has_animated_images(true);
PaintOpBuffer buffer; PaintOpBuffer buffer;
PaintFlags flags; PaintFlags flags;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "cc/paint/scoped_raster_flags.h" #include "cc/paint/scoped_raster_flags.h"
#include "cc/paint/image_provider.h" #include "cc/paint/image_provider.h"
#include "cc/paint/paint_filter.h"
#include "cc/paint/paint_image_builder.h" #include "cc/paint/paint_image_builder.h"
namespace cc { namespace cc {
...@@ -15,20 +16,13 @@ ScopedRasterFlags::ScopedRasterFlags(const PaintFlags* flags, ...@@ -15,20 +16,13 @@ ScopedRasterFlags::ScopedRasterFlags(const PaintFlags* flags,
bool create_skia_shader) bool create_skia_shader)
: original_flags_(flags) { : original_flags_(flags) {
if (flags->HasDiscardableImages() && image_provider) { if (flags->HasDiscardableImages() && image_provider) {
DCHECK(flags->HasShader());
// TODO(khushalsagar): The decoding of images in PaintFlags here is a bit of // TODO(khushalsagar): The decoding of images in PaintFlags here is a bit of
// a mess. We decode image shaders at the correct scale but ignore that // a mess. We decode image shaders at the correct scale but ignore that
// during serialization and just use the original image. // during serialization and just use the original image.
decode_stashing_image_provider_.emplace(image_provider); decode_stashing_image_provider_.emplace(image_provider);
if (flags->getShader()->shader_type() == PaintShader::Type::kImage) { DecodeImageShader(ctm);
DecodeImageShader(ctm); DecodeRecordShader(ctm, create_skia_shader);
} else if (flags->getShader()->shader_type() == DecodeFilter();
PaintShader::Type::kPaintRecord) {
DecodeRecordShader(ctm, create_skia_shader);
} else {
NOTREACHED();
}
// We skip the op if any images fail to decode. // We skip the op if any images fail to decode.
if (decode_failed_) if (decode_failed_)
...@@ -46,6 +40,10 @@ ScopedRasterFlags::ScopedRasterFlags(const PaintFlags* flags, ...@@ -46,6 +40,10 @@ ScopedRasterFlags::ScopedRasterFlags(const PaintFlags* flags,
ScopedRasterFlags::~ScopedRasterFlags() = default; ScopedRasterFlags::~ScopedRasterFlags() = default;
void ScopedRasterFlags::DecodeImageShader(const SkMatrix& ctm) { void ScopedRasterFlags::DecodeImageShader(const SkMatrix& ctm) {
if (!flags()->HasShader() ||
flags()->getShader()->shader_type() != PaintShader::Type::kImage)
return;
const PaintImage& paint_image = flags()->getShader()->paint_image(); const PaintImage& paint_image = flags()->getShader()->paint_image();
SkMatrix matrix = flags()->getShader()->GetLocalMatrix(); SkMatrix matrix = flags()->getShader()->GetLocalMatrix();
...@@ -100,9 +98,14 @@ void ScopedRasterFlags::DecodeImageShader(const SkMatrix& ctm) { ...@@ -100,9 +98,14 @@ void ScopedRasterFlags::DecodeImageShader(const SkMatrix& ctm) {
void ScopedRasterFlags::DecodeRecordShader(const SkMatrix& ctm, void ScopedRasterFlags::DecodeRecordShader(const SkMatrix& ctm,
bool create_skia_shader) { bool create_skia_shader) {
if (!flags()->HasShader() ||
flags()->getShader()->shader_type() != PaintShader::Type::kPaintRecord)
return;
// TODO(khushalsagar): For OOP, we have to decode everything during // TODO(khushalsagar): For OOP, we have to decode everything during
// serialization. This will force us to use original sized decodes. // serialization. This will force us to use original sized decodes.
if (!flags()->getShader()->has_animated_images()) if (flags()->getShader()->image_analysis_state() !=
ImageAnalysisState::kAnimatedImages)
return; return;
auto decoded_shader = flags()->getShader()->CreateDecodedPaintRecord( auto decoded_shader = flags()->getShader()->CreateDecodedPaintRecord(
...@@ -117,6 +120,18 @@ void ScopedRasterFlags::DecodeRecordShader(const SkMatrix& ctm, ...@@ -117,6 +120,18 @@ void ScopedRasterFlags::DecodeRecordShader(const SkMatrix& ctm,
MutableFlags()->setShader(std::move(decoded_shader)); MutableFlags()->setShader(std::move(decoded_shader));
} }
void ScopedRasterFlags::DecodeFilter() {
if (!flags()->getImageFilter() ||
!flags()->getImageFilter()->has_discardable_images() ||
flags()->getImageFilter()->image_analysis_state() !=
ImageAnalysisState::kAnimatedImages) {
return;
}
MutableFlags()->setImageFilter(flags()->getImageFilter()->SnapshotWithImages(
&*decode_stashing_image_provider_));
}
void ScopedRasterFlags::AdjustStrokeIfNeeded(const SkMatrix& ctm) { void ScopedRasterFlags::AdjustStrokeIfNeeded(const SkMatrix& ctm) {
// With anti-aliasing turned off, strokes with a device space width in (0, 1) // With anti-aliasing turned off, strokes with a device space width in (0, 1)
// may not raster at all. To avoid this, we have two options: // may not raster at all. To avoid this, we have two options:
......
...@@ -37,6 +37,7 @@ class CC_PAINT_EXPORT ScopedRasterFlags { ...@@ -37,6 +37,7 @@ class CC_PAINT_EXPORT ScopedRasterFlags {
private: private:
void DecodeImageShader(const SkMatrix& ctm); void DecodeImageShader(const SkMatrix& ctm);
void DecodeRecordShader(const SkMatrix& ctm, bool create_skia_shader); void DecodeRecordShader(const SkMatrix& ctm, bool create_skia_shader);
void DecodeFilter();
void AdjustStrokeIfNeeded(const SkMatrix& ctm); void AdjustStrokeIfNeeded(const SkMatrix& ctm);
PaintFlags* MutableFlags() { PaintFlags* MutableFlags() {
......
...@@ -54,7 +54,7 @@ TEST(ScopedRasterFlagsTest, KeepsDecodesAlive) { ...@@ -54,7 +54,7 @@ TEST(ScopedRasterFlagsTest, KeepsDecodesAlive) {
auto record_shader = PaintShader::MakePaintRecord( auto record_shader = PaintShader::MakePaintRecord(
record, SkRect::MakeWH(100, 100), SkShader::TileMode::kClamp_TileMode, record, SkRect::MakeWH(100, 100), SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode, &SkMatrix::I()); SkShader::TileMode::kClamp_TileMode, &SkMatrix::I());
record_shader->set_has_animated_images(); record_shader->set_has_animated_images(true);
MockImageProvider provider; MockImageProvider provider;
PaintFlags flags; PaintFlags flags;
......
...@@ -8594,6 +8594,20 @@ class LayerTreeHostTestImageAnimationDrawRecordShader ...@@ -8594,6 +8594,20 @@ class LayerTreeHostTestImageAnimationDrawRecordShader
MULTI_THREAD_TEST_F(LayerTreeHostTestImageAnimationDrawRecordShader); MULTI_THREAD_TEST_F(LayerTreeHostTestImageAnimationDrawRecordShader);
class LayerTreeHostTestImageAnimationPaintFilter
: public LayerTreeHostTestImageAnimation {
void AddImageOp(const PaintImage& image) override {
auto record = sk_make_sp<PaintOpBuffer>();
record->push<DrawImageOp>(image, 0.f, 0.f, nullptr);
PaintFlags flags;
flags.setImageFilter(
sk_make_sp<RecordPaintFilter>(record, SkRect::MakeWH(500, 500)));
content_layer_client_.add_draw_rect(gfx::Rect(500, 500), flags);
}
};
MULTI_THREAD_TEST_F(LayerTreeHostTestImageAnimationPaintFilter);
class LayerTreeHostTestImageAnimationSynchronousScheduling class LayerTreeHostTestImageAnimationSynchronousScheduling
: public LayerTreeHostTestImageAnimationDrawImage { : public LayerTreeHostTestImageAnimationDrawImage {
public: public:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment