Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit db45a4bf authored by Stan Iliev's avatar Stan Iliev
Browse files

Fix Skia render node projection to match HWUI

Fix Skia render node projection to match HWUI. Port
FrameBuilderTests_projectionReorder test for Skia pipeline.
Add new tests in both HWUI and Skia to cover more projection
use cases.

Test: built and run on angler-eng
Change-Id: Ibf27af211452ae95d595aca7723ea63f48b0b282
parent 3dfca02d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -181,6 +181,7 @@ hwui_c_includes += \
    external/skia/include/private \
    external/skia/src/core \
    external/skia/src/effects \
    external/skia/src/image \
    external/harfbuzz_ng/src \
    external/freetype/include

+3 −0
Original line number Diff line number Diff line
@@ -116,6 +116,9 @@ public:
        bool canDrawThisFrame = true;
    } out;

    // This flag helps to disable projection for receiver nodes that do not have any backward
    // projected children.
    bool hasBackwardProjectedNodes = false;
    // TODO: Damage calculations
};

+54 −45
Original line number Diff line number Diff line
@@ -24,8 +24,41 @@ namespace android {
namespace uirenderer {
namespace skiapipeline {

void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
        int nestLevel) {
    LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
    for (auto& child : displayList.mChildNodes) {
        const RenderProperties& childProperties = child.getNodeProperties();

        //immediate children cannot be projected on their parent
        if (childProperties.getProjectBackwards() && nestLevel > 0) {
            SkAutoCanvasRestore acr2(canvas, true);
            //Apply recorded matrix, which is a total matrix saved at recording time to avoid
            //replaying all DL commands.
            canvas->concat(child.getRecordedMatrix());
            child.drawContent(canvas);
        }

        //skip walking sub-nodes if current display list contains a receiver with exception of
        //level 0, which is a known receiver
        if (0 == nestLevel || !displayList.containsProjectionReceiver()) {
            SkAutoCanvasRestore acr(canvas, true);
            SkMatrix nodeMatrix;
            mat4 hwuiMatrix(child.getRecordedMatrix());
            auto childNode = child.getRenderNode();
            childNode->applyViewPropertyTransforms(hwuiMatrix);
            hwuiMatrix.copyTo(nodeMatrix);
            canvas->concat(nodeMatrix);
            SkiaDisplayList* childDisplayList = static_cast<SkiaDisplayList*>(
                (const_cast<DisplayList*>(childNode->getDisplayList())));
            if (childDisplayList) {
                drawBackwardsProjectedNodes(canvas, *childDisplayList, nestLevel+1);
            }
        }
    }
}

static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* pendingClip) {
    SkASSERT(outline.willClip());
    Rect possibleRect;
    float radius;
    LOG_ALWAYS_FATAL_IF(!outline.getAsRoundRect(&possibleRect, &radius),
@@ -74,53 +107,25 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) {
    SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();

    SkAutoCanvasRestore acr(canvas, true);

    const RenderProperties& properties = this->getNodeProperties();
    if (displayList->mIsProjectionReceiver) {
        // this node is a projection receiver. We will gather the projected nodes as we draw our
        // children, and then draw them on top of this node's content.
        std::vector<ProjectedChild> newList;
        for (auto& child : displayList->mChildNodes) {
            // our direct children are not supposed to project into us (nodes project to, at the
            // nearest, their grandparents). So we "delay" the list's activation one level by
            // passing it into mNextProjectedChildrenTarget rather than mProjectedChildrenTarget.
            child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
            child.mNextProjectedChildrenTarget = &newList;
        }
        // draw ourselves and our children. As a side effect, this will add projected nodes to
        // newList.
        this->drawContent(canvas);
        bool willClip = properties.getOutline().willClip();
        if (willClip) {
            canvas->save();
            clipOutline(properties.getOutline(), canvas, nullptr);
        }
        // draw the collected projected nodes
        for (auto& projectedChild : newList) {
            canvas->setMatrix(projectedChild.matrix);
            projectedChild.node->drawContent(canvas);
        }
        if (willClip) {
            canvas->restore();
    //pass this outline to the children that may clip backward projected nodes
    displayList->mProjectedOutline = displayList->containsProjectionReceiver()
            ? &properties.getOutline() : nullptr;
    if (!properties.getProjectBackwards()) {
        drawContent(canvas);
        if (mProjectedDisplayList) {
            acr.restore(); //draw projected children using parent matrix
            LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline);
            const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath();
            SkAutoCanvasRestore acr2(canvas, shouldClip);
            canvas->setMatrix(mProjectedDisplayList->mProjectedReceiverParentMatrix);
            if (shouldClip) {
                clipOutline(*mProjectedDisplayList->mProjectedOutline, canvas, nullptr);
            }
    } else {
        if (properties.getProjectBackwards() && mProjectedChildrenTarget) {
            // We are supposed to project this node, so add it to the list and do not actually draw
            // yet. It will be drawn by its projection receiver.
            mProjectedChildrenTarget->push_back({ this, canvas->getTotalMatrix() });
            return;
        }
        for (auto& child : displayList->mChildNodes) {
            // storing these values in the nodes themselves is a bit ugly; they should "really" be
            // function parameters, but we have to go through the preexisting draw() method and
            // therefore cannot add additional parameters to it
            child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
            child.mNextProjectedChildrenTarget = mNextProjectedChildrenTarget;
            drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
        }
        this->drawContent(canvas);
    }
    mProjectedChildrenTarget = nullptr;
    mNextProjectedChildrenTarget = nullptr;
    displayList->mProjectedOutline = nullptr;
}

static bool layerNeedsPaint(const LayerProperties& properties,
@@ -148,6 +153,10 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
    if (mComposeLayer) {
        setViewProperties(properties, canvas, &alphaMultiplier);
    }
    SkiaDisplayList* displayList = (SkiaDisplayList*)mRenderNode->getDisplayList();
    if (displayList->containsProjectionReceiver()) {
        displayList->mProjectedReceiverParentMatrix = canvas->getTotalMatrix();
    }

    //TODO should we let the bound of the drawable do this for us?
    const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
+25 −25
Original line number Diff line number Diff line
@@ -29,24 +29,14 @@ class RenderProperties;

namespace skiapipeline {

class SkiaDisplayList;

/**
 * This drawable wraps a RenderNode and enables it to be recorded into a list
 * of Skia drawing commands.
 */
class RenderNodeDrawable : public SkDrawable {
public:
    /**
     * This struct contains a pointer to a node that is to be
     * projected into the drawing order of its closest ancestor
     * (excluding its parent) that is marked as a projection
     * receiver. The matrix is used to ensure that the node is
     * drawn with same matrix as it would have prior to projection.
     */
    struct ProjectedChild {
        const RenderNodeDrawable* node;
        const SkMatrix matrix;
    };

    /**
     * Creates a new RenderNodeDrawable backed by a render node.
     *
@@ -86,6 +76,14 @@ public:
     */
    const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }

    /**
     * Sets a pointer to a display list of the parent render node. The display list is used when
     * drawing backward projected nodes, when this node is a projection receiver.
     */
    void setProjectedDisplayList(SkiaDisplayList* projectedDisplayList) {
        mProjectedDisplayList = projectedDisplayList;
    }

protected:
    /*
     * Return the (conservative) bounds of what the drawable will draw.
@@ -107,6 +105,16 @@ private:
     */
    sp<RenderNode> mRenderNode;

    /**
     * Walks recursively the display list and draws the content of backward projected nodes.
     *
     * @param canvas used to draw the backward projected nodes
     * @param displayList is a display list that contains a projection receiver
     * @param nestLevel should be always 0. Used to track how far we are from the receiver.
     */
    void drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
            int nestLevel = 0);

    /**
     * Applies the rendering properties of a view onto a SkCanvas.
     */
@@ -126,19 +134,6 @@ private:
     */
    const bool mComposeLayer;

    /**
     * List to which we will add any projected children we encounter while walking our descendents.
     * This pointer is valid only while the node (including its children) is actively being drawn.
     */
    std::vector<ProjectedChild>* mProjectedChildrenTarget = nullptr;

    /**
     * The value to which we should set our children's mProjectedChildrenTarget. We use two pointers
     * (mProjectedChildrenTarget and mNextProjectedChildrenTarget) because we need to skip over our
     * parent when looking for a projection receiver.
     */
    std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;

    /*
     * True if the render node is in a reordering section
     */
@@ -148,6 +143,11 @@ private:
     *  Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
     */
    void drawContent(SkCanvas* canvas) const;

    /*
     * display list that is searched for any render nodes with getProjectBackwards==true
     */
    SkiaDisplayList* mProjectedDisplayList = nullptr;
};

}; // namespace skiapipeline
+18 −3
Original line number Diff line number Diff line
@@ -64,16 +64,31 @@ bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLa
        info.canvasContext.unpinImages();
    }

    bool hasBackwardProjectedNodesHere = false;
    bool hasBackwardProjectedNodesSubtree= false;

    for (auto& child : mChildNodes) {
        hasBackwardProjectedNodesHere |= child.getNodeProperties().getProjectBackwards();
        RenderNode* childNode = child.getRenderNode();
        Matrix4 mat4(child.getRecordedMatrix());
        info.damageAccumulator->pushTransform(&mat4);
        // TODO: a layer is needed if the canvas is rotated or has a non-rect clip
        bool childFunctorsNeedLayer = functorsNeedLayer;
        childFn(childNode, info, childFunctorsNeedLayer);
        info.hasBackwardProjectedNodes = false;
        childFn(childNode, info, functorsNeedLayer);
        hasBackwardProjectedNodesSubtree |= info.hasBackwardProjectedNodes;
        info.damageAccumulator->popTransform();
    }

    //The purpose of next block of code is to reset projected display list if there are no
    //backward projected nodes. This speeds up drawing, by avoiding an extra walk of the tree
    if (mProjectionReceiver) {
        mProjectionReceiver->setProjectedDisplayList(hasBackwardProjectedNodesSubtree ? this : nullptr);
        info.hasBackwardProjectedNodes = hasBackwardProjectedNodesHere;
    } else {
        info.hasBackwardProjectedNodes = hasBackwardProjectedNodesSubtree
                || hasBackwardProjectedNodesHere;
    }

    bool isDirty = false;
    for (auto& vectorDrawable : mVectorDrawables) {
        // If any vector drawable in the display list needs update, damage the node.
@@ -86,7 +101,7 @@ bool SkiaDisplayList::prepareListAndChildren(TreeInfo& info, bool functorsNeedLa
}

void SkiaDisplayList::reset(SkRect bounds) {
    mIsProjectionReceiver = false;
    mProjectionReceiver = nullptr;

    mDrawable->reset(bounds);

Loading