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

Commit 487a92ca authored by Rob Tsuk's avatar Rob Tsuk
Browse files

Clipping performance improvements

Create a ClipArea class to handle tracking clip regions. This class can
select the most efficient implementation depending on the types of
clipping presented.

ClipArea re-used the rectangle and region-based clipping
implementations as well as adding a "list of rotated rectangles"
approach that is more efficient for rotated views with children.

Change-Id: I2133761a2462ebc0852b394220e265974b3086f0
parent 382d6a9e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ LOCAL_SRC_FILES := \
    AssetAtlas.cpp \
    Caches.cpp \
    CanvasState.cpp \
    ClipArea.cpp \
    DamageAccumulator.cpp \
    DisplayList.cpp \
    DeferredDisplayList.cpp \
+7 −36
Original line number Diff line number Diff line
@@ -155,47 +155,18 @@ void CanvasState::concatMatrix(const Matrix4& matrix) {
///////////////////////////////////////////////////////////////////////////////

bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
    if (CC_LIKELY(currentTransform()->rectToRect())) {
    mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op);
        return !mSnapshot->clipRect->isEmpty();
    }

    SkPath path;
    path.addRect(left, top, right, bottom);

    return CanvasState::clipPath(&path, op);
    return !mSnapshot->clipIsEmpty();
}

bool CanvasState::clipPath(const SkPath* path, SkRegion::Op op) {
    SkMatrix transform;
    currentTransform()->copyTo(transform);

    SkPath transformed;
    path->transform(transform, &transformed);

    SkRegion clip;
    if (!mSnapshot->previous->clipRegion->isEmpty()) {
        clip.setRegion(*mSnapshot->previous->clipRegion);
    } else {
        if (mSnapshot->previous == firstSnapshot()) {
            clip.setRect(0, 0, getWidth(), getHeight());
        } else {
            Rect* bounds = mSnapshot->previous->clipRect;
            clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
        }
    }

    SkRegion region;
    region.setPath(transformed, clip);

    // region is the transformed input path, masked by the previous clip
    mDirtyClip |= mSnapshot->clipRegionTransformed(region, op);
    return !mSnapshot->clipRect->isEmpty();
    mDirtyClip |= mSnapshot->clipPath(*path, op);
    return !mSnapshot->clipIsEmpty();
}

bool CanvasState::clipRegion(const SkRegion* region, SkRegion::Op op) {
    mDirtyClip |= mSnapshot->clipRegionTransformed(*region, op);
    return !mSnapshot->clipRect->isEmpty();
    return !mSnapshot->clipIsEmpty();
}

