Skip to content

Commit

Permalink
[SPv175] Output bounds for SaveLayerOp
Browse files Browse the repository at this point in the history
This is mainly for performance. Without bounds, skia may execute some
code to find a finite bounds, and the bounds may be too big for the
SaveLayerOp which may lead other cost.

Cluster telemetry showed that this CL can improve raster_time by 8%.
https://ct.skia.org/results/cluster-telemetry/tasks/chromium_perf_runs/wangxianzhu-20180318183406/html/index.html

Pin-point showed 20% improvement for raster_time in
rasterize_and_record_micro.top_25.
https://pinpoint-dot-chromeperf.appspot.com/results2/14f80e29440000

Another purpose is for some skia bug causing crbug.com/816854. In the
bug, a small chunk with opacity without bounds in SaveLayerOp may
cause ghost rasterization far from the target area.

Bug: 803867,816854
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2
Change-Id: Ife282e44b09fb2978e850e4c8cd6ca4e0150c857
Reviewed-on: https://chromium-review.googlesource.com/966699
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Reviewed-by: vmpstr <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#544561}
  • Loading branch information
wangxianzhu authored and Commit Bot committed Mar 20, 2018
1 parent d289dba commit b7e8c6e
Show file tree
Hide file tree
Showing 71 changed files with 362 additions and 122 deletions.
16 changes: 14 additions & 2 deletions cc/paint/display_item_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,26 @@ class CC_PAINT_EXPORT DisplayItemList

