blob: 7f61d9b1baadc659030c4925fcd1398abedd5592 [file] [log] [blame]
// Copyright 2014 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 "core/paint/PaintLayerPainter.h"
#include "core/frame/LocalFrame.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutView.h"
#include "core/paint/ClipPathClipper.h"
#include "core/paint/FilterPainter.h"
#include "core/paint/LayerClipRecorder.h"
#include "core/paint/ObjectPaintProperties.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/ScrollRecorder.h"
#include "core/paint/ScrollableAreaPainter.h"
#include "core/paint/Transform3DRecorder.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/geometry/FloatPoint3D.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/graphics/paint/CompositingRecorder.h"
#include "platform/graphics/paint/DisplayItemCacheSkipper.h"
#include "platform/graphics/paint/PaintChunkProperties.h"
#include "platform/graphics/paint/ScopedPaintChunkProperties.h"
#include "platform/graphics/paint/SubsequenceRecorder.h"
#include "platform/graphics/paint/Transform3DDisplayItem.h"
#include "wtf/Optional.h"
namespace blink {
static inline bool shouldSuppressPaintingLayer(const PaintLayer& layer)
{
// Avoid painting descendants of the root layer when stylesheets haven't loaded. This avoids some FOUC.
// It's ok not to draw, because later on, when all the stylesheets do load, Document::styleResolverMayHaveChanged()
// will invalidate all painted output via a call to LayoutView::invalidatePaintForViewAndCompositedLayers().
// We also avoid caching subsequences in this mode; see shouldCreateSubsequence().
if (layer.layoutObject()->document().didLayoutWithPendingStylesheets() && !layer.isRootLayer() && !layer.layoutObject()->isDocumentElement())
return true;
return false;
}
void PaintLayerPainter::paint(GraphicsContext& context, const LayoutRect& damageRect, const GlobalPaintFlags globalPaintFlags, PaintLayerFlags paintFlags)
{
PaintLayerPaintingInfo paintingInfo(&m_paintLayer, LayoutRect(enclosingIntRect(damageRect)), globalPaintFlags, LayoutSize());
if (shouldPaintLayerInSoftwareMode(globalPaintFlags, paintFlags))
paintLayer(context, paintingInfo, paintFlags);
}
static ShouldRespectOverflowClipType shouldRespectOverflowClip(PaintLayerFlags paintFlags, const LayoutObject* layoutObject)
{
return (paintFlags & PaintLayerPaintingOverflowContents || (paintFlags & PaintLayerPaintingChildClippingMaskPhase && layoutObject->hasClipPath())) ? IgnoreOverflowClip : RespectOverflowClip;
}
PaintLayerPainter::PaintResult PaintLayerPainter::paintLayer(GraphicsContext& context, const PaintLayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
// https://code.google.com/p/chromium/issues/detail?id=343772
DisableCompositingQueryAsserts disabler;
if (m_paintLayer.compositingState() != NotComposited) {
if (paintingInfo.getGlobalPaintFlags() & GlobalPaintFlattenCompositingLayers) {
// FIXME: ok, but what about GlobalPaintFlattenCompositingLayers? That's for printing and drag-image.
// FIXME: why isn't the code here global, as opposed to being set on each paintLayer() call?
paintFlags |= PaintLayerUncachedClipRects;
}
}
// Non self-painting layers without self-painting descendants don't need to be painted as their
// layoutObject() should properly paint itself.
if (!m_paintLayer.isSelfPaintingLayer() && !m_paintLayer.hasSelfPaintingLayerDescendant())
return FullyPainted;
if (shouldSuppressPaintingLayer(m_paintLayer))
return FullyPainted;
if (m_paintLayer.layoutObject()->view()->frame() && m_paintLayer.layoutObject()->view()->frame()->shouldThrottleRendering())
return FullyPainted;
// If this layer is totally invisible then there is nothing to paint.
if (!m_paintLayer.layoutObject()->opacity() && !m_paintLayer.layoutObject()->hasBackdropFilter())
return FullyPainted;
if (m_paintLayer.paintsWithTransparency(paintingInfo.getGlobalPaintFlags()))
paintFlags |= PaintLayerHaveTransparency;
// Transforms will be applied by property nodes directly for SPv2.
// PaintLayerAppliedTransform is used in LayoutReplica, to avoid applying the transform twice.
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() && m_paintLayer.paintsWithTransform(paintingInfo.getGlobalPaintFlags()) && !(paintFlags & PaintLayerAppliedTransform))
return paintLayerWithTransform(context, paintingInfo, paintFlags);
return paintLayerContentsAndReflection(context, paintingInfo, paintFlags);
}
PaintLayerPainter::PaintResult PaintLayerPainter::paintLayerContentsAndReflection(GraphicsContext& context, const PaintLayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags, FragmentPolicy fragmentPolicy)
{
ASSERT(m_paintLayer.isSelfPaintingLayer() || m_paintLayer.hasSelfPaintingLayerDescendant());
PaintLayerFlags localPaintFlags = paintFlags & ~(PaintLayerAppliedTransform);
PaintResult result = FullyPainted;
// Paint the reflection first if we have one.
if (m_paintLayer.reflectionInfo() && !RuntimeEnabledFeatures::cssBoxReflectFilterEnabled()) {
DisplayItemCacheSkipper skipper(context);
if (m_paintLayer.reflectionInfo()->paint(context, paintingInfo, localPaintFlags) == MayBeClippedByPaintDirtyRect)
result = MayBeClippedByPaintDirtyRect;
}
localPaintFlags |= PaintLayerPaintingCompositingAllPhases;
if (paintLayerContents(context, paintingInfo, localPaintFlags, fragmentPolicy) == MayBeClippedByPaintDirtyRect)
result = MayBeClippedByPaintDirtyRect;
return result;
}
static bool shouldCreateSubsequence(const PaintLayer& paintLayer, GraphicsContext& context, const PaintLayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
// Caching is not needed during printing.
if (context.printing())
return false;
// Don't create subsequence for a composited layer because if it can be cached,
// we can skip the whole painting in GraphicsLayer::paint() with CachedDisplayItemList.
// This also avoids conflict of PaintLayer::previousXXX() when paintLayer is composited
// scrolling and is painted twice for GraphicsLayers of container and scrolling contents.
if (paintLayer.compositingState() == PaintsIntoOwnBacking)
return false;
// Don't create subsequence during special painting to avoid cache conflict with normal painting.
if (paintingInfo.getGlobalPaintFlags() & GlobalPaintFlattenCompositingLayers)
return false;
if (paintFlags & (PaintLayerPaintingReflection | PaintLayerPaintingRootBackgroundOnly | PaintLayerPaintingOverlayScrollbars | PaintLayerUncachedClipRects))
return false;
// Create subsequence for only stacking contexts whose painting are atomic.
if (!paintLayer.stackingNode()->isStackingContext())
return false;
// The layer doesn't have children. Subsequence caching is not worth because normally the actual painting will be cheap.
if (!PaintLayerStackingNodeIterator(*paintLayer.stackingNode(), AllChildren).next())
return false;
// When in FOUC-avoidance mode, don't cache any subsequences, to avoid having
// to invalidate all of them when leaving this mode. There is an early-out in BlockPainter::paintContents that may result
// in nothing getting painted in thos mode, in addition to early-out logic in PaintLayerPainter.
if (paintLayer.layoutObject()->document().didLayoutWithPendingStylesheets())
return false;
return true;
}
static bool shouldRepaintSubsequence(PaintLayer& paintLayer, const PaintLayerPaintingInfo& paintingInfo, ShouldRespectOverflowClipType respectOverflowClip, const LayoutSize& subpixelAccumulation, bool& shouldClearEmptyPaintPhaseFlags)
{
bool needsRepaint = false;
// We should set shouldResetEmptyPaintPhaseFlags if some previously unpainted objects may begin
// to be painted, causing a previously empty paint phase to become non-empty.
// Repaint subsequence if the layer is marked for needing repaint.
// We don't set needsResetEmptyPaintPhase here, but clear the empty paint phase flags
// in PaintLayer::setNeedsPaintPhaseXXX(), to ensure that we won't clear
// previousPaintPhaseXXXEmpty flags when unrelated things changed which won't
// cause the paint phases to become non-empty.
if (paintLayer.needsRepaint())
needsRepaint = true;
// Repaint if layer's clip changes.
ClipRects& clipRects = paintLayer.clipper().paintingClipRects(paintingInfo.rootLayer, respectOverflowClip, subpixelAccumulation);
ClipRects* previousClipRects = paintLayer.previousPaintingClipRects();
if (&clipRects != previousClipRects && (!previousClipRects || clipRects != *previousClipRects)) {
needsRepaint = true;
shouldClearEmptyPaintPhaseFlags = true;
}
paintLayer.setPreviousPaintingClipRects(clipRects);
// Repaint if previously the layer might be clipped by paintDirtyRect and paintDirtyRect changes.
if (paintLayer.previousPaintResult() == PaintLayerPainter::MayBeClippedByPaintDirtyRect && paintLayer.previousPaintDirtyRect() != paintingInfo.paintDirtyRect) {
needsRepaint = true;
shouldClearEmptyPaintPhaseFlags = true;
}
paintLayer.setPreviousPaintDirtyRect(paintingInfo.paintDirtyRect);
// Repaint if scroll offset accumulation changes.
if (paintingInfo.scrollOffsetAccumulation != paintLayer.previousScrollOffsetAccumulationForPainting()) {
needsRepaint = true;
shouldClearEmptyPaintPhaseFlags = true;
}
paintLayer.setPreviousScrollOffsetAccumulationForPainting(paintingInfo.scrollOffsetAccumulation);
return needsRepaint;
}
PaintLayerPainter::PaintResult PaintLayerPainter::paintLayerContents(GraphicsContext& context, const PaintLayerPaintingInfo& paintingInfoArg, PaintLayerFlags paintFlags, FragmentPolicy fragmentPolicy)
{
ASSERT(m_paintLayer.isSelfPaintingLayer() || m_paintLayer.hasSelfPaintingLayerDescendant());
ASSERT(!(paintFlags & PaintLayerAppliedTransform));
bool isSelfPaintingLayer = m_paintLayer.isSelfPaintingLayer();
bool isPaintingOverlayScrollbars = paintFlags & PaintLayerPaintingOverlayScrollbars;
bool isPaintingScrollingContent = paintFlags & PaintLayerPaintingCompositingScrollingPhase;
bool isPaintingCompositedForeground = paintFlags & PaintLayerPaintingCompositingForegroundPhase;
bool isPaintingCompositedBackground = paintFlags & PaintLayerPaintingCompositingBackgroundPhase;
bool isPaintingOverflowContents = paintFlags & PaintLayerPaintingOverflowContents;
// Outline always needs to be painted even if we have no visible content. Also,
// the outline is painted in the background phase during composited scrolling.
// If it were painted in the foreground phase, it would move with the scrolled
// content. When not composited scrolling, the outline is painted in the
// foreground phase. Since scrolled contents are moved by paint invalidation in this
// case, the outline won't get 'dragged along'.
bool shouldPaintSelfOutline = isSelfPaintingLayer && !isPaintingOverlayScrollbars
&& ((isPaintingScrollingContent && isPaintingCompositedBackground)
|| (!isPaintingScrollingContent && isPaintingCompositedForeground))
&& m_paintLayer.layoutObject()->styleRef().hasOutline();
bool shouldPaintContent = m_paintLayer.hasVisibleContent() && isSelfPaintingLayer && !isPaintingOverlayScrollbars;
PaintResult result = FullyPainted;
if (paintFlags & PaintLayerPaintingRootBackgroundOnly && !m_paintLayer.layoutObject()->isLayoutView())
return result;
if (m_paintLayer.layoutObject()->view()->frame() && m_paintLayer.layoutObject()->view()->frame()->shouldThrottleRendering())
return result;
// Ensure our lists are up to date.
m_paintLayer.stackingNode()->updateLayerListsIfNeeded();
LayoutSize subpixelAccumulation = m_paintLayer.compositingState() == PaintsIntoOwnBacking ? m_paintLayer.subpixelAccumulation() : paintingInfoArg.subPixelAccumulation;
ShouldRespectOverflowClipType respectOverflowClip = shouldRespectOverflowClip(paintFlags, m_paintLayer.layoutObject());
Optional<SubsequenceRecorder> subsequenceRecorder;
bool shouldClearEmptyPaintPhaseFlags = false;
if (shouldCreateSubsequence(m_paintLayer, context, paintingInfoArg, paintFlags)) {
if (!shouldRepaintSubsequence(m_paintLayer, paintingInfoArg, respectOverflowClip, subpixelAccumulation, shouldClearEmptyPaintPhaseFlags)
&& SubsequenceRecorder::useCachedSubsequenceIfPossible(context, m_paintLayer))
return result;
subsequenceRecorder.emplace(context, m_paintLayer);
} else {
shouldClearEmptyPaintPhaseFlags = true;
}
if (shouldClearEmptyPaintPhaseFlags) {
m_paintLayer.setPreviousPaintPhaseDescendantOutlinesEmpty(false);
m_paintLayer.setPreviousPaintPhaseFloatEmpty(false);
m_paintLayer.setPreviousPaintPhaseDescendantBlockBackgroundsEmpty(false);
}
PaintLayerPaintingInfo paintingInfo = paintingInfoArg;
LayoutPoint offsetFromRoot;
m_paintLayer.convertToLayerCoords(paintingInfo.rootLayer, offsetFromRoot);
offsetFromRoot.move(subpixelAccumulation);
LayoutRect bounds = m_paintLayer.physicalBoundingBox(offsetFromRoot);
if (!paintingInfo.paintDirtyRect.contains(bounds))
result = MayBeClippedByPaintDirtyRect;
if (paintingInfo.ancestorHasClipPathClipping && m_paintLayer.layoutObject()->isPositioned())
UseCounter::count(m_paintLayer.layoutObject()->document(), UseCounter::ClipPathOfPositionedElement);
// These helpers output clip and compositing operations using a RAII pattern. Stack-allocated-varibles are destructed in the reverse order of construction,
// so they are nested properly.
Optional<ClipPathClipper> clipPathClipper;
// Clip-path, like border radius, must not be applied to the contents of a composited-scrolling container.
// It must, however, still be applied to the mask layer, so that the compositor can properly mask the
// scrolling contents and scrollbars.
if (m_paintLayer.layoutObject()->hasClipPath() && (!m_paintLayer.needsCompositedScrolling() || (paintFlags & PaintLayerPaintingChildClippingMaskPhase))) {
paintingInfo.ancestorHasClipPathClipping = true;
LayoutRect referenceBox(m_paintLayer.boxForClipPath());
// Note that this isn't going to work correctly if crossing a column boundary. The reference box should be
// determined per-fragment, and hence this ought to be performed after fragmentation.
if (m_paintLayer.enclosingPaginationLayer())
m_paintLayer.convertFromFlowThreadToVisualBoundingBoxInAncestor(paintingInfo.rootLayer, referenceBox);
else
referenceBox.moveBy(offsetFromRoot);
clipPathClipper.emplace(
context, *m_paintLayer.layoutObject(), FloatRect(referenceBox), FloatPoint(referenceBox.location()));
}
Optional<CompositingRecorder> compositingRecorder;
// Blending operations must be performed only with the nearest ancestor stacking context.
// Note that there is no need to composite if we're painting the root.
// FIXME: this should be unified further into PaintLayer::paintsWithTransparency().
bool shouldCompositeForBlendMode = (!m_paintLayer.layoutObject()->isDocumentElement() || m_paintLayer.layoutObject()->isSVGRoot()) && m_paintLayer.stackingNode()->isStackingContext() && m_paintLayer.hasNonIsolatedDescendantWithBlendMode();
if (shouldCompositeForBlendMode || m_paintLayer.paintsWithTransparency(paintingInfo.getGlobalPaintFlags())) {
FloatRect compositingBounds = FloatRect(m_paintLayer.paintingExtent(paintingInfo.rootLayer, paintingInfo.subPixelAccumulation, paintingInfo.getGlobalPaintFlags()));
compositingRecorder.emplace(context, *m_paintLayer.layoutObject(),
WebCoreCompositeToSkiaComposite(CompositeSourceOver, m_paintLayer.layoutObject()->style()->blendMode()),
m_paintLayer.layoutObject()->opacity(), &compositingBounds);
}
PaintLayerPaintingInfo localPaintingInfo(paintingInfo);
localPaintingInfo.subPixelAccumulation = subpixelAccumulation;
PaintLayerFragments layerFragments;
if (shouldPaintContent || shouldPaintSelfOutline || isPaintingOverlayScrollbars) {
// Collect the fragments. This will compute the clip rectangles and paint offsets for each layer fragment.
ClipRectsCacheSlot cacheSlot = (paintFlags & PaintLayerUncachedClipRects) ? UncachedClipRects : PaintingClipRects;
// TODO(trchen): We haven't decided how to handle visual fragmentation with SPv2.
// Related thread https://groups.google.com/a/chromium.org/forum/#!topic/graphics-dev/81XuWFf-mxM
if (fragmentPolicy == ForceSingleFragment || RuntimeEnabledFeatures::slimmingPaintV2Enabled())
m_paintLayer.appendSingleFragmentIgnoringPagination(layerFragments, localPaintingInfo.rootLayer, localPaintingInfo.paintDirtyRect, cacheSlot, IgnoreOverlayScrollbarSize, respectOverflowClip, &offsetFromRoot, localPaintingInfo.subPixelAccumulation);
else
m_paintLayer.collectFragments(layerFragments, localPaintingInfo.rootLayer, localPaintingInfo.paintDirtyRect, cacheSlot, IgnoreOverlayScrollbarSize, respectOverflowClip, &offsetFromRoot, localPaintingInfo.subPixelAccumulation);
// TODO(trchen): Needs to adjust cull rect between transform spaces. https://crbug.com/593596
// Disables layer culling for SPv2 for now because the space of the cull rect doesn't match
// the space we paint in. Clipping will still be done by clip nodes, so this won't cause
// rendering issues, only performance.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
layerFragments[0].backgroundRect = LayoutRect(LayoutRect::infiniteIntRect());
layerFragments[0].foregroundRect = LayoutRect(LayoutRect::infiniteIntRect());
}
if (shouldPaintContent) {
// TODO(wangxianzhu): This is for old slow scrolling. Implement similar optimization for slimming paint v2.
shouldPaintContent = atLeastOneFragmentIntersectsDamageRect(layerFragments, localPaintingInfo, paintFlags, offsetFromRoot);
if (!shouldPaintContent)
result = MayBeClippedByPaintDirtyRect;
}
}
bool selectionOnly = localPaintingInfo.getGlobalPaintFlags() & GlobalPaintSelectionOnly;
{ // Begin block for the lifetime of any filter.
FilterPainter filterPainter(m_paintLayer, context, offsetFromRoot, layerFragments.isEmpty() ? ClipRect() : layerFragments[0].backgroundRect, localPaintingInfo, paintFlags);
Optional<ScopedPaintChunkProperties> scopedPaintChunkProperties;
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
const ObjectPaintProperties* objectPaintProperties = m_paintLayer.layoutObject()->objectPaintProperties();
ASSERT(objectPaintProperties && objectPaintProperties->localBorderBoxProperties());
PaintChunkProperties properties(context.getPaintController().currentPaintChunkProperties());
auto& localBorderBoxProperties = *objectPaintProperties->localBorderBoxProperties();
properties.transform = localBorderBoxProperties.propertyTreeState.transform;
properties.clip = localBorderBoxProperties.propertyTreeState.clip;
properties.effect = localBorderBoxProperties.propertyTreeState.effect;
properties.backfaceHidden = m_paintLayer.layoutObject()->hasHiddenBackface();
scopedPaintChunkProperties.emplace(context.getPaintController(), m_paintLayer, properties);
}
bool isPaintingRootLayer = (&m_paintLayer) == paintingInfo.rootLayer;
bool shouldPaintBackground = shouldPaintContent && !selectionOnly
&& (isPaintingCompositedBackground || (isPaintingRootLayer && !(paintFlags & PaintLayerPaintingSkipRootBackground)));
bool shouldPaintNegZOrderList = (isPaintingScrollingContent && isPaintingOverflowContents) || (!isPaintingScrollingContent && isPaintingCompositedBackground);
bool shouldPaintOwnContents = isPaintingCompositedForeground && shouldPaintContent;
bool shouldPaintNormalFlowAndPosZOrderLists = isPaintingCompositedForeground;
bool shouldPaintOverlayScrollbars = isPaintingOverlayScrollbars;
if (shouldPaintBackground) {
paintBackgroundForFragments(layerFragments, context, paintingInfo.paintDirtyRect,
localPaintingInfo, paintFlags);
}
if (shouldPaintNegZOrderList) {
if (paintChildren(NegativeZOrderChildren, context, paintingInfo, paintFlags) == MayBeClippedByPaintDirtyRect)
result = MayBeClippedByPaintDirtyRect;
}
if (shouldPaintOwnContents) {
paintForegroundForFragments(layerFragments, context, paintingInfo.paintDirtyRect,
localPaintingInfo, selectionOnly, paintFlags);
}
if (shouldPaintSelfOutline)
paintSelfOutlineForFragments(layerFragments, context, localPaintingInfo, paintFlags);
if (shouldPaintNormalFlowAndPosZOrderLists) {
if (paintChildren(NormalFlowChildren | PositiveZOrderChildren, context, paintingInfo, paintFlags) == MayBeClippedByPaintDirtyRect)
result = MayBeClippedByPaintDirtyRect;
}
if (shouldPaintOverlayScrollbars)
paintOverflowControlsForFragments(layerFragments, context, localPaintingInfo, paintFlags);
} // FilterPainter block
bool shouldPaintMask = (paintFlags & PaintLayerPaintingCompositingMaskPhase) && shouldPaintContent && m_paintLayer.layoutObject()->hasMask() && !selectionOnly;
bool shouldPaintClippingMask = (paintFlags & PaintLayerPaintingChildClippingMaskPhase) && shouldPaintContent && !selectionOnly;
if (shouldPaintMask)
paintMaskForFragments(layerFragments, context, localPaintingInfo, paintFlags);
if (shouldPaintClippingMask) {
// Paint the border radius mask for the fragments.
paintChildClippingMaskForFragments(layerFragments, context, localPaintingInfo, paintFlags);
}
if (subsequenceRecorder)
m_paintLayer.setPreviousPaintResult(result);
return result;
}
bool PaintLayerPainter::needsToClip(const PaintLayerPaintingInfo& localPaintingInfo, const ClipRect& clipRect)
{
// Clipping will be applied by property nodes directly for SPv2.
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled())
return false;
return clipRect.rect() != localPaintingInfo.paintDirtyRect || clipRect.hasRadius();
}
bool PaintLayerPainter::atLeastOneFragmentIntersectsDamageRect(PaintLayerFragments& fragments, const PaintLayerPaintingInfo& localPaintingInfo, PaintLayerFlags localPaintFlags, const LayoutPoint& offsetFromRoot)
{
if (m_paintLayer.enclosingPaginationLayer())
return true; // The fragments created have already been found to intersect with the damage rect.
if (&m_paintLayer == localPaintingInfo.rootLayer && (localPaintFlags & PaintLayerPaintingOverflowContents))
return true;
for (PaintLayerFragment& fragment: fragments) {
LayoutPoint newOffsetFromRoot = offsetFromRoot + fragment.paginationOffset;
// Note that this really only works reliably on the first fragment. If the layer has visible
// overflow and a subsequent fragment doesn't intersect with the border box of the layer
// (i.e. only contains an overflow portion of the layer), intersection will fail. The reason
// for this is that fragment.layerBounds is set to the border box, not the bounding box, of
// the layer.
if (m_paintLayer.intersectsDamageRect(fragment.layerBounds, fragment.backgroundRect.rect(), newOffsetFromRoot))
return true;
}
return false;
}
PaintLayerPainter::PaintResult PaintLayerPainter::paintLayerWithTransform(GraphicsContext& context, const PaintLayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
// Transforms will be applied by property nodes directly for SPv2.
ASSERT(!RuntimeEnabledFeatures::slimmingPaintV2Enabled());
TransformationMatrix layerTransform = m_paintLayer.renderableTransform(paintingInfo.getGlobalPaintFlags());
// If the transform can't be inverted, then don't paint anything.
if (!layerTransform.isInvertible())
return FullyPainted;
// FIXME: We should make sure that we don't walk past paintingInfo.rootLayer here.
// m_paintLayer may be the "root", and then we should avoid looking at its parent.
PaintLayer* parentLayer = m_paintLayer.parent();
ClipRect ancestorBackgroundClipRect;
if (parentLayer) {
// Calculate the clip rectangle that the ancestors establish.
ClipRectsContext clipRectsContext(paintingInfo.rootLayer, (paintFlags & PaintLayerUncachedClipRects) ? UncachedClipRects : PaintingClipRects, IgnoreOverlayScrollbarSize);
if (shouldRespectOverflowClip(paintFlags, m_paintLayer.layoutObject()) == IgnoreOverflowClip)
clipRectsContext.setIgnoreOverflowClip();
ancestorBackgroundClipRect = m_paintLayer.clipper().backgroundClipRect(clipRectsContext);
}
LayoutObject* object = m_paintLayer.layoutObject();
LayoutView* view = object->view();
bool isFixedPosObjectInPagedMedia = object->style()->position() == FixedPosition && object->container() == view && view->pageLogicalHeight();
PaintLayer* paginationLayer = m_paintLayer.enclosingPaginationLayer();
PaintLayerFragments fragments;
// TODO(crbug.com/619094): Figure out the correct behaviour for fixed position objects
// in paged media with vertical writing modes.
if (isFixedPosObjectInPagedMedia && view->isHorizontalWritingMode()) {
// "For paged media, boxes with fixed positions are repeated on every page."
// - https://www.w3.org/TR/2011/REC-CSS2-20110607/visuren.html#fixed-positioning
unsigned pages = ceilf(view->documentRect().height() / view->pageLogicalHeight());
LayoutPoint paginationOffset;
for (unsigned i = 0; i < pages; i++) {
PaintLayerFragment fragment;
fragment.backgroundRect = paintingInfo.paintDirtyRect;
fragment.paginationOffset = paginationOffset;
fragments.append(fragment);
paginationOffset += LayoutPoint(LayoutUnit(), view->pageLogicalHeight());
}
} else if (paginationLayer) {
// FIXME: This is a mess. Look closely at this code and the code in Layer and fix any
// issues in it & refactor to make it obvious from code structure what it does and that it's
// correct.
ClipRectsCacheSlot cacheSlot = (paintFlags & PaintLayerUncachedClipRects) ? UncachedClipRects : PaintingClipRects;
ShouldRespectOverflowClipType respectOverflowClip = shouldRespectOverflowClip(paintFlags, m_paintLayer.layoutObject());
// Calculate the transformed bounding box in the current coordinate space, to figure out
// which fragmentainers (e.g. columns) we need to visit.
LayoutRect transformedExtent = PaintLayer::transparencyClipBox(&m_paintLayer, paginationLayer, PaintLayer::PaintingTransparencyClipBox, PaintLayer::RootOfTransparencyClipBox, paintingInfo.subPixelAccumulation, paintingInfo.getGlobalPaintFlags());
// FIXME: we don't check if paginationLayer is within paintingInfo.rootLayer here.
paginationLayer->collectFragments(fragments, paintingInfo.rootLayer, paintingInfo.paintDirtyRect, cacheSlot, IgnoreOverlayScrollbarSize, respectOverflowClip, 0, paintingInfo.subPixelAccumulation, &transformedExtent);
} else {
// We don't need to collect any fragments in the regular way here. We have already
// calculated a clip rectangle for the ancestry if it was needed, and clipping this
// layer is something that can be done further down the path, when the transform has
// been applied.
PaintLayerFragment fragment;
fragment.backgroundRect = paintingInfo.paintDirtyRect;
fragments.append(fragment);
}
Optional<DisplayItemCacheSkipper> cacheSkipper;
if (fragments.size() > 1)
cacheSkipper.emplace(context);
PaintResult result = FullyPainted;
for (const auto& fragment : fragments) {
Optional<LayerClipRecorder> clipRecorder;
if (parentLayer) {
ClipRect clipRectForFragment(ancestorBackgroundClipRect);
// A fixed-position object is repeated on every page, but if it is clipped by an ancestor layer then
// the repetitions are clipped out.
if (!isFixedPosObjectInPagedMedia)
clipRectForFragment.moveBy(fragment.paginationOffset);
clipRectForFragment.intersect(fragment.backgroundRect);
if (clipRectForFragment.isEmpty())
continue;
if (needsToClip(paintingInfo, clipRectForFragment)) {
if (m_paintLayer.layoutObject()->isPositioned() && clipRectForFragment.isClippedByClipCss())
UseCounter::count(m_paintLayer.layoutObject()->document(), UseCounter::ClipCssOfPositionedElement);
clipRecorder.emplace(context, *parentLayer->layoutObject(), DisplayItem::kClipLayerParent, clipRectForFragment, &paintingInfo, fragment.paginationOffset, paintFlags);
}
}
if (paintFragmentByApplyingTransform(context, paintingInfo, paintFlags, fragment.paginationOffset) == MayBeClippedByPaintDirtyRect)
result = MayBeClippedByPaintDirtyRect;
}
return result;
}
PaintLayerPainter::PaintResult PaintLayerPainter::paintFragmentByApplyingTransform(GraphicsContext& context, const PaintLayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags, const LayoutPoint& fragmentTranslation)
{
// Transforms will be applied by property nodes directly for SPv2.
ASSERT(!RuntimeEnabledFeatures::slimmingPaintV2Enabled());
// This involves subtracting out the position of the layer in our current coordinate space, but preserving
// the accumulated error for sub-pixel layout.
LayoutPoint delta;
m_paintLayer.convertToLayerCoords(paintingInfo.rootLayer, delta);
delta.moveBy(fragmentTranslation);
TransformationMatrix transform(m_paintLayer.renderableTransform(paintingInfo.getGlobalPaintFlags()));
IntPoint roundedDelta = roundedIntPoint(delta);
transform.translateRight(roundedDelta.x(), roundedDelta.y());
LayoutSize adjustedSubPixelAccumulation = paintingInfo.subPixelAccumulation + (delta - roundedDelta);
// TODO(jbroman): Put the real transform origin here, instead of using a
// matrix with the origin baked in.
FloatPoint3D transformOrigin;
Transform3DRecorder transform3DRecorder(context, *m_paintLayer.layoutObject(), DisplayItem::kTransform3DElementTransform, transform, transformOrigin);
// Now do a paint with the root layer shifted to be us.
PaintLayerPaintingInfo transformedPaintingInfo(&m_paintLayer, LayoutRect(enclosingIntRect(transform.inverse().mapRect(paintingInfo.paintDirtyRect))), paintingInfo.getGlobalPaintFlags(),
adjustedSubPixelAccumulation);
transformedPaintingInfo.ancestorHasClipPathClipping = paintingInfo.ancestorHasClipPathClipping;
// Remove skip root background flag when we're painting with a new root.
if (&m_paintLayer != paintingInfo.rootLayer)
paintFlags &= ~PaintLayerPaintingSkipRootBackground;
return paintLayerContentsAndReflection(context, transformedPaintingInfo, paintFlags, ForceSingleFragment);
}
PaintLayerPainter::PaintResult PaintLayerPainter::paintChildren(unsigned childrenToVisit, GraphicsContext& context, const PaintLayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags)
{
PaintResult result = FullyPainted;
if (!m_paintLayer.hasSelfPaintingLayerDescendant())
return result;
#if ENABLE(ASSERT)
LayerListMutationDetector mutationChecker(m_paintLayer.stackingNode());
#endif
PaintLayerStackingNodeIterator iterator(*m_paintLayer.stackingNode(), childrenToVisit);
PaintLayerStackingNode* child = iterator.next();
if (!child)
return result;
IntSize scrollOffsetAccumulationForChildren = paintingInfo.scrollOffsetAccumulation;
if (m_paintLayer.layoutObject()->hasOverflowClip())
scrollOffsetAccumulationForChildren += m_paintLayer.layoutBox()->scrolledContentOffset();
for (; child; child = iterator.next()) {
PaintLayerPainter childPainter(*child->layer());
// If this Layer should paint into its own backing or a grouped backing, that will be done via CompositedLayerMapping::paintContents()
// and CompositedLayerMapping::doPaintTask().
if (!childPainter.shouldPaintLayerInSoftwareMode(paintingInfo.getGlobalPaintFlags(), paintFlags))
continue;
PaintLayerPaintingInfo childPaintingInfo = paintingInfo;
childPaintingInfo.scrollOffsetAccumulation = scrollOffsetAccumulationForChildren;
// Rare case: accumulate scroll offset of non-stacking-context ancestors up to m_paintLayer.
for (PaintLayer* parentLayer = child->layer()->parent(); parentLayer != &m_paintLayer; parentLayer = parentLayer->parent()) {
if (parentLayer->layoutObject()->hasOverflowClip())
childPaintingInfo.scrollOffsetAccumulation += parentLayer->layoutBox()->scrolledContentOffset();
}
if (childPainter.paintLayer(context, childPaintingInfo, paintFlags) == MayBeClippedByPaintDirtyRect)
result = MayBeClippedByPaintDirtyRect;
}
return result;
}
bool PaintLayerPainter::shouldPaintLayerInSoftwareMode(const GlobalPaintFlags globalPaintFlags, PaintLayerFlags paintFlags)
{
DisableCompositingQueryAsserts disabler;
return m_paintLayer.compositingState() == NotComposited
|| (globalPaintFlags & GlobalPaintFlattenCompositingLayers)
|| ((paintFlags & PaintLayerPaintingReflection) && !m_paintLayer.has3DTransform());
}
void PaintLayerPainter::paintOverflowControlsForFragments(const PaintLayerFragments& layerFragments, GraphicsContext& context, const PaintLayerPaintingInfo& localPaintingInfo, PaintLayerFlags paintFlags)
{
PaintLayerScrollableArea* scrollableArea = m_paintLayer.getScrollableArea();
if (!scrollableArea)
return;
Optional<DisplayItemCacheSkipper> cacheSkipper;
if (layerFragments.size() > 1)
cacheSkipper.emplace(context);
for (auto& fragment : layerFragments) {
// We need to apply the same clips and transforms that
// paintFragmentWithPhase would have.
LayoutRect cullRect = fragment.backgroundRect.rect();
Optional<LayerClipRecorder> clipRecorder;
if (needsToClip(localPaintingInfo, fragment.backgroundRect))
clipRecorder.emplace(context, *m_paintLayer.layoutObject(), DisplayItem::kClipLayerOverflowControls, fragment.backgroundRect, &localPaintingInfo, fragment.paginationOffset, paintFlags);
Optional<ScrollRecorder> scrollRecorder;
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() && !localPaintingInfo.scrollOffsetAccumulation.isZero()) {
cullRect.move(localPaintingInfo.scrollOffsetAccumulation);
scrollRecorder.emplace(context, *m_paintLayer.layoutObject(), DisplayItem::kScrollOverflowControls, localPaintingInfo.scrollOffsetAccumulation);
}
// We pass IntPoint() as the paint offset here, because
// ScrollableArea::paintOverflowControls just ignores it and uses the
// offset found in a previous pass.
CullRect snappedCullRect(pixelSnappedIntRect(cullRect));
ScrollableAreaPainter(*scrollableArea).paintOverflowControls(context, IntPoint(), snappedCullRect, true);
}
}
void PaintLayerPainter::paintFragmentWithPhase(PaintPhase phase, const PaintLayerFragment& fragment, GraphicsContext& context, const ClipRect& clipRect, const PaintLayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags, ClipState clipState)
{
ASSERT(m_paintLayer.isSelfPaintingLayer());
Optional<LayerClipRecorder> clipRecorder;
if (clipState != HasClipped && paintingInfo.clipToDirtyRect && needsToClip(paintingInfo, clipRect)) {
DisplayItem::Type clipType = DisplayItem::paintPhaseToClipLayerFragmentType(phase);
LayerClipRecorder::BorderRadiusClippingRule clippingRule;
switch (phase) {
case PaintPhaseSelfBlockBackgroundOnly: // Background painting will handle clipping to self.
case PaintPhaseSelfOutlineOnly:
case PaintPhaseMask: // Mask painting will handle clipping to self.
clippingRule = LayerClipRecorder::DoNotIncludeSelfForBorderRadius;
break;
default:
clippingRule = LayerClipRecorder::IncludeSelfForBorderRadius;
break;
}
clipRecorder.emplace(context, *m_paintLayer.layoutObject(), clipType, clipRect, &paintingInfo, fragment.paginationOffset, paintFlags, clippingRule);
}
LayoutRect newCullRect(clipRect.rect());
Optional<ScrollRecorder> scrollRecorder;
LayoutPoint paintOffset = -m_paintLayer.layoutBoxLocation();
if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) {
const ObjectPaintProperties* objectPaintProperties = m_paintLayer.layoutObject()->objectPaintProperties();
ASSERT(objectPaintProperties && objectPaintProperties->localBorderBoxProperties());
paintOffset += toSize(objectPaintProperties->localBorderBoxProperties()->paintOffset);
} else {
paintOffset += toSize(fragment.layerBounds.location());
if (!paintingInfo.scrollOffsetAccumulation.isZero()) {
// As a descendant of the root layer, m_paintLayer's painting is not controlled by the ScrollRecorders
// created by BlockPainter of the ancestor layers up to the root layer, so we need to issue ScrollRecorder
// for this layer seperately, with the scroll offset accumulated from the root layer to the parent of this
// layer, to get the same result as ScrollRecorder in BlockPainter.
paintOffset += paintingInfo.scrollOffsetAccumulation;
newCullRect.move(paintingInfo.scrollOffsetAccumulation);
scrollRecorder.emplace(context, *m_paintLayer.layoutObject(), phase, paintingInfo.scrollOffsetAccumulation);
}
}
PaintInfo paintInfo(context, pixelSnappedIntRect(newCullRect), phase,
paintingInfo.getGlobalPaintFlags(), paintFlags, paintingInfo.rootLayer->layoutObject());
m_paintLayer.layoutObject()->paint(paintInfo, paintOffset);
}
void PaintLayerPainter::paintBackgroundForFragments(const PaintLayerFragments& layerFragments,
GraphicsContext& context, const LayoutRect& transparencyPaintDirtyRect,
const PaintLayerPaintingInfo& localPaintingInfo, PaintLayerFlags paintFlags)
{
Optional<DisplayItemCacheSkipper> cacheSkipper;
if (layerFragments.size() > 1)
cacheSkipper.emplace(context);
for (auto& fragment : layerFragments)
paintFragmentWithPhase(PaintPhaseSelfBlockBackgroundOnly, fragment, context, fragment.backgroundRect, localPaintingInfo, paintFlags, HasNotClipped);
}
void PaintLayerPainter::paintForegroundForFragments(const PaintLayerFragments& layerFragments,
GraphicsContext& context, const LayoutRect& transparencyPaintDirtyRect,
const PaintLayerPaintingInfo& localPaintingInfo, bool selectionOnly, PaintLayerFlags paintFlags)
{
DCHECK(!(paintFlags & PaintLayerPaintingRootBackgroundOnly));
// Optimize clipping for the single fragment case.
bool shouldClip = localPaintingInfo.clipToDirtyRect && layerFragments.size() == 1 && !layerFragments[0].foregroundRect.isEmpty();
ClipState clipState = HasNotClipped;
Optional<LayerClipRecorder> clipRecorder;
if (shouldClip && needsToClip(localPaintingInfo, layerFragments[0].foregroundRect)) {
clipRecorder.emplace(context, *m_paintLayer.layoutObject(), DisplayItem::kClipLayerForeground, layerFragments[0].foregroundRect, &localPaintingInfo, layerFragments[0].paginationOffset, paintFlags);
clipState = HasClipped;
}
// We have to loop through every fragment multiple times, since we have to issue paint invalidations in each specific phase in order for
// interleaving of the fragments to work properly.
if (selectionOnly) {
paintForegroundForFragmentsWithPhase(PaintPhaseSelection, layerFragments, context, localPaintingInfo, paintFlags, clipState);
} else {
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() || m_paintLayer.needsPaintPhaseDescendantBlockBackgrounds()) {
size_t sizeBefore = context.getPaintController().newDisplayItemList().size();
paintForegroundForFragmentsWithPhase(PaintPhaseDescendantBlockBackgroundsOnly, layerFragments, context, localPaintingInfo, paintFlags, clipState);
// Don't set the empty flag if we are not painting the whole background.
if (!(paintFlags & PaintLayerPaintingSkipRootBackground)) {
bool phaseIsEmpty = context.getPaintController().newDisplayItemList().size() == sizeBefore;
DCHECK(phaseIsEmpty || m_paintLayer.needsPaintPhaseDescendantBlockBackgrounds());
m_paintLayer.setPreviousPaintPhaseDescendantBlockBackgroundsEmpty(phaseIsEmpty);
}
}
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() || m_paintLayer.needsPaintPhaseFloat()) {
size_t sizeBefore = context.getPaintController().newDisplayItemList().size();
paintForegroundForFragmentsWithPhase(PaintPhaseFloat, layerFragments, context, localPaintingInfo, paintFlags, clipState);
bool phaseIsEmpty = context.getPaintController().newDisplayItemList().size() == sizeBefore;
DCHECK(phaseIsEmpty || m_paintLayer.needsPaintPhaseFloat());
m_paintLayer.setPreviousPaintPhaseFloatEmpty(phaseIsEmpty);
}
paintForegroundForFragmentsWithPhase(PaintPhaseForeground, layerFragments, context, localPaintingInfo, paintFlags, clipState);
if (RuntimeEnabledFeatures::paintUnderInvalidationCheckingEnabled() || m_paintLayer.needsPaintPhaseDescendantOutlines()) {
size_t sizeBefore = context.getPaintController().newDisplayItemList().size();
paintForegroundForFragmentsWithPhase(PaintPhaseDescendantOutlinesOnly, layerFragments, context, localPaintingInfo, paintFlags, clipState);
bool phaseIsEmpty = context.getPaintController().newDisplayItemList().size() == sizeBefore;
DCHECK(phaseIsEmpty || m_paintLayer.needsPaintPhaseDescendantOutlines());
m_paintLayer.setPreviousPaintPhaseDescendantOutlinesEmpty(phaseIsEmpty);
}
}
}
void PaintLayerPainter::paintForegroundForFragmentsWithPhase(PaintPhase phase,
const PaintLayerFragments& layerFragments, GraphicsContext& context,
const PaintLayerPaintingInfo& localPaintingInfo, PaintLayerFlags paintFlags, ClipState clipState)
{
Optional<DisplayItemCacheSkipper> cacheSkipper;
if (layerFragments.size() > 1)
cacheSkipper.emplace(context);
for (auto& fragment : layerFragments) {
if (!fragment.foregroundRect.isEmpty())
paintFragmentWithPhase(phase, fragment, context, fragment.foregroundRect, localPaintingInfo, paintFlags, clipState);
}
}
void PaintLayerPainter::paintSelfOutlineForFragments(const PaintLayerFragments& layerFragments,
GraphicsContext& context, const PaintLayerPaintingInfo& localPaintingInfo, PaintLayerFlags paintFlags)
{
Optional<DisplayItemCacheSkipper> cacheSkipper;
if (layerFragments.size() > 1)
cacheSkipper.emplace(context);
for (auto& fragment : layerFragments) {
if (!fragment.backgroundRect.isEmpty())
paintFragmentWithPhase(PaintPhaseSelfOutlineOnly, fragment, context, fragment.backgroundRect, localPaintingInfo, paintFlags, HasNotClipped);
}
}
void PaintLayerPainter::paintMaskForFragments(const PaintLayerFragments& layerFragments,
GraphicsContext& context, const PaintLayerPaintingInfo& localPaintingInfo, PaintLayerFlags paintFlags)
{
Optional<DisplayItemCacheSkipper> cacheSkipper;
if (layerFragments.size() > 1)
cacheSkipper.emplace(context);
for (auto& fragment : layerFragments)
paintFragmentWithPhase(PaintPhaseMask, fragment, context, fragment.backgroundRect, localPaintingInfo, paintFlags, HasNotClipped);
}
void PaintLayerPainter::paintChildClippingMaskForFragments(const PaintLayerFragments& layerFragments,
GraphicsContext& context, const PaintLayerPaintingInfo& localPaintingInfo, PaintLayerFlags paintFlags)
{
Optional<DisplayItemCacheSkipper> cacheSkipper;
if (layerFragments.size() > 1)
cacheSkipper.emplace(context);
for (auto& fragment: layerFragments)
paintFragmentWithPhase(PaintPhaseClippingMask, fragment, context, fragment.foregroundRect, localPaintingInfo, paintFlags, HasNotClipped);
}
void PaintLayerPainter::paintOverlayScrollbars(GraphicsContext& context, const LayoutRect& damageRect, const GlobalPaintFlags paintFlags)
{
if (!m_paintLayer.containsDirtyOverlayScrollbars())
return;
PaintLayerPaintingInfo paintingInfo(&m_paintLayer, LayoutRect(enclosingIntRect(damageRect)), paintFlags, LayoutSize());
paintLayer(context, paintingInfo, PaintLayerPaintingOverlayScrollbars);
m_paintLayer.setContainsDirtyOverlayScrollbars(false);
}
} // namespace blink