void CanvasState::setClippingOutline(LinearAllocator& allocator, const Outline* outline) {
@@ -245,7 +216,7 @@ bool CanvasState::calculateQuickRejectForScissor(float left, float top,
    currentTransform()->mapRect(r);
    r.snapGeometryToPixelBoundaries(snapOut);

    Rect clipRect(*currentClipRect());
    Rect clipRect(currentClipRect());
    clipRect.snapToPixelBoundaries();

    if (!clipRect.intersects(r)) return true;
@@ -273,7 +244,7 @@ bool CanvasState::quickRejectConservative(float left, float top,
    currentTransform()->mapRect(r);
    r.roundOut(); // rounded out to be conservative

    Rect clipRect(*currentClipRect());
    Rect clipRect(currentClipRect());
    clipRect.snapToPixelBoundaries();

    if (!clipRect.intersects(r)) return true;
+1 −5
Original line number Diff line number Diff line
@@ -120,10 +120,6 @@ public:
    bool clipPath(const SkPath* path, SkRegion::Op op);
    bool clipRegion(const SkRegion* region, SkRegion::Op op);

    bool isCurrentClipSimple() const {
        return currentSnapshot()->clipRegion->isEmpty();
    }

    /**
     * Sets a "clipping outline", which is independent from the regular clip.
     * Currently only supports rectangles or rounded rectangles; passing in a
@@ -150,7 +146,7 @@ public:
    void setInvisible(bool value) { mSnapshot->invisible = value; }

    inline const mat4* currentTransform() const { return currentSnapshot()->transform; }
    inline const Rect* currentClipRect() const { return currentSnapshot()->clipRect; }
    inline const Rect& currentClipRect() const { return currentSnapshot()->getClipRect(); }
    inline Region* currentRegion() const { return currentSnapshot()->region; }
    inline int currentFlags() const { return currentSnapshot()->flags; }
    const Vector3& currentLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); }

libs/hwui/ClipArea.cpp

0 → 100644
+368 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "ClipArea.h"

#include <SkPath.h>
#include <limits>

#include "Rect.h"

namespace android {
namespace uirenderer {

static bool intersect(Rect& r, const Rect& r2) {
    bool hasIntersection = r.intersect(r2);
    if (!hasIntersection) {
        r.setEmpty();
    }
    return hasIntersection;
}

static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
    Vertex v;
    v.x = x;
    v.y = y;
    transform.mapPoint(v.x, v.y);
    transformedBounds.expandToCoverVertex(v.x, v.y);
}

Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
    const float kMinFloat = std::numeric_limits<float>::lowest();
    const float kMaxFloat = std::numeric_limits<float>::max();
    Rect transformedBounds = { kMaxFloat, kMaxFloat, kMinFloat, kMinFloat };
    handlePoint(transformedBounds, transform, r.left, r.top);
    handlePoint(transformedBounds, transform, r.right, r.top);
    handlePoint(transformedBounds, transform, r.left, r.bottom);
    handlePoint(transformedBounds, transform, r.right, r.bottom);
    return transformedBounds;
}

/*
 * TransformedRectangle
 */

TransformedRectangle::TransformedRectangle() {
}

TransformedRectangle::TransformedRectangle(const Rect& bounds,
        const Matrix4& transform)
        : mBounds(bounds)
        , mTransform(transform) {
}

bool TransformedRectangle::canSimplyIntersectWith(
        const TransformedRectangle& other) const {

    return mTransform == other.mTransform;
}

bool TransformedRectangle::intersectWith(const TransformedRectangle& other) {
    Rect translatedBounds(other.mBounds);
    return intersect(mBounds, translatedBounds);
}

bool TransformedRectangle::isEmpty() const {
    return mBounds.isEmpty();
}

/*
 * RectangleList
 */

RectangleList::RectangleList()
        : mTransformedRectanglesCount(0) {
}

bool RectangleList::isEmpty() const {
    if (mTransformedRectanglesCount < 1) {
        return true;
    }

    for (int i = 0; i < mTransformedRectanglesCount; i++) {
        if (mTransformedRectangles[i].isEmpty()) {
            return true;
        }
    }
    return false;
}

int RectangleList::getTransformedRectanglesCount() const {
    return mTransformedRectanglesCount;
}

const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const {
    return mTransformedRectangles[i];
}

void RectangleList::setEmpty() {
    mTransformedRectanglesCount = 0;
}

void RectangleList::set(const Rect& bounds, const Matrix4& transform) {
    mTransformedRectanglesCount = 1;
    mTransformedRectangles[0] = TransformedRectangle(bounds, transform);
}

bool RectangleList::intersectWith(const Rect& bounds,
        const Matrix4& transform) {
    TransformedRectangle newRectangle(bounds, transform);

    // Try to find a rectangle with a compatible transformation
    int index = 0;
    for (; index < mTransformedRectanglesCount; index++) {
        TransformedRectangle& tr(mTransformedRectangles[index]);
        if (tr.canSimplyIntersectWith(newRectangle)) {
            tr.intersectWith(newRectangle);
            return true;
        }
    }

    // Add it to the list if there is room
    if (index < kMaxTransformedRectangles) {
        mTransformedRectangles[index] = newRectangle;
        mTransformedRectanglesCount += 1;
        return true;
    }

    // This rectangle list is full
    return false;
}

Rect RectangleList::calculateBounds() const {
    Rect bounds;
    for (int index = 0; index < mTransformedRectanglesCount; index++) {
        const TransformedRectangle& tr(mTransformedRectangles[index]);
        if (index == 0) {
            bounds = tr.transformedBounds();
        } else {
            bounds.intersect(tr.transformedBounds());
        }
    }
    return bounds;
}

static SkPath pathFromTransformedRectangle(const Rect& bounds,
        const Matrix4& transform) {
    SkPath rectPath;
    SkPath rectPathTransformed;
    rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom);
    SkMatrix skTransform;
    transform.copyTo(skTransform);
    rectPath.transform(skTransform, &rectPathTransformed);
    return rectPathTransformed;
}

SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
    SkRegion rectangleListAsRegion;
    for (int index = 0; index < mTransformedRectanglesCount; index++) {
        const TransformedRectangle& tr(mTransformedRectangles[index]);
        SkPath rectPathTransformed = pathFromTransformedRectangle(
                tr.getBounds(), tr.getTransform());
        if (index == 0) {
            rectangleListAsRegion.setPath(rectPathTransformed, clip);
        } else {
            SkRegion rectRegion;
            rectRegion.setPath(rectPathTransformed, clip);
            rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op);
        }
    }
    return rectangleListAsRegion;
}

/*
 * ClipArea
 */

ClipArea::ClipArea()
        : mMode(kModeRectangle) {
}

/*
 * Interface
 */

void ClipArea::setViewportDimensions(int width, int height) {
    mViewportBounds.set(0, 0, width, height);
    mClipRect = mViewportBounds;
}

