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

Commit 9cd1bbe5 authored by Chris Craik's avatar Chris Craik
Browse files

Improve multi-window render clipping logic

Fixes: 28125010

Restructures 'scene defer', to implement window backdrop overdraw
avoidance in new render pipeline, and disable clipping to content draw
bounds.

Also restructures FrameBuilder's constructors, to separate out into
multiple defer methods.

Change-Id: I53facb904c1a4a4acc493d8a489921a79a50494e
parent e1b1ce77
Loading
Loading
Loading
Loading
+6 −9
Original line number Diff line number Diff line
@@ -270,15 +270,12 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame
            mLastXOffset = xOffset;
            mLastYOffset = yOffset;

            // Only clip the content to the bounds if we are not fullscreen. In the other case, we
            // actually need to draw outside these.
            if (mResizeMode == RESIZE_MODE_FREEFORM) {
            // Inform the renderer of the content's new bounds
            mRenderer.setContentDrawBounds(
                    mLastXOffset,
                    mLastYOffset,
                    mLastXOffset + mLastContentWidth,
                    mLastYOffset + mLastCaptionHeight + mLastContentHeight);
            }

            // If this was the first call and redrawLocked got already called prior
            // to us, we should re-issue a redrawLocked now.
+108 −44
Original line number Diff line number Diff line
@@ -31,18 +31,16 @@
namespace android {
namespace uirenderer {

FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
FrameBuilder::FrameBuilder(const SkRect& clip,
        uint32_t viewportWidth, uint32_t viewportHeight,
        const std::vector< sp<RenderNode> >& nodes,
        const LightGeometry& lightGeometry, const Rect &contentDrawBounds, Caches& caches)
        : mCanvasState(*this)
        const LightGeometry& lightGeometry, Caches& caches)
        : mStdAllocator(mAllocator)
        , mLayerBuilders(mStdAllocator)
        , mLayerStack(mStdAllocator)
        , mCanvasState(*this)
        , mCaches(caches)
        , mLightRadius(lightGeometry.radius)
        , mDrawFbo0(!nodes.empty()) {
    ATRACE_NAME("prepare drawing commands");

    mLayerBuilders.reserve(layers.entries().size());
    mLayerStack.reserve(layers.entries().size());
        , mDrawFbo0(true) {

    // Prepare to defer Fbo0
    auto fbo0 = mAllocator.create<LayerBuilder>(viewportWidth, viewportHeight, Rect(clip));
@@ -51,7 +49,31 @@ FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
    mCanvasState.initializeSaveStack(viewportWidth, viewportHeight,
            clip.fLeft, clip.fTop, clip.fRight, clip.fBottom,
            lightGeometry.center);
}

FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers,
        const LightGeometry& lightGeometry, Caches& caches)
        : mStdAllocator(mAllocator)
        , mLayerBuilders(mStdAllocator)
        , mLayerStack(mStdAllocator)
        , mCanvasState(*this)
        , mCaches(caches)
        , mLightRadius(lightGeometry.radius)
        , mDrawFbo0(false) {
    // TODO: remove, with each layer on its own save stack

    // Prepare to defer Fbo0 (which will be empty)
    auto fbo0 = mAllocator.create<LayerBuilder>(1, 1, Rect(1, 1));
    mLayerBuilders.push_back(fbo0);
    mLayerStack.push_back(0);
    mCanvasState.initializeSaveStack(1, 1,
            0, 0, 1, 1,
            lightGeometry.center);

    deferLayers(layers);
}

void FrameBuilder::deferLayers(const LayerUpdateQueue& layers) {
    // Render all layers to be updated, in order. Defer in reverse order, so that they'll be
    // updated in the order they're passed in (mLayerBuilders are issued to Renderer in reverse)
    for (int i = layers.entries().size() - 1; i >= 0; i--) {
@@ -76,10 +98,45 @@ FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
            restoreForLayer();
        }
    }
}

void FrameBuilder::deferRenderNode(RenderNode& renderNode) {
    renderNode.computeOrdering();

    mCanvasState.save(SaveFlags::MatrixClip);
    deferNodePropsAndOps(renderNode);
    mCanvasState.restore();
}

void FrameBuilder::deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode) {
    renderNode.computeOrdering();

    mCanvasState.save(SaveFlags::MatrixClip);
    mCanvasState.translate(tx, ty);
    mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom,
            SkRegion::kIntersect_Op);
    deferNodePropsAndOps(renderNode);
    mCanvasState.restore();
}

