Implement clipRect with a transform, clipRegion & clipPath
Bug #7146141
When non-rectangular clipping occurs in a layer the render buffer
used as the stencil buffer is not cached. If this happens on a
View's hardware layer the render buffer will live for as long
as the layer is bound to the view. When a stencil buffer is
required because of a call to Canvas.saveLayer() it will be allocated
on every frame. A future change will address this problem.
If "show GPU overdraw" is enabled, non-rectangular clips are not
supported anymore and we fall back to rectangular clips instead.
This is a limitation imposed by OpenGL ES that cannot be worked
around at this time.
This change also improves the Matrix4 implementation to easily
detect when a rect remains a rect after transform.
Change-Id: I0e69fb901792d38bc0c4ca1bf9fdb02d7db415b9
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 628d8a0..ae188be 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -270,9 +270,7 @@
GammaFontRenderer* fontRenderer;
Dither dither;
-#if STENCIL_BUFFER_SIZE
Stencil stencil;
-#endif
// Debug methods
PFNGLINSERTEVENTMARKEREXTPROC eventMark;
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index ee1d391..1d85b70 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -41,6 +41,7 @@
renderer = NULL;
displayList = NULL;
fbo = 0;
+ stencil = 0;
debugDrawUpdate = false;
Caches::getInstance().resourceCache.incrementRefcount(this);
}
@@ -53,9 +54,22 @@
deleteTexture();
}
-void Layer::removeFbo() {
+void Layer::removeFbo(bool flush) {
+ if (stencil) {
+ // TODO: recycle & cache instead of simply deleting
+ GLuint previousFbo;
+ glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+ if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+ if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+ glDeleteRenderbuffers(1, &stencil);
+ stencil = 0;
+ }
+
if (fbo) {
- LayerRenderer::flushLayer(this);
+ if (flush) LayerRenderer::flushLayer(this);
+ // If put fails the cache will delete the FBO
Caches::getInstance().fboCache.put(fbo);
fbo = 0;
}
@@ -75,7 +89,5 @@
}
}
-
-
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 181eb6c..9ef4894 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -48,7 +48,12 @@
Layer(const uint32_t layerWidth, const uint32_t layerHeight);
~Layer();
- void removeFbo();
+ /**
+ * Calling this method will remove (either by recycling or
+ * destroying) the associated FBO, if present, and any render
+ * buffer (stencil for instance.)
+ */
+ void removeFbo(bool flush = true);
/**
* Sets this layer's region to a rectangle. Computes the appropriate
@@ -134,6 +139,14 @@
return fbo;
}
+ inline void setStencilRenderBuffer(GLuint renderBuffer) {
+ this->stencil = renderBuffer;
+ }
+
+ inline GLuint getStencilRenderBuffer() {
+ return stencil;
+ }
+
inline GLuint getTexture() {
return texture.id;
}
@@ -212,10 +225,6 @@
texture.id = 0;
}
- inline void deleteFbo() {
- if (fbo) glDeleteFramebuffers(1, &fbo);
- }
-
inline void allocateTexture(GLenum format, GLenum storage) {
#if DEBUG_LAYERS
ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight());
@@ -275,6 +284,12 @@
GLuint fbo;
/**
+ * Name of the render buffer used as the stencil buffer. If the
+ * name is 0, this layer does not have a stencil buffer.
+ */
+ GLuint stencil;
+
+ /**
* Indicates whether this layer has been used already.
*/
bool empty;
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 3484d41..ba59bb39 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -100,13 +100,21 @@
}
///////////////////////////////////////////////////////////////////////////////
-// Dirty region tracking
+// Layer support
///////////////////////////////////////////////////////////////////////////////
bool LayerRenderer::hasLayer() {
return true;
}
+void LayerRenderer::ensureStencilBuffer() {
+ attachStencilBufferToLayer(mLayer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Dirty region tracking
+///////////////////////////////////////////////////////////////////////////////
+
Region* LayerRenderer::getRegion() {
if (getSnapshot()->flags & Snapshot::kFlagFboTarget) {
return OpenGLRenderer::getRegion();
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index c44abce..7a8bdc5 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -64,6 +64,7 @@
static void flushLayer(Layer* layer);
protected:
+ virtual void ensureStencilBuffer();
virtual bool hasLayer();
virtual Region* getRegion();
virtual GLint getTargetFbo();
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index a924362..79fae2b 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -30,6 +30,16 @@
namespace android {
namespace uirenderer {
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+static const float EPSILON = 0.0000001f;
+
+///////////////////////////////////////////////////////////////////////////////
+// Matrix
+///////////////////////////////////////////////////////////////////////////////
+
void Matrix4::loadIdentity() {
data[kScaleX] = 1.0f;
data[kSkewY] = 0.0f;
@@ -51,44 +61,91 @@
data[kTranslateZ] = 0.0f;
data[kPerspective2] = 1.0f;
- mIsIdentity = true;
- mSimpleMatrix = true;
+ mType = kTypeIdentity | kTypeRectToRect;
+}
+
+static bool isZero(float f) {
+ return fabs(f) <= EPSILON;
+}
+
+uint32_t Matrix4::getType() const {
+ if (mType & kTypeUnknown) {
+ mType = kTypeIdentity;
+
+ if (data[kPerspective0] != 0.0f || data[kPerspective1] != 0.0f ||
+ data[kPerspective2] != 1.0f) {
+ mType |= kTypePerspective;
+ }
+
+ if (data[kTranslateX] != 0.0f || data[kTranslateY] != 0.0f) {
+ mType |= kTypeTranslate;
+ }
+
+ float m00 = data[kScaleX];
+ float m01 = data[kSkewX];
+ float m10 = data[kSkewY];
+ float m11 = data[kScaleY];
+
+ if (m01 != 0.0f || m10 != 0.0f) {
+ mType |= kTypeAffine;
+ }
+
+ if (m00 != 1.0f || m11 != 1.0f) {
+ mType |= kTypeScale;
+ }
+
+ // The following section determines whether the matrix will preserve
+ // rectangles. For instance, a rectangle transformed by a pure
+ // translation matrix will result in a rectangle. A rectangle
+ // transformed by a 45 degrees rotation matrix is not a rectangle.
+ // If the matrix has a perspective component then we already know
+ // it doesn't preserve rectangles.
+ if (!(mType & kTypePerspective)) {
+ if ((isZero(m00) && isZero(m11) && !isZero(m01) && !isZero(m10)) ||
+ (isZero(m01) && isZero(m10) && !isZero(m00) && !isZero(m11))) {
+ mType |= kTypeRectToRect;
+ }
+ }
+ }
+ return mType;
+}
+
+uint32_t Matrix4::getGeometryType() const {
+ return getType() & sGeometryMask;
+}
+
+bool Matrix4::rectToRect() const {
+ return getType() & kTypeRectToRect;
}
bool Matrix4::changesBounds() const {
- return !(data[0] == 1.0f && data[1] == 0.0f && data[2] == 0.0f && data[4] == 0.0f &&
- data[5] == 1.0f && data[6] == 0.0f && data[8] == 0.0f && data[9] == 0.0f &&
- data[10] == 1.0f);
+ return getType() & (kTypeScale | kTypeAffine | kTypePerspective);
}
bool Matrix4::isPureTranslate() const {
- return mSimpleMatrix && data[kScaleX] == 1.0f && data[kScaleY] == 1.0f;
+ return getGeometryType() == kTypeTranslate;
}
bool Matrix4::isSimple() const {
- return mSimpleMatrix;
+ return getGeometryType() <= (kTypeScale | kTypeTranslate);
}
bool Matrix4::isIdentity() const {
- return mIsIdentity;
+ return getGeometryType() == kTypeIdentity;
}
bool Matrix4::isPerspective() const {
- return data[kPerspective0] != 0.0f || data[kPerspective1] != 0.0f ||
- data[kPerspective2] != 1.0f;
+ return getType() & kTypePerspective;
}
void Matrix4::load(const float* v) {
memcpy(data, v, sizeof(data));
- // TODO: Do something smarter here
- mSimpleMatrix = false;
- mIsIdentity = false;
+ mType = kTypeUnknown;
}
void Matrix4::load(const Matrix4& v) {
memcpy(data, v.data, sizeof(data));
- mSimpleMatrix = v.mSimpleMatrix;
- mIsIdentity = v.mIsIdentity;
+ mType = v.getType();
}
void Matrix4::load(const SkMatrix& v) {
@@ -108,8 +165,14 @@
data[kScaleZ] = 1.0f;
- mSimpleMatrix = (v.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask));
- mIsIdentity = v.isIdentity();
+ // NOTE: The flags are compatible between SkMatrix and this class.
+ // However, SkMatrix::getType() does not return the flag
+ // kRectStaysRect. The return value is masked with 0xF
+ // so we need the extra rectStaysRect() check
+ mType = v.getType();
+ if (v.rectStaysRect()) {
+ mType |= kTypeRectToRect;
+ }
}
void Matrix4::copyTo(SkMatrix& v) const {
@@ -158,8 +221,7 @@
data[kPerspective2] = (v.data[kScaleX] * v.data[kScaleY] -
v.data[kSkewX] * v.data[kSkewY]) * scale;
- mSimpleMatrix = v.mSimpleMatrix;
- mIsIdentity = v.mIsIdentity;
+ mType = kTypeUnknown;
}
void Matrix4::copyTo(float* v) const {
@@ -178,7 +240,7 @@
for (int i = 0; i < 16; i++) {
data[i] *= v;
}
- mIsIdentity = false;
+ mType = kTypeUnknown;
}
void Matrix4::loadTranslate(float x, float y, float z) {
@@ -188,7 +250,7 @@
data[kTranslateY] = y;
data[kTranslateZ] = z;
- mIsIdentity = false;
+ mType = kTypeTranslate | kTypeRectToRect;
}
void Matrix4::loadScale(float sx, float sy, float sz) {
@@ -198,7 +260,7 @@
data[kScaleY] = sy;
data[kScaleZ] = sz;
- mIsIdentity = false;
+ mType = kTypeScale | kTypeRectToRect;
}
void Matrix4::loadSkew(float sx, float sy) {
@@ -216,8 +278,23 @@
data[kPerspective1] = 0.0f;
data[kPerspective2] = 1.0f;
- mSimpleMatrix = false;
- mIsIdentity = false;
+ mType = kTypeUnknown;
+}
+
+void Matrix4::loadRotate(float angle) {
+ angle *= float(M_PI / 180.0f);
+ float c = cosf(angle);
+ float s = sinf(angle);
+
+ loadIdentity();
+
+ data[kScaleX] = c;
+ data[kSkewX] = -s;
+
+ data[kSkewY] = s;
+ data[kScaleY] = c;
+
+ mType = kTypeUnknown;
}
void Matrix4::loadRotate(float angle, float x, float y, float z) {
@@ -257,8 +334,7 @@
data[6] = yz * nc + xs;
data[kScaleZ] = z * z * nc + c;
- mSimpleMatrix = false;
- mIsIdentity = false;
+ mType = kTypeUnknown;
}
void Matrix4::loadMultiply(const Matrix4& u, const Matrix4& v) {
@@ -282,8 +358,7 @@
set(i, 3, w);
}
- mSimpleMatrix = u.mSimpleMatrix && v.mSimpleMatrix;
- mIsIdentity = false;
+ mType = kTypeUnknown;
}
void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
@@ -296,13 +371,13 @@
data[kTranslateY] = -(top + bottom) / (top - bottom);
data[kTranslateZ] = -(far + near) / (far - near);
- mIsIdentity = false;
+ mType = kTypeTranslate | kTypeScale | kTypeRectToRect;
}
#define MUL_ADD_STORE(a, b, c) a = (a) * (b) + (c)
void Matrix4::mapPoint(float& x, float& y) const {
- if (mSimpleMatrix) {
+ if (isSimple()) {
MUL_ADD_STORE(x, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(y, data[kScaleY], data[kTranslateY]);
return;
@@ -318,7 +393,7 @@
}
void Matrix4::mapRect(Rect& r) const {
- if (mSimpleMatrix) {
+ if (isSimple()) {
MUL_ADD_STORE(r.left, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(r.right, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(r.top, data[kScaleY], data[kTranslateY]);
@@ -376,7 +451,7 @@
}
void Matrix4::dump() const {
- ALOGD("Matrix4[simple=%d", mSimpleMatrix);
+ ALOGD("Matrix4[simple=%d, type=0x%x", isSimple(), getType());
ALOGD(" %f %f %f %f", data[kScaleX], data[kSkewX], data[8], data[kTranslateX]);
ALOGD(" %f %f %f %f", data[kSkewY], data[kScaleY], data[9], data[kTranslateY]);
ALOGD(" %f %f %f %f", data[2], data[6], data[kScaleZ], data[kTranslateZ]);
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index f86823d..46a5597 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -48,6 +48,21 @@
kPerspective2 = 15
};
+ // NOTE: The flags from kTypeIdentity to kTypePerspective
+ // must be kept in sync with the type flags found
+ // in SkMatrix
+ enum Type {
+ kTypeIdentity = 0,
+ kTypeTranslate = 0x1,
+ kTypeScale = 0x2,
+ kTypeAffine = 0x4,
+ kTypePerspective = 0x8,
+ kTypeRectToRect = 0x10,
+ kTypeUnknown = 0x20,
+ };
+
+ static const int sGeometryMask = 0xf;
+
Matrix4() {
loadIdentity();
}
@@ -75,11 +90,14 @@
void loadTranslate(float x, float y, float z);
void loadScale(float sx, float sy, float sz);
void loadSkew(float sx, float sy);
+ void loadRotate(float angle);
void loadRotate(float angle, float x, float y, float z);
void loadMultiply(const Matrix4& u, const Matrix4& v);
void loadOrtho(float left, float right, float bottom, float top, float near, float far);
+ uint32_t getType() const;
+
void multiply(const Matrix4& v) {
Matrix4 u;
u.loadMultiply(*this, v);
@@ -112,10 +130,14 @@
multiply(u);
}
- bool isPureTranslate() const;
+ /**
+ * If the matrix is identity or translate and/or scale.
+ */
bool isSimple() const;
+ bool isPureTranslate() const;
bool isIdentity() const;
bool isPerspective() const;
+ bool rectToRect() const;
bool changesBounds() const;
@@ -131,8 +153,7 @@
void dump() const;
private:
- bool mSimpleMatrix;
- bool mIsIdentity;
+ mutable uint32_t mType;
inline float get(int i, int j) const {
return data[i * 4 + j];
@@ -141,6 +162,9 @@
inline void set(int i, int j, float v) {
data[i * 4 + j] = v;
}
+
+ uint32_t getGeometryType() const;
+
}; // class Matrix4
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index bb1edbb..be34b40 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -837,6 +837,8 @@
return;
}
+ Layer* layer = current->layer;
+ const Rect& rect = layer->layer;
const bool fboLayer = current->flags & Snapshot::kFlagIsFboLayer;
if (fboLayer) {
@@ -844,6 +846,9 @@
// Detach the texture from the FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+
+ layer->removeFbo(false);
+
// Unbind current FBO and restore previous one
glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
debugOverdraw(true, false);
@@ -851,9 +856,6 @@
startTiling(previous);
}
- Layer* layer = current->layer;
- const Rect& rect = layer->layer;
-
if (!fboLayer && layer->getAlpha() < 255) {
drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
layer->getAlpha() << 24, SkXfermode::kDstIn_Mode, true);
@@ -881,17 +883,6 @@
composeLayerRect(layer, rect, true);
}
- if (fboLayer) {
- // Note: No need to use glDiscardFramebufferEXT() since we never
- // create/compose layers that are not on screen with this
- // code path
- // See LayerRenderer::destroyLayer(Layer*)
-
- // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
- mCaches.fboCache.put(current->fbo);
- layer->setFbo(0);
- }
-
dirtyClip();
// Failing to add the layer to the cache should happen only if the layer is too large
@@ -1001,10 +992,14 @@
const float texY = 1.0f / float(layer->getHeight());
const float height = rect.getHeight();
+ setupDraw();
+
+ // We must get (and therefore bind) the region mesh buffer
+ // after we setup drawing in case we need to mess with the
+ // stencil buffer in setupDraw()
TextureVertex* mesh = mCaches.getRegionMesh();
GLsizei numQuads = 0;
- setupDraw();
setupDrawWithTexture();
setupDrawColor(alpha, alpha, alpha, alpha);
setupDrawColorFilter();
@@ -1089,6 +1084,25 @@
#endif
}
+void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color,
+ SkXfermode::Mode mode, bool dirty) {
+ int count = 0;
+ Vector<float> rects;
+
+ SkRegion::Iterator it(region);
+ while (!it.done()) {
+ const SkIRect& r = it.rect();
+ rects.push(r.fLeft);
+ rects.push(r.fTop);
+ rects.push(r.fRight);
+ rects.push(r.fBottom);
+ count++;
+ it.next();
+ }
+
+ drawColorRects(rects.array(), count, color, mode, true, dirty);
+}
+
void OpenGLRenderer::dirtyLayer(const float left, const float top,
const float right, const float bottom, const mat4 transform) {
if (hasLayer()) {
@@ -1219,6 +1233,65 @@
}
}
+void OpenGLRenderer::ensureStencilBuffer() {
+ // Thanks to the mismatch between EGL and OpenGL ES FBO we
+ // cannot attach a stencil buffer to fbo0 dynamically. Let's
+ // just hope we have one when hasLayer() returns false.
+ if (hasLayer()) {
+ attachStencilBufferToLayer(mSnapshot->layer);
+ }
+}
+
+void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) {
+ // The layer's FBO is already bound when we reach this stage
+ if (!layer->getStencilRenderBuffer()) {
+ // TODO: See Layer::removeFbo(). The stencil renderbuffer should be cached
+ GLuint buffer;
+ glGenRenderbuffers(1, &buffer);
+ glBindRenderbuffer(GL_RENDERBUFFER, buffer);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8,
+ layer->getWidth(), layer->getHeight());
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
+ layer->setStencilRenderBuffer(buffer);
+ }
+}
+
+void OpenGLRenderer::setStencilFromClip() {
+ if (!mCaches.debugOverdraw) {
+ if (!mSnapshot->clipRegion->isEmpty()) {
+ // NOTE: The order here is important, we must set dirtyClip to false
+ // before any draw call to avoid calling back into this method
+ mDirtyClip = false;
+
+ ensureStencilBuffer();
+
+ mCaches.stencil.enableWrite();
+
+ // Clear 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();
+ if (resetScissor) mCaches.disableScissor();
+
+ // 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(*mSnapshot->clipRegion, 0xff000000, SkXfermode::kSrc_Mode, false);
+
+ mCaches.stencil.enableTest();
+ } else {
+ mCaches.stencil.disable();
+ }
+ }
+}
+
const Rect& OpenGLRenderer::getClipBounds() {
return mSnapshot->getLocalClip();
}
@@ -1284,40 +1357,60 @@
return rejected;
}
+void OpenGLRenderer::debugClip() {
+#if DEBUG_CLIP_REGIONS
+ if (!isDeferred() && !mSnapshot->clipRegion->isEmpty()) {
+ drawRegionRects(*mSnapshot->clipRegion, 0x7f00ff00, SkXfermode::kSrcOver_Mode);
+ }
+#endif
+}
+
bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
- bool clipped = mSnapshot->clip(left, top, right, bottom, op);
+ if (CC_LIKELY(mSnapshot->transform->rectToRect())) {
+ bool clipped = mSnapshot->clip(left, top, right, bottom, op);
+ if (clipped) {
+ dirtyClip();
+ }
+ return !mSnapshot->clipRect->isEmpty();
+ }
+
+ SkPath path;
+ path.addRect(left, top, right, bottom);
+
+ return clipPath(&path, op);
+}
+
+bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) {
+ SkMatrix transform;
+ mSnapshot->transform->copyTo(transform);
+
+ SkPath transformed;
+ path->transform(transform, &transformed);
+
+ SkRegion clip;
+ if (!mSnapshot->clipRegion->isEmpty()) {
+ clip.setRegion(*mSnapshot->clipRegion);
+ } else {
+ Rect* bounds = mSnapshot->clipRect;
+ clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
+ }
+
+ SkRegion region;
+ region.setPath(transformed, clip);
+
+ bool clipped = mSnapshot->clipRegionTransformed(region, op);
if (clipped) {
dirtyClip();
-#if DEBUG_CLIP_REGIONS
- if (!isDeferred() && mSnapshot->clipRegion && !mSnapshot->clipRegion->isRect()) {
- int count = 0;
- Vector<float> rects;
- SkRegion::Iterator it(*mSnapshot->clipRegion);
- while (!it.done()) {
- const SkIRect& r = it.rect();
- rects.push(r.fLeft);
- rects.push(r.fTop);
- rects.push(r.fRight);
- rects.push(r.fBottom);
- count++;
- it.next();
- }
-
- drawColorRects(rects.array(), count, 0x7f00ff00, SkXfermode::kSrcOver_Mode, true);
- }
-#endif
}
return !mSnapshot->clipRect->isEmpty();
}
-bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) {
- const SkRect& bounds = path->getBounds();
- return clipRect(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, op);
-}
-
bool OpenGLRenderer::clipRegion(SkRegion* region, SkRegion::Op op) {
- const SkIRect& bounds = region->getBounds();
- return clipRect(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, op);
+ bool clipped = mSnapshot->clipRegionTransformed(*region, op);
+ if (clipped) {
+ dirtyClip();
+ }
+ return !mSnapshot->clipRect->isEmpty();
}
Rect* OpenGLRenderer::getClipRect() {
@@ -1332,8 +1425,11 @@
// TODO: It would be best if we could do this before quickReject()
// changes the scissor test state
if (clear) clearLayerRegions();
+ // Make sure setScissor & setStencil happen at the beginning of
+ // this method
if (mDirtyClip) {
setScissorFromClip();
+ setStencilFromClip();
}
mDescription.reset();
mSetShaderColor = false;
@@ -3085,7 +3181,7 @@
}
status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color,
- SkXfermode::Mode mode, bool ignoreTransform) {
+ SkXfermode::Mode mode, bool ignoreTransform, bool dirty) {
float left = FLT_MAX;
float top = FLT_MAX;
@@ -3103,7 +3199,7 @@
float r = rects[index + 2];
float b = rects[index + 3];
- if (!quickRejectNoScissor(left, top, right, bottom)) {
+ if (ignoreTransform || !quickRejectNoScissor(left, top, right, bottom)) {
Vertex::set(vertex++, l, b);
Vertex::set(vertex++, l, t);
Vertex::set(vertex++, r, t);
@@ -3136,7 +3232,7 @@
setupDrawColorFilterUniforms();
setupDrawVertices((GLvoid*) &mesh[0].position[0]);
- if (hasLayer()) {
+ if (dirty && hasLayer()) {
dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
}
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index f07325f..d4e1eb5 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -287,6 +287,19 @@
void resumeAfterLayer();
/**
+ * This method is called whenever a stencil buffer is required. Subclasses
+ * should override this method and call attachStencilBufferToLayer() on the
+ * appropriate layer(s).
+ */
+ virtual void ensureStencilBuffer();
+
+ /**
+ * Obtains a stencil render buffer (allocating it if necessary) and
+ * attaches it to the specified layer.
+ */
+ void attachStencilBufferToLayer(Layer* layer);
+
+ /**
* Compose the layer defined in the current snapshot with the layer
* defined by the previous snapshot.
*
@@ -423,6 +436,12 @@
void setScissorFromClip();
/**
+ * Sets the clipping region using the stencil buffer. The clip region
+ * is defined by the current snapshot's clipRegion member.
+ */
+ void setStencilFromClip();
+
+ /**
* Performs a quick reject but does not affect the scissor. Returns
* the transformed rect to test and the current clip.
*/
@@ -524,9 +543,10 @@
* @param color The rectangles' ARGB color, defined as a packed 32 bits word
* @param mode The Skia xfermode to use
* @param ignoreTransform True if the current transform should be ignored
+ * @param dirty True if calling this method should dirty the current layer
*/
status_t drawColorRects(const float* rects, int count, int color,
- SkXfermode::Mode mode, bool ignoreTransform = false);
+ SkXfermode::Mode mode, bool ignoreTransform = false, bool dirty = true);
/**
* Draws the shape represented by the specified path texture.
@@ -774,6 +794,19 @@
*/
void drawRegionRects(const Region& region);
+ /**
+ * Renders the specified region as a series of rectangles. The region
+ * must be in screen-space coordinates.
+ */
+ void drawRegionRects(const SkRegion& region, int color, SkXfermode::Mode mode,
+ bool dirty = false);
+
+ /**
+ * Draws the current clip region if any. Only when DEBUG_CLIP_REGIONS
+ * is turned on.
+ */
+ void debugClip();
+
void debugOverdraw(bool enable, bool clear);
void renderOverdraw();
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index d947299..22c7dde 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -31,7 +31,7 @@
transform = &mTransformRoot;
clipRect = &mClipRectRoot;
region = NULL;
- clipRegion = NULL;
+ clipRegion = &mClipRegionRoot;
}
/**
@@ -39,12 +39,10 @@
* the previous snapshot.
*/
Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags):
- flags(0), previous(s), layer(NULL), fbo(s->fbo),
+ flags(0), previous(s), layer(s->layer), fbo(s->fbo),
invisible(s->invisible), empty(false),
viewport(s->viewport), height(s->height), alpha(s->alpha) {
- clipRegion = NULL;
-
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
mTransformRoot.load(*s->transform);
transform = &mTransformRoot;
@@ -55,17 +53,13 @@
if (saveFlags & SkCanvas::kClip_SaveFlag) {
mClipRectRoot.set(*s->clipRect);
clipRect = &mClipRectRoot;
-#if STENCIL_BUFFER_SIZE
- if (s->clipRegion) {
+ if (!s->clipRegion->isEmpty()) {
mClipRegionRoot.op(*s->clipRegion, SkRegion::kUnion_Op);
- clipRegion = &mClipRegionRoot;
}
-#endif
+ clipRegion = &mClipRegionRoot;
} else {
clipRect = s->clipRect;
-#if STENCIL_BUFFER_SIZE
clipRegion = s->clipRegion;
-#endif
}
if (s->flags & Snapshot::kFlagFboTarget) {
@@ -81,41 +75,38 @@
///////////////////////////////////////////////////////////////////////////////
void Snapshot::ensureClipRegion() {
-#if STENCIL_BUFFER_SIZE
- if (!clipRegion) {
- clipRegion = &mClipRegionRoot;
+ if (clipRegion->isEmpty()) {
clipRegion->setRect(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
}
-#endif
}
void Snapshot::copyClipRectFromRegion() {
-#if STENCIL_BUFFER_SIZE
if (!clipRegion->isEmpty()) {
const SkIRect& bounds = clipRegion->getBounds();
clipRect->set(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
if (clipRegion->isRect()) {
clipRegion->setEmpty();
- clipRegion = NULL;
}
} else {
clipRect->setEmpty();
- clipRegion = NULL;
}
-#endif
}
bool Snapshot::clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op) {
-#if STENCIL_BUFFER_SIZE
SkIRect tmp;
tmp.set(left, top, right, bottom);
clipRegion->op(tmp, op);
copyClipRectFromRegion();
return true;
-#else
- return false;
-#endif
+}
+
+bool Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) {
+ ensureClipRegion();
+ clipRegion->op(region, op);
+ copyClipRectFromRegion();
+ flags |= Snapshot::kFlagClipSet;
+ return true;
}
bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) {
@@ -129,7 +120,7 @@
switch (op) {
case SkRegion::kIntersect_Op: {
- if (CC_UNLIKELY(clipRegion)) {
+ if (CC_UNLIKELY(!clipRegion->isEmpty())) {
ensureClipRegion();
clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kIntersect_Op);
} else {
@@ -142,7 +133,7 @@
break;
}
case SkRegion::kUnion_Op: {
- if (CC_UNLIKELY(clipRegion)) {
+ if (CC_UNLIKELY(!clipRegion->isEmpty())) {
ensureClipRegion();
clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kUnion_Op);
} else {
@@ -171,12 +162,9 @@
void Snapshot::setClip(float left, float top, float right, float bottom) {
clipRect->set(left, top, right, bottom);
-#if STENCIL_BUFFER_SIZE
- if (clipRegion) {
+ if (!clipRegion->isEmpty()) {
clipRegion->setEmpty();
- clipRegion = NULL;
}
-#endif
flags |= Snapshot::kFlagClipSet;
}
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 9c612ff..ffd4729 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -94,6 +94,12 @@
bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op);
/**
+ * Modifies the current clip with the specified region and operation.
+ * The specified region is considered already transformed.
+ */
+ bool clipRegionTransformed(const SkRegion& region, SkRegion::Op op);
+
+ /**
* Sets the current clip.
*/
void setClip(float left, float top, float right, float bottom);
@@ -136,7 +142,7 @@
sp<Snapshot> previous;
/**
- * Only set when the flag kFlagIsLayer is set.
+ * A pointer to the currently active layer.
*
* This snapshot does not own the layer, this pointer must not be freed.
*/
@@ -200,8 +206,6 @@
*
* This is a reference to a region owned by this snapshot or another
* snapshot. This pointer must not be freed. See ::mClipRegionRoot.
- *
- * This field is used only if STENCIL_BUFFER_SIZE is > 0.
*/
SkRegion* clipRegion;
@@ -234,9 +238,7 @@
Rect mClipRectRoot;
Rect mLocalClip;
-#if STENCIL_BUFFER_SIZE
SkRegion mClipRegionRoot;
-#endif
}; // class Snapshot
diff --git a/libs/hwui/Stencil.cpp b/libs/hwui/Stencil.cpp
index 84df82b..4fcd51d 100644
--- a/libs/hwui/Stencil.cpp
+++ b/libs/hwui/Stencil.cpp
@@ -37,7 +37,7 @@
void Stencil::enableTest() {
if (mState != kTest) {
enable();
- glStencilFunc(GL_EQUAL, 0x1, 0x1);
+ glStencilFunc(GL_EQUAL, 0xff, 0xff);
// 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);
@@ -48,7 +48,7 @@
void Stencil::enableWrite() {
if (mState != kWrite) {
enable();
- glStencilFunc(GL_ALWAYS, 0x1, 0x1);
+ glStencilFunc(GL_ALWAYS, 0xff, 0xff);
// 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);