blob: 9c5ce1c746b4241b1438173b4d0fb31ccc729f35 [file] [log] [blame]
// 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/paint_shader.h"
#include "base/atomic_sequence_num.h"
#include "cc/paint/paint_image_builder.h"
#include "cc/paint/paint_op_writer.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 {
base::AtomicSequenceNumber g_next_shader_id;
sk_sp<SkPicture> ToSkPicture(sk_sp<PaintRecord> record,
const SkRect& bounds,
const gfx::SizeF* raster_scale,
ImageProvider* image_provider) {
SkPictureRecorder recorder;
SkCanvas* canvas =
recorder.beginRecording(SkRect::MakeWH(bounds.width(), bounds.height()));
canvas->translate(-bounds.fLeft, -bounds.fTop);
if (raster_scale)
canvas->scale(raster_scale->width(), raster_scale->height());
record->Playback(canvas, PlaybackParams(image_provider));
return recorder.finishRecordingAsPicture();
}
bool CompareMatrices(const SkMatrix& a,
const SkMatrix& b,
bool ignore_scaling_differences) {
if (!ignore_scaling_differences)
return PaintOp::AreSkMatricesEqual(a, b);
SkSize scale;
SkMatrix a_without_scale;
SkMatrix b_without_scale;
const bool decomposes = a.decomposeScale(&scale, &a_without_scale);
if (decomposes != b.decomposeScale(&scale, &b_without_scale))
return false;
if (!decomposes)
return true;
return PaintOp::AreSkMatricesEqual(a_without_scale, b_without_scale);
}
} // namespace
const PaintShader::RecordShaderId PaintShader::kInvalidRecordShaderId = -1;
sk_sp<PaintShader> PaintShader::MakeEmpty() {
sk_sp<PaintShader> shader(new PaintShader(Type::kEmpty));
shader->CreateSkShader();
return shader;
}
sk_sp<PaintShader> PaintShader::MakeColor(SkColor color) {
sk_sp<PaintShader> shader(new PaintShader(Type::kColor));
// Just one color. Store it in the fallback color. Easy.
shader->fallback_color_ = color;
shader->CreateSkShader();
return shader;
}
sk_sp<PaintShader> PaintShader::MakeLinearGradient(const SkPoint points[],
const SkColor colors[],
const SkScalar pos[],
int count,
SkShader::TileMode mode,
uint32_t flags,
const SkMatrix* local_matrix,
SkColor fallback_color) {
sk_sp<PaintShader> shader(new PaintShader(Type::kLinearGradient));
// There are always two points, the start and the end.
shader->start_point_ = points[0];
shader->end_point_ = points[1];
shader->SetColorsAndPositions(colors, pos, count);
shader->SetMatrixAndTiling(local_matrix, mode, mode);
shader->SetFlagsAndFallback(flags, fallback_color);
shader->CreateSkShader();
return shader;
}
sk_sp<PaintShader> PaintShader::MakeRadialGradient(const SkPoint& center,
SkScalar radius,
const SkColor colors[],
const SkScalar pos[],
int count,
SkShader::TileMode mode,
uint32_t flags,
const SkMatrix* local_matrix,
SkColor fallback_color) {
sk_sp<PaintShader> shader(new PaintShader(Type::kRadialGradient));
shader->center_ = center;
shader->start_radius_ = shader->end_radius_ = radius;
shader->SetColorsAndPositions(colors, pos, count);
shader->SetMatrixAndTiling(local_matrix, mode, mode);
shader->SetFlagsAndFallback(flags, fallback_color);
shader->CreateSkShader();
return shader;
}
sk_sp<PaintShader> PaintShader::MakeTwoPointConicalGradient(
const SkPoint& start,
SkScalar start_radius,
const SkPoint& end,
SkScalar end_radius,
const SkColor colors[],
const SkScalar pos[],
int count,
SkShader::TileMode mode,
uint32_t flags,
const SkMatrix* local_matrix,
SkColor fallback_color) {
sk_sp<PaintShader> shader(new PaintShader(Type::kTwoPointConicalGradient));
shader->start_point_ = start;
shader->end_point_ = end;
shader->start_radius_ = start_radius;
shader->end_radius_ = end_radius;
shader->SetColorsAndPositions(colors, pos, count);
shader->SetMatrixAndTiling(local_matrix, mode, mode);
shader->SetFlagsAndFallback(flags, fallback_color);
shader->CreateSkShader();
return shader;
}
sk_sp<PaintShader> PaintShader::MakeSweepGradient(SkScalar cx,
SkScalar cy,
const SkColor colors[],
const SkScalar pos[],
int color_count,
SkShader::TileMode mode,
SkScalar start_degrees,
SkScalar end_degrees,
uint32_t flags,
const SkMatrix* local_matrix,
SkColor fallback_color) {
sk_sp<PaintShader> shader(new PaintShader(Type::kSweepGradient));
shader->center_ = SkPoint::Make(cx, cy);
shader->start_degrees_ = start_degrees;
shader->end_degrees_ = end_degrees;
shader->SetColorsAndPositions(colors, pos, color_count);
shader->SetMatrixAndTiling(local_matrix, mode, mode);
shader->SetFlagsAndFallback(flags, fallback_color);
shader->CreateSkShader();
return shader;
}
sk_sp<PaintShader> PaintShader::MakeImage(const PaintImage& image,
SkShader::TileMode tx,
SkShader::TileMode ty,
const SkMatrix* local_matrix) {
sk_sp<PaintShader> shader(new PaintShader(Type::kImage));
shader->image_ = image;
shader->SetMatrixAndTiling(local_matrix, tx, ty);
shader->CreateSkShader();
return shader;
}
sk_sp<PaintShader> PaintShader::MakePaintRecord(
sk_sp<PaintRecord> record,
const SkRect& tile,
SkShader::TileMode tx,
SkShader::TileMode ty,
const SkMatrix* local_matrix,
ScalingBehavior scaling_behavior) {
sk_sp<PaintShader> shader(new PaintShader(Type::kPaintRecord));
shader->record_ = std::move(record);
shader->id_ = g_next_shader_id.GetNext();
shader->tile_ = tile;
shader->scaling_behavior_ = scaling_behavior;
shader->SetMatrixAndTiling(local_matrix, tx, ty);
shader->CreateSkShader();
return shader;
}
// static
size_t PaintShader::GetSerializedSize(const PaintShader* shader) {
size_t bool_size = sizeof(bool);
if (!shader)
return bool_size;
return bool_size + sizeof(shader->shader_type_) + sizeof(shader->flags_) +
sizeof(shader->end_radius_) + sizeof(shader->start_radius_) +
sizeof(shader->tx_) + sizeof(shader->ty_) +
sizeof(shader->fallback_color_) + sizeof(shader->scaling_behavior_) +
bool_size + sizeof(*shader->local_matrix_) + sizeof(shader->center_) +
sizeof(shader->tile_) + sizeof(shader->start_point_) +
sizeof(shader->end_point_) + sizeof(shader->start_degrees_) +
sizeof(shader->end_degrees_) +
PaintOpWriter::GetImageSize(shader->image_) +
PaintOpWriter::GetImageSize(shader->image_) + bool_size +
sizeof(shader->id_) +
PaintOpWriter::GetRecordSize(shader->record_.get()) +
sizeof(shader->colors_.size()) +
shader->colors_.size() * sizeof(SkColor) +
sizeof(shader->positions_.size()) +
shader->positions_.size() * sizeof(SkScalar);
}
PaintShader::PaintShader(Type type) : shader_type_(type) {}
PaintShader::~PaintShader() = default;
bool PaintShader::has_discardable_images() const {
return (image_ && !image_.IsTextureBacked()) ||
(record_ && record_->HasDiscardableImages());
}
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()));
}
SkScalar tile_area =
tile_.width() * tile_.height() * scale.width() * scale.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;
if (tile_area > kMaxTileArea) {
SkScalar clamp_scale = SkScalarSqrt(kMaxTileArea / tile_area);
scale.set(clamp_scale, clamp_scale);
}
*tile_rect = SkRect::MakeXYWH(
tile_.fLeft * scale.width(), tile_.fTop * scale.height(),
SkScalarCeilToInt(SkScalarAbs(scale.width() * tile_.width())),
SkScalarCeilToInt(SkScalarAbs(scale.height() * tile_.height())));
if (tile_rect->isEmpty())
return false;
return true;
}
sk_sp<PaintShader> PaintShader::CreateScaledPaintRecord(
const SkMatrix& ctm,
gfx::SizeF* raster_scale) const {
DCHECK_EQ(shader_type_, Type::kPaintRecord);
// If this is already fixed scale, then this is already good to go.
if (scaling_behavior_ == ScalingBehavior::kFixedScale) {
*raster_scale = gfx::SizeF(1.f, 1.f);
return sk_ref_sp<PaintShader>(this);
}
// 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 raster_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->id_ = id_;
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->tx_ = tx_;
shader->ty_ = ty_;
*raster_scale =
gfx::SizeF(SkIntToScalar(tile_rect.width()) / tile_.width(),
SkIntToScalar(tile_rect.height()) / tile_.height());
shader->local_matrix_ = GetLocalMatrix();
shader->local_matrix_->preScale(1 / raster_scale->width(),
1 / raster_scale->height());
return shader;
}
sk_sp<PaintShader> PaintShader::CreateDecodedImage(
const SkMatrix& ctm,
SkFilterQuality quality,
ImageProvider* image_provider,
uint32_t* transfer_cache_entry_id,
SkFilterQuality* raster_quality,
bool* needs_mips) const {
DCHECK_EQ(shader_type_, Type::kImage);
if (!image_)
return nullptr;
SkMatrix total_image_matrix = GetLocalMatrix();
total_image_matrix.preConcat(ctm);
SkRect src_rect = SkRect::MakeIWH(image_.width(), image_.height());
SkIRect int_src_rect;
src_rect.roundOut(&int_src_rect);
DrawImage draw_image(image_, int_src_rect, quality, total_image_matrix);
auto decoded_draw_image = image_provider->GetDecodedDrawImage(draw_image);
if (!decoded_draw_image)
return nullptr;
auto decoded_image = decoded_draw_image.decoded_image();
SkMatrix final_matrix = GetLocalMatrix();
bool need_scale = !decoded_image.is_scale_adjustment_identity();
if (need_scale) {
final_matrix.preScale(1.f / decoded_image.scale_adjustment().width(),
1.f / decoded_image.scale_adjustment().height());
}
PaintImage decoded_paint_image;
if (decoded_image.transfer_cache_entry_id()) {
decoded_paint_image = image_;
*transfer_cache_entry_id = *decoded_image.transfer_cache_entry_id();
} else {
DCHECK(decoded_image.image());
sk_sp<SkImage> sk_image =
sk_ref_sp<SkImage>(const_cast<SkImage*>(decoded_image.image().get()));
decoded_paint_image =
PaintImageBuilder::WithDefault()
.set_id(image_.stable_id())
.set_image(std::move(sk_image), image_.content_id())
.TakePaintImage();
}
// TODO(khushalsagar): Remove filter quality from DecodedDrawImage. All we
// want to do is cap the filter quality used, but Gpu and Sw cache have
// different behaviour. D:
*raster_quality = decoded_image.filter_quality();
*needs_mips = decoded_image.transfer_cache_entry_needs_mips();
return PaintShader::MakeImage(decoded_paint_image, tx_, ty_, &final_matrix);
}
sk_sp<SkShader> PaintShader::GetSkShader() const {
return cached_shader_;
}
void PaintShader::CreateSkShader(const gfx::SizeF* raster_scale,
ImageProvider* image_provider) {
DCHECK(!cached_shader_);
switch (shader_type_) {
case Type::kEmpty:
cached_shader_ = SkShader::MakeEmptyShader();
break;
case Type::kColor:
// This will be handled by the fallback check below.
break;
case Type::kLinearGradient: {
SkPoint points[2] = {start_point_, end_point_};
cached_shader_ = SkGradientShader::MakeLinear(
points, colors_.data(),
positions_.empty() ? nullptr : positions_.data(),
static_cast<int>(colors_.size()), tx_, flags_,
base::OptionalOrNullptr(local_matrix_));
break;
}
case Type::kRadialGradient:
cached_shader_ = SkGradientShader::MakeRadial(
center_, start_radius_, colors_.data(),
positions_.empty() ? nullptr : positions_.data(),
static_cast<int>(colors_.size()), tx_, flags_,
base::OptionalOrNullptr(local_matrix_));
break;
case Type::kTwoPointConicalGradient:
cached_shader_ = SkGradientShader::MakeTwoPointConical(
start_point_, start_radius_, end_point_, end_radius_, colors_.data(),
positions_.empty() ? nullptr : positions_.data(),
static_cast<int>(colors_.size()), tx_, flags_,
base::OptionalOrNullptr(local_matrix_));
break;
case Type::kSweepGradient:
cached_shader_ = SkGradientShader::MakeSweep(
center_.x(), center_.y(), colors_.data(),
positions_.empty() ? nullptr : positions_.data(),
static_cast<int>(colors_.size()), tx_, start_degrees_, end_degrees_,
flags_, base::OptionalOrNullptr(local_matrix_));
break;
case Type::kImage:
if (image_) {
cached_shader_ = image_.GetSkImage()->makeShader(
tx_, ty_, base::OptionalOrNullptr(local_matrix_));
}
break;
case Type::kPaintRecord: {
// Create a recording at the desired scale if this record has images which
// have been decoded before raster.
auto picture = ToSkPicture(record_, tile_, raster_scale, image_provider);
switch (scaling_behavior_) {
// For raster scale, we create a picture shader directly.
case ScalingBehavior::kRasterAtScale:
cached_shader_ = SkShader::MakePictureShader(
std::move(picture), tx_, ty_,
base::OptionalOrNullptr(local_matrix_), nullptr);
break;
// For fixed scale, we create an image shader with an image backed by
// the picture.
case ScalingBehavior::kFixedScale: {
auto image = SkImage::MakeFromPicture(
std::move(picture), SkISize::Make(tile_.width(), tile_.height()),
nullptr, nullptr, SkImage::BitDepth::kU8,
SkColorSpace::MakeSRGB());
cached_shader_ = image->makeShader(
tx_, ty_, base::OptionalOrNullptr(local_matrix_));
break;
}
}
break;
}
case Type::kShaderCount:
NOTREACHED();
break;
}
// If we didn't create a shader for whatever reason, create a fallback color
// one.
if (!cached_shader_)
cached_shader_ = SkShader::MakeColorShader(fallback_color_);
}
void PaintShader::SetColorsAndPositions(const SkColor* colors,
const SkScalar* positions,
int count) {
#if DCHECK_IS_ON()
static const int kMaxShaderColorsSupported = 10000;
DCHECK_GE(count, 2);
DCHECK_LE(count, kMaxShaderColorsSupported);
#endif
colors_.assign(colors, colors + count);
if (positions)
positions_.assign(positions, positions + count);
}
void PaintShader::SetMatrixAndTiling(const SkMatrix* matrix,
SkShader::TileMode tx,
SkShader::TileMode ty) {
if (matrix)
local_matrix_ = *matrix;
tx_ = tx;
ty_ = ty;
}
void PaintShader::SetFlagsAndFallback(uint32_t flags, SkColor fallback_color) {
flags_ = flags;
fallback_color_ = fallback_color;
}
bool PaintShader::IsOpaque() const {
// TODO(enne): don't create a shader to answer this.
return GetSkShader()->isOpaque();
}
bool PaintShader::IsValid() const {
// If we managed to create a shader already, then we should be valid.
if (cached_shader_)
return true;
switch (shader_type_) {
case Type::kEmpty:
case Type::kColor:
return true;
case Type::kSweepGradient:
if (!std::isfinite(start_degrees_) || !std::isfinite(end_degrees_) ||
start_degrees_ >= end_degrees_) {
return false;
}
FALLTHROUGH;
case Type::kLinearGradient:
case Type::kRadialGradient:
case Type::kTwoPointConicalGradient:
return colors_.size() >= 2 &&
(positions_.empty() || positions_.size() == colors_.size());
case Type::kImage:
// We may not be able to decode the image, in which case it would be
// false, but that would still make a valid shader.
return true;
case Type::kPaintRecord:
return !!record_;
case Type::kShaderCount:
return false;
}
return false;
}
bool PaintShader::operator==(const PaintShader& other) const {
if (shader_type_ != other.shader_type_)
return false;
// Record and image shaders are scaled during serialization.
const bool ignore_scaling_differences =
shader_type_ == PaintShader::Type::kPaintRecord ||
shader_type_ == PaintShader::Type::kImage;
// Variables that all shaders use.
const SkMatrix& local_matrix = local_matrix_ ? *local_matrix_ : SkMatrix::I();
const SkMatrix& other_local_matrix =
other.local_matrix_ ? *other.local_matrix_ : SkMatrix::I();
if (!CompareMatrices(local_matrix, other_local_matrix,
ignore_scaling_differences)) {
return false;
}
if (fallback_color_ != other.fallback_color_)
return false;
if (flags_ != other.flags_)
return false;
if (tx_ != other.tx_)
return false;
if (ty_ != other.ty_)
return false;
if (!ignore_scaling_differences &&
scaling_behavior_ != other.scaling_behavior_)
return false;
// Variables that only some shaders use.
switch (shader_type_) {
case Type::kEmpty:
case Type::kColor:
break;
case Type::kSweepGradient:
if (!PaintOp::AreEqualEvenIfNaN(start_degrees_, other.start_degrees_))
return false;
if (!PaintOp::AreEqualEvenIfNaN(end_degrees_, other.end_degrees_))
return false;
FALLTHROUGH;
case Type::kLinearGradient:
case Type::kRadialGradient:
case Type::kTwoPointConicalGradient:
if (!PaintOp::AreEqualEvenIfNaN(start_radius_, other.start_radius_))
return false;
if (!PaintOp::AreEqualEvenIfNaN(end_radius_, other.end_radius_))
return false;
if (!PaintOp::AreSkPointsEqual(center_, other.center_))
return false;
if (!PaintOp::AreSkPointsEqual(start_point_, other.start_point_))
return false;
if (!PaintOp::AreSkPointsEqual(end_point_, other.end_point_))
return false;
if (colors_ != other.colors_)
return false;
if (positions_.size() != other.positions_.size())
return false;
for (size_t i = 0; i < positions_.size(); ++i) {
if (!PaintOp::AreEqualEvenIfNaN(positions_[i], other.positions_[i]))
return false;
}
break;
case Type::kImage:
// TODO(enne): add comparison of images once those are serialized.
break;
case Type::kPaintRecord:
// If we have a record but not other.record, or vice versa, then shaders
// aren't the same.
if (!record_ != !other.record_)
return false;
// tile_ and record_ intentionally omitted since they are modified on the
// serialized shader based on the ctm.
break;
case Type::kShaderCount:
break;
}
return true;
}
} // namespace cc