static Rect nodeBounds(RenderNode& node) {
    auto& props = node.properties();
    return Rect(props.getLeft(), props.getTop(),
            props.getRight(), props.getBottom());
}

void FrameBuilder::deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes,
        const Rect& contentDrawBounds) {
    if (nodes.size() < 1) return;
    if (nodes.size() == 1) {
        if (!nodes[0]->nothingToDraw()) {
            deferRenderNode(*nodes[0]);
        }
        return;
    }
    // It there are multiple render nodes, they are laid out as follows:
    // #0 - backdrop (content + caption)
    // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds)
    // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop)
    // #2 - additional overlay nodes
    // Usually the backdrop cannot be seen since it will be entirely covered by the content. While
    // resizing however it might become partially visible. The following render loop will crop the
@@ -88,45 +145,52 @@ FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
    //
    // Additional nodes will be drawn on top with no particular clipping semantics.

    // The bounds of the backdrop against which the content should be clipped.
    Rect backdropBounds = contentDrawBounds;
    // Usually the contents bounds should be mContentDrawBounds - however - we will
    // move it towards the fixed edge to give it a more stable appearance (for the moment).
    // If there is no content bounds we ignore the layering as stated above and start with 2.
    int layer = (contentDrawBounds.isEmpty() || nodes.size() == 1) ? 2 : 0;

    for (const sp<RenderNode>& node : nodes) {
        if (node->nothingToDraw()) continue;
        node->computeOrdering();
        int count = mCanvasState.save(SaveFlags::MatrixClip);
    // Backdrop bounds in render target space
    const Rect backdrop = nodeBounds(*nodes[0]);

        if (layer == 0) {
            const RenderProperties& properties = node->properties();
            Rect targetBounds(properties.getLeft(), properties.getTop(),
                              properties.getRight(), properties.getBottom());
            // Move the content bounds towards the fixed corner of the backdrop.
            const int x = targetBounds.left;
            const int y = targetBounds.top;
            // Remember the intersection of the target bounds and the intersection bounds against
            // which we have to crop the content.
            backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight());
            backdropBounds.doIntersect(targetBounds);
        } else if (layer == 1) {
            // We shift and clip the content to match its final location in the window.
            const float left = contentDrawBounds.left;
            const float top = contentDrawBounds.top;
            const float dx = backdropBounds.left - left;
            const float dy = backdropBounds.top - top;
            const float width = backdropBounds.getWidth();
            const float height = backdropBounds.getHeight();
            mCanvasState.translate(dx, dy);
            // It gets cropped against the bounds of the backdrop to stay inside.
            mCanvasState.clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op);
        }

        deferNodePropsAndOps(*node);
        mCanvasState.restoreToCount(count);
        layer++;
    // Bounds that content will fill in render target space (note content node bounds may be bigger)
    Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight());
    content.translate(backdrop.left, backdrop.top);
    if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) {
        // Content doesn't entirely overlap backdrop, so fill around content (right/bottom)

        // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to
        // also fill left/top. Currently, both 2up and freeform position content at the top/left of
        // the backdrop, so this isn't necessary.
        if (content.right < backdrop.right) {
            // draw backdrop to right side of content
            deferRenderNode(0, 0, Rect(content.right, backdrop.top,
                    backdrop.right, backdrop.bottom), *nodes[0]);
        }
        if (content.bottom < backdrop.bottom) {
            // draw backdrop to bottom of content
            // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill
            deferRenderNode(0, 0, Rect(content.left, content.bottom,
                    content.right, backdrop.bottom), *nodes[0]);
        }
    }

    if (!backdrop.isEmpty()) {
        // content node translation to catch up with backdrop
        float dx = contentDrawBounds.left - backdrop.left;
        float dy = contentDrawBounds.top - backdrop.top;

        Rect contentLocalClip = backdrop;
        contentLocalClip.translate(dx, dy);
        deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]);
    } else {
        deferRenderNode(*nodes[1]);
    }

    // remaining overlay nodes, simply defer
    for (size_t index = 2; index < nodes.size(); index++) {
        if (!nodes[index]->nothingToDraw()) {
            deferRenderNode(*nodes[index]);
        }
    }
}

+21 −20
Original line number Diff line number Diff line
@@ -37,8 +37,8 @@ class OffscreenBuffer;
class Rect;