void ClipArea::setEmpty() {
    mMode = kModeRectangle;
    mClipRect.setEmpty();
    mClipRegion.setEmpty();
    mRectangleList.setEmpty();
}

void ClipArea::setClip(float left, float top, float right, float bottom) {
    mMode = kModeRectangle;
    mClipRect.set(left, top, right, bottom);
    mClipRegion.setEmpty();
}

bool ClipArea::clipRectWithTransform(float left, float top, float right,
        float bottom, const mat4* transform, SkRegion::Op op) {
    Rect r(left, top, right, bottom);
    return clipRectWithTransform(r, transform, op);
}

bool ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
        SkRegion::Op op) {
    switch (mMode) {
    case kModeRectangle:
        return rectangleModeClipRectWithTransform(r, transform, op);
    case kModeRectangleList:
        return rectangleListModeClipRectWithTransform(r, transform, op);
    case kModeRegion:
        return regionModeClipRectWithTransform(r, transform, op);
    }
    return false;
}

bool ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
    enterRegionMode();
    mClipRegion.op(region, op);
    setClipRectToRegionBounds();
    return true;
}

bool ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
        SkRegion::Op op) {
    SkMatrix skTransform;
    transform->copyTo(skTransform);
    SkPath transformed;
    path.transform(skTransform, &transformed);
    SkRegion region;
    regionFromPath(transformed, region);
    return clipRegion(region, op);
}

/*
 * Rectangle mode
 */

void ClipArea::enterRectangleMode() {
    // Entering rectangle mode discards any
    // existing clipping information from the other modes.
    // The only way this occurs is by a clip setting operation.
    mMode = kModeRectangle;
}

bool ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
        const mat4* transform, SkRegion::Op op) {

    if (op != SkRegion::kIntersect_Op) {
        enterRegionMode();
        return regionModeClipRectWithTransform(r, transform, op);
    }

    if (transform->rectToRect()) {
        Rect transformed(r);
        transform->mapRect(transformed);
        bool hasIntersection = mClipRect.intersect(transformed);
        if (!hasIntersection) {
            mClipRect.setEmpty();
        }
        return true;
    }

    enterRectangleListMode();
    return rectangleListModeClipRectWithTransform(r, transform, op);
}

bool ClipArea::rectangleModeClipRectWithTransform(float left, float top,
        float right, float bottom, const mat4* transform, SkRegion::Op op) {
    Rect r(left, top, right, bottom);
    bool result = rectangleModeClipRectWithTransform(r, transform, op);
    mClipRect = mRectangleList.calculateBounds();
    return result;
}

/*
 * RectangleList mode implementation
 */

void ClipArea::enterRectangleListMode() {
    // Is is only legal to enter rectangle list mode from
    // rectangle mode, since rectangle list mode cannot represent
    // all clip areas that can be represented by a region.
    ALOG_ASSERT(mMode == kModeRectangle);
    mMode = kModeRectangleList;
    mRectangleList.set(mClipRect, Matrix4::identity());
}

bool ClipArea::rectangleListModeClipRectWithTransform(const Rect& r,
        const mat4* transform, SkRegion::Op op) {
    if (op != SkRegion::kIntersect_Op
            || !mRectangleList.intersectWith(r, *transform)) {
        enterRegionMode();
        return regionModeClipRectWithTransform(r, transform, op);
    }
    return true;
}

bool ClipArea::rectangleListModeClipRectWithTransform(float left, float top,
        float right, float bottom, const mat4* transform, SkRegion::Op op) {
    Rect r(left, top, right, bottom);
    return rectangleListModeClipRectWithTransform(r, transform, op);
}

/*
 * Region mode implementation
 */

void ClipArea::enterRegionMode() {
    if (mMode != kModeRegion) {
        if (mMode == kModeRectangle) {
            mClipRegion.setRect(mClipRect.left, mClipRect.top,
                    mClipRect.right, mClipRect.bottom);
        } else {
            mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
            setClipRectToRegionBounds();
        }
        mMode = kModeRegion;
    }
}

bool ClipArea::regionModeClipRectWithTransform(const Rect& r,
        const mat4* transform, SkRegion::Op op) {
    SkPath transformedRect = pathFromTransformedRectangle(r, *transform);
    SkRegion transformedRectRegion;
    regionFromPath(transformedRect, transformedRectRegion);
    mClipRegion.op(transformedRectRegion, op);
    setClipRectToRegionBounds();
    return true;
}

bool ClipArea::regionModeClipRectWithTransform(float left, float top,
        float right, float bottom, const mat4* transform, SkRegion::Op op) {
    return regionModeClipRectWithTransform(Rect(left, top, right, bottom),
            transform, op);
}