// Push functions construct a new op on the paint op buffer, while maintaining
// bookkeeping information. Must be called after invoking StartPaint().
// Returns the id (which is an opaque value) of the operation that can be used
// in UpdateSaveLayerBounds().
template <typename T, typename... Args>
void push(Args&&... args) {
size_t push(Args&&... args) {
#if DCHECK_IS_ON()
DCHECK(IsPainting());
#endif
size_t offset = paint_op_buffer_.next_op_offset();
if (usage_hint_ == kTopLevelDisplayItemList)
offsets_.push_back(paint_op_buffer_.next_op_offset());
offsets_.push_back(offset);
paint_op_buffer_.push<T>(std::forward<Args>(args)...);
return offset;
}

// Called by blink::PaintChunksToCcLayer when an effect ends, to update the
// bounds of a SaveLayerOp which was emitted when the effect started. This is
// needed because blink doesn't know the bounds when an effect starts. Don't
// add other mutation methods like this if there is better alternative.
void UpdateSaveLayerBounds(size_t id, const SkRect& bounds) {
paint_op_buffer_.UpdateSaveLayerBounds(id, bounds);
}

void EndPaintOfUnpaired(const gfx::Rect& visual_rect) {
Expand Down
4 changes: 4 additions & 0 deletions cc/paint/paint_op_buffer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ std::string PaintOpTypeToString(PaintOpType type) {
return "UNKNOWN";
}

std::ostream& operator<<(std::ostream& os, PaintOpType type) {
return os << PaintOpTypeToString(type);
}

template <typename T>
size_t SimpleSerialize(const PaintOp* op, void* memory, size_t size) {
if (sizeof(T) > size)
Expand Down
23 changes: 23 additions & 0 deletions cc/paint/paint_op_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ enum class PaintOpType : uint8_t {
};

CC_PAINT_EXPORT std::string PaintOpTypeToString(PaintOpType type);
CC_PAINT_EXPORT std::ostream& operator<<(std::ostream&, PaintOpType);

struct CC_PAINT_EXPORT PlaybackParams {
using CustomDataRasterCallback =
Expand Down Expand Up @@ -893,6 +894,18 @@ class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt {
AnalyzeAddedOp(op);
}

void UpdateSaveLayerBounds(size_t offset, const SkRect& bounds) {
CHECK_LT(offset, used_);
CHECK_LE(offset + sizeof(SaveLayerOp), used_);

auto* op = reinterpret_cast<SaveLayerOp*>(data_.get() + offset);
CHECK_EQ(op->type, static_cast<uint32_t>(SaveLayerOp::kType));
size_t skip = op->skip;
DCHECK_EQ(skip, ComputeOpSkip(sizeof(SaveLayerOp)));

op->bounds = bounds;
}

template <typename T>
void AnalyzeAddedOp(const T* op) {
static_assert(!std::is_same<T, PaintOp>::value,
Expand All @@ -909,6 +922,16 @@ class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt {
subrecord_bytes_used_ += op->AdditionalBytesUsed();
}

template <typename T>
const T* GetOpAtForTesting(size_t index) const {
size_t i = 0;
for (PaintOpBuffer::Iterator it(this); it && i <= index; ++it, ++i) {
if (i == index && (*it)->GetType() == T::kType)
return static_cast<const T*>(*it);
}
return nullptr;
}

class CC_PAINT_EXPORT Iterator {
public:
explicit Iterator(const PaintOpBuffer* buffer)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,15 @@ class ConversionContext {
void ApplyTransform(const TransformPaintPropertyNode* target_transform) {
if (target_transform == current_transform_)
return;
cc_list_.push<cc::ConcatOp>(GetSkMatrix(target_transform));
}

cc_list_.push<cc::ConcatOp>(
static_cast<SkMatrix>(TransformationMatrix::ToSkMatrix44(
GeometryMapper::SourceToDestinationProjection(
target_transform, current_transform_))));
SkMatrix GetSkMatrix(
const TransformPaintPropertyNode* target_transform) const {
return AffineTransformToSkMatrix(
GeometryMapper::SourceToDestinationProjection(target_transform,
current_transform_)
.ToAffineTransform());
}

void AppendRestore(size_t n) {
Expand All @@ -128,6 +132,10 @@ class ConversionContext {
cc_list_.EndPaintOfPairedEnd();
}

void UpdateEffectBounds(const FloatRect&, const TransformPaintPropertyNode*);
void PopToParentEffect();
void PopClips();

const PropertyTreeState& layer_state_;
gfx::Vector2dF layer_offset_;

Expand All @@ -152,12 +160,30 @@ class ConversionContext {
};
Vector<StateEntry> state_stack_;

// This structure accumulates bounds of all chunks under an effect. When an
// effect starts, we emit a SaveLayerOp with null bounds starts, and push a
// new |EffectBoundsInfo| onto |effect_bounds_stack_|. When the effect ends,
// we update the bounds of the SaveLayerOp.
struct EffectBoundsInfo {
// The id of the SaveLayerOp for this effect. It's recorded when we push the
// SaveLayerOp for this effect, and used when this effect ends in
// UpdateSaveLayerBounds().
size_t save_layer_id;
// The transform space when the SaveLayerOp was emitted.
const TransformPaintPropertyNode* transform;
// Records the bounds of the effect which initiated the entry. Note that
// the effect is not |this->effect| (which is the previous effect), but the
// |current_effect_| when this entry is the top of the stack.
FloatRect bounds;
};
Vector<EffectBoundsInfo> effect_bounds_stack_;

cc::DisplayItemList& cc_list_;
};

ConversionContext::~ConversionContext() {
for (auto& entry : state_stack_)
AppendRestore(entry.saved_count);
while (state_stack_.size())
PopToParentEffect();
}

void ConversionContext::SwitchToClip(const ClipPaintPropertyNode* target_clip) {
Expand Down Expand Up @@ -298,13 +324,7 @@ void ConversionContext::SwitchToEffect(
#endif
if (!state_stack_.size())
break;

StateEntry& previous_state = state_stack_.back();
AppendRestore(previous_state.saved_count);
current_transform_ = previous_state.transform;
current_clip_ = previous_state.clip;
current_effect_ = previous_state.effect;
state_stack_.pop_back();
PopToParentEffect();
}

// Step 2: Collect all effects between the target effect and the current
Expand All @@ -325,76 +345,67 @@ void ConversionContext::SwitchToEffect(

// Step 3a: Before each effect can be applied, we must enter its output
// clip first, or exit all clips if it doesn't have one.
if (sub_effect->OutputClip()) {
if (sub_effect->OutputClip())
SwitchToClip(sub_effect->OutputClip());
} else {
while (state_stack_.size() &&
state_stack_.back().type == StateEntry::kClip) {
StateEntry& previous_state = state_stack_.back();
AppendRestore(previous_state.saved_count);
current_transform_ = previous_state.transform;
current_clip_ = previous_state.clip;
DCHECK_EQ(previous_state.effect, current_effect_);
state_stack_.pop_back();
}
}
else
PopClips();

// Step 3b: Apply non-spatial effects first, adjust CTM, then apply spatial
// effects. Strictly speaking the CTM shall be appled first, it is done
// in this particular order only to save one SaveOp.
// Step 3b: Apply effects.
cc_list_.StartPaint();
cc::PaintFlags flags;
int saved_count = 0;

auto save_layer_once = [this, &flags, &saved_count]() {
if (!saved_count) {
saved_count = 1;
cc_list_.push<cc::SaveLayerOp>(nullptr, &flags);
}
};

if (sub_effect->BlendMode() != SkBlendMode::kSrcOver ||
sub_effect->Opacity() != 1.f ||
sub_effect->GetColorFilter() != kColorFilterNone) {
size_t save_layer_id = kNotFound;
const auto* target_transform = current_transform_;

// We always create separate effect nodes for normal effects and filter
// effects, so we can handle them separately.
bool has_filter = !sub_effect->Filter().IsEmpty();
bool has_other_effects = sub_effect->Opacity() != 1.f ||
sub_effect->BlendMode() != SkBlendMode::kSrcOver ||
sub_effect->GetColorFilter() != kColorFilterNone;
DCHECK(!has_filter || !has_other_effects);

if (!has_filter) {
// No need to adjust transform for non-filter effects because transform
// doesn't matter.
cc::PaintFlags flags;
flags.setBlendMode(sub_effect->BlendMode());
// TODO(ajuma): This should really be rounding instead of flooring the
// alpha value, but that breaks slimming paint reftests.
flags.setAlpha(
static_cast<uint8_t>(gfx::ToFlooredInt(255 * sub_effect->Opacity())));
flags.setColorFilter(GraphicsContext::WebCoreColorFilterToSkiaColorFilter(
sub_effect->GetColorFilter()));
save_layer_once();
}

const TransformPaintPropertyNode* target_transform =
sub_effect->LocalTransformSpace();
if (current_transform_ != target_transform) {
save_layer_once();
ApplyTransform(target_transform);
}

if (sub_effect->Filter().IsEmpty()) {
save_layer_once();
save_layer_id = cc_list_.push<cc::SaveLayerOp>(nullptr, &flags);
saved_count++;
} else {
// Handle filter effect. Adjust transform first.
target_transform = sub_effect->LocalTransformSpace();
FloatPoint filter_origin = sub_effect->PaintOffset();
if (filter_origin != FloatPoint()) {
save_layer_once();
cc_list_.push<cc::TranslateOp>(filter_origin.X(), filter_origin.Y());
if (current_transform_ != target_transform ||
filter_origin != FloatPoint()) {
auto matrix = GetSkMatrix(target_transform);
matrix.preTranslate(filter_origin.X(), filter_origin.Y());
cc_list_.push<cc::SaveOp>();
cc_list_.push<cc::ConcatOp>(matrix);
saved_count++;
}

// The size parameter is only used to computed the origin of zoom
// operation, which we never generate.
gfx::SizeF empty;
cc::PaintFlags filter_flags;
filter_flags.setImageFilter(cc::RenderSurfaceFilters::BuildImageFilter(
sub_effect->Filter().AsCcFilterOperations(), empty));
cc_list_.push<cc::SaveLayerOp>(nullptr, &filter_flags);
save_layer_id = cc_list_.push<cc::SaveLayerOp>(nullptr, &filter_flags);

if (filter_origin != FloatPoint())
cc_list_.push<cc::TranslateOp>(-filter_origin.X(), -filter_origin.Y());

saved_count++;
}

DCHECK(saved_count);
DCHECK_GT(saved_count, 0);
DCHECK_LE(saved_count, 2);
DCHECK_NE(save_layer_id, kNotFound);
cc_list_.EndPaintOfPairedBegin();

// Step 3c: Adjust state and push previous state onto effect stack.
Expand All @@ -403,12 +414,84 @@ void ConversionContext::SwitchToEffect(
state_stack_.emplace_back(StateEntry{StateEntry::PairedType::kEffect,
saved_count, current_transform_,
current_clip_, current_effect_});
effect_bounds_stack_.emplace_back(
EffectBoundsInfo{save_layer_id, target_transform});
current_transform_ = target_transform;
current_clip_ = input_clip;
current_effect_ = sub_effect;
}
}

void ConversionContext::UpdateEffectBounds(
const FloatRect& bounds,
const TransformPaintPropertyNode* transform) {
if (effect_bounds_stack_.IsEmpty() || bounds.IsEmpty())
return;

auto& effect_bounds_info = effect_bounds_stack_.back();
FloatRect mapped_bounds = bounds;
GeometryMapper::SourceToDestinationRect(
transform, effect_bounds_info.transform, mapped_bounds);
effect_bounds_info.bounds.Unite(mapped_bounds);
}

// Pop clip states (if any) and one effect state (if any) on the top of the
// stack. Update the bounds of the SaveLayerOp of the effect.
void ConversionContext::PopToParentEffect() {
DCHECK(state_stack_.size());
PopClips();
if (state_stack_.IsEmpty())
return;

const auto& previous_state = state_stack_.back();
DCHECK_EQ(previous_state.type, StateEntry::kEffect);
DCHECK_EQ(current_effect_->Parent(), previous_state.effect);
DCHECK_EQ(current_clip_, previous_state.clip);

DCHECK(effect_bounds_stack_.size());
const auto& bounds_info = effect_bounds_stack_.back();
FloatRect bounds = bounds_info.bounds;
if (!bounds.IsEmpty()) {
if (current_effect_->Filter().IsEmpty()) {
cc_list_.UpdateSaveLayerBounds(bounds_info.save_layer_id, bounds);
} else {
// The bounds for the SaveLayerOp should be the source bounds before the
// filter is applied, in the space of the TranslateOp which was emitted
// before the SaveLayerOp.
auto save_layer_bounds = bounds;
save_layer_bounds.MoveBy(-current_effect_->PaintOffset());
cc_list_.UpdateSaveLayerBounds(bounds_info.save_layer_id,
save_layer_bounds);
// We need to propagate the filtered bounds to the parent.
bounds = current_effect_->MapRect(bounds);
}
}

effect_bounds_stack_.pop_back();
current_effect_ = previous_state.effect;

// Propagate the bounds to the parent effect.
UpdateEffectBounds(bounds, current_transform_);

AppendRestore(previous_state.saved_count);
current_effect_ = previous_state.effect;
current_transform_ = previous_state.transform;
state_stack_.pop_back();
}

// Pop clip states on the top of the stack until the top is an effect state
// or the stack is empty.
void ConversionContext::PopClips() {
while (state_stack_.size() && state_stack_.back().type == StateEntry::kClip) {
const auto& previous_state = state_stack_.back();
AppendRestore(previous_state.saved_count);
current_transform_ = previous_state.transform;
current_clip_ = previous_state.clip;
DCHECK_EQ(previous_state.effect, current_effect_);
state_stack_.pop_back();
}
}

void ConversionContext::Convert(const Vector<const PaintChunk*>& paint_chunks,
const DisplayItemList& display_items) {
bool translated = false;
Expand Down Expand Up @@ -478,6 +561,7 @@ void ConversionContext::Convert(const Vector<const PaintChunk*>& paint_chunks,
}
if (transformed)
AppendRestore(1);
UpdateEffectBounds(chunk.bounds, chunk_state.Transform());
}
if (translated)
AppendRestore(1);
Expand Down
Loading

0 comments on commit b7e8c6e

Please sign in to comment.