/**
 * Traverses all of the drawing commands from the layers and RenderNodes passed into it, preparing
 * them to be rendered.
 * Processes, optimizes, and stores rendering commands from RenderNodes and
 * LayerUpdateQueue, building content needed to render a frame.
 *
 * Resolves final drawing state for each operation (including clip, alpha and matrix), and then
 * reorder and merge each op as it is resolved for drawing efficiency. Each layer of content (either
@@ -60,21 +60,21 @@ public:
        float radius;
    };

    // TODO: remove
    FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
    FrameBuilder(const SkRect& clip,
            uint32_t viewportWidth, uint32_t viewportHeight,
            const std::vector< sp<RenderNode> >& nodes,
            const LightGeometry& lightGeometry,
            Caches& caches)
            : FrameBuilder(layers, clip, viewportWidth, viewportHeight,
                    nodes, lightGeometry, Rect(), caches) {}
            const LightGeometry& lightGeometry, Caches& caches);

    FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip,
            uint32_t viewportWidth, uint32_t viewportHeight,
            const std::vector< sp<RenderNode> >& nodes,
            const LightGeometry& lightGeometry,
            const Rect &contentDrawBounds,
            Caches& caches);
    FrameBuilder(const LayerUpdateQueue& layerUpdateQueue,
            const LightGeometry& lightGeometry, Caches& caches);

    void deferLayers(const LayerUpdateQueue& layers);

    void deferRenderNode(RenderNode& renderNode);

    void deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode);

    void deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes,
            const Rect& contentDrawBounds);

    virtual ~FrameBuilder() {}

@@ -223,8 +223,12 @@ private:
    MAP_DEFERRABLE_OPS(X)
#undef X

    // contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches
    LinearAllocator mAllocator;
    LinearStdAllocator<void*> mStdAllocator;

    // List of every deferred layer's render state. Replayed in reverse order to render a frame.
    std::vector<LayerBuilder*> mLayerBuilders;
    LsaVector<LayerBuilder*> mLayerBuilders;

    /*
     * Stack of indices within mLayerBuilders representing currently active layers. If drawing
@@ -238,7 +242,7 @@ private:
     * won't be in mLayerStack. This is because it can be replayed, but can't have any more drawing
     * ops added to it.
    */
    std::vector<size_t> mLayerStack;
    LsaVector<size_t> mLayerStack;

    CanvasState mCanvasState;

@@ -246,9 +250,6 @@ private:

    float mLightRadius;

    // contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches
    LinearAllocator mAllocator;

    const bool mDrawFbo0;
};

+2 −1
Original line number Diff line number Diff line
@@ -244,7 +244,8 @@ void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bak

        if (CC_UNLIKELY(activeUnclippedSaveLayers.empty()
                && bakedState->computedState.opaqueOverClippedBounds
                && bakedState->computedState.clippedBounds.contains(repaintRect))) {
                && bakedState->computedState.clippedBounds.contains(repaintRect)
                && !Properties::debugOverdraw)) {
            // discard all deferred drawing ops, since new one will occlude them
            clear();
        }
+7 −4
Original line number Diff line number Diff line
@@ -348,9 +348,13 @@ void CanvasContext::draw() {

#if HWUI_NEW_OPS
    auto& caches = Caches::getInstance();
    FrameBuilder frameBuilder(mLayerUpdateQueue, dirty, frame.width(), frame.height(),
            mRenderNodes, mLightGeometry, mContentDrawBounds, caches);
    FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), mLightGeometry, caches);

    frameBuilder.deferLayers(mLayerUpdateQueue);
    mLayerUpdateQueue.clear();

    frameBuilder.deferRenderNodeScene(mRenderNodes, mContentDrawBounds);

    BakedOpRenderer renderer(caches, mRenderThread.renderState(),
            mOpaque, mLightInfo);
    frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
@@ -615,8 +619,7 @@ void CanvasContext::buildLayer(RenderNode* node, TreeObserver* observer) {
#if HWUI_NEW_OPS
    static const std::vector< sp<RenderNode> > emptyNodeList;
    auto& caches = Caches::getInstance();
    FrameBuilder frameBuilder(mLayerUpdateQueue, SkRect::MakeWH(1, 1), 1, 1,
            emptyNodeList, mLightGeometry, mContentDrawBounds, caches);
    FrameBuilder frameBuilder(mLayerUpdateQueue, mLightGeometry, caches);
    mLayerUpdateQueue.clear();
    BakedOpRenderer renderer(caches, mRenderThread.renderState(),
            mOpaque, mLightInfo);
Loading