void ClipArea::setClipRectToRegionBounds() {
    if (!mClipRegion.isEmpty()) {
        mClipRect.set(mClipRegion.getBounds());

        if (mClipRegion.isRect()) {
            mClipRegion.setEmpty();
        }
    } else {
        mClipRect.setEmpty();
    }
}

} /* namespace uirenderer */
} /* namespace android */

libs/hwui/ClipArea.h

0 → 100644
+175 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef CLIPAREA_H
#define CLIPAREA_H

#include <SkRegion.h>

#include "Matrix.h"
#include "Rect.h"
#include "utils/Pair.h"

namespace android {
namespace uirenderer {

Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform);

class TransformedRectangle {
public:
    TransformedRectangle();
    TransformedRectangle(const Rect& bounds, const Matrix4& transform);

    bool canSimplyIntersectWith(const TransformedRectangle& other) const;
    bool intersectWith(const TransformedRectangle& other);

    bool isEmpty() const;

    const Rect& getBounds() const {
        return mBounds;
    }

    Rect transformedBounds() const {
        Rect transformedBounds(transformAndCalculateBounds(mBounds, mTransform));
        return transformedBounds;
    }

    const Matrix4& getTransform() const {
        return mTransform;
    }

private:
    Rect mBounds;
    Matrix4 mTransform;
};

class RectangleList {
public:
    RectangleList();

    bool isEmpty() const;
    int getTransformedRectanglesCount() const;
    const TransformedRectangle& getTransformedRectangle(int i) const;

    void setEmpty();
    void set(const Rect& bounds, const Matrix4& transform);
    bool intersectWith(const Rect& bounds, const Matrix4& transform);

    SkRegion convertToRegion(const SkRegion& clip) const;
    Rect calculateBounds() const;

private:
    enum {
        kMaxTransformedRectangles = 5
    };

    int mTransformedRectanglesCount;
    TransformedRectangle mTransformedRectangles[kMaxTransformedRectangles];
};

class ClipArea {
public:
    ClipArea();

    void setViewportDimensions(int width, int height);

    bool isEmpty() const {
        return mClipRect.isEmpty();
    }

    void setEmpty();
    void setClip(float left, float top, float right, float bottom);
    bool clipRectWithTransform(float left, float top, float right, float bottom,
            const mat4* transform, SkRegion::Op op = SkRegion::kIntersect_Op);
    bool clipRectWithTransform(const Rect& r, const mat4* transform,
            SkRegion::Op op = SkRegion::kIntersect_Op);
    bool clipRegion(const SkRegion& region, SkRegion::Op op = SkRegion::kIntersect_Op);
    bool clipPathWithTransform(const SkPath& path, const mat4* transform,
            SkRegion::Op op);

    const Rect& getClipRect() const {
        return mClipRect;
    }

    const SkRegion& getClipRegion() const {
        return mClipRegion;
    }

    const RectangleList& getRectangleList() const {
        return mRectangleList;
    }

    bool isRegion() const {
        return kModeRegion == mMode;
    }

    bool isSimple() const {
        return mMode == kModeRectangle;
    }

    bool isRectangleList() const {
        return mMode == kModeRectangleList;
    }

private:
    void enterRectangleMode();
    bool rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op);
    bool rectangleModeClipRectWithTransform(float left, float top, float right,
            float bottom, const mat4* transform, SkRegion::Op op);

    void enterRectangleListMode();
    bool rectangleListModeClipRectWithTransform(float left, float top,
            float right, float bottom, const mat4* transform, SkRegion::Op op);
    bool rectangleListModeClipRectWithTransform(const Rect& r,
            const mat4* transform, SkRegion::Op op);

    void enterRegionModeFromRectangleMode();
    void enterRegionModeFromRectangleListMode();
    void enterRegionMode();
    bool regionModeClipRectWithTransform(const Rect& r, const mat4* transform,
            SkRegion::Op op);
    bool regionModeClipRectWithTransform(float left, float top, float right,
            float bottom, const mat4* transform, SkRegion::Op op);

    void ensureClipRegion();
    void setClipRectToRegionBounds();
    bool clipRegionOp(float left, float top, float right, float bottom,
            SkRegion::Op op);

    SkRegion createViewportRegion() {
        return SkRegion(mViewportBounds.toSkIRect());
    }

    void regionFromPath(const SkPath& path, SkRegion& pathAsRegion) {
        pathAsRegion.setPath(path, createViewportRegion());
    }

    enum Mode {
        kModeRectangle,
        kModeRegion,
        kModeRectangleList
    };

    Mode mMode;
    Rect mViewportBounds;
    Rect mClipRect;
    SkRegion mClipRegion;
    RectangleList mRectangleList;
};

} /* namespace uirenderer */
} /* namespace android */

#endif /* CLIPAREA_H_ */
Loading