diff options
author | Rob Tsuk <robtsuk@google.com> | 2015-01-15 18:21:33 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-01-15 18:21:35 +0000 |
commit | f28f5cec5644ed175c8ad7a59b5d20b33ee89bc8 (patch) | |
tree | 131a045645ffd3596cfe47583b804a8fb3165629 /libs/hwui | |
parent | 45a669e5e9aba0feeca4f768a09299a2f88a2d3f (diff) | |
parent | 487a92caef2eb90a62e8f8d7a6fe6315f1c1d8d8 (diff) | |
download | frameworks_base-f28f5cec5644ed175c8ad7a59b5d20b33ee89bc8.zip frameworks_base-f28f5cec5644ed175c8ad7a59b5d20b33ee89bc8.tar.gz frameworks_base-f28f5cec5644ed175c8ad7a59b5d20b33ee89bc8.tar.bz2 |
Merge "Clipping performance improvements"
Diffstat (limited to 'libs/hwui')
-rw-r--r-- | libs/hwui/Android.common.mk | 1 | ||||
-rw-r--r-- | libs/hwui/CanvasState.cpp | 43 | ||||
-rw-r--r-- | libs/hwui/CanvasState.h | 6 | ||||
-rw-r--r-- | libs/hwui/ClipArea.cpp | 368 | ||||
-rw-r--r-- | libs/hwui/ClipArea.h | 175 | ||||
-rw-r--r--[-rwxr-xr-x] | libs/hwui/OpenGLRenderer.cpp | 141 | ||||
-rwxr-xr-x | libs/hwui/OpenGLRenderer.h | 10 | ||||
-rw-r--r-- | libs/hwui/Rect.h | 12 | ||||
-rw-r--r-- | libs/hwui/Snapshot.cpp | 108 | ||||
-rw-r--r-- | libs/hwui/Snapshot.h | 52 | ||||
-rw-r--r-- | libs/hwui/Stencil.cpp | 27 | ||||
-rw-r--r-- | libs/hwui/Stencil.h | 14 | ||||
-rw-r--r-- | libs/hwui/tests/Android.mk | 13 | ||||
-rw-r--r-- | libs/hwui/tests/ClipAreaTests.cpp | 116 | ||||
-rw-r--r-- | libs/hwui/utils/Pair.h | 2 |
15 files changed, 884 insertions, 204 deletions
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 @@ LOCAL_SRC_FILES := \ 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 @@ 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); + 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 @@ 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; 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 @@ 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(); } 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 index d1e51e1..82f6ddd 100755..100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -354,7 +354,7 @@ void OpenGLRenderer::resumeAfterLayer() { 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 @@ void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool 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::dirtyLayer(const float left, const float top, } 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 @@ void OpenGLRenderer::clearLayerRegions() { /////////////////////////////////////////////////////////////////////////////// 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 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef // 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::setupMergedMultiDraw(const Rect* clipRect) { /////////////////////////////////////////////////////////////////////////////// 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 @@ void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) { } } +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 @@ void OpenGLRenderer::setStencilFromClip() { 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 @@ void OpenGLRenderer::setStencilFromClip() { 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 @@ void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t // 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); @@ -2432,7 +2513,7 @@ void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) { // 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; @@ -2686,13 +2767,13 @@ void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count, } 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) { @@ -2846,7 +2927,7 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float 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 75e79d6..76ad6ce 100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -222,9 +222,6 @@ public: return mCaches; } - // simple rect clip - bool isCurrentClipSimple() { return mState.isCurrentClipSimple(); } - int getViewportWidth() { return mState.getViewportWidth(); } int getViewportHeight() { return mState.getViewportHeight(); } @@ -432,6 +429,13 @@ protected: */ 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 @@ public: 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 @@ public: 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 @@ Snapshot::Snapshot() , 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 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) , 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 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) } 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 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) // 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 @@ const Rect& Snapshot::getLocalClip() { mat4 inverse; inverse.loadInverse(*transform); - mLocalClip.set(*clipRect); + mLocalClip.set(mClipArea->getClipRect()); inverse.mapRect(mLocalClip); return mLocalClip; @@ -191,8 +121,7 @@ void Snapshot::resetClip(float left, float top, float right, float bottom) { // 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::resetTransform(float x, float y, float z) { void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds, float radius, bool highPriority) { if (bounds.isEmpty()) { - clipRect->setEmpty(); + mClipArea->setEmpty(); return; } @@ -272,9 +201,10 @@ bool Snapshot::isIgnored() const { 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 @@ public: 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 @@ public: /** * 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 @@ public: void initializeViewport(int width, int height) { mViewportData.initialize(width, height); + mClipAreaRoot.setViewportDimensions(width, height); } int getViewportWidth() const { return mViewportData.mWidth; } @@ -175,7 +191,7 @@ public: /** * 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 @@ public: 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 @@ public: * 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 @@ private: 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 @@ namespace uirenderer { #define STENCIL_MASK_VALUE 0x1 #endif -Stencil::Stencil(): mState(kDisabled) { +Stencil::Stencil() + : mState(kDisabled) { } uint8_t Stencil::getStencilSize() { @@ -56,24 +57,36 @@ void Stencil::clear() { 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 @@ public: 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 @@ LOCAL_SRC_FILES += \ 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 { |