blob: 171e6a32cccbe3afa5c50c22ab5ace1a9449c4bb [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 "platform/graphics/compositing/PaintChunksToCcLayer.h"
#include "cc/base/render_surface_filters.h"
#include "cc/paint/display_item_list.h"
#include "cc/paint/paint_op_buffer.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/paint/DisplayItemList.h"
#include "platform/graphics/paint/DrawingDisplayItem.h"
#include "platform/graphics/paint/GeometryMapper.h"
#include "platform/graphics/paint/PaintChunk.h"
#include "platform/graphics/paint/PropertyTreeState.h"
#include "platform/graphics/paint/RasterInvalidationTracking.h"
namespace blink {
namespace {
// Applies the clips between |localState| and |ancestorState| into a single
// combined cc::FloatClipDisplayItem on |ccList|.
static void ApplyClipsBetweenStates(const PropertyTreeState& local_state,
const PropertyTreeState& ancestor_state,
cc::DisplayItemList& cc_list,
Vector<int>& needed_restores) {
DCHECK(local_state.Transform() == ancestor_state.Transform());
#if DCHECK_IS_ON()
const TransformPaintPropertyNode* transform_node =
local_state.Clip()->LocalTransformSpace();
if (transform_node != ancestor_state.Transform()) {
const TransformationMatrix& local_to_ancestor_matrix =
GeometryMapper::LocalToAncestorMatrix(transform_node,
ancestor_state.Transform());
// Clips are only in descendant spaces that are transformed by one
// or more scrolls.
DCHECK(local_to_ancestor_matrix.IsIdentityOrTranslation());
}
#endif
const FloatClipRect& combined_clip =
GeometryMapper::LocalToAncestorClipRect(local_state, ancestor_state);
bool antialias = false;
{
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
buffer->push<cc::SaveOp>();
buffer->push<cc::ClipRectOp>(combined_clip.Rect(), SkClipOp::kIntersect,
antialias);
cc_list.EndPaintOfPairedBegin();
}
needed_restores.push_back(1);
}
static void RecordPairedBeginDisplayItems(
const Vector<PropertyTreeState>& paired_states,
const PropertyTreeState& pending_layer_state,
cc::DisplayItemList& cc_list,
Vector<int>& needed_restores) {
PropertyTreeState mapped_clip_destination_space = pending_layer_state;
PropertyTreeState clip_space = pending_layer_state;
bool has_clip = false;
for (Vector<PropertyTreeState>::const_reverse_iterator paired_state =
paired_states.rbegin();
paired_state != paired_states.rend(); ++paired_state) {
switch (paired_state->GetInnermostNode()) {
case PropertyTreeState::kTransform: {
if (has_clip) {
ApplyClipsBetweenStates(clip_space, mapped_clip_destination_space,
cc_list, needed_restores);
has_clip = false;
}
mapped_clip_destination_space = *paired_state;
clip_space = *paired_state;
TransformationMatrix matrix = paired_state->Transform()->Matrix();
matrix.ApplyTransformOrigin(paired_state->Transform()->Origin());
SkMatrix skmatrix =
static_cast<SkMatrix>(TransformationMatrix::ToSkMatrix44(matrix));
{
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
buffer->push<cc::SaveOp>();
buffer->push<cc::ConcatOp>(skmatrix);
cc_list.EndPaintOfPairedBegin();
}
needed_restores.push_back(1);
break;
}
case PropertyTreeState::kClip: {
// Clips are handled in |applyClips| when ending the iterator, or
// transitioning between transform spaces. Here we store off the
// PropertyTreeState of the first found clip, under the transform of
// pairedState->transform(). All subsequent clips before applying the
// transform will be applied in applyClips.
clip_space = *paired_state;
has_clip = true;
#if DCHECK_IS_ON()
if (paired_state->Clip()->LocalTransformSpace() !=
paired_state->Transform()) {
const TransformationMatrix& local_transform_matrix =
paired_state->Effect()->LocalTransformSpace()->Matrix();
// Clips are only in descendant spaces that are transformed by scroll.
DCHECK(local_transform_matrix.IsIdentityOrTranslation());
}
#endif
break;
}
case PropertyTreeState::kEffect: {
// TODO(chrishtr): skip effect and/or compositing display items if
// not necessary.
FloatRect clip_rect =
paired_state->Effect()->OutputClip()->ClipRect().Rect();
// TODO(chrishtr): specify origin of the filter.
FloatPoint filter_origin;
if (paired_state->Effect()->LocalTransformSpace() !=
paired_state->Transform()) {
const TransformPaintPropertyNode* transform_node =
paired_state->Effect()->LocalTransformSpace();
const TransformationMatrix& local_to_ancestor_matrix =
GeometryMapper::LocalToAncestorMatrix(transform_node,
paired_state->Transform());
// Effects are only in descendant spaces that are transformed by one
// or more scrolls.
DCHECK(local_to_ancestor_matrix.IsIdentityOrTranslation());
clip_rect = local_to_ancestor_matrix.MapRect(clip_rect);
filter_origin = local_to_ancestor_matrix.MapPoint(filter_origin);
}
{
cc::PaintFlags flags;
flags.setBlendMode(paired_state->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 * paired_state->Effect()->Opacity())));
flags.setColorFilter(
GraphicsContext::WebCoreColorFilterToSkiaColorFilter(
paired_state->Effect()->GetColorFilter()));
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
// TODO(chrishtr): compute bounds as necessary.
buffer->push<cc::SaveLayerOp>(nullptr, &flags);
cc_list.EndPaintOfPairedBegin();
}
needed_restores.push_back(1);
{
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
buffer->push<cc::SaveOp>();
buffer->push<cc::TranslateOp>(filter_origin.X(), filter_origin.Y());
cc::PaintFlags flags;
flags.setImageFilter(cc::RenderSurfaceFilters::BuildImageFilter(
paired_state->Effect()->Filter().AsCcFilterOperations(),
gfx::SizeF(clip_rect.Width(), clip_rect.Height())));
SkRect layer_bounds = clip_rect;
layer_bounds.offset(-filter_origin.X(), -filter_origin.Y());
buffer->push<cc::SaveLayerOp>(&layer_bounds, &flags);
buffer->push<cc::TranslateOp>(-filter_origin.X(), -filter_origin.Y());
cc_list.EndPaintOfPairedBegin();
}
// The SaveOp+SaveLayerOp above are grouped such that they share a
// visual rect, so group the two restores in the same way so we don't
// have a mismatch in the number of EndPaintOfPairedBegin() vs
// EndPaintOfPairedEnd().
needed_restores.push_back(2);
break;
}
case PropertyTreeState::kNone:
break;
}
}
if (has_clip) {
ApplyClipsBetweenStates(clip_space, mapped_clip_destination_space, cc_list,
needed_restores);
}
}
static void RecordPairedEndDisplayItems(const Vector<int>& needed_restores,
cc::DisplayItemList& cc_list) {
// TODO(danakj): This loop could use base::Reversed once it's allowed here.
for (auto it = needed_restores.rbegin(); it != needed_restores.rend(); ++it) {
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
int num_restores = *it;
for (int i = 0; i < num_restores; ++i)
buffer->push<cc::RestoreOp>();
cc_list.EndPaintOfPairedEnd();
}
}
constexpr gfx::Rect g_large_rect(-200000, -200000, 400000, 400000);
static void AppendDisplayItemToCcDisplayItemList(
const DisplayItem& display_item,
cc::DisplayItemList& cc_list) {
DCHECK(DisplayItem::IsDrawingType(display_item.GetType()));
if (DisplayItem::IsDrawingType(display_item.GetType())) {
const auto& drawing_display_item =
static_cast<const DrawingDisplayItem&>(display_item);
sk_sp<const cc::PaintOpBuffer> record =
drawing_display_item.GetPaintRecord();
if (!record)
return;
// In theory we would pass the bounds of the record, previously done as:
// gfx::Rect bounds = gfx::SkIRectToRect(record->cullRect().roundOut());
// or use the visual rect directly. However, clip content layers attempt
// to raster in a different space than that of the visual rects. We'll be
// reworking visual rects further for SPv2, so for now we just pass a
// visual rect large enough to make sure items raster.
{
cc::PaintOpBuffer* buffer = cc_list.StartPaint();
buffer->push<cc::DrawRecordOp>(std::move(record));
cc_list.EndPaintOfUnpaired(g_large_rect);
}
}
}
} // unnamed namespace
scoped_refptr<cc::DisplayItemList> PaintChunksToCcLayer::Convert(
const Vector<const PaintChunk*>& paint_chunks,
const PropertyTreeState& layer_state,
const gfx::Vector2dF& layer_offset,
const DisplayItemList& display_items,
RasterUnderInvalidationCheckingParams* under_invalidation_checking_params) {
auto cc_list = make_scoped_refptr(new cc::DisplayItemList);
bool need_translate = !layer_offset.IsZero();
if (need_translate) {
cc::PaintOpBuffer* buffer = cc_list->StartPaint();
buffer->push<cc::SaveOp>();
buffer->push<cc::TranslateOp>(-layer_offset.x(), -layer_offset.y());
cc_list->EndPaintOfPairedBegin();
}
for (const auto* paint_chunk : paint_chunks) {
const PropertyTreeState* state =
&paint_chunk->properties.property_tree_state;
PropertyTreeStateIterator iterator(*state);
Vector<PropertyTreeState> paired_states;
for (; state && *state != layer_state; state = iterator.Next()) {
if (state->GetInnermostNode() != PropertyTreeState::kNone)
paired_states.push_back(*state);
}
// TODO(chrishtr): we can avoid some extra paired display items if
// multiple PaintChunks share them. We can also collapse clips between
// transforms into single clips in the same way that PaintLayerClipper does.
Vector<int> needed_restores;
RecordPairedBeginDisplayItems(paired_states, layer_state, *cc_list,
needed_restores);
for (const auto& display_item :
display_items.ItemsInPaintChunk(*paint_chunk))
AppendDisplayItemToCcDisplayItemList(display_item, *cc_list);
RecordPairedEndDisplayItems(needed_restores, *cc_list);
}
if (need_translate) {
cc::PaintOpBuffer* buffer = cc_list->StartPaint();
buffer->push<cc::RestoreOp>();
cc_list->EndPaintOfPairedEnd();
}
if (under_invalidation_checking_params) {
auto& params = *under_invalidation_checking_params;
PaintRecorder recorder;
recorder.beginRecording(params.interest_rect);
// Create a complete cloned list for under-invalidation checking. We can't
// use cc_list because it is not finalized yet.
auto list_clone =
Convert(paint_chunks, layer_state, layer_offset, display_items);
recorder.getRecordingCanvas()->drawPicture(list_clone->ReleaseAsRecord());
params.tracking.CheckUnderInvalidations(params.debug_name,
recorder.finishRecordingAsPicture(),
params.interest_rect);
if (auto record = params.tracking.under_invalidation_record) {
cc::PaintOpBuffer* buffer = cc_list->StartPaint();
buffer->push<cc::DrawRecordOp>(record);
cc_list->EndPaintOfUnpaired(g_large_rect);
}
}
cc_list->Finalize();
return cc_list;
}
} // namespace blink