CoordinatedGraphicsLayer.cpp   [plain text]


/*
 Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
 Copyright (C) 2010 Apple Inc. All rights reserved.
 Copyright (C) 2012 Company 100, Inc.
 Copyright (C) 2012 Intel Corporation. All rights reserved.
 Copyright (C) 2017 Sony Interactive Entertainment Inc.

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Library General Public
 License as published by the Free Software Foundation; either
 version 2 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Library General Public License for more details.

 You should have received a copy of the GNU Library General Public License
 along with this library; see the file COPYING.LIB.  If not, write to
 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "CoordinatedGraphicsLayer.h"

#if USE(COORDINATED_GRAPHICS)

#include "FloatQuad.h"
#include "GraphicsContext.h"
#include "GraphicsLayer.h"
#include "GraphicsLayerFactory.h"
#include "NicosiaBackingStoreTextureMapperImpl.h"
#include "NicosiaCompositionLayerTextureMapperImpl.h"
#include "NicosiaContentLayerTextureMapperImpl.h"
#include "NicosiaImageBackingTextureMapperImpl.h"
#include "NicosiaPaintingContext.h"
#include "NicosiaPaintingEngine.h"
#include "ScrollableArea.h"
#include "TextureMapperPlatformLayerProxyProvider.h"
#include "TiledBackingStore.h"
#ifndef NDEBUG
#include <wtf/SetForScope.h>
#endif
#include <wtf/text/CString.h>

namespace WebCore {

Ref<GraphicsLayer> GraphicsLayer::create(GraphicsLayerFactory* factory, GraphicsLayerClient& client, Type layerType)
{
    if (!factory)
        return adoptRef(*new CoordinatedGraphicsLayer(layerType, client));

    return factory->createGraphicsLayer(layerType, client);
}

void CoordinatedGraphicsLayer::notifyFlushRequired()
{
    if (!m_coordinator)
        return;

    if (m_coordinator->isFlushingLayerChanges())
        return;

    client().notifyFlushRequired(this);
}

void CoordinatedGraphicsLayer::didChangeAnimations()
{
    m_nicosia.delta.animationsChanged = true;
    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::didChangeChildren()
{
    m_nicosia.delta.childrenChanged = true;
    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::didChangeFilters()
{
    m_nicosia.delta.filtersChanged = true;
    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::didUpdateTileBuffers()
{
    if (!isShowingRepaintCounter())
        return;

    auto repaintCount = incrementRepaintCount();
    m_nicosia.repaintCounter.count = repaintCount;
    m_nicosia.delta.repaintCounterChanged = true;
}

void CoordinatedGraphicsLayer::setShouldUpdateVisibleRect()
{
    m_shouldUpdateVisibleRect = true;
    for (auto& child : children())
        downcast<CoordinatedGraphicsLayer>(child.get()).setShouldUpdateVisibleRect();
    if (replicaLayer())
        downcast<CoordinatedGraphicsLayer>(*replicaLayer()).setShouldUpdateVisibleRect();
}

void CoordinatedGraphicsLayer::didChangeGeometry()
{
    notifyFlushRequired();
    setShouldUpdateVisibleRect();
}

CoordinatedGraphicsLayer::CoordinatedGraphicsLayer(Type layerType, GraphicsLayerClient& client)
    : GraphicsLayer(layerType, client)
#ifndef NDEBUG
    , m_isPurging(false)
#endif
    , m_shouldUpdateVisibleRect(true)
    , m_movingVisibleRect(false)
    , m_pendingContentsScaleAdjustment(false)
    , m_pendingVisibleRectAdjustment(false)
    , m_shouldUpdatePlatformLayer(false)
    , m_coordinator(0)
    , m_compositedNativeImagePtr(0)
    , m_animationStartedTimer(*this, &CoordinatedGraphicsLayer::animationStartedTimerFired)
{
    static Nicosia::PlatformLayer::LayerID nextLayerID = 1;
    m_id = nextLayerID++;

    m_nicosia.layer = Nicosia::CompositionLayer::create(m_id,
        Nicosia::CompositionLayerTextureMapperImpl::createFactory());

    // Enforce a complete flush on the first occasion.
    m_nicosia.delta.value = UINT_MAX;
}

CoordinatedGraphicsLayer::~CoordinatedGraphicsLayer()
{
    if (m_coordinator) {
        purgeBackingStores();
        m_coordinator->detachLayer(this);
    }
    ASSERT(!m_nicosia.imageBacking);
    ASSERT(!m_nicosia.backingStore);
    willBeDestroyed();
}

bool CoordinatedGraphicsLayer::isCoordinatedGraphicsLayer() const
{
    return true;
}

Nicosia::PlatformLayer::LayerID CoordinatedGraphicsLayer::id() const
{
    return m_id;
}

auto CoordinatedGraphicsLayer::primaryLayerID() const -> PlatformLayerID
{
    return id();
}

bool CoordinatedGraphicsLayer::setChildren(Vector<Ref<GraphicsLayer>>&& children)
{
    bool ok = GraphicsLayer::setChildren(WTFMove(children));
    if (!ok)
        return false;
    didChangeChildren();
    return true;
}

void CoordinatedGraphicsLayer::addChild(Ref<GraphicsLayer>&& layer)
{
    GraphicsLayer* rawLayer = layer.ptr();
    GraphicsLayer::addChild(WTFMove(layer));
    downcast<CoordinatedGraphicsLayer>(*rawLayer).setCoordinatorIncludingSubLayersIfNeeded(m_coordinator);
    didChangeChildren();
}

void CoordinatedGraphicsLayer::addChildAtIndex(Ref<GraphicsLayer>&& layer, int index)
{
    GraphicsLayer* rawLayer = layer.ptr();
    GraphicsLayer::addChildAtIndex(WTFMove(layer), index);
    downcast<CoordinatedGraphicsLayer>(*rawLayer).setCoordinatorIncludingSubLayersIfNeeded(m_coordinator);
    didChangeChildren();
}

void CoordinatedGraphicsLayer::addChildAbove(Ref<GraphicsLayer>&& layer, GraphicsLayer* sibling)
{
    GraphicsLayer* rawLayer = layer.ptr();
    GraphicsLayer::addChildAbove(WTFMove(layer), sibling);
    downcast<CoordinatedGraphicsLayer>(*rawLayer).setCoordinatorIncludingSubLayersIfNeeded(m_coordinator);
    didChangeChildren();
}

void CoordinatedGraphicsLayer::addChildBelow(Ref<GraphicsLayer>&& layer, GraphicsLayer* sibling)
{
    GraphicsLayer* rawLayer = layer.ptr();
    GraphicsLayer::addChildBelow(WTFMove(layer), sibling);
    downcast<CoordinatedGraphicsLayer>(*rawLayer).setCoordinatorIncludingSubLayersIfNeeded(m_coordinator);
    didChangeChildren();
}

bool CoordinatedGraphicsLayer::replaceChild(GraphicsLayer* oldChild, Ref<GraphicsLayer>&& newChild)
{
    GraphicsLayer* rawLayer = newChild.ptr();
    bool ok = GraphicsLayer::replaceChild(oldChild, WTFMove(newChild));
    if (!ok)
        return false;
    downcast<CoordinatedGraphicsLayer>(*rawLayer).setCoordinatorIncludingSubLayersIfNeeded(m_coordinator);
    didChangeChildren();
    return true;
}

void CoordinatedGraphicsLayer::removeFromParent()
{
    if (CoordinatedGraphicsLayer* parentLayer = downcast<CoordinatedGraphicsLayer>(parent()))
        parentLayer->didChangeChildren();
    GraphicsLayer::removeFromParent();
}

void CoordinatedGraphicsLayer::setPosition(const FloatPoint& p)
{
    if (position() == p)
        return;

    GraphicsLayer::setPosition(p);
    m_nicosia.delta.positionChanged = true;
    didChangeGeometry();
}

void CoordinatedGraphicsLayer::setAnchorPoint(const FloatPoint3D& p)
{
    if (anchorPoint() == p)
        return;

    GraphicsLayer::setAnchorPoint(p);
    m_nicosia.delta.anchorPointChanged = true;
    didChangeGeometry();
}

void CoordinatedGraphicsLayer::setSize(const FloatSize& size)
{
    if (this->size() == size)
        return;

    GraphicsLayer::setSize(size);
    m_nicosia.delta.sizeChanged = true;

    if (maskLayer())
        maskLayer()->setSize(size);
    didChangeGeometry();
}

void CoordinatedGraphicsLayer::setTransform(const TransformationMatrix& t)
{
    if (transform() == t)
        return;

    GraphicsLayer::setTransform(t);
    m_nicosia.delta.transformChanged = true;

    didChangeGeometry();
}

void CoordinatedGraphicsLayer::setChildrenTransform(const TransformationMatrix& t)
{
    if (childrenTransform() == t)
        return;

    GraphicsLayer::setChildrenTransform(t);
    m_nicosia.delta.childrenTransformChanged = true;

    didChangeGeometry();
}

void CoordinatedGraphicsLayer::setPreserves3D(bool b)
{
    if (preserves3D() == b)
        return;

    GraphicsLayer::setPreserves3D(b);
    m_nicosia.delta.flagsChanged = true;

    didChangeGeometry();
}

void CoordinatedGraphicsLayer::setMasksToBounds(bool b)
{
    if (masksToBounds() == b)
        return;
    GraphicsLayer::setMasksToBounds(b);
    m_nicosia.delta.flagsChanged = true;

    didChangeGeometry();
}

void CoordinatedGraphicsLayer::setDrawsContent(bool b)
{
    if (drawsContent() == b)
        return;
    GraphicsLayer::setDrawsContent(b);
    m_nicosia.delta.flagsChanged = true;

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setContentsVisible(bool b)
{
    if (contentsAreVisible() == b)
        return;
    GraphicsLayer::setContentsVisible(b);
    m_nicosia.delta.flagsChanged = true;

    if (maskLayer())
        maskLayer()->setContentsVisible(b);

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setContentsOpaque(bool b)
{
    if (contentsOpaque() == b)
        return;

    GraphicsLayer::setContentsOpaque(b);
    m_nicosia.delta.flagsChanged = true;

    // Demand a repaint of the whole layer.
    if (!m_needsDisplay.completeLayer) {
        m_needsDisplay.completeLayer = true;
        m_needsDisplay.rects.clear();

        addRepaintRect({ { }, m_size });
    }

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setBackfaceVisibility(bool b)
{
    if (backfaceVisibility() == b)
        return;

    GraphicsLayer::setBackfaceVisibility(b);
    m_nicosia.delta.flagsChanged = true;

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setOpacity(float opacity)
{
    if (this->opacity() == opacity)
        return;

    GraphicsLayer::setOpacity(opacity);
    m_nicosia.delta.opacityChanged = true;

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setContentsRect(const FloatRect& r)
{
    if (contentsRect() == r)
        return;

    GraphicsLayer::setContentsRect(r);
    m_nicosia.delta.contentsRectChanged = true;

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setContentsTileSize(const FloatSize& s)
{
    if (contentsTileSize() == s)
        return;

    GraphicsLayer::setContentsTileSize(s);
    m_nicosia.delta.contentsTilingChanged = true;
    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setContentsTilePhase(const FloatSize& p)
{
    if (contentsTilePhase() == p)
        return;

    GraphicsLayer::setContentsTilePhase(p);
    m_nicosia.delta.contentsTilingChanged = true;
    notifyFlushRequired();
}

bool GraphicsLayer::supportsContentsTiling()
{
    return true;
}

void CoordinatedGraphicsLayer::setContentsNeedsDisplay()
{
#if USE(COORDINATED_GRAPHICS_THREADED) && USE(NICOSIA)
    if (m_nicosia.contentLayer)
        m_shouldUpdatePlatformLayer = true;
#endif

    notifyFlushRequired();
    addRepaintRect(contentsRect());
}

void CoordinatedGraphicsLayer::setContentsToPlatformLayer(PlatformLayer* platformLayer, ContentsLayerPurpose)
{
#if USE(COORDINATED_GRAPHICS_THREADED) && USE(NICOSIA)
    auto* contentLayer = downcast<Nicosia::ContentLayer>(platformLayer);
    if (m_nicosia.contentLayer != contentLayer) {
        m_nicosia.contentLayer = contentLayer;
        m_nicosia.delta.contentLayerChanged = true;
    }
    notifyFlushRequired();
#else
    UNUSED_PARAM(platformLayer);
#endif
}

bool CoordinatedGraphicsLayer::filtersCanBeComposited(const FilterOperations& filters) const
{
    if (!filters.size())
        return false;

    for (const auto& filterOperation : filters.operations()) {
        if (filterOperation->type() == FilterOperation::REFERENCE)
            return false;
    }

    return true;
}

bool CoordinatedGraphicsLayer::setFilters(const FilterOperations& newFilters)
{
    bool canCompositeFilters = filtersCanBeComposited(newFilters);
    if (filters() == newFilters)
        return canCompositeFilters;

    if (canCompositeFilters) {
        if (!GraphicsLayer::setFilters(newFilters))
            return false;
        didChangeFilters();
    } else if (filters().size()) {
        clearFilters();
        didChangeFilters();
    }

    return canCompositeFilters;
}

void CoordinatedGraphicsLayer::setContentsToSolidColor(const Color& color)
{
    if (m_solidColor == color)
        return;

    m_solidColor = color;
    m_nicosia.delta.solidColorChanged = true;

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setShowDebugBorder(bool show)
{
    if (isShowingDebugBorder() == show)
        return;

    GraphicsLayer::setShowDebugBorder(show);
    m_nicosia.debugBorder.visible = show;
    m_nicosia.delta.debugBorderChanged = true;

    if (m_nicosia.debugBorder.visible)
        updateDebugIndicators();

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setShowRepaintCounter(bool show)
{
    if (isShowingRepaintCounter() == show)
        return;

    GraphicsLayer::setShowRepaintCounter(show);
    m_nicosia.repaintCounter.visible = show;
    m_nicosia.delta.repaintCounterChanged = true;

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setContentsToImage(Image* image)
{
    NativeImagePtr nativeImagePtr = image ? image->nativeImageForCurrentFrame() : nullptr;
    if (m_compositedImage == image && m_compositedNativeImagePtr == nativeImagePtr)
        return;

    m_compositedImage = image;
    m_compositedNativeImagePtr = nativeImagePtr;

    GraphicsLayer::setContentsToImage(image);
    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setMaskLayer(RefPtr<GraphicsLayer>&& layer)
{
    if (layer == maskLayer())
        return;

    GraphicsLayer* rawLayer = layer.get();
    GraphicsLayer::setMaskLayer(WTFMove(layer));

    if (!rawLayer)
        return;

    rawLayer->setSize(size());
    rawLayer->setContentsVisible(contentsAreVisible());

    m_nicosia.delta.maskChanged = true;

    notifyFlushRequired();
}

bool CoordinatedGraphicsLayer::shouldDirectlyCompositeImage(Image* image) const
{
    if (!image || !image->isBitmapImage())
        return false;

    enum { MaxDimenstionForDirectCompositing = 2000 };
    if (image->width() > MaxDimenstionForDirectCompositing || image->height() > MaxDimenstionForDirectCompositing)
        return false;

    return true;
}

void CoordinatedGraphicsLayer::setReplicatedByLayer(RefPtr<GraphicsLayer>&& layer)
{
    if (layer == replicaLayer())
        return;

    GraphicsLayer::setReplicatedByLayer(WTFMove(layer));
    m_nicosia.delta.replicaChanged = true;
    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setNeedsDisplay()
{
    if (!drawsContent() || !contentsAreVisible() || m_size.isEmpty() || m_needsDisplay.completeLayer)
        return;

    m_needsDisplay.completeLayer = true;
    m_needsDisplay.rects.clear();

    notifyFlushRequired();
    addRepaintRect({ { }, m_size });
}

void CoordinatedGraphicsLayer::setNeedsDisplayInRect(const FloatRect& initialRect, ShouldClipToLayer shouldClip)
{
    if (!drawsContent() || !contentsAreVisible() || m_size.isEmpty() || m_needsDisplay.completeLayer)
        return;

    auto rect = initialRect;
    if (shouldClip == ClipToLayer)
        rect.intersect({ { }, m_size });

    if (rect.isEmpty())
        return;

    auto& rects = m_needsDisplay.rects;
    bool alreadyRecorded = std::any_of(rects.begin(), rects.end(),
        [&](auto& dirtyRect) { return dirtyRect.contains(rect); });
    if (alreadyRecorded)
        return;

    if (rects.size() < 32)
        rects.append(rect);
    else
        rects[0].unite(rect);

    notifyFlushRequired();
    addRepaintRect(rect);
}

void CoordinatedGraphicsLayer::flushCompositingState(const FloatRect& rect)
{
    if (CoordinatedGraphicsLayer* mask = downcast<CoordinatedGraphicsLayer>(maskLayer()))
        mask->flushCompositingStateForThisLayerOnly();

    if (CoordinatedGraphicsLayer* replica = downcast<CoordinatedGraphicsLayer>(replicaLayer()))
        replica->flushCompositingStateForThisLayerOnly();

    flushCompositingStateForThisLayerOnly();

    for (auto& child : children())
        child->flushCompositingState(rect);
}

void CoordinatedGraphicsLayer::setDebugBorder(const Color& color, float width)
{
    ASSERT(m_nicosia.debugBorder.visible);
    if (m_nicosia.debugBorder.color != color) {
        m_nicosia.debugBorder.color = color;
        m_nicosia.delta.debugBorderChanged = true;
    }

    if (m_nicosia.debugBorder.width != width) {
        m_nicosia.debugBorder.width = width;
        m_nicosia.delta.debugBorderChanged = true;
    }
}

void CoordinatedGraphicsLayer::updatePlatformLayer()
{
    if (!m_shouldUpdatePlatformLayer)
        return;

    m_shouldUpdatePlatformLayer = false;
#if USE(COORDINATED_GRAPHICS_THREADED) && USE(NICOSIA)
    if (m_nicosia.contentLayer)
        downcast<Nicosia::ContentLayerTextureMapperImpl>(m_nicosia.contentLayer->impl()).swapBuffersIfNeeded();
#endif
}

void CoordinatedGraphicsLayer::flushCompositingStateForThisLayerOnly()
{
    // When we have a transform animation, we need to update visible rect every frame to adjust the visible rect of a backing store.
    bool hasActiveTransformAnimation = selfOrAncestorHasActiveTransformAnimation();
    if (hasActiveTransformAnimation)
        m_movingVisibleRect = true;

    // Sets the values.
    computePixelAlignment(m_adjustedPosition, m_adjustedSize, m_adjustedAnchorPoint, m_pixelAlignmentOffset);

    computeTransformedVisibleRect();
    updatePlatformLayer();

    // Only unset m_movingVisibleRect after we have updated the visible rect after the animation stopped.
    if (!hasActiveTransformAnimation)
        m_movingVisibleRect = false;

    // Determine the backing store presence. Content is painted later, in the updateContentBuffers() traversal.
    if (shouldHaveBackingStore()) {
        if (!m_nicosia.backingStore) {
            m_nicosia.backingStore = Nicosia::BackingStore::create(Nicosia::BackingStoreTextureMapperImpl::createFactory());
            m_nicosia.delta.backingStoreChanged = true;
        }
    } else if (m_nicosia.backingStore) {
        auto& layerState = downcast<Nicosia::BackingStoreTextureMapperImpl>(m_nicosia.backingStore->impl()).layerState();
        layerState.isPurging = true;
        layerState.mainBackingStore = nullptr;

        m_nicosia.backingStore = nullptr;
        m_nicosia.delta.backingStoreChanged = true;
    }

    // Determine image backing presence according to the composited image source.
    if (m_compositedNativeImagePtr) {
        ASSERT(m_compositedImage);
        auto& image = *m_compositedImage;
        uintptr_t imageID = reinterpret_cast<uintptr_t>(&image);
        uintptr_t nativeImageID = reinterpret_cast<uintptr_t>(m_compositedNativeImagePtr.get());

        // Respawn the ImageBacking object if the underlying image changed.
        if (m_nicosia.imageBacking) {
            auto& impl = downcast<Nicosia::ImageBackingTextureMapperImpl>(m_nicosia.imageBacking->impl());
            if (impl.layerState().imageID != imageID) {
                impl.layerState().update = Nicosia::ImageBackingTextureMapperImpl::Update { };
                m_nicosia.imageBacking = nullptr;
            }
        }
        if (!m_nicosia.imageBacking) {
            m_nicosia.imageBacking = Nicosia::ImageBacking::create(Nicosia::ImageBackingTextureMapperImpl::createFactory());
            m_nicosia.delta.imageBackingChanged = true;
        }

        // Update the image contents only when the image layer is visible and the native image changed.
        auto& impl = downcast<Nicosia::ImageBackingTextureMapperImpl>(m_nicosia.imageBacking->impl());
        auto& layerState = impl.layerState();
        layerState.imageID = imageID;
        layerState.update.isVisible = transformedVisibleRect().intersects(IntRect(contentsRect()));
        if (layerState.update.isVisible && layerState.nativeImageID != nativeImageID) {
            auto buffer = Nicosia::Buffer::create(IntSize(image.size()),
                !image.currentFrameKnownToBeOpaque() ? Nicosia::Buffer::SupportsAlpha : Nicosia::Buffer::NoFlags);
            Nicosia::PaintingContext::paint(buffer,
                [&image](GraphicsContext& context)
                {
                    IntRect rect { { }, IntSize { image.size() } };
                    context.drawImage(image, rect, rect, ImagePaintingOptions(CompositeCopy));
                });
            layerState.nativeImageID = nativeImageID;
            layerState.update.buffer = WTFMove(buffer);
            m_nicosia.delta.imageBackingChanged = true;
        }
    } else if (m_nicosia.imageBacking) {
        auto& layerState = downcast<Nicosia::ImageBackingTextureMapperImpl>(m_nicosia.imageBacking->impl()).layerState();
        layerState.update = Nicosia::ImageBackingTextureMapperImpl::Update { };
        m_nicosia.imageBacking = nullptr;
        m_nicosia.delta.imageBackingChanged = true;
    }

    {
        m_nicosia.layer->updateState(
            [this](Nicosia::CompositionLayer::LayerState& state)
            {
                // OR the local delta value into the layer's pending state delta. After that,
                // go through each local change and update the pending state accordingly.
                auto& localDelta = m_nicosia.delta;
                state.delta.value |= localDelta.value;

                if (localDelta.positionChanged)
                    state.position = m_adjustedPosition;
                if (localDelta.anchorPointChanged)
                    state.anchorPoint = m_adjustedAnchorPoint;
                if (localDelta.sizeChanged)
                    state.size = m_adjustedSize;

                if (localDelta.transformChanged)
                    state.transform = transform();
                if (localDelta.childrenTransformChanged)
                    state.childrenTransform = childrenTransform();

                if (localDelta.contentsRectChanged)
                    state.contentsRect = contentsRect();
                if (localDelta.contentsTilingChanged) {
                    state.contentsTilePhase = contentsTilePhase();
                    state.contentsTileSize = contentsTileSize();
                }

                if (localDelta.opacityChanged)
                    state.opacity = opacity();
                if (localDelta.solidColorChanged)
                    state.solidColor = m_solidColor;

                if (localDelta.filtersChanged)
                    state.filters = filters();
                if (localDelta.animationsChanged)
                    state.animations = m_animations.getActiveAnimations();

                if (localDelta.childrenChanged) {
                    state.children = WTF::map(children(),
                        [](auto& child)
                        {
                            return downcast<CoordinatedGraphicsLayer>(child.get()).m_nicosia.layer;
                        });
                }

                if (localDelta.maskChanged) {
                    auto* mask = downcast<CoordinatedGraphicsLayer>(maskLayer());
                    state.mask = mask ? mask->m_nicosia.layer : nullptr;
                }

                if (localDelta.replicaChanged) {
                    auto* replica = downcast<CoordinatedGraphicsLayer>(replicaLayer());
                    state.replica = replica ? replica->m_nicosia.layer : nullptr;
                }

                if (localDelta.flagsChanged) {
                    state.flags.contentsOpaque = contentsOpaque();
                    state.flags.drawsContent = drawsContent();
                    state.flags.contentsVisible = contentsAreVisible();
                    state.flags.backfaceVisible = backfaceVisibility();
                    state.flags.masksToBounds = masksToBounds();
                    state.flags.preserves3D = preserves3D();
                }

                if (localDelta.repaintCounterChanged)
                    state.repaintCounter = m_nicosia.repaintCounter;
                if (localDelta.debugBorderChanged)
                    state.debugBorder = m_nicosia.debugBorder;

                if (localDelta.backingStoreChanged)
                    state.backingStore = m_nicosia.backingStore;
                if (localDelta.contentLayerChanged)
                    state.contentLayer = m_nicosia.contentLayer;
                if (localDelta.imageBackingChanged)
                    state.imageBacking = m_nicosia.imageBacking;
            });
        m_nicosia.performLayerSync = !!m_nicosia.delta.value;
        m_nicosia.delta = { };
    }
}

void CoordinatedGraphicsLayer::syncPendingStateChangesIncludingSubLayers()
{
    if (m_nicosia.performLayerSync)
        m_coordinator->syncLayerState();
    m_nicosia.performLayerSync = false;

    if (maskLayer())
        downcast<CoordinatedGraphicsLayer>(*maskLayer()).syncPendingStateChangesIncludingSubLayers();

    for (auto& child : children())
        downcast<CoordinatedGraphicsLayer>(child.get()).syncPendingStateChangesIncludingSubLayers();
}

void CoordinatedGraphicsLayer::deviceOrPageScaleFactorChanged()
{
    if (shouldHaveBackingStore())
        m_pendingContentsScaleAdjustment = true;
}

float CoordinatedGraphicsLayer::effectiveContentsScale()
{
    return selfOrAncestorHaveNonAffineTransforms() ? 1 : deviceScaleFactor() * pageScaleFactor();
}

static void clampToContentsRectIfRectIsInfinite(FloatRect& rect, const FloatSize& contentsSize)
{
    if (rect.width() >= LayoutUnit::nearlyMax() || rect.width() <= LayoutUnit::nearlyMin()) {
        rect.setX(0);
        rect.setWidth(contentsSize.width());
    }

    if (rect.height() >= LayoutUnit::nearlyMax() || rect.height() <= LayoutUnit::nearlyMin()) {
        rect.setY(0);
        rect.setHeight(contentsSize.height());
    }
}

IntRect CoordinatedGraphicsLayer::transformedVisibleRect()
{
    // Non-invertible layers are not visible.
    if (!m_layerTransform.combined().isInvertible())
        return IntRect();

    // Return a projection of the visible rect (surface coordinates) onto the layer's plane (layer coordinates).
    // The resulting quad might be squewed and the visible rect is the bounding box of this quad,
    // so it might spread further than the real visible area (and then even more amplified by the cover rect multiplier).
    ASSERT(m_cachedInverseTransform == m_layerTransform.combined().inverse().valueOr(TransformationMatrix()));
    FloatRect rect = m_cachedInverseTransform.clampedBoundsOfProjectedQuad(FloatQuad(m_coordinator->visibleContentsRect()));
    clampToContentsRectIfRectIsInfinite(rect, size());
    return enclosingIntRect(rect);
}

void CoordinatedGraphicsLayer::updateContentBuffersIncludingSubLayers()
{
    if (CoordinatedGraphicsLayer* mask = downcast<CoordinatedGraphicsLayer>(maskLayer()))
        mask->updateContentBuffers();

    if (CoordinatedGraphicsLayer* replica = downcast<CoordinatedGraphicsLayer>(replicaLayer()))
        replica->updateContentBuffers();

    updateContentBuffers();

    for (auto& child : children())
        downcast<CoordinatedGraphicsLayer>(child.get()).updateContentBuffersIncludingSubLayers();
}

void CoordinatedGraphicsLayer::updateContentBuffers()
{
    if (!m_nicosia.backingStore)
        return;

    // Prepare for painting on the impl-contained backing store. isFlushing is used there
    // for internal sanity checks.
    auto& impl = downcast<Nicosia::BackingStoreTextureMapperImpl>(m_nicosia.backingStore->impl());
    auto& layerState = impl.layerState();
    layerState.isFlushing = true;

    // Helper lambda that finished the flush update and determines layer sync necessity.
    auto finishUpdate =
        [this, &layerState] {
            auto& update = layerState.update;
            m_nicosia.performLayerSync |= !update.tilesToCreate.isEmpty()
                || !update.tilesToRemove.isEmpty() || !update.tilesToUpdate.isEmpty();
            layerState.isFlushing = false;
        };

    // Address the content scale adjustment.
    if (m_pendingContentsScaleAdjustment) {
        if (layerState.mainBackingStore && layerState.mainBackingStore->contentsScale() != effectiveContentsScale()) {
            // Discard the TiledBackingStore object to reconstruct it with new content scale.
            layerState.mainBackingStore = nullptr;
        }
        m_pendingContentsScaleAdjustment = false;
    }

    // Ensure the TiledBackingStore object, and enforce a complete repaint if it's not been present yet.
    if (!layerState.mainBackingStore) {
        layerState.mainBackingStore = std::make_unique<TiledBackingStore>(impl, effectiveContentsScale());
        m_pendingVisibleRectAdjustment = true;
    }

    // Bail if there's no painting recorded or enforced.
    if (!m_pendingVisibleRectAdjustment && !m_needsDisplay.completeLayer && m_needsDisplay.rects.isEmpty()) {
        finishUpdate();
        return;
    }

    if (!m_needsDisplay.completeLayer) {
        for (auto& rect : m_needsDisplay.rects)
            layerState.mainBackingStore->invalidate(IntRect { rect });
    } else
        layerState.mainBackingStore->invalidate({ { }, IntSize { m_size } });

    m_needsDisplay.completeLayer = false;
    m_needsDisplay.rects.clear();

    if (m_pendingVisibleRectAdjustment) {
        m_pendingVisibleRectAdjustment = false;
        layerState.mainBackingStore->createTilesIfNeeded(transformedVisibleRect(), IntRect(0, 0, m_size.width(), m_size.height()));
    }

    ASSERT(m_coordinator && m_coordinator->isFlushingLayerChanges());

    // With all the affected tiles created and/or invalidated, we can finally paint them.
    auto dirtyTiles = layerState.mainBackingStore->dirtyTiles();
    if (!dirtyTiles.isEmpty()) {
        bool didUpdateTiles = false;

        for (auto& tileReference : dirtyTiles) {
            auto& tile = tileReference.get();
            tile.ensureTileID();

            auto& tileRect = tile.rect();
            auto& dirtyRect = tile.dirtyRect();

            auto coordinatedBuffer = Nicosia::Buffer::create(dirtyRect.size(), contentsOpaque() ? Nicosia::Buffer::NoFlags : Nicosia::Buffer::SupportsAlpha);
            SurfaceUpdateInfo updateInfo;
            updateInfo.updateRect = dirtyRect;
            updateInfo.updateRect.move(-tileRect.x(), -tileRect.y());
            updateInfo.buffer = coordinatedBuffer.copyRef();

            if (!m_coordinator->paintingEngine().paint(*this, WTFMove(coordinatedBuffer),
                dirtyRect, layerState.mainBackingStore->mapToContents(dirtyRect),
                IntRect { { 0, 0 }, dirtyRect.size() }, layerState.mainBackingStore->contentsScale()))
                continue;

            impl.updateTile(tile.tileID(), updateInfo, tileRect);

            tile.markClean();
            didUpdateTiles |= true;
        }

        if (didUpdateTiles)
            didUpdateTileBuffers();
    }

    // Request a second update immediately if some tiles are still pending creation.
    if (layerState.hasPendingTileCreation) {
        setNeedsVisibleRectAdjustment();
        notifyFlushRequired();
    }

    finishUpdate();
}

void CoordinatedGraphicsLayer::purgeBackingStores()
{
#ifndef NDEBUG
    SetForScope<bool> updateModeProtector(m_isPurging, true);
#endif
    if (m_nicosia.backingStore) {
        auto& layerState = downcast<Nicosia::BackingStoreTextureMapperImpl>(m_nicosia.backingStore->impl()).layerState();
        layerState.isPurging = true;
        layerState.mainBackingStore = nullptr;

        m_nicosia.backingStore = nullptr;
    }

    if (m_nicosia.imageBacking) {
        auto& layerState = downcast<Nicosia::ImageBackingTextureMapperImpl>(m_nicosia.imageBacking->impl()).layerState();
        layerState.imageID = 0;
        layerState.nativeImageID = 0;
        layerState.update = { };

        m_nicosia.imageBacking = nullptr;
    }

    notifyFlushRequired();
}

void CoordinatedGraphicsLayer::setCoordinator(CoordinatedGraphicsLayerClient* coordinator)
{
    m_coordinator = coordinator;
}

void CoordinatedGraphicsLayer::setCoordinatorIncludingSubLayersIfNeeded(CoordinatedGraphicsLayerClient* coordinator)
{
    if (m_coordinator == coordinator)
        return;

    // If the coordinators are different it means that we are attaching a layer that was created by a different
    // CompositingCoordinator than the current one. This happens because the layer was taken out of the tree
    // and then added back after AC was disabled and enabled again. We need to set the new coordinator to the
    // layer and its children.
    //
    // During each layer flush, the state stores the values that have changed since the previous one, and these
    // are updated once in the scene. When adding CoordinatedGraphicsLayers back to the tree, the fields that
    // are not updated during the next flush won't be sent to the scene, so they won't be updated there and the
    // rendering will fail.
    //
    // For example the drawsContent flag. This is set when the layer is created and is not updated anymore (unless
    // the content changes). When the layer is added back to the tree, the state won't reflect any change in the
    // flag value, so the scene won't update it and the layer won't be rendered.
    //
    // We need to update here the layer changeMask so the scene gets all the current values.
    m_nicosia.delta.value = UINT_MAX;

    coordinator->attachLayer(this);
    for (auto& child : children())
        downcast<CoordinatedGraphicsLayer>(child.get()).setCoordinatorIncludingSubLayersIfNeeded(coordinator);
}

const RefPtr<Nicosia::CompositionLayer>& CoordinatedGraphicsLayer::compositionLayer() const
{
    return m_nicosia.layer;
}

void CoordinatedGraphicsLayer::setNeedsVisibleRectAdjustment()
{
    if (shouldHaveBackingStore())
        m_pendingVisibleRectAdjustment = true;
}

static inline bool isIntegral(float value)
{
    return static_cast<int>(value) == value;
}

FloatPoint CoordinatedGraphicsLayer::computePositionRelativeToBase()
{
    FloatPoint offset;
    for (const GraphicsLayer* currLayer = this; currLayer; currLayer = currLayer->parent())
        offset += currLayer->position();

    return offset;
}

void CoordinatedGraphicsLayer::computePixelAlignment(FloatPoint& position, FloatSize& size, FloatPoint3D& anchorPoint, FloatSize& alignmentOffset)
{
    if (isIntegral(effectiveContentsScale())) {
        position = m_position;
        size = m_size;
        anchorPoint = m_anchorPoint;
        alignmentOffset = FloatSize();
        return;
    }

    FloatPoint positionRelativeToBase = computePositionRelativeToBase();

    FloatRect baseRelativeBounds(positionRelativeToBase, m_size);
    FloatRect scaledBounds = baseRelativeBounds;

    // Scale by the effective scale factor to compute the screen-relative bounds.
    scaledBounds.scale(effectiveContentsScale());

    // Round to integer boundaries.
    // NOTE: When using enclosingIntRect (as mac) it will have different sizes depending on position.
    FloatRect alignedBounds = enclosingIntRect(scaledBounds);

    // Convert back to layer coordinates.
    alignedBounds.scale(1 / effectiveContentsScale());

    // Convert back to layer coordinates.
    alignmentOffset = baseRelativeBounds.location() - alignedBounds.location();

    position = m_position - alignmentOffset;
    size = alignedBounds.size();

    // Now we have to compute a new anchor point which compensates for rounding.
    float anchorPointX = m_anchorPoint.x();
    float anchorPointY = m_anchorPoint.y();

    if (alignedBounds.width())
        anchorPointX = (baseRelativeBounds.width() * anchorPointX + alignmentOffset.width()) / alignedBounds.width();

    if (alignedBounds.height())
        anchorPointY = (baseRelativeBounds.height() * anchorPointY + alignmentOffset.height()) / alignedBounds.height();

    anchorPoint = FloatPoint3D(anchorPointX, anchorPointY, m_anchorPoint.z() * effectiveContentsScale());
}

void CoordinatedGraphicsLayer::computeTransformedVisibleRect()
{
    if (!m_shouldUpdateVisibleRect && !m_movingVisibleRect)
        return;

    m_shouldUpdateVisibleRect = false;
    TransformationMatrix currentTransform = transform();
    if (m_movingVisibleRect)
        client().getCurrentTransform(this, currentTransform);
    m_layerTransform.setLocalTransform(currentTransform);

    m_layerTransform.setAnchorPoint(m_adjustedAnchorPoint);
    m_layerTransform.setPosition(m_adjustedPosition);
    m_layerTransform.setSize(m_adjustedSize);

    m_layerTransform.setFlattening(!preserves3D());
    m_layerTransform.setChildrenTransform(childrenTransform());
    m_layerTransform.combineTransforms(parent() ? downcast<CoordinatedGraphicsLayer>(*parent()).m_layerTransform.combinedForChildren() : TransformationMatrix());

    m_cachedInverseTransform = m_layerTransform.combined().inverse().valueOr(TransformationMatrix());

    // The combined transform will be used in tiledBackingStoreVisibleRect.
    setNeedsVisibleRectAdjustment();
}

bool CoordinatedGraphicsLayer::shouldHaveBackingStore() const
{
    return drawsContent() && contentsAreVisible() && !m_size.isEmpty();
}

bool CoordinatedGraphicsLayer::selfOrAncestorHasActiveTransformAnimation() const
{
    if (m_animations.hasActiveAnimationsOfType(AnimatedPropertyTransform))
        return true;

    if (!parent())
        return false;

    return downcast<CoordinatedGraphicsLayer>(*parent()).selfOrAncestorHasActiveTransformAnimation();
}

bool CoordinatedGraphicsLayer::selfOrAncestorHaveNonAffineTransforms()
{
    if (!m_layerTransform.combined().isAffine())
        return true;

    if (!parent())
        return false;

    return downcast<CoordinatedGraphicsLayer>(*parent()).selfOrAncestorHaveNonAffineTransforms();
}

bool CoordinatedGraphicsLayer::addAnimation(const KeyframeValueList& valueList, const FloatSize& boxSize, const Animation* anim, const String& keyframesName, double delayAsNegativeTimeOffset)
{
    ASSERT(!keyframesName.isEmpty());

    if (!anim || anim->isEmptyOrZeroDuration() || valueList.size() < 2 || (valueList.property() != AnimatedPropertyTransform && valueList.property() != AnimatedPropertyOpacity && valueList.property() != AnimatedPropertyFilter))
        return false;

    if (valueList.property() == AnimatedPropertyFilter) {
        int listIndex = validateFilterOperations(valueList);
        if (listIndex < 0)
            return false;

        const auto& filters = static_cast<const FilterAnimationValue&>(valueList.at(listIndex)).value();
        if (!filtersCanBeComposited(filters))
            return false;
    }

    bool listsMatch = false;
    bool ignoredHasBigRotation;

    if (valueList.property() == AnimatedPropertyTransform)
        listsMatch = validateTransformOperations(valueList, ignoredHasBigRotation) >= 0;

    m_lastAnimationStartTime = MonotonicTime::now() - Seconds(delayAsNegativeTimeOffset);
    m_animations.add(TextureMapperAnimation(keyframesName, valueList, boxSize, *anim, listsMatch, m_lastAnimationStartTime, 0_s, TextureMapperAnimation::AnimationState::Playing));
    m_animationStartedTimer.startOneShot(0_s);
    didChangeAnimations();
    return true;
}

void CoordinatedGraphicsLayer::pauseAnimation(const String& animationName, double time)
{
    m_animations.pause(animationName, Seconds(time));
    didChangeAnimations();
}

void CoordinatedGraphicsLayer::removeAnimation(const String& animationName)
{
    m_animations.remove(animationName);
    didChangeAnimations();
}

void CoordinatedGraphicsLayer::suspendAnimations(MonotonicTime time)
{
    m_animations.suspend(time);
    didChangeAnimations();
}

void CoordinatedGraphicsLayer::resumeAnimations()
{
    m_animations.resume();
    didChangeAnimations();
}

void CoordinatedGraphicsLayer::animationStartedTimerFired()
{
    client().notifyAnimationStarted(this, "", m_lastAnimationStartTime);
}

bool CoordinatedGraphicsLayer::usesContentsLayer() const
{
    return m_nicosia.contentLayer || m_compositedImage;
}

} // namespace WebCore

#endif // USE(COORDINATED_GRAPHICS)