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
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
index fba7254..b044e02 100644
--- a/libs/hwui/Android.common.mk
+++ b/libs/hwui/Android.common.mk
@@ -18,6 +18,7 @@
AssetAtlas.cpp \
Caches.cpp \
CanvasState.cpp \
+ ClipArea.cpp \
DamageAccumulator.cpp \
DisplayList.cpp \
DeferredDisplayList.cpp \
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index 9d2ccf1..85f860d 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -155,47 +155,18 @@
///////////////////////////////////////////////////////////////////////////////
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);
+ mDirtyClip |= mSnapshot->clip(left, top, right, bottom, 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 @@
currentTransform()->mapRect(r);
r.snapGeometryToPixelBoundaries(snapOut);
- Rect clipRect(*currentClipRect());
+ Rect clipRect(currentClipRect());
clipRect.snapToPixelBoundaries();
if (!clipRect.intersects(r)) return true;
@@ -273,7 +244,7 @@
currentTransform()->mapRect(r);
r.roundOut(); // rounded out to be conservative
- Rect clipRect(*currentClipRect());
+ Rect clipRect(currentClipRect());
clipRect.snapToPixelBoundaries();
if (!clipRect.intersects(r)) return true;
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index 9e6a9c3..121112b 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -120,10 +120,6 @@
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 @@
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(); }
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
new file mode 100644
index 0000000..852204a
--- /dev/null
+++ b/libs/hwui/ClipArea.cpp
@@ -0,0 +1,368 @@
+/*
+ * 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 */
diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h
new file mode 100644
index 0000000..16e6df9
--- /dev/null
+++ b/libs/hwui/ClipArea.h
@@ -0,0 +1,175 @@
+/*
+ * 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_ */
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
old mode 100755
new mode 100644
index 3c8fb8b..9ee3704
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -354,7 +354,7 @@
void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
if (mState.currentlyIgnored()) return;
- Rect clip(*mState.currentClipRect());
+ Rect clip(mState.currentClipRect());
clip.snapToPixelBoundaries();
// Since we don't know what the functor will draw, let's dirty
@@ -619,7 +619,7 @@
currentTransform()->mapRect(bounds);
// Layers only make sense if they are in the framebuffer's bounds
- if (bounds.intersect(*mState.currentClipRect())) {
+ if (bounds.intersect(mState.currentClipRect())) {
// We cannot work with sub-pixels in this case
bounds.snapToPixelBoundaries();
@@ -1249,7 +1249,7 @@
}
void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
- if (bounds.intersect(*mState.currentClipRect())) {
+ if (bounds.intersect(mState.currentClipRect())) {
bounds.snapToPixelBoundaries();
android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
if (!dirty.isEmpty()) {
@@ -1328,7 +1328,7 @@
///////////////////////////////////////////////////////////////////////////////
bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
- const Rect* currentClip = mState.currentClipRect();
+ const Rect& currentClip = mState.currentClipRect();
const mat4* currentMatrix = currentTransform();
if (stateDeferFlags & kStateDeferFlag_Draw) {
@@ -1340,32 +1340,32 @@
// is used, it should more closely duplicate the quickReject logic (in how it uses
// snapToPixelBoundaries)
- if(!clippedBounds.intersect(*currentClip)) {
+ if (!clippedBounds.intersect(currentClip)) {
// quick rejected
return true;
}
state.mClipSideFlags = kClipSide_None;
- if (!currentClip->contains(state.mBounds)) {
+ if (!currentClip.contains(state.mBounds)) {
int& flags = state.mClipSideFlags;
// op partially clipped, so record which sides are clipped for clip-aware merging
- if (currentClip->left > state.mBounds.left) flags |= kClipSide_Left;
- if (currentClip->top > state.mBounds.top) flags |= kClipSide_Top;
- if (currentClip->right < state.mBounds.right) flags |= kClipSide_Right;
- if (currentClip->bottom < state.mBounds.bottom) flags |= kClipSide_Bottom;
+ if (currentClip.left > state.mBounds.left) flags |= kClipSide_Left;
+ if (currentClip.top > state.mBounds.top) flags |= kClipSide_Top;
+ if (currentClip.right < state.mBounds.right) flags |= kClipSide_Right;
+ if (currentClip.bottom < state.mBounds.bottom) flags |= kClipSide_Bottom;
}
state.mBounds.set(clippedBounds);
} else {
// Empty bounds implies size unknown. Label op as conservatively clipped to disable
// overdraw avoidance (since we don't know what it overlaps)
state.mClipSideFlags = kClipSide_ConservativeFull;
- state.mBounds.set(*currentClip);
+ state.mBounds.set(currentClip);
}
}
state.mClipValid = (stateDeferFlags & kStateDeferFlag_Clip);
if (state.mClipValid) {
- state.mClip.set(*currentClip);
+ state.mClip.set(currentClip);
}
// Transform, drawModifiers, and alpha always deferred, since they are used by state operations
@@ -1414,7 +1414,7 @@
///////////////////////////////////////////////////////////////////////////////
void OpenGLRenderer::setScissorFromClip() {
- Rect clip(*mState.currentClipRect());
+ Rect clip(mState.currentClipRect());
clip.snapToPixelBoundaries();
if (mCaches.setScissor(clip.left, getViewportHeight() - clip.bottom,
@@ -1448,9 +1448,74 @@
}
}
+static void handlePoint(std::vector<Vertex>& rectangleVertices, const Matrix4& transform,
+ float x, float y) {
+ Vertex v;
+ v.x = x;
+ v.y = y;
+ transform.mapPoint(v.x, v.y);
+ rectangleVertices.push_back(v);
+}
+
+static void handlePointNoTransform(std::vector<Vertex>& rectangleVertices, float x, float y) {
+ Vertex v;
+ v.x = x;
+ v.y = y;
+ rectangleVertices.push_back(v);
+}
+
+void OpenGLRenderer::drawRectangleList(const RectangleList& rectangleList) {
+ int count = rectangleList.getTransformedRectanglesCount();
+ std::vector<Vertex> rectangleVertices(count * 4);
+ Rect scissorBox = rectangleList.calculateBounds();
+ scissorBox.snapToPixelBoundaries();
+ for (int i = 0; i < count; ++i) {
+ const TransformedRectangle& tr(rectangleList.getTransformedRectangle(i));
+ const Matrix4& transform = tr.getTransform();
+ Rect bounds = tr.getBounds();
+ if (transform.rectToRect()) {
+ transform.mapRect(bounds);
+ if (!bounds.intersect(scissorBox)) {
+ bounds.setEmpty();
+ } else {
+ handlePointNoTransform(rectangleVertices, bounds.left, bounds.top);
+ handlePointNoTransform(rectangleVertices, bounds.right, bounds.top);
+ handlePointNoTransform(rectangleVertices, bounds.left, bounds.bottom);
+ handlePointNoTransform(rectangleVertices, bounds.right, bounds.bottom);
+ }
+ } else {
+ handlePoint(rectangleVertices, transform, bounds.left, bounds.top);
+ handlePoint(rectangleVertices, transform, bounds.right, bounds.top);
+ handlePoint(rectangleVertices, transform, bounds.left, bounds.bottom);
+ handlePoint(rectangleVertices, transform, bounds.right, bounds.bottom);
+ }
+ }
+
+ mCaches.setScissor(scissorBox.left, getViewportHeight() - scissorBox.bottom,
+ scissorBox.getWidth(), scissorBox.getHeight());
+
+ const SkPaint* paint = nullptr;
+ setupDraw();
+ setupDrawNoTexture();
+ setupDrawColor(0, 0xff * currentSnapshot()->alpha);
+ setupDrawShader(getShader(paint));
+ setupDrawColorFilter(getColorFilter(paint));
+ setupDrawBlending(paint);
+ setupDrawProgram();
+ setupDrawDirtyRegionsDisabled();
+ setupDrawModelView(kModelViewMode_Translate, false,
+ 0.0f, 0.0f, 0.0f, 0.0f, true);
+ setupDrawColorUniforms(getShader(paint));
+ setupDrawShaderUniforms(getShader(paint));
+ setupDrawColorFilterUniforms(getColorFilter(paint));
+
+ issueIndexedQuadDraw(&rectangleVertices[0], rectangleVertices.size() / 4);
+}
+
void OpenGLRenderer::setStencilFromClip() {
if (!mCaches.debugOverdraw) {
- if (!currentSnapshot()->clipRegion->isEmpty()) {
+ if (!currentSnapshot()->clipIsSimple()) {
+ int incrementThreshold;
EVENT_LOGD("setStencilFromClip - enabling");
// NOTE: The order here is important, we must set dirtyClip to false
@@ -1459,15 +1524,25 @@
ensureStencilBuffer();
- mCaches.stencil.enableWrite();
+ const ClipArea& clipArea = currentSnapshot()->getClipArea();
- // Clear and update the stencil, but first make sure we restrict drawing
+ bool isRectangleList = clipArea.isRectangleList();
+ if (isRectangleList) {
+ incrementThreshold = clipArea.getRectangleList().getTransformedRectanglesCount();
+ } else {
+ incrementThreshold = 0;
+ }
+
+ mCaches.stencil.enableWrite(incrementThreshold);
+
+ // Clean and update the stencil, but first make sure we restrict drawing
// to the region's bounds
bool resetScissor = mCaches.enableScissor();
if (resetScissor) {
// The scissor was not set so we now need to update it
setScissorFromClip();
}
+
mCaches.stencil.clear();
// stash and disable the outline clip state, since stencil doesn't account for outline
@@ -1478,24 +1553,30 @@
paint.setColor(SK_ColorBLACK);
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
- // NOTE: We could use the region contour path to generate a smaller mesh
- // Since we are using the stencil we could use the red book path
- // drawing technique. It might increase bandwidth usage though.
+ if (isRectangleList) {
+ drawRectangleList(clipArea.getRectangleList());
+ } else {
+ // NOTE: We could use the region contour path to generate a smaller mesh
+ // Since we are using the stencil we could use the red book path
+ // drawing technique. It might increase bandwidth usage though.
- // The last parameter is important: we are not drawing in the color buffer
- // so we don't want to dirty the current layer, if any
- drawRegionRects(*(currentSnapshot()->clipRegion), paint, false);
+ // The last parameter is important: we are not drawing in the color buffer
+ // so we don't want to dirty the current layer, if any
+ drawRegionRects(clipArea.getClipRegion(), paint, false);
+ }
if (resetScissor) mCaches.disableScissor();
mSkipOutlineClip = storedSkipOutlineClip;
- mCaches.stencil.enableTest();
+ mCaches.stencil.enableTest(incrementThreshold);
// Draw the region used to generate the stencil if the appropriate debug
// mode is enabled
- if (mCaches.debugStencilClip == Caches::kStencilShowRegion) {
+ // TODO: Implement for rectangle list clip areas
+ if (mCaches.debugStencilClip == Caches::kStencilShowRegion &&
+ !clipArea.isRectangleList()) {
paint.setColor(0x7f0000ff);
paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
- drawRegionRects(*(currentSnapshot()->clipRegion), paint);
+ drawRegionRects(currentSnapshot()->getClipRegion(), paint);
}
} else {
EVENT_LOGD("setStencilFromClip - disabling");
@@ -1913,7 +1994,7 @@
// Don't avoid overdraw when visualizing, since that makes it harder to
// debug where it's coming from, and when the problem occurs.
bool avoidOverdraw = !mCaches.debugOverdraw;
- DeferredDisplayList deferredList(*mState.currentClipRect(), avoidOverdraw);
+ DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
DeferStateStruct deferStruct(deferredList, *this, replayFlags);
renderNode->defer(deferStruct, 0);
@@ -2450,7 +2531,7 @@
// No need to check against the clip, we fill the clip region
if (mState.currentlyIgnored()) return;
- Rect clip(*mState.currentClipRect());
+ Rect clip(mState.currentClipRect());
clip.snapToPixelBoundaries();
SkPaint paint;
@@ -2704,13 +2785,13 @@
}
fontRenderer.setTextureFiltering(linearFilter);
- const Rect* clip = pureTranslate ? writableSnapshot()->clipRect : &writableSnapshot()->getLocalClip();
+ const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip());
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
const bool hasActiveLayer = hasLayer();
TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
- if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
+ if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y,
positions, hasActiveLayer ? &bounds : nullptr, &functor)) {
if (hasActiveLayer) {
if (!pureTranslate) {
@@ -2864,7 +2945,7 @@
fontRenderer.setTextureFiltering(linearFilter);
// TODO: Implement better clipping for scaled/rotated text
- const Rect* clip = !pureTranslate ? nullptr : mState.currentClipRect();
+ const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect();
Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 9d9b3d2..f613183 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -223,9 +223,6 @@
return mCaches;
}
- // simple rect clip
- bool isCurrentClipSimple() { return mState.isCurrentClipSimple(); }
-
int getViewportWidth() { return mState.getViewportWidth(); }
int getViewportHeight() { return mState.getViewportHeight(); }
@@ -433,6 +430,13 @@
*/
void attachStencilBufferToLayer(Layer* layer);
+ /**
+ * Draw a rectangle list. Currently only used for the the stencil buffer so that the stencil
+ * will have a value of 'n' in every unclipped pixel, where 'n' is the number of rectangles
+ * in the list.
+ */
+ void drawRectangleList(const RectangleList& rectangleList);
+
bool quickRejectSetupScissor(float left, float top, float right, float bottom,
const SkPaint* paint = nullptr);
bool quickRejectSetupScissor(const Rect& bounds, const SkPaint* paint = nullptr) {
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index b046b6f..1716cf0 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -111,6 +111,10 @@
set(r.left, r.top, r.right, r.bottom);
}
+ inline void set(const SkIRect& r) {
+ set(r.left(), r.top(), r.right(), r.bottom());
+ }
+
inline float getWidth() const {
return right - left;
}
@@ -248,6 +252,14 @@
bottom = fmaxf(bottom, y);
}
+ SkRect toSkRect() const {
+ return SkRect::MakeLTRB(left, top, right, bottom);
+ }
+
+ SkIRect toSkIRect() const {
+ return SkIRect::MakeLTRB(left, top, right, bottom);
+ }
+
void dump(const char* label = nullptr) const {
ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom);
}
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index d4ca99e..49fb4ba 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -35,11 +35,10 @@
, invisible(false)
, empty(false)
, alpha(1.0f)
- , roundRectClipState(nullptr) {
+ , roundRectClipState(nullptr)
+ , mClipArea(&mClipAreaRoot) {
transform = &mTransformRoot;
- clipRect = &mClipRectRoot;
region = nullptr;
- clipRegion = &mClipRegionRoot;
}
/**
@@ -55,6 +54,7 @@
, empty(false)
, alpha(s->alpha)
, roundRectClipState(s->roundRectClipState)
+ , mClipArea(nullptr)
, mViewportData(s->mViewportData)
, mRelativeLightCenter(s->mRelativeLightCenter) {
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
@@ -65,15 +65,10 @@
}
if (saveFlags & SkCanvas::kClip_SaveFlag) {
- mClipRectRoot.set(*s->clipRect);
- clipRect = &mClipRectRoot;
- if (!s->clipRegion->isEmpty()) {
- mClipRegionRoot.op(*s->clipRegion, SkRegion::kUnion_Op);
- }
- clipRegion = &mClipRegionRoot;
+ mClipAreaRoot = s->getClipArea();
+ mClipArea = &mClipAreaRoot;
} else {
- clipRect = s->clipRect;
- clipRegion = s->clipRegion;
+ mClipArea = s->mClipArea;
}
if (s->flags & Snapshot::kFlagFboTarget) {
@@ -88,88 +83,23 @@
// Clipping
///////////////////////////////////////////////////////////////////////////////
-void Snapshot::ensureClipRegion() {
- if (clipRegion->isEmpty()) {
- clipRegion->setRect(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
- }
-}
-
-void Snapshot::copyClipRectFromRegion() {
- if (!clipRegion->isEmpty()) {
- const SkIRect& bounds = clipRegion->getBounds();
- clipRect->set(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-
- if (clipRegion->isRect()) {
- clipRegion->setEmpty();
- }
- } else {
- clipRect->setEmpty();
- }
-}
-
-bool Snapshot::clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op) {
- SkIRect tmp;
- tmp.set(left, top, right, bottom);
- clipRegion->op(tmp, op);
- copyClipRectFromRegion();
- return true;
-}
-
bool Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) {
- ensureClipRegion();
- clipRegion->op(region, op);
- copyClipRectFromRegion();
flags |= Snapshot::kFlagClipSet;
- return true;
+ return mClipArea->clipRegion(region, op);
}
bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) {
- Rect r(left, top, right, bottom);
- transform->mapRect(r);
- return clipTransformed(r, op);
+ flags |= Snapshot::kFlagClipSet;
+ return mClipArea->clipRectWithTransform(left, top, right, bottom, transform, op);
}
-bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) {
- bool clipped = false;
-
- switch (op) {
- case SkRegion::kIntersect_Op: {
- if (CC_UNLIKELY(!clipRegion->isEmpty())) {
- ensureClipRegion();
- clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kIntersect_Op);
- } else {
- clipped = clipRect->intersect(r);
- if (!clipped) {
- clipRect->setEmpty();
- clipped = true;
- }
- }
- break;
- }
- case SkRegion::kReplace_Op: {
- setClip(r.left, r.top, r.right, r.bottom);
- clipped = true;
- break;
- }
- default: {
- ensureClipRegion();
- clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, op);
- break;
- }
- }
-
- if (clipped) {
- flags |= Snapshot::kFlagClipSet;
- }
-
- return clipped;
+bool Snapshot::clipPath(const SkPath& path, SkRegion::Op op) {
+ flags |= Snapshot::kFlagClipSet;
+ return mClipArea->clipPathWithTransform(path, transform, op);
}
void Snapshot::setClip(float left, float top, float right, float bottom) {
- clipRect->set(left, top, right, bottom);
- if (!clipRegion->isEmpty()) {
- clipRegion->setEmpty();
- }
+ mClipArea->setClip(left, top, right, bottom);
flags |= Snapshot::kFlagClipSet;
}
@@ -181,7 +111,7 @@
mat4 inverse;
inverse.loadInverse(*transform);
- mLocalClip.set(*clipRect);
+ mLocalClip.set(mClipArea->getClipRect());
inverse.mapRect(mLocalClip);
return mLocalClip;
@@ -191,8 +121,7 @@
// TODO: This is incorrect, when we start rendering into a new layer,
// we may have to modify the previous snapshot's clip rect and clip
// region if the previous restore() call did not restore the clip
- clipRect = &mClipRectRoot;
- clipRegion = &mClipRegionRoot;
+ mClipArea = &mClipAreaRoot;
setClip(left, top, right, bottom);
}
@@ -219,7 +148,7 @@
void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds,
float radius, bool highPriority) {
if (bounds.isEmpty()) {
- clipRect->setEmpty();
+ mClipArea->setEmpty();
return;
}
@@ -272,9 +201,10 @@
void Snapshot::dump() const {
ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
- this, flags, previous.get(), getViewportHeight(), isIgnored(), clipRegion && !clipRegion->isEmpty());
+ this, flags, previous.get(), getViewportHeight(), isIgnored(), !mClipArea->isSimple());
+ const Rect& clipRect(mClipArea->getClipRect());
ALOGD(" ClipRect (at %p) %.1f %.1f %.1f %.1f",
- clipRect, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
+ clipRect, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
ALOGD(" Transform (at %p):", transform);
transform->dump();
}
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 549de9b..4d704ab 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -26,6 +26,7 @@
#include <SkRegion.h>
+#include "ClipArea.h"
#include "Layer.h"
#include "Matrix.h"
#include "Outline.h"
@@ -129,6 +130,11 @@
bool clipRegionTransformed(const SkRegion& region, SkRegion::Op op);
/**
+ * Modifies the current clip with the specified path and operation.
+ */
+ bool clipPath(const SkPath& path, SkRegion::Op op);
+
+ /**
* Sets the current clip.
*/
void setClip(float left, float top, float right, float bottom);
@@ -142,7 +148,16 @@
/**
* Returns the current clip in render target coordinates.
*/
- const Rect& getRenderTargetClip() { return *clipRect; }
+ const Rect& getRenderTargetClip() { return mClipArea->getClipRect(); }
+
+ /*
+ * Accessor functions so that the clip area can stay private
+ */
+ bool clipIsEmpty() const { return mClipArea->isEmpty(); }
+ const Rect& getClipRect() const { return mClipArea->getClipRect(); }
+ const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
+ bool clipIsSimple() const { return mClipArea->isSimple(); }
+ const ClipArea& getClipArea() const { return *mClipArea; }
/**
* Resets the clip to the specified rect.
@@ -156,6 +171,7 @@
void initializeViewport(int width, int height) {
mViewportData.initialize(width, height);
+ mClipAreaRoot.setViewportDimensions(width, height);
}
int getViewportWidth() const { return mViewportData.mWidth; }
@@ -175,7 +191,7 @@
/**
* Indicates whether this snapshot should be ignored. A snapshot
- * is typicalled ignored if its layer is invisible or empty.
+ * is typically ignored if its layer is invisible or empty.
*/
bool isIgnored() const;
@@ -230,24 +246,6 @@
mat4* transform;
/**
- * Current clip rect. The clip is stored in canvas-space coordinates,
- * (screen-space coordinates in the regular case.)
- *
- * This is a reference to a rect owned by this snapshot or another
- * snapshot. This pointer must not be freed. See ::mClipRectRoot.
- */
- Rect* clipRect;
-
- /**
- * Current clip region. The clip is stored in canvas-space coordinates,
- * (screen-space coordinates in the regular case.)
- *
- * This is a reference to a region owned by this snapshot or another
- * snapshot. This pointer must not be freed. See ::mClipRegionRoot.
- */
- SkRegion* clipRegion;
-
- /**
* The ancestor layer's dirty region.
*
* This is a reference to a region owned by a layer. This pointer must
@@ -260,7 +258,7 @@
* has translucent rendering in a non-overlapping View. This value will be used by
* the renderer to set the alpha in the current color being used for ensuing drawing
* operations. The value is inherited by child snapshots because the same value should
- * be applied to descendents of the current DisplayList (for example, a TextView contains
+ * be applied to descendants of the current DisplayList (for example, a TextView contains
* the base alpha value which should be applied to the child DisplayLists used for drawing
* the actual text).
*/
@@ -298,16 +296,12 @@
mat4 mOrthoMatrix;
};
- void ensureClipRegion();
- void copyClipRectFromRegion();
-
- bool clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op);
-
mat4 mTransformRoot;
- Rect mClipRectRoot;
- Rect mLocalClip; // don't use directly, call getLocalClip() which initializes this
- SkRegion mClipRegionRoot;
+ ClipArea mClipAreaRoot;
+ ClipArea* mClipArea;
+ Rect mLocalClip;
+
ViewportData mViewportData;
Vector3 mRelativeLightCenter;
diff --git a/libs/hwui/Stencil.cpp b/libs/hwui/Stencil.cpp
index 8ce57db..f56a02e 100644
--- a/libs/hwui/Stencil.cpp
+++ b/libs/hwui/Stencil.cpp
@@ -32,7 +32,8 @@
#define STENCIL_MASK_VALUE 0x1
#endif
-Stencil::Stencil(): mState(kDisabled) {
+Stencil::Stencil()
+ : mState(kDisabled) {
}
uint8_t Stencil::getStencilSize() {
@@ -56,24 +57,36 @@
glClear(GL_STENCIL_BUFFER_BIT);
}
-void Stencil::enableTest() {
+void Stencil::enableTest(int incrementThreshold) {
if (mState != kTest) {
enable();
- glStencilFunc(GL_EQUAL, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
+ if (incrementThreshold > 0) {
+ glStencilFunc(GL_EQUAL, incrementThreshold, 0xff);
+ } else {
+ glStencilFunc(GL_EQUAL, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
+ }
// We only want to test, let's keep everything
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glStencilMask(0);
mState = kTest;
}
}
-void Stencil::enableWrite() {
+void Stencil::enableWrite(int incrementThreshold) {
if (mState != kWrite) {
enable();
- glStencilFunc(GL_ALWAYS, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
- // The test always passes so the first two values are meaningless
- glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+ if (incrementThreshold > 0) {
+ glStencilFunc(GL_ALWAYS, 1, 0xff);
+ // The test always passes so the first two values are meaningless
+ glStencilOp(GL_INCR, GL_INCR, GL_INCR);
+ } else {
+ glStencilFunc(GL_ALWAYS, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
+ // The test always passes so the first two values are meaningless
+ glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+ }
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ glStencilMask(0xff);
mState = kWrite;
}
}
diff --git a/libs/hwui/Stencil.h b/libs/hwui/Stencil.h
index c5e5186..20bb955 100644
--- a/libs/hwui/Stencil.h
+++ b/libs/hwui/Stencil.h
@@ -53,17 +53,21 @@
void clear();
/**
- * Enables stencil test. When the stencil test is enabled the stencil
- * buffer is not written into.
+ * Enables stencil test. When the stencil test is enabled the stencil buffer is not written
+ * into. An increment threshold of zero causes the stencil to use a constant reference value
+ * and GL_EQUAL for the test. A non-zero increment threshold causes the stencil to use that
+ * value as the reference value and GL_EQUAL for the test.
*/
- void enableTest();
+ void enableTest(int incrementThreshold);
/**
* Enables stencil write. When stencil write is enabled, the stencil
* test always succeeds and the value 0x1 is written in the stencil
- * buffer for each fragment.
+ * buffer for each fragment. An increment threshold of zero causes the stencil to use a constant
+ * reference value and GL_EQUAL for the test. A non-zero increment threshold causes the stencil
+ * to use that value as the reference value and GL_EQUAL for the test.
*/
- void enableWrite();
+ void enableWrite(int incrementThreshold);
/**
* The test passes only when equal to the specified value.
diff --git a/libs/hwui/tests/Android.mk b/libs/hwui/tests/Android.mk
index 2fff07d..a69f3fb 100644
--- a/libs/hwui/tests/Android.mk
+++ b/libs/hwui/tests/Android.mk
@@ -32,3 +32,16 @@
tests/main.cpp
include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.common.mk
+LOCAL_MODULE := hwui_unit_tests
+LOCAL_MODULE_TAGS := tests
+
+include $(LOCAL_PATH)/Android.common.mk
+
+LOCAL_SRC_FILES += \
+ tests/ClipAreaTests.cpp \
+
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/hwui/tests/ClipAreaTests.cpp b/libs/hwui/tests/ClipAreaTests.cpp
new file mode 100644
index 0000000..166d5b6
--- /dev/null
+++ b/libs/hwui/tests/ClipAreaTests.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <SkPath.h>
+#include <SkRegion.h>
+
+#include "ClipArea.h"
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "utils/LinearAllocator.h"
+
+namespace android {
+namespace uirenderer {
+
+static Rect kViewportBounds(0, 0, 2048, 2048);
+
+static ClipArea createClipArea() {
+ ClipArea area;
+ area.setViewportDimensions(kViewportBounds.getWidth(), kViewportBounds.getHeight());
+ return area;
+}
+
+TEST(TransformedRectangle, basics) {
+ Rect r(0, 0, 100, 100);
+ Matrix4 minus90;
+ minus90.loadRotate(-90);
+ minus90.mapRect(r);
+ Rect r2(20, 40, 120, 60);
+
+ Matrix4 m90;
+ m90.loadRotate(90);
+ TransformedRectangle tr(r, m90);
+ EXPECT_TRUE(tr.canSimplyIntersectWith(tr));
+
+ Matrix4 m0;
+ TransformedRectangle tr0(r2, m0);
+ EXPECT_FALSE(tr.canSimplyIntersectWith(tr0));
+
+ Matrix4 m45;
+ m45.loadRotate(45);
+ TransformedRectangle tr2(r, m45);
+ EXPECT_FALSE(tr2.canSimplyIntersectWith(tr));
+}
+
+TEST(RectangleList, basics) {
+ RectangleList list;
+ EXPECT_TRUE(list.isEmpty());
+
+ Rect r(0, 0, 100, 100);
+ Matrix4 m45;
+ m45.loadRotate(45);
+ list.set(r, m45);
+ EXPECT_FALSE(list.isEmpty());
+
+ Rect r2(20, 20, 200, 200);
+ list.intersectWith(r2, m45);
+ EXPECT_FALSE(list.isEmpty());
+ EXPECT_EQ(1, list.getTransformedRectanglesCount());
+
+ Rect r3(20, 20, 200, 200);
+ Matrix4 m30;
+ m30.loadRotate(30);
+ list.intersectWith(r2, m30);
+ EXPECT_FALSE(list.isEmpty());
+ EXPECT_EQ(2, list.getTransformedRectanglesCount());
+
+ SkRegion clip;
+ clip.setRect(0, 0, 2000, 2000);
+ SkRegion rgn(list.convertToRegion(clip));
+ EXPECT_FALSE(rgn.isEmpty());
+}
+
+TEST(ClipArea, basics) {
+ ClipArea area(createClipArea());
+ EXPECT_FALSE(area.isEmpty());
+}
+
+TEST(ClipArea, paths) {
+ ClipArea area(createClipArea());
+ Matrix4 transform;
+ transform.loadIdentity();
+ SkPath path;
+ SkScalar r = 100;
+ path.addCircle(r, r, r);
+ area.clipPathWithTransform(path, &transform, SkRegion::kIntersect_Op);
+ EXPECT_FALSE(area.isEmpty());
+ EXPECT_FALSE(area.isSimple());
+ EXPECT_FALSE(area.isRectangleList());
+ Rect clipRect(area.getClipRect());
+ clipRect.dump("clipRect");
+ Rect expected(0, 0, r * 2, r * 2);
+ expected.dump("expected");
+ EXPECT_EQ(expected, clipRect);
+ SkRegion clipRegion(area.getClipRegion());
+ auto skRect(clipRegion.getBounds());
+ Rect regionBounds;
+ regionBounds.set(skRect);
+ EXPECT_EQ(expected, regionBounds);
+}
+}
+}
diff --git a/libs/hwui/utils/Pair.h b/libs/hwui/utils/Pair.h
index 172606a..0db3aa3 100644
--- a/libs/hwui/utils/Pair.h
+++ b/libs/hwui/utils/Pair.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_HWUI_PAIR_H
#define ANDROID_HWUI_PAIR_H
+#include <utils/TypeHelpers.h>
+
namespace android {
namespace uirenderer {