From 3f085429fd47ebd32ac2463b3eae2a5a6c17be25 Mon Sep 17 00:00:00 2001 From: Chris Craik Date: Tue, 15 Apr 2014 16:18:08 -0700 Subject: [PATCH] Clip TouchFeedbackDrawable effect to receiver Outline Projected RenderNodes are now wrapped with a ClipRect or masked SaveLayer, so that they are clipped to the outline of the projection receiver surface. Change-Id: I1d4afc1bb5d638d650bc0b1dac51a498f216773e --- core/jni/android_view_GLES20Canvas.cpp | 9 +- .../graphics/drawable/TouchFeedbackDrawable.java | 7 +- libs/hwui/DisplayList.cpp | 5 +- libs/hwui/DisplayListOp.h | 23 ++- libs/hwui/DisplayListRenderer.cpp | 13 +- libs/hwui/Layer.cpp | 1 + libs/hwui/Layer.h | 15 ++ libs/hwui/OpenGLRenderer.cpp | 178 ++++++++++++--------- libs/hwui/OpenGLRenderer.h | 12 +- libs/hwui/Rect.h | 2 + libs/hwui/RenderNode.cpp | 75 +++++++-- libs/hwui/RenderNode.h | 1 + libs/hwui/Renderer.h | 2 +- libs/hwui/SkiaShader.cpp | 45 +++++- libs/hwui/SkiaShader.h | 22 ++- libs/hwui/Snapshot.h | 9 +- libs/hwui/StatefulBaseRenderer.h | 5 +- tests/HwAccelerationTest/AndroidManifest.xml | 11 ++ .../res/drawable/round_rect_background.xml | 6 + .../res/layout/projection_clipping.xml | 26 +++ .../test/hwui/ProjectionClippingActivity.java | 27 ++++ 21 files changed, 377 insertions(+), 117 deletions(-) create mode 100644 tests/HwAccelerationTest/res/drawable/round_rect_background.xml create mode 100644 tests/HwAccelerationTest/res/layout/projection_clipping.xml create mode 100644 tests/HwAccelerationTest/src/com/android/test/hwui/ProjectionClippingActivity.java diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 85490043908e..ef5ebd00e8db 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -287,7 +287,7 @@ static jint android_view_GLES20Canvas_saveLayerClip(JNIEnv* env, jobject clazz, jlong rendererPtr, jlong paintPtr, jint saveFlags) { OpenGLRenderer* renderer = reinterpret_cast(rendererPtr); SkPaint* paint = reinterpret_cast(paintPtr); - const android::uirenderer::Rect& bounds(renderer->getClipBounds()); + const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds()); return renderer->saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags); } @@ -302,7 +302,7 @@ static jint android_view_GLES20Canvas_saveLayerAlpha(JNIEnv* env, jobject clazz, static jint android_view_GLES20Canvas_saveLayerAlphaClip(JNIEnv* env, jobject clazz, jlong rendererPtr, jint alpha, jint saveFlags) { OpenGLRenderer* renderer = reinterpret_cast(rendererPtr); - const android::uirenderer::Rect& bounds(renderer->getClipBounds()); + const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds()); return renderer->saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, alpha, saveFlags); } @@ -356,7 +356,7 @@ static jboolean android_view_GLES20Canvas_clipRegion(JNIEnv* env, jobject clazz, static jboolean android_view_GLES20Canvas_getClipBounds(JNIEnv* env, jobject clazz, jlong rendererPtr, jobject rect) { OpenGLRenderer* renderer = reinterpret_cast(rendererPtr); - const android::uirenderer::Rect& bounds(renderer->getClipBounds()); + const android::uirenderer::Rect& bounds(renderer->getLocalClipBounds()); env->CallVoidMethod(rect, gRectClassInfo.set, int(bounds.left), int(bounds.top), int(bounds.right), int(bounds.bottom)); @@ -870,8 +870,7 @@ static jlong android_view_GLES20Canvas_finishRecording(JNIEnv* env, return reinterpret_cast(renderer->finishRecording()); } -static jlong android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env, - jobject clazz) { +static jlong android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env, jobject clazz) { return reinterpret_cast(new DisplayListRenderer); } diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java index 5f594678f730..792d62a73138 100644 --- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java +++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java @@ -362,8 +362,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { // first. This will merge SRC_OVER (directly) onto the canvas. if (!projected && rippleRestoreCount < 0) { rippleRestoreCount = canvas.saveLayer(bounds.left, bounds.top, - bounds.right, bounds.bottom, null, 0); - canvas.clipRect(bounds); + bounds.right, bounds.bottom, null); } drewRipples |= ripple.draw(canvas, getRipplePaint()); @@ -381,7 +380,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { if (drewRipples && !projected && rippleRestoreCount >= 0) { final PorterDuffXfermode xfermode = mState.getTintXfermode(); canvas.saveLayer(bounds.left, bounds.top, - bounds.right, bounds.bottom, getMaskingPaint(xfermode), 0); + bounds.right, bounds.bottom, getMaskingPaint(xfermode)); } Drawable mask = null; @@ -399,7 +398,7 @@ public class TouchFeedbackDrawable extends LayerDrawable { if (mask != null && drewRipples) { // TODO: This will also mask the lower layer, which is bad. canvas.saveLayer(bounds.left, bounds.top, bounds.right, - bounds.bottom, getMaskingPaint(DST_IN), 0); + bounds.bottom, getMaskingPaint(DST_IN)); mask.draw(canvas); } diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index a5d8dcb3fe0f..dac86cbacfa5 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -29,7 +29,10 @@ namespace android { namespace uirenderer { -DisplayListData::DisplayListData() : projectionReceiveIndex(-1), functorCount(0), hasDrawOps(false) { +DisplayListData::DisplayListData() + : projectionReceiveIndex(-1) + , functorCount(0) + , hasDrawOps(false) { } DisplayListData::~DisplayListData() { diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index f19da9d6af3e..6dfb9189fe60 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -318,12 +318,19 @@ private: class SaveLayerOp : public StateOp { public: SaveLayerOp(float left, float top, float right, float bottom, int alpha, int flags) - : mArea(left, top, right, bottom), mPaint(&mCachedPaint), mFlags(flags) { + : mArea(left, top, right, bottom) + , mPaint(&mCachedPaint) + , mFlags(flags) + , mConvexMask(NULL) { mCachedPaint.setAlpha(alpha); } SaveLayerOp(float left, float top, float right, float bottom, const SkPaint* paint, int flags) - : mArea(left, top, right, bottom), mPaint(paint), mFlags(flags) {} + : mArea(left, top, right, bottom) + , mPaint(paint) + , mFlags(flags) + , mConvexMask(NULL) + {} virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level, bool useQuickReject) { @@ -338,7 +345,8 @@ public: } virtual void applyState(OpenGLRenderer& renderer, int saveCount) const { - renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom, mPaint, mFlags); + renderer.saveLayer(mArea.left, mArea.top, mArea.right, mArea.bottom, + mPaint, mFlags, mConvexMask); } virtual void output(int level, uint32_t logFlags) const { @@ -350,6 +358,11 @@ public: int getFlags() { return mFlags; } + // Called to make SaveLayerOp clip to the provided mask when drawing back/restored + void setMask(const SkPath* convexMask) { + mConvexMask = convexMask; + } + private: bool isSaveLayerAlpha() const { SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint); @@ -361,6 +374,10 @@ private: const SkPaint* mPaint; SkPaint mCachedPaint; int mFlags; + + // Convex path, points at data in RenderNode, valid for the duration of the frame only + // Only used for masking the SaveLayer which wraps projected RenderNodes + const SkPath* mConvexMask; }; class TranslateOp : public StateOp { diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 6c73d6888016..e36d975de0a0 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -184,20 +184,15 @@ status_t DisplayListRenderer::drawDisplayList(RenderNode* displayList, // dirty is an out parameter and should not be recorded, // it matters only when replaying the display list - // TODO: To be safe, the display list should be ref-counted in the - // resources cache, but we rely on the caller (UI toolkit) to - // do the right thing for now + if (displayList->stagingProperties().isProjectionReceiver()) { + // use staging property, since recording on UI thread + mDisplayListData->projectionReceiveIndex = mDisplayListData->displayListOps.size(); + } DrawDisplayListOp* op = new (alloc()) DrawDisplayListOp(displayList, flags, *currentTransform()); addDrawOp(op); mDisplayListData->addChild(op); - - if (displayList->stagingProperties().isProjectionReceiver()) { - // use staging property, since recording on UI thread - mDisplayListData->projectionReceiveIndex = mDisplayListData->displayListOps.size() - 1; - } - return DrawGlInfo::kStatusDone; } diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index bfe4eda44ddc..9606e58ef79d 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -48,6 +48,7 @@ Layer::Layer(const uint32_t layerWidth, const uint32_t layerHeight): hasDrawnSinceUpdate = false; forceFilter = false; deferredList = NULL; + convexMask = NULL; caches.resourceCache.incrementRefcount(this); } diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index 5375b4509db1..49610d58b60d 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -227,6 +227,14 @@ public: ANDROID_API void setColorFilter(SkColorFilter* filter); + inline void setConvexMask(const SkPath* convexMask) { + this->convexMask = convexMask; + } + + inline const SkPath* getConvexMask() { + return convexMask; + } + void bindStencilRenderBuffer() const; void bindTexture() const; @@ -378,6 +386,13 @@ private: */ DeferredDisplayList* deferredList; + /** + * This convex path should be used to mask the layer's draw to the screen. + * + * Data not owned/managed by layer object. + */ + const SkPath* convexMask; + }; // struct Layer }; // namespace uirenderer diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index f37487fcfd68..1f5389c70a2a 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -704,11 +704,11 @@ void OpenGLRenderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& /////////////////////////////////////////////////////////////////////////////// int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom, - const SkPaint* paint, int flags) { + const SkPaint* paint, int flags, const SkPath* convexMask) { const int count = saveSnapshot(flags); if (!currentSnapshot()->isIgnored()) { - createLayer(left, top, right, bottom, paint, flags); + createLayer(left, top, right, bottom, paint, flags, convexMask); } return count; @@ -782,7 +782,6 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float return count; } - /** * Layers are viewed by Skia are slightly different than layers in image editing * programs (for instance.) When a layer is created, previously created layers @@ -835,7 +834,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float * something actually gets drawn are the layers regions cleared. */ bool OpenGLRenderer::createLayer(float left, float top, float right, float bottom, - const SkPaint* paint, int flags) { + const SkPaint* paint, int flags, const SkPath* convexMask) { LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top); LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize()); @@ -865,6 +864,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto layer->setBlend(true); layer->setDirty(false); + layer->setConvexMask(convexMask); // note: the mask must be cleared before returning to the cache // Save the layer in the snapshot mSnapshot->flags |= Snapshot::kFlagIsLayer; @@ -1013,6 +1013,7 @@ void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& resto dirtyClip(); // Failing to add the layer to the cache should happen only if the layer is too large + layer->setConvexMask(NULL); if (!mCaches.layerCache.put(layer)) { LAYER_LOGD("Deleting layer"); Caches::getInstance().resourceCache.decrementRefcount(layer); @@ -1122,6 +1123,38 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap) #define DRAW_DOUBLE_STENCIL(DRAW_COMMAND) DRAW_DOUBLE_STENCIL_IF(true, DRAW_COMMAND) void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { + if (CC_UNLIKELY(layer->region.isEmpty())) return; // nothing to draw + + if (layer->getConvexMask()) { + save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + + // clip to the area of the layer the mask can be larger + clipRect(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kIntersect_Op); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(SkColorSetARGB(int(getLayerAlpha(layer) * 255), 0, 0, 0)); + + SkiaShader* oldShader = mDrawModifiers.mShader; + + // create LayerShader to map SaveLayer content into subsequent draw + SkMatrix shaderMatrix; + shaderMatrix.setTranslate(rect.left, rect.bottom); + shaderMatrix.preScale(1, -1); + SkiaLayerShader layerShader(layer, &shaderMatrix); + mDrawModifiers.mShader = &layerShader; + + // Since the drawing primitive is defined in local drawing space, + // we don't need to modify the draw matrix + const SkPath* maskPath = layer->getConvexMask(); + DRAW_DOUBLE_STENCIL(drawConvexPath(*maskPath, &paint)); + + mDrawModifiers.mShader = oldShader; + restore(); + + return; + } + if (layer->region.isRect()) { layer->setRegionAsRect(); @@ -1131,88 +1164,87 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { return; } - if (CC_LIKELY(!layer->region.isEmpty())) { - size_t count; - const android::Rect* rects; - Region safeRegion; - if (CC_LIKELY(hasRectToRectTransform())) { - rects = layer->region.getArray(&count); - } else { - safeRegion = Region::createTJunctionFreeRegion(layer->region); - rects = safeRegion.getArray(&count); - } + // standard Region based draw + size_t count; + const android::Rect* rects; + Region safeRegion; + if (CC_LIKELY(hasRectToRectTransform())) { + rects = layer->region.getArray(&count); + } else { + safeRegion = Region::createTJunctionFreeRegion(layer->region); + rects = safeRegion.getArray(&count); + } - const float alpha = getLayerAlpha(layer); - const float texX = 1.0f / float(layer->getWidth()); - const float texY = 1.0f / float(layer->getHeight()); - const float height = rect.getHeight(); + const float alpha = getLayerAlpha(layer); + const float texX = 1.0f / float(layer->getWidth()); + const float texY = 1.0f / float(layer->getHeight()); + const float height = rect.getHeight(); - setupDraw(); + 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(); - uint32_t numQuads = 0; + // 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(); + uint32_t numQuads = 0; - setupDrawWithTexture(); - setupDrawColor(alpha, alpha, alpha, alpha); - setupDrawColorFilter(layer->getColorFilter()); - setupDrawBlending(layer); - setupDrawProgram(); - setupDrawDirtyRegionsDisabled(); - setupDrawPureColorUniforms(); - setupDrawColorFilterUniforms(layer->getColorFilter()); - setupDrawTexture(layer->getTexture()); - if (currentTransform()->isPureTranslate()) { - const float x = (int) floorf(rect.left + currentTransform()->getTranslateX() + 0.5f); - const float y = (int) floorf(rect.top + currentTransform()->getTranslateY() + 0.5f); + setupDrawWithTexture(); + setupDrawColor(alpha, alpha, alpha, alpha); + setupDrawColorFilter(layer->getColorFilter()); + setupDrawBlending(layer); + setupDrawProgram(); + setupDrawDirtyRegionsDisabled(); + setupDrawPureColorUniforms(); + setupDrawColorFilterUniforms(layer->getColorFilter()); + setupDrawTexture(layer->getTexture()); + if (currentTransform()->isPureTranslate()) { + const float x = (int) floorf(rect.left + currentTransform()->getTranslateX() + 0.5f); + const float y = (int) floorf(rect.top + currentTransform()->getTranslateY() + 0.5f); - layer->setFilter(GL_NEAREST); - setupDrawModelView(kModelViewMode_Translate, false, - x, y, x + rect.getWidth(), y + rect.getHeight(), true); - } else { - layer->setFilter(GL_LINEAR); - setupDrawModelView(kModelViewMode_Translate, false, - rect.left, rect.top, rect.right, rect.bottom); - } - setupDrawMeshIndices(&mesh[0].x, &mesh[0].u); + layer->setFilter(GL_NEAREST); + setupDrawModelView(kModelViewMode_Translate, false, + x, y, x + rect.getWidth(), y + rect.getHeight(), true); + } else { + layer->setFilter(GL_LINEAR); + setupDrawModelView(kModelViewMode_Translate, false, + rect.left, rect.top, rect.right, rect.bottom); + } + setupDrawMeshIndices(&mesh[0].x, &mesh[0].u); - for (size_t i = 0; i < count; i++) { - const android::Rect* r = &rects[i]; - - const float u1 = r->left * texX; - const float v1 = (height - r->top) * texY; - const float u2 = r->right * texX; - const float v2 = (height - r->bottom) * texY; - - // TODO: Reject quads outside of the clip - TextureVertex::set(mesh++, r->left, r->top, u1, v1); - TextureVertex::set(mesh++, r->right, r->top, u2, v1); - TextureVertex::set(mesh++, r->left, r->bottom, u1, v2); - TextureVertex::set(mesh++, r->right, r->bottom, u2, v2); - - numQuads++; - - if (numQuads >= gMaxNumberOfQuads) { - DRAW_DOUBLE_STENCIL(glDrawElements(GL_TRIANGLES, numQuads * 6, - GL_UNSIGNED_SHORT, NULL)); - numQuads = 0; - mesh = mCaches.getRegionMesh(); - } - } + for (size_t i = 0; i < count; i++) { + const android::Rect* r = &rects[i]; + + const float u1 = r->left * texX; + const float v1 = (height - r->top) * texY; + const float u2 = r->right * texX; + const float v2 = (height - r->bottom) * texY; - if (numQuads > 0) { + // TODO: Reject quads outside of the clip + TextureVertex::set(mesh++, r->left, r->top, u1, v1); + TextureVertex::set(mesh++, r->right, r->top, u2, v1); + TextureVertex::set(mesh++, r->left, r->bottom, u1, v2); + TextureVertex::set(mesh++, r->right, r->bottom, u2, v2); + + numQuads++; + + if (numQuads >= gMaxNumberOfQuads) { DRAW_DOUBLE_STENCIL(glDrawElements(GL_TRIANGLES, numQuads * 6, GL_UNSIGNED_SHORT, NULL)); + numQuads = 0; + mesh = mCaches.getRegionMesh(); } + } + + if (numQuads > 0) { + DRAW_DOUBLE_STENCIL(glDrawElements(GL_TRIANGLES, numQuads * 6, + GL_UNSIGNED_SHORT, NULL)); + } #if DEBUG_LAYERS_AS_REGIONS - drawRegionRectsDebug(layer->region); + drawRegionRectsDebug(layer->region); #endif - layer->region.clear(); - } + layer->region.clear(); } #if DEBUG_LAYERS_AS_REGIONS @@ -2926,7 +2958,7 @@ status_t OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { if (layer->isTextureLayer()) { transform = &layer->getTransform(); if (!transform->isIdentity()) { - save(0); + save(SkCanvas::kMatrix_SaveFlag); concatMatrix(*transform); } } diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 2debd2e00315..b49d1e199ed3 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -161,7 +161,14 @@ public: ANDROID_API void flushLayerUpdates(); ANDROID_API virtual int saveLayer(float left, float top, float right, float bottom, - const SkPaint* paint, int flags); + const SkPaint* paint, int flags) { + return saveLayer(left, top, right, bottom, paint, flags, NULL); + } + + // Specialized saveLayer implementation, which will pass the convexMask to an FBO layer, if + // created, which will in turn clip to that mask when drawn back/restored. + int saveLayer(float left, float top, float right, float bottom, + const SkPaint* paint, int flags, const SkPath* convexMask); int saveLayerDeferred(float left, float top, float right, float bottom, const SkPaint* paint, int flags); @@ -523,11 +530,12 @@ private: * @param alpha The translucency of the layer * @param mode The blending mode of the layer * @param flags The layer save flags + * @param mask A mask to use when drawing the layer back, may be empty * * @return True if the layer was successfully created, false otherwise */ bool createLayer(float left, float top, float right, float bottom, - const SkPaint* paint, int flags); + const SkPaint* paint, int flags, const SkPath* convexMask); /** * Creates a new layer stored in the specified snapshot as an FBO. diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 0083b77777c2..92964a8ca624 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -30,6 +30,8 @@ namespace uirenderer { #define RECT_STRING "%7.2f %7.2f %7.2f %7.2f" #define RECT_ARGS(r) \ (r).left, (r).top, (r).right, (r).bottom +#define SK_RECT_ARGS(r) \ + (r).left(), (r).top(), (r).right(), (r).bottom() /////////////////////////////////////////////////////////////////////////////// // Structs diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index cf218347ee2a..10c5fb8b003e 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -76,7 +76,7 @@ void RenderNode::setStagingDisplayList(DisplayListData* data) { */ void RenderNode::output(uint32_t level) { ALOGD("%*sStart display list (%p, %s, render=%d)", (level - 1) * 2, "", this, - mName.string(), isRenderable()); + getName(), isRenderable()); ALOGD("%*s%s %d", level * 2, "", "Save", SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); @@ -86,7 +86,7 @@ void RenderNode::output(uint32_t level) { mDisplayListData->displayListOps[i]->output(level, flags); } - ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, mName.string()); + ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName()); } void RenderNode::prepareTree(TreeInfo& info) { @@ -260,12 +260,13 @@ void RenderNode::computeOrdering() { for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) { DrawDisplayListOp* childOp = mDisplayListData->children()[i]; childOp->mDisplayList->computeOrderingImpl(childOp, - &mProjectedNodes, &mat4::identity()); + properties().getOutline().getPath(), &mProjectedNodes, &mat4::identity()); } } void RenderNode::computeOrderingImpl( DrawDisplayListOp* opState, + const SkPath* outlineOfProjectionSurface, Vector* compositedChildrenOfProjectionSurface, const mat4* transformFromProjectionSurface) { mProjectedNodes.clear(); @@ -293,6 +294,7 @@ void RenderNode::computeOrderingImpl( DrawDisplayListOp* childOp = mDisplayListData->children()[i]; RenderNode* child = childOp->mDisplayList; + const SkPath* projectionOutline = NULL; Vector* projectionChildren = NULL; const mat4* projectionTransform = NULL; if (isProjectionReceiver && !child->properties().getProjectBackwards()) { @@ -301,6 +303,7 @@ void RenderNode::computeOrderingImpl( // Note that if a direct descendent is projecting backwards, we pass it's // grandparent projection collection, since it shouldn't project onto it's // parent, where it will already be drawing. + projectionOutline = properties().getOutline().getPath(); projectionChildren = &mProjectedNodes; projectionTransform = &mat4::identity(); } else { @@ -308,10 +311,12 @@ void RenderNode::computeOrderingImpl( applyViewPropertyTransforms(localTransformFromProjectionSurface); haveAppliedPropertiesToProjection = true; } + projectionOutline = outlineOfProjectionSurface; projectionChildren = compositedChildrenOfProjectionSurface; projectionTransform = &localTransformFromProjectionSurface; } - child->computeOrderingImpl(childOp, projectionChildren, projectionTransform); + child->computeOrderingImpl(childOp, + projectionOutline, projectionChildren, projectionTransform); } } } @@ -351,7 +356,7 @@ public: : mReplayStruct(replayStruct), mLevel(level) {} inline void operator()(DisplayListOp* operation, int saveCount, bool clipToBounds) { #if DEBUG_DISPLAY_LIST_OPS_AS_EVENTS - properties().getReplayStruct().mRenderer.eventMark(operation->name()); + mReplayStruct.mRenderer.eventMark(operation->name()); #endif operation->replay(mReplayStruct, saveCount, mLevel, clipToBounds); } @@ -361,8 +366,6 @@ public: } inline void endMark() { mReplayStruct.mRenderer.endMark(); - DISPLAY_LIST_LOGD("%*sDone (%p, %s), returning %d", level * 2, "", this, mName.string(), - mReplayStruct.mDrawGlStatus); } inline int level() { return mLevel; } inline int replayFlags() { return mReplayStruct.mReplayFlags; } @@ -467,6 +470,10 @@ void RenderNode::issueOperationsOf3dChildren(const Vector void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& handler) { + DISPLAY_LIST_LOGD("%*s%d projected children:", (handler.level() + 1) * 2, "", mProjectedNodes.size()); + const SkPath* projectionReceiverOutline = properties().getOutline().getPath(); + bool maskProjecteesWithPath = projectionReceiverOutline != NULL + && !projectionReceiverOutline->isRect(NULL); + int restoreTo = renderer.getSaveCount(); + + // If the projection reciever has an outline, we mask each of the projected rendernodes to it + // Either with clipRect, or special saveLayer masking + LinearAllocator& alloc = handler.allocator(); + if (projectionReceiverOutline != NULL) { + const SkRect& outlineBounds = projectionReceiverOutline->getBounds(); + if (projectionReceiverOutline->isRect(NULL)) { + // mask to the rect outline simply with clipRect + handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag), + PROPERTY_SAVECOUNT, properties().getClipToBounds()); + ClipRectOp* clipOp = new (alloc) ClipRectOp( + outlineBounds.left(), outlineBounds.top(), + outlineBounds.right(), outlineBounds.bottom(), SkRegion::kIntersect_Op); + handler(clipOp, PROPERTY_SAVECOUNT, properties().getClipToBounds()); + } else { + // wrap the projected RenderNodes with a SaveLayer that will mask to the outline + SaveLayerOp* op = new (alloc) SaveLayerOp( + outlineBounds.left(), outlineBounds.top(), + outlineBounds.right(), outlineBounds.bottom(), + 255, SkCanvas::kARGB_ClipLayer_SaveFlag); + op->setMask(projectionReceiverOutline); + handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds()); + + /* TODO: add optimizations here to take advantage of placement/size of projected + * children (which may shrink saveLayer area significantly). This is dependent on + * passing actual drawing/dirtying bounds of projected content down to native. + */ + } + } + + // draw projected nodes for (size_t i = 0; i < mProjectedNodes.size(); i++) { DrawDisplayListOp* childOp = mProjectedNodes[i]; @@ -514,6 +557,11 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& childOp->mSkipInOrderDraw = true; renderer.restoreToCount(restoreTo); } + + if (projectionReceiverOutline != NULL) { + handler(new (alloc) RestoreToCountOp(restoreTo), + PROPERTY_SAVECOUNT, properties().getClipToBounds()); + } } /** @@ -529,17 +577,17 @@ template void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { const int level = handler.level(); if (mDisplayListData->isEmpty() || properties().getAlpha() <= 0) { - DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, mName.string()); + DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", level * 2, "", this, getName()); return; } - handler.startMark(mName.string()); + handler.startMark(getName()); #if DEBUG_DISPLAY_LIST - Rect* clipRect = renderer.getClipRect(); - DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), clipRect: %.0f, %.0f, %.0f, %.0f", - level * 2, "", this, mName.string(), clipRect->left, clipRect->top, - clipRect->right, clipRect->bottom); + const Rect& clipRect = renderer.getLocalClipBounds(); + DISPLAY_LIST_LOGD("%*sStart display list (%p, %s), localClipBounds: %.0f, %.0f, %.0f, %.0f", + level * 2, "", this, getName(), + clipRect.left, clipRect.top, clipRect.right, clipRect.bottom); #endif LinearAllocator& alloc = handler.allocator(); @@ -587,6 +635,7 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { PROPERTY_SAVECOUNT, properties().getClipToBounds()); renderer.setOverrideLayerAlpha(1.0f); + DISPLAY_LIST_LOGD("%*sDone (%p, %s)", level * 2, "", this, getName()); handler.endMark(); } diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 6688952836bc..b9edbe5ac2a6 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -172,6 +172,7 @@ private: void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false); void computeOrderingImpl(DrawDisplayListOp* opState, + const SkPath* outlineOfProjectionSurface, Vector* compositedChildrenOfProjectionSurface, const mat4* transformFromProjectionSurface); diff --git a/libs/hwui/Renderer.h b/libs/hwui/Renderer.h index efcea5ffa50e..3209a531ace0 100644 --- a/libs/hwui/Renderer.h +++ b/libs/hwui/Renderer.h @@ -167,7 +167,7 @@ public: virtual void concatMatrix(const SkMatrix* matrix) = 0; // clip - virtual const Rect& getClipBounds() const = 0; + virtual const Rect& getLocalClipBounds() const = 0; virtual bool quickRejectConservative(float left, float top, float right, float bottom) const = 0; virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) = 0; diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 4f2a4328b4f8..6a4a0c813ba0 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -73,7 +73,7 @@ SkiaShader::SkiaShader(): mCaches(NULL) { } SkiaShader::SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX, - SkShader::TileMode tileY, SkMatrix* matrix, bool blend): + SkShader::TileMode tileY, const SkMatrix* matrix, bool blend): mType(type), mKey(key), mTileX(tileX), mTileY(tileY), mBlend(blend), mCaches(NULL) { setMatrix(matrix); @@ -101,6 +101,49 @@ void SkiaShader::computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelVi } /////////////////////////////////////////////////////////////////////////////// +// Layer shader +/////////////////////////////////////////////////////////////////////////////// + +SkiaLayerShader::SkiaLayerShader(Layer* layer, const SkMatrix* matrix): + SkiaShader(kBitmap, NULL, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode, + matrix, layer->isBlend()), mLayer(layer) { + updateLocalMatrix(matrix); +} + +SkiaShader* SkiaLayerShader::copy() { + SkiaLayerShader* copy = new SkiaLayerShader(); + copy->copyFrom(*this); + copy->mLayer = mLayer; + return copy; +} + +void SkiaLayerShader::describe(ProgramDescription& description, const Extensions& extensions) { + description.hasBitmap = true; +} + +void SkiaLayerShader::setupProgram(Program* program, const mat4& modelView, + const Snapshot& snapshot, GLuint* textureUnit) { + GLuint textureSlot = (*textureUnit)++; + Caches::getInstance().activeTexture(textureSlot); + + const float width = mLayer->getWidth(); + const float height = mLayer->getHeight(); + + mat4 textureTransform; + computeScreenSpaceMatrix(textureTransform, modelView); + + // Uniforms + mLayer->bindTexture(); + mLayer->setWrap(GL_CLAMP_TO_EDGE); + mLayer->setFilter(GL_LINEAR); + + glUniform1i(program->getUniform("bitmapSampler"), textureSlot); + glUniformMatrix4fv(program->getUniform("textureTransform"), 1, + GL_FALSE, &textureTransform.data[0]); + glUniform2f(program->getUniform("textureDimension"), 1.0f / width, 1.0f / height); +} + +/////////////////////////////////////////////////////////////////////////////// // Bitmap shader /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h index 601576186fe6..9f3025720d5c 100644 --- a/libs/hwui/SkiaShader.h +++ b/libs/hwui/SkiaShader.h @@ -58,7 +58,7 @@ public: }; ANDROID_API SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX, - SkShader::TileMode tileY, SkMatrix* matrix, bool blend); + SkShader::TileMode tileY, const SkMatrix* matrix, bool blend); virtual ~SkiaShader(); virtual SkiaShader* copy() = 0; @@ -88,7 +88,7 @@ public: return mGenerationId; } - void setMatrix(SkMatrix* matrix) { + void setMatrix(const SkMatrix* matrix) { updateLocalMatrix(matrix); mGenerationId++; } @@ -134,6 +134,24 @@ private: /////////////////////////////////////////////////////////////////////////////// /** + * A shader that draws a layer. + */ +struct SkiaLayerShader: public SkiaShader { + SkiaLayerShader(Layer* layer, const SkMatrix* matrix); + SkiaShader* copy(); + + void describe(ProgramDescription& description, const Extensions& extensions); + void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot, + GLuint* textureUnit); + +private: + SkiaLayerShader() { + } + + Layer* mLayer; +}; // struct SkiaLayerShader + +/** * A shader that draws a bitmap. */ struct SkiaBitmapShader: public SkiaShader { diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index 5bdb18abf873..038aea8fc7e3 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -108,7 +108,12 @@ public: * Returns the current clip in local coordinates. The clip rect is * transformed by the inverse transform matrix. */ - ANDROID_API const Rect& getLocalClip(); + const Rect& getLocalClip(); + + /** + * Returns the current clip in render target coordinates. + */ + const Rect& getRenderTargetClip() { return *clipRect; } /** * Resets the clip to the specified rect. @@ -238,7 +243,7 @@ private: mat4 mTransformRoot; Rect mClipRectRoot; - Rect mLocalClip; + Rect mLocalClip; // don't use directly, call getLocalClip() which initializes this SkRegion mClipRegionRoot; diff --git a/libs/hwui/StatefulBaseRenderer.h b/libs/hwui/StatefulBaseRenderer.h index bf34beccc80e..64354acecbff 100644 --- a/libs/hwui/StatefulBaseRenderer.h +++ b/libs/hwui/StatefulBaseRenderer.h @@ -75,7 +75,8 @@ public: void concatMatrix(const Matrix4& matrix); // internal only convenience method // Clip - const Rect& getClipBounds() const { return mSnapshot->getLocalClip(); } + virtual const Rect& getLocalClipBounds() const { return mSnapshot->getLocalClip(); } + virtual bool quickRejectConservative(float left, float top, float right, float bottom) const; virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op); @@ -83,6 +84,8 @@ public: virtual bool clipRegion(const SkRegion* region, SkRegion::Op op); protected: + const Rect& getRenderTargetClipBounds() const { return mSnapshot->getRenderTargetClip(); } + int getWidth() { return mWidth; } int getHeight() { return mHeight; } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 0ad345610deb..ac741e76d878 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -867,5 +867,16 @@ + + + + + + + + + diff --git a/tests/HwAccelerationTest/res/drawable/round_rect_background.xml b/tests/HwAccelerationTest/res/drawable/round_rect_background.xml new file mode 100644 index 000000000000..14d40736b3b9 --- /dev/null +++ b/tests/HwAccelerationTest/res/drawable/round_rect_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/tests/HwAccelerationTest/res/layout/projection_clipping.xml b/tests/HwAccelerationTest/res/layout/projection_clipping.xml new file mode 100644 index 000000000000..7caf90aa7222 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/projection_clipping.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ProjectionClippingActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ProjectionClippingActivity.java new file mode 100644 index 000000000000..2ae960bd08db --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ProjectionClippingActivity.java @@ -0,0 +1,27 @@ +package com.android.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.RenderNode; +import android.view.View; + +public class ProjectionClippingActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.projection_clipping); + View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(View v) { + // woo! nothing! + } + }; + findViewById(R.id.clickable1).setOnClickListener(listener); + findViewById(R.id.clickable2).setOnClickListener(listener); + } +} -- 2.11.0