Commit 576bda00 authored by Khushal's avatar Khushal Committed by Commit Bot

cc: Decode discardable images in PaintRecord backed shaders.

For PaintRecord backed shaders, the rasterization is done internally in
skia, which means images present in these shaders are not pre-decoded
by compositor's decode cache. This change ensures that we capture these
images during discardable image analysis, and replace them with decoded
images from the compositor's cache.

Replacing of these images requires transforming the PaintRecords in
these shaders to SkPictures with decoded images before executing the op
with the shader.

Bug: 735741, 728359
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Change-Id: Ie9314e566eaeb1a393c7490e6309d1315ec3733e
Reviewed-on: https://chromium-review.googlesource.com/673826
Commit-Queue: Khushal <khushalsagar@chromium.org>
Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Reviewed-by: default avatarenne <enne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#504860}
parent a964e6f5
......@@ -607,6 +607,8 @@ cc_test("cc_unittests") {
"paint/display_item_list_unittest.cc",
"paint/paint_image_unittest.cc",
"paint/paint_op_buffer_unittest.cc",
"paint/paint_shader_unittest.cc",
"paint/scoped_image_flags_unittest.cc",
"paint/solid_color_analyzer_unittest.cc",
"raster/playback_image_provider_unittest.cc",
"raster/raster_buffer_provider_unittest.cc",
......
......@@ -45,6 +45,8 @@ cc_component("paint") {
"paint_shader.h",
"record_paint_canvas.cc",
"record_paint_canvas.h",
"scoped_image_flags.cc",
"scoped_image_flags.h",
"skia_paint_canvas.cc",
"skia_paint_canvas.h",
"skia_paint_image_generator.cc",
......
This diff is collapsed.
......@@ -747,6 +747,43 @@ TEST_F(DiscardableImageMapTest, GathersAnimatedImages) {
EXPECT_DCHECK_DEATH(images[2]->frame_index());
}
TEST_F(DiscardableImageMapTest, CapturesImagesInPaintRecordShaders) {
// Create the record to use in the shader.
auto shader_record = sk_make_sp<PaintOpBuffer>();
shader_record->push<ScaleOp>(2.0f, 2.0f);
PaintImage paint_image = CreateDiscardablePaintImage(gfx::Size(100, 100));
shader_record->push<DrawImageOp>(paint_image, 0.f, 0.f, nullptr);
gfx::Rect visible_rect(500, 500);
scoped_refptr<DisplayItemList> display_list = new DisplayItemList();
display_list->StartPaint();
display_list->push<ScaleOp>(2.0f, 2.0f);
PaintFlags flags;
SkRect tile = SkRect::MakeWH(100, 100);
flags.setShader(PaintShader::MakePaintRecord(
shader_record, tile, SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode, nullptr));
display_list->push<DrawRectOp>(SkRect::MakeWH(200, 200), flags);
display_list->EndPaintOfUnpaired(visible_rect);
display_list->Finalize();
display_list->GenerateDiscardableImagesMetadata();
const auto& image_map = display_list->discardable_image_map();
// The image rect is set to the rect for the DrawRectOp.
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, paint_image);
// The position of the image is the position of the DrawRectOp that uses the
// shader.
EXPECT_EQ(gfx::Rect(400, 400), inset_rects[0]);
// The scale of the image includes the scale at which the shader record is
// rasterized.
EXPECT_EQ(SkSize::Make(4.f, 4.f), draw_images[0].scale);
}
class DiscardableImageMapColorSpaceTest
: public DiscardableImageMapTest,
public testing::WithParamInterface<gfx::ColorSpace> {};
......
......@@ -13,6 +13,7 @@
#include "cc/paint/paint_op_reader.h"
#include "cc/paint/paint_op_writer.h"
#include "cc/paint/paint_record.h"
#include "cc/paint/scoped_image_flags.h"
#include "third_party/skia/include/core/SkAnnotation.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkRegion.h"
......@@ -25,22 +26,6 @@ SkIRect RoundOutRect(const SkRect& rect) {
return result;
}
bool IsImageShader(const PaintFlags& flags) {
return flags.HasShader() &&
flags.getShader()->shader_type() == PaintShader::Type::kImage;
}
bool IsImageOp(const PaintOp* op) {
if (op->GetType() == PaintOpType::DrawImage)
return true;
else if (op->GetType() == PaintOpType::DrawImageRect)
return true;
else if (op->IsDrawOp() && op->IsPaintOpWithFlags())
return IsImageShader(static_cast<const PaintOpWithFlags*>(op)->flags);
return false;
}
bool QuickRejectDraw(const PaintOp* op, const SkCanvas* canvas) {
DCHECK(op->IsDrawOp());
......@@ -58,65 +43,6 @@ bool QuickRejectDraw(const PaintOp* op, const SkCanvas* canvas) {
return canvas->quickReject(rect);
}
// Encapsulates a ImageProvider::DecodedImageHolder and a SkPaint. Use of
// this class ensures that the DecodedImageHolder outlives the dependent
// SkPaint.
class ScopedImageFlags {
public:
ScopedImageFlags(ImageProvider* image_provider,
const PaintFlags& flags,
const SkMatrix& ctm) {
DCHECK(IsImageShader(flags));
const PaintImage& paint_image = flags.getShader()->paint_image();
SkMatrix matrix = flags.getShader()->GetLocalMatrix();
SkMatrix total_image_matrix = matrix;
total_image_matrix.preConcat(ctm);
SkRect src_rect =
SkRect::MakeIWH(paint_image.width(), paint_image.height());
DrawImage draw_image(paint_image, RoundOutRect(src_rect),
flags.getFilterQuality(), total_image_matrix);
scoped_decoded_draw_image_ =
image_provider->GetDecodedDrawImage(draw_image);
if (!scoped_decoded_draw_image_)
return;
const auto& decoded_image = scoped_decoded_draw_image_.decoded_image();
DCHECK(decoded_image.image());
bool need_scale = !decoded_image.is_scale_adjustment_identity();
if (need_scale) {
matrix.preScale(1.f / decoded_image.scale_adjustment().width(),
1.f / decoded_image.scale_adjustment().height());
}
sk_sp<SkImage> sk_image =
sk_ref_sp<SkImage>(const_cast<SkImage*>(decoded_image.image().get()));
PaintImage decoded_paint_image = PaintImageBuilder()
.set_id(paint_image.stable_id())
.set_image(std::move(sk_image))
.TakePaintImage();
decoded_flags_.emplace(flags);
decoded_flags_.value().setFilterQuality(decoded_image.filter_quality());
decoded_flags_.value().setShader(
PaintShader::MakeImage(decoded_paint_image, flags.getShader()->tx(),
flags.getShader()->ty(), &matrix));
}
PaintFlags* decoded_flags() {
return decoded_flags_ ? &decoded_flags_.value() : nullptr;
}
~ScopedImageFlags() = default;
private:
base::Optional<PaintFlags> decoded_flags_;
ImageProvider::ScopedDecodedDrawImage scoped_decoded_draw_image_;
DISALLOW_COPY_AND_ASSIGN(ScopedImageFlags);
};
void RasterWithAlpha(const PaintOp* op,
SkCanvas* canvas,
const PlaybackParams& params,
......@@ -133,7 +59,7 @@ void RasterWithAlpha(const PaintOp* op,
// ImageProvider if it consists of an image shader.
base::Optional<ScopedImageFlags> scoped_flags;
const PaintFlags* decoded_flags = &flags_op->flags;
if (params.image_provider && IsImageShader(flags_op->flags)) {
if (params.image_provider && flags_op->HasDiscardableImagesFromFlags()) {
scoped_flags.emplace(params.image_provider, flags_op->flags,
canvas->getTotalMatrix());
decoded_flags = scoped_flags.value().decoded_flags();
......@@ -1457,12 +1383,47 @@ bool PaintOp::GetBounds(const PaintOp* op, SkRect* rect) {
return false;
}
// static
bool PaintOp::OpHasDiscardableImages(const PaintOp* op) {
if (op->IsPaintOpWithFlags() && static_cast<const PaintOpWithFlags*>(op)
->HasDiscardableImagesFromFlags()) {
return true;
}
if (op->GetType() == PaintOpType::DrawImage &&
static_cast<const DrawImageOp*>(op)->HasDiscardableImages()) {
return true;
} else if (op->GetType() == PaintOpType::DrawImageRect &&
static_cast<const DrawImageRectOp*>(op)->HasDiscardableImages()) {
return true;
} else if (op->GetType() == PaintOpType::DrawRecord &&
static_cast<const DrawRecordOp*>(op)->HasDiscardableImages()) {
return true;
}
return false;
}
void PaintOp::DestroyThis() {
auto func = g_destructor_functions[type];
if (func)
func(this);
}
bool PaintOpWithFlags::HasDiscardableImagesFromFlags() const {
if (!IsDrawOp())
return false;
if (!flags.HasShader())
return false;
else if (flags.getShader()->shader_type() == PaintShader::Type::kImage)
return flags.getShader()->paint_image().IsLazyGenerated();
else if (flags.getShader()->shader_type() == PaintShader::Type::kPaintRecord)
return flags.getShader()->paint_record()->HasDiscardableImages();
return false;
}
void PaintOpWithFlags::RasterWithFlags(SkCanvas* canvas,
const PaintFlags* flags,
const PlaybackParams& params) const {
......@@ -1740,7 +1701,8 @@ void PaintOpBuffer::Playback(SkCanvas* canvas,
// general case we defer this to the SkCanvas but if we will be
// using an ImageProvider for pre-decoding images, we can save
// performing an expensive decode that will never be rasterized.
const bool skip_op = params.image_provider && IsImageOp(draw_op) &&
const bool skip_op = params.image_provider &&
PaintOp::OpHasDiscardableImages(draw_op) &&
QuickRejectDraw(draw_op, canvas);
if (skip_op) {
// Now that we know this op will be skipped, we can push the save
......@@ -1770,15 +1732,15 @@ void PaintOpBuffer::Playback(SkCanvas* canvas,
}
}
if (params.image_provider && IsImageOp(op)) {
if (params.image_provider && PaintOp::OpHasDiscardableImages(op)) {
if (QuickRejectDraw(op, canvas))
continue;
auto* flags_op = op->IsPaintOpWithFlags()
? static_cast<const PaintOpWithFlags*>(op)
: nullptr;
if (flags_op && IsImageShader(flags_op->flags)) {
ScopedImageFlags scoped_flags(image_provider, flags_op->flags,
if (flags_op && flags_op->HasDiscardableImagesFromFlags()) {
ScopedImageFlags scoped_flags(params.image_provider, flags_op->flags,
canvas->getTotalMatrix());
// Only rasterize the op if we successfully decoded the image.
......
......@@ -15,6 +15,7 @@
#include "base/memory/aligned_memory.h"
#include "base/optional.h"
#include "cc/base/math_util.h"
#include "cc/paint/image_provider.h"
#include "cc/paint/paint_canvas.h"
#include "cc/paint/paint_export.h"
#include "cc/paint/paint_flags.h"
......@@ -29,7 +30,6 @@
namespace cc {
class ImageDecodeCache;
class ImageProvider;
class CC_PAINT_EXPORT ThreadsafeMatrix : public SkMatrix {
public:
......@@ -137,6 +137,10 @@ class CC_PAINT_EXPORT PaintOp {
// for the op.
static bool GetBounds(const PaintOp* op, SkRect* rect);
// Returns true if executing this op will require decoding of any lazy
// generated images.
static bool OpHasDiscardableImages(const PaintOp* op);
int CountSlowPaths() const { return 0; }
int CountSlowPathsFromFlags() const { return 0; }
......@@ -195,14 +199,7 @@ class CC_PAINT_EXPORT PaintOpWithFlags : public PaintOp {
int CountSlowPathsFromFlags() const { return flags.getPathEffect() ? 1 : 0; }
bool HasNonAAPaint() const { return !flags.isAntiAlias(); }
bool HasDiscardableImagesFromFlags() const {
if (!IsDrawOp())
return false;
SkShader* shader = flags.getSkShader();
SkImage* image = shader ? shader->isAImage(nullptr, nullptr) : nullptr;
return image && image->isLazyGenerated();
}
bool HasDiscardableImagesFromFlags() const;
void RasterWithFlags(SkCanvas* canvas,
const PaintFlags* flags,
......@@ -981,6 +978,7 @@ class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt {
friend class DisplayItemList;
friend class PaintOpBufferOffsetsTest;
friend class SolidColorAnalyzer;
friend class ScopedImageFlags;
// Replays the paint op buffer into the canvas. If |indices| is specified, it
// contains indices in an increasing order and only the indices specified in
......
......@@ -6,9 +6,25 @@
#include "base/memory/ptr_util.h"
#include "cc/paint/paint_record.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
namespace cc {
namespace {
sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record,
const SkRect& bounds,
const SkMatrix* matrix,
ImageProvider* image_provider) {
SkPictureRecorder recorder;
SkCanvas* canvas = recorder.beginRecording(bounds);
if (matrix)
canvas->setMatrix(*matrix);
record->Playback(canvas, image_provider);
return recorder.finishRecordingAsPicture();
}
} // namespace
sk_sp<PaintShader> PaintShader::MakeColor(SkColor color) {
sk_sp<PaintShader> shader(new PaintShader(Type::kColor));
......@@ -139,6 +155,96 @@ sk_sp<PaintShader> PaintShader::MakePaintRecord(
PaintShader::PaintShader(Type type) : shader_type_(type) {}
PaintShader::~PaintShader() = default;
bool PaintShader::GetRasterizationTileRect(const SkMatrix& ctm,
SkRect* tile_rect) const {
DCHECK_EQ(shader_type_, Type::kPaintRecord);
// If we are using a fixed scale, the record is rasterized with the original
// tile size and scaling is applied to the generated output.
if (scaling_behavior_ == ScalingBehavior::kFixedScale) {
*tile_rect = tile_;
return true;
}
SkMatrix matrix = ctm;
if (local_matrix_.has_value())
matrix.preConcat(local_matrix_.value());
SkSize scale;
if (!matrix.decomposeScale(&scale)) {
// Decomposition failed, use an approximation.
scale.set(SkScalarSqrt(matrix.getScaleX() * matrix.getScaleX() +
matrix.getSkewX() * matrix.getSkewX()),
SkScalarSqrt(matrix.getScaleY() * matrix.getScaleY() +
matrix.getSkewY() * matrix.getSkewY()));
}
SkSize scaled_size =
SkSize::Make(SkScalarAbs(scale.width() * tile_.width()),
SkScalarAbs(scale.height() * tile_.height()));
// Clamp the tile size to about 4M pixels.
// TODO(khushalsagar): We need to consider the max texture size as well.
static const SkScalar kMaxTileArea = 2048 * 2048;
SkScalar tile_area = scaled_size.width() * scaled_size.height();
if (tile_area > kMaxTileArea) {
SkScalar clamp_scale = SkScalarSqrt(kMaxTileArea / tile_area);
scaled_size.set(scaled_size.width() * clamp_scale,
scaled_size.height() * clamp_scale);
}
scaled_size = scaled_size.toCeil();
if (scaled_size.isEmpty())
return false;
*tile_rect = SkRect::MakeWH(scaled_size.width(), scaled_size.height());
return true;
}
sk_sp<PaintShader> PaintShader::CreateDecodedPaintRecord(
const SkMatrix& ctm,
ImageProvider* image_provider) const {
DCHECK_EQ(shader_type_, Type::kPaintRecord);
// For creating a decoded PaintRecord shader, we need to do the following:
// 1) Figure out the scale at which the record should be rasterization given
// the ctm and local_matrix on the shader.
// 2) Transform this record to an SkPicture with this scale and replace
// encoded images in this record with decodes from the ImageProvider. This
// is done by setting the rasterization_matrix_ for this shader to be used
// in GetSkShader.
// 3) Since the SkShader will use a scaled SkPicture, we use a kFixedScale for
// the decoded shader which creates an SkPicture backed SkImage for
// creating the decoded SkShader.
// Note that the scaling logic here is replicated from
// SkPictureShader::refBitmapShader.
SkRect tile_rect;
if (!GetRasterizationTileRect(ctm, &tile_rect))
return nullptr;
sk_sp<PaintShader> shader(new PaintShader(Type::kPaintRecord));
shader->record_ = record_;
shader->tile_ = tile_rect;
// Use a fixed scale since we have already scaled the tile rect and fixed the
// raster scale.
shader->scaling_behavior_ = ScalingBehavior::kFixedScale;
shader->rasterization_matrix_.emplace();
shader->rasterization_matrix_.value().setRectToRect(
tile_, tile_rect, SkMatrix::kFill_ScaleToFit);
shader->tx_ = tx_;
shader->ty_ = ty_;
const SkSize tile_scale =
SkSize::Make(SkIntToScalar(tile_rect.width()) / tile_.width(),
SkIntToScalar(tile_rect.height()) / tile_.height());
shader->local_matrix_ = GetLocalMatrix();
shader->local_matrix_->preScale(1 / tile_scale.width(),
1 / tile_scale.height());
shader->image_provider_ = image_provider;
return shader;
}
sk_sp<SkShader> PaintShader::GetSkShader() const {
if (cached_shader_)
return cached_shader_;
......@@ -182,7 +288,12 @@ sk_sp<SkShader> PaintShader::GetSkShader() const {
tx_, ty_, local_matrix_ ? &*local_matrix_ : nullptr);
break;
case Type::kPaintRecord: {
auto picture = ToSkPicture(record_, tile_);
// Create a recording at the desired scale if this record has images which
// have been decoded before raster.
auto picture = ToSkPicture(
record_, tile_,
rasterization_matrix_ ? &rasterization_matrix_.value() : nullptr,
image_provider_);
switch (scaling_behavior_) {
// For raster scale, we create a picture shader directly.
......@@ -191,7 +302,7 @@ sk_sp<SkShader> PaintShader::GetSkShader() const {
std::move(picture), tx_, ty_,
local_matrix_ ? &*local_matrix_ : nullptr, nullptr);
break;
// For fixed scale, we create an image shader with and image backed by
// For fixed scale, we create an image shader with an image backed by
// the picture.
case ScalingBehavior::kFixedScale: {
auto image = SkImage::MakeFromPicture(
......
......@@ -16,7 +16,7 @@
#include "third_party/skia/include/core/SkShader.h"
namespace cc {
class ImageProvider;
class PaintOpBuffer;
using PaintRecord = PaintOpBuffer;
......@@ -111,8 +111,12 @@ class CC_PAINT_EXPORT PaintShader : public SkRefCnt {
return image_;
}
const sk_sp<PaintRecord>& paint_record() const { return record_; }
bool GetRasterizationTileRect(const SkMatrix& ctm, SkRect* tile_rect) const;
SkShader::TileMode tx() const { return tx_; }
SkShader::TileMode ty() const { return ty_; }
SkRect tile() const { return tile_; }
bool IsOpaque() const;
......@@ -127,11 +131,17 @@ class CC_PAINT_EXPORT PaintShader : public SkRefCnt {
friend class PaintOpReader;
friend class PaintOpSerializationTestUtils;
friend class PaintOpWriter;
friend class ScopedImageFlags;
FRIEND_TEST_ALL_PREFIXES(PaintShaderTest, DecodePaintRecord);
explicit PaintShader(Type type);
sk_sp<SkShader> GetSkShader() const;
sk_sp<PaintShader> CreateDecodedPaintRecord(
const SkMatrix& ctm,
ImageProvider* image_provider) const;
void SetColorsAndPositions(const SkColor* colors,
const SkScalar* positions,
int count);
......@@ -166,6 +176,12 @@ class CC_PAINT_EXPORT PaintShader : public SkRefCnt {
std::vector<SkColor> colors_;
std::vector<SkScalar> positions_;
// The following are only used during raster to replace the decoded images in
// the record for this shader. The |image_provider_| and
// |decoded_image_stash_| must outlive this shader.
ImageProvider* image_provider_ = nullptr;
base::Optional<SkMatrix> rasterization_matrix_;
mutable sk_sp<SkShader> cached_shader_;
DISALLOW_COPY_AND_ASSIGN(PaintShader);
......
// Copyright 2015 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_shader.h"
#include "cc/paint/draw_image.h"
#include "cc/paint/image_provider.h"
#include "cc/paint/paint_image_builder.h"
#include "cc/paint/paint_op_buffer.h"
#include "cc/test/fake_paint_image_generator.h"
#include "cc/test/skia_common.h"
#include "cc/test/test_skcanvas.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace cc {
namespace {
class MockImageGenerator : public FakePaintImageGenerator {
public:
explicit MockImageGenerator(const gfx::Size& size)
: FakePaintImageGenerator(
SkImageInfo::MakeN32Premul(size.width(), size.height())) {}
MOCK_METHOD5(GetPixels,
bool(const SkImageInfo&, void*, size_t, size_t, uint32_t));
};
class MockImageProvider : public ImageProvider {
public:
MockImageProvider() = default;
~MockImageProvider() override = default;
ScopedDecodedDrawImage GetDecodedDrawImage(
const DrawImage& draw_image) override {
draw_image_ = draw_image;
SkBitmap bitmap;
bitmap.allocN32Pixels(10, 10);
sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
return ScopedDecodedDrawImage(
DecodedDrawImage(image, SkSize::MakeEmpty(), SkSize::Make(1.0f, 1.0f),
draw_image.filter_quality()));
}
const DrawImage& draw_image() const { return draw_image_; }
private:
DrawImage draw_image_;
};
} // namespace
TEST(PaintShaderTest, RasterizationRectForRecordShaders) {
SkMatrix local_matrix = SkMatrix::MakeScale(0.5f, 0.5f);
auto record_shader = PaintShader::MakePaintRecord(
sk_make_sp<PaintOpBuffer>(), SkRect::MakeWH(100, 100),
SkShader::TileMode::kClamp_TileMode, SkShader::TileMode::kClamp_TileMode,
&local_matrix);
SkRect tile_rect;
SkMatrix ctm = SkMatrix::MakeScale(0.5f, 0.5f);
EXPECT_TRUE(record_shader->GetRasterizationTileRect(ctm, &tile_rect));
EXPECT_EQ(tile_rect, SkRect::MakeWH(25, 25));
}
TEST(PaintShaderTest, DecodePaintRecord) {
auto record = sk_make_sp<PaintOpBuffer>();
// Use a strict mock for the generator. It should never be used when
// rasterizing this shader, since the decode should be done by the
// ImageProvider.
auto generator =
sk_make_sp<testing::StrictMock<MockImageGenerator>>(gfx::Size(100, 100));
PaintImage paint_image = PaintImageBuilder()
.set_id(PaintImage::GetNextId())
.set_paint_image_generator(generator)
.TakePaintImage();
record->push<DrawImageOp>(paint_image, 0.f, 0.f, nullptr);
SkMatrix local_matrix = SkMatrix::MakeScale(0.5f, 0.5f);
auto record_shader = PaintShader::MakePaintRecord(
record, SkRect::MakeWH(100, 100), SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode, &local_matrix);
PaintOpBuffer buffer;
PaintFlags flags;
flags.setShader(record_shader);
buffer.push<ScaleOp>(0.5f, 0.5f);
buffer.push<DrawRectOp>(SkRect::MakeWH(100, 100), flags);
MockImageProvider image_provider;
SaveCountingCanvas canvas;
buffer.Playback(&canvas, &image_provider);
EXPECT_EQ(canvas.draw_rect_, SkRect::MakeWH(100, 100));
SkShader* shader = canvas.paint_.getShader();
ASSERT_TRUE(shader);
SkMatrix decoded_local_matrix;
SkShader::TileMode xy[2];
SkImage* skia_image = shader->isAImage(&decoded_local_matrix, xy);
ASSERT_TRUE(skia_image);
EXPECT_TRUE(skia_image->isLazyGenerated());
EXPECT_EQ(xy[0], record_shader->tx());
EXPECT_EQ(xy[1], record_shader->ty());
EXPECT_EQ(decoded_local_matrix, SkMatrix::MakeScale(2.f, 2.f));
// The rasterization of the shader is internal to skia, so use a raster canvas
// to verify that the decoded paint does not have the encoded image.
auto surface = SkSurface::MakeRaster(SkImageInfo::MakeN32Premul(100, 100));
surface->getCanvas()->drawPaint(canvas.paint_);
// Using the shader requests decode for images at the correct scale.
EXPECT_EQ(image_provider.draw_image().paint_image(), paint_image);
EXPECT_EQ(image_provider.draw_image().scale(), SkSize::Make(0.25f, 0.25f));
}
} // namespace cc
// 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 "cc/paint/scoped_image_flags.h"
#include "cc/paint/image_provider.h"
#include "cc/paint/paint_image_builder.h"
namespace cc {
namespace {
SkIRect RoundOutRect(const SkRect& rect) {
SkIRect result;
rect.roundOut(&result);
return result;
}
} // namespace
ScopedImageFlags::DecodeStashingImageProvider::DecodeStashingImageProvider(
ImageProvider* source_provider)
: source_provider_(source_provider) {}
ScopedImageFlags::DecodeStashingImageProvider::~DecodeStashingImageProvider() =
default;
ImageProvider::ScopedDecodedDrawImage
ScopedImageFlags::DecodeStashingImageProvider::GetDecodedDrawImage(
const DrawImage& draw_image) {
auto decode = source_provider_->GetDecodedDrawImage(draw_image);
if (!decode)
return ScopedDecodedDrawImage();
// No need to add any destruction callback to the returned image. The images
// decoded here match the lifetime of this provider.
auto image_to_return = ScopedDecodedDrawImage(decode.decoded_image());
decoded_images_.push_back(std::move(decode));
return image_to_return;
}
ScopedImageFlags::ScopedImageFlags(ImageProvider* image_provider,
const PaintFlags& flags,
const SkMatrix& ctm)
: decode_stashing_image_provider_(image_provider) {
if (flags.getShader()->shader_type() == PaintShader::Type::kImage) {
DecodeImageShader(flags, ctm);
} else {
DCHECK_EQ(flags.getShader()->shader_type(),
PaintShader::Type::kPaintRecord);
DecodeRecordShader(flags, ctm);
}
}
ScopedImageFlags::~ScopedImageFlags() = default;
void ScopedImageFlags::DecodeImageShader(const PaintFlags& flags,
const SkMatrix& ctm) {
const PaintImage& paint_image = flags.getShader()->paint_image();
SkMatrix matrix = flags.getShader()->GetLocalMatrix();
SkMatrix total_image_matrix = matrix;
total_image_matrix.preConcat(ctm);
SkRect src_rect = SkRect::MakeIWH(paint_image.width(), paint_image.height());
DrawImage draw_image(paint_image, RoundOutRect(src_rect),
flags.getFilterQuality(), total_image_matrix);
auto decoded_draw_image =
decode_stashing_image_provider_.GetDecodedDrawImage(draw_image);
if (!decoded_draw_image)
return;
const auto& decoded_image = decoded_draw_image.decoded_image();
DCHECK(decoded_image.image());
bool need_scale = !decoded_image.is_scale_adjustment_identity();
if (need_scale) {
matrix.preScale(1.f / decoded_image.scale_adjustment().width(),
1.f / decoded_image.scale_adjustment().height());
}
sk_sp<SkImage> sk_image =
sk_ref_sp<SkImage>(const_cast<SkImage*>(decoded_image.image().get()));
PaintImage decoded_paint_image = PaintImageBuilder()
.set_id(paint_image.stable_id())
.set_image(std::move(sk_image))
.TakePaintImage();
decoded_flags_.emplace(flags);
decoded_flags_.value().setFilterQuality(decoded_image.filter_quality());
decoded_flags_.value().setShader(
PaintShader::MakeImage(decoded_paint_image, flags.getShader()->tx(),
flags.getShader()->ty(), &matrix));
}
void ScopedImageFlags::DecodeRecordShader(const PaintFlags& flags,
const SkMatrix& ctm) {
auto decoded_shader = flags.getShader()->CreateDecodedPaintRecord(
ctm, &decode_stashing_image_provider_);
if (!decoded_shader)
return;
decoded_flags_.emplace(flags);
decoded_flags_.value().setShader(std::move(decoded_shader));
}
} // namespace cc
// 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_SCOPED_IMAGE_FLAGS_H_
#define CC_PAINT_SCOPED_IMAGE_FLAGS_H_
#include "base/macros.h"
#include "cc/paint/paint_export.h"
#include "cc/paint/paint_op_buffer.h"
namespace cc {
class ImageProvider;
// A helper class to decode images inside the provided |flags| and provide a
// PaintFlags with the decoded images that can directly be used for
// rasterization.
// This class should only be used if |flags| has any discardable images.
class CC_PAINT_EXPORT ScopedImageFlags {
public:
// |image_provider| must outlive this class.
ScopedImageFlags(ImageProvider* image_provider,
const PaintFlags& flags,
const SkMatrix& ctm);
~ScopedImageFlags();
// The usage of these flags should not extend beyond the lifetime of this
// object.
PaintFlags* decoded_flags() {
return decoded_flags_ ? &decoded_flags_.value() : nullptr;
}
private:
// An ImageProvider that passes decode requests through to the
// |source_provider| but keeps the decode cached throughtout its lifetime,
// instead of passing the ref to the caller.
class DecodeStashingImageProvider : public ImageProvider {
public:
// |source_provider| must outlive this class.
explicit DecodeStashingImageProvider(ImageProvider* source_provider);
~DecodeStashingImageProvider() override;
// ImageProvider implementation.
ScopedDecodedDrawImage GetDecodedDrawImage(
const DrawImage& draw_image) override;
private:
ImageProvider* source_provider_;
std::vector<ScopedDecodedDrawImage> decoded_images_;
DISALLOW_COPY_AND_ASSIGN(DecodeStashingImageProvider);
};
void DecodeImageShader(const PaintFlags& flags, const SkMatrix& ctm);
void DecodeRecordShader(const PaintFlags& flags, const SkMatrix& ctm);
base::Optional<PaintFlags> decoded_flags_;
DecodeStashingImageProvider decode_stashing_image_provider_;
DISALLOW_COPY_AND_ASSIGN(ScopedImageFlags);
};
} // namespace cc
#endif // CC_PAINT_SCOPED_IMAGE_FLAGS_H_
// 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 "cc/paint/scoped_image_flags.h"
#include "base/bind.h"
#include "base/callback.h"
#include "cc/test/skia_common.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace cc {
namespace {
class MockImageProvider : public ImageProvider {
public:
MockImageProvider() = default;
~MockImageProvider() override { EXPECT_EQ(ref_count_, 0); }
ScopedDecodedDrawImage GetDecodedDrawImage(
const DrawImage& draw_image) override {
ref_count_++;
SkBitmap bitmap;
bitmap.allocN32Pixels(10, 10);
sk_sp<SkImage> image = SkImage::MakeFromBitmap(bitmap);
return ScopedDecodedDrawImage(
DecodedDrawImage(image, SkSize::MakeEmpty(), SkSize::Make(1.0f, 1.0f),
draw_image.filter_quality()),
base::BindOnce(&MockImageProvider::UnrefImage, base::Unretained(this)));
}
void UnrefImage(DecodedDrawImage decoded_image) {
ref_count_--;
CHECK_GE(ref_count_, 0);
}
int ref_count() const { return ref_count_; }
private:
int ref_count_ = 0;
};
} // namespace
TEST(ScopedImageFlagsTest, KeepsDecodesAlive) {
auto record = sk_make_sp<PaintOpBuffer>();
record->push<DrawImageOp>(CreateDiscardablePaintImage(gfx::Size(10, 10)), 0.f,
0.f, nullptr);
record->push<DrawImageOp>(CreateDiscardablePaintImage(gfx::Size(10, 10)), 0.f,
0.f, nullptr);
record->push<DrawImageOp>(CreateDiscardablePaintImage(gfx::Size(10, 10)), 0.f,
0.f, nullptr);
auto record_shader = PaintShader::MakePaintRecord(
record, SkRect::MakeWH(100, 100), SkShader::TileMode::kClamp_TileMode,
SkShader::TileMode::kClamp_TileMode, &SkMatrix::I());
MockImageProvider provider;
PaintFlags flags;
flags.setShader(record_shader);
{
ScopedImageFlags scoped_flags(&provider, flags, SkMatrix::I());
ASSERT_TRUE(scoped_flags.decoded_flags());
SkPaint paint = scoped_flags.decoded_flags()->ToSkPaint();
ASSERT_TRUE(paint.getShader());
EXPECT_EQ(provider.ref_count(), 3);
}
EXPECT_EQ(provider.ref_count(), 0);
}
} // namespace cc
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