OSDN Git Service

Clip TouchFeedbackDrawable effect to receiver Outline
authorChris Craik <ccraik@google.com>
Tue, 15 Apr 2014 23:18:08 +0000 (16:18 -0700)
committerChris Craik <ccraik@google.com>
Wed, 23 Apr 2014 23:15:11 +0000 (16:15 -0700)
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

21 files changed:
core/jni/android_view_GLES20Canvas.cpp
graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java
libs/hwui/DisplayList.cpp
libs/hwui/DisplayListOp.h
libs/hwui/DisplayListRenderer.cpp
libs/hwui/Layer.cpp
libs/hwui/Layer.h
libs/hwui/OpenGLRenderer.cpp
libs/hwui/OpenGLRenderer.h
libs/hwui/Rect.h
libs/hwui/RenderNode.cpp
libs/hwui/RenderNode.h
libs/hwui/Renderer.h
libs/hwui/SkiaShader.cpp
libs/hwui/SkiaShader.h
libs/hwui/Snapshot.h
libs/hwui/StatefulBaseRenderer.h
tests/HwAccelerationTest/AndroidManifest.xml
tests/HwAccelerationTest/res/drawable/round_rect_background.xml [new file with mode: 0644]
tests/HwAccelerationTest/res/layout/projection_clipping.xml [new file with mode: 0644]
tests/HwAccelerationTest/src/com/android/test/hwui/ProjectionClippingActivity.java [new file with mode: 0644]

index 8549004..ef5ebd0 100644 (file)
@@ -287,7 +287,7 @@ static jint android_view_GLES20Canvas_saveLayerClip(JNIEnv* env, jobject clazz,
         jlong rendererPtr, jlong paintPtr, jint saveFlags) {
     OpenGLRenderer* renderer = reinterpret_cast<OpenGLRenderer*>(rendererPtr);
     SkPaint* paint = reinterpret_cast<SkPaint*>(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<OpenGLRenderer*>(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<OpenGLRenderer*>(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<jlong>(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<jlong>(new DisplayListRenderer);
 }
 
index 5f59467..792d62a 100644 (file)
@@ -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);
         }
 
index a5d8dcb..dac86cb 100644 (file)
 namespace android {
 namespace uirenderer {
 
-DisplayListData::DisplayListData() : projectionReceiveIndex(-1), functorCount(0), hasDrawOps(false) {
+DisplayListData::DisplayListData()
+        : projectionReceiveIndex(-1)
+        , functorCount(0)
+        , hasDrawOps(false) {
 }
 
 DisplayListData::~DisplayListData() {
index f19da9d..6dfb918 100644 (file)
@@ -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 {
index 6c73d68..e36d975 100644 (file)
@@ -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;
 }
 
index bfe4eda..9606e58 100644 (file)
@@ -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);
 }
 
index 5375b45..49610d5 100644 (file)
@@ -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
index f37487f..1f5389c 100644 (file)
@@ -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);
         }
     }
index 2debd2e..b49d1e1 100644 (file)
@@ -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.
index 0083b77..92964a8 100644 (file)
@@ -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
index cf21834..10c5fb8 100644 (file)
@@ -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<DrawDisplayListOp*>* 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<DrawDisplayListOp*>* 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<ZDrawDisplayListOpPair
         endIndex = size;
         shadowIndex = drawIndex; // potentially draw shadow for each pos Z child
     }
+
+    DISPLAY_LIST_LOGD("%*s%d %s 3d children:", (handler.level() + 1) * 2, "",
+            endIndex - drawIndex, mode == kNegativeZChildren ? "negative" : "positive");
+
     float lastCasterZ = 0.0f;
     while (shadowIndex < endIndex || drawIndex < endIndex) {
         if (shadowIndex < endIndex) {
@@ -503,6 +510,42 @@ void RenderNode::issueOperationsOf3dChildren(const Vector<ZDrawDisplayListOpPair
 
 template <class T>
 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 <class T>
 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();
 }
 
index 6688952..b9edbe5 100644 (file)
@@ -172,6 +172,7 @@ private:
     void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false);
 
     void computeOrderingImpl(DrawDisplayListOp* opState,
+            const SkPath* outlineOfProjectionSurface,
             Vector<DrawDisplayListOp*>* compositedChildrenOfProjectionSurface,
             const mat4* transformFromProjectionSurface);
 
index efcea5f..3209a53 100644 (file)
@@ -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;
index 4f2a432..6a4a0c8 100644 (file)
@@ -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
 ///////////////////////////////////////////////////////////////////////////////
 
index 6015761..9f30257 100644 (file)
@@ -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 {
index 5bdb18a..038aea8 100644 (file)
@@ -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;
 
index bf34bec..64354ac 100644 (file)
@@ -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; }
 
index 0ad3456..ac741e7 100644 (file)
             </intent-filter>
         </activity>
 
+        <activity
+                android:name=".ProjectionClippingActivity"
+                android:label="Reordering/Projection Clipping">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.hwui.TEST" />
+
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/res/drawable/round_rect_background.xml b/tests/HwAccelerationTest/res/drawable/round_rect_background.xml
new file mode 100644 (file)
index 0000000..14d4073
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android" >
+    <solid android:color="#eee" />
+    <corners android:radius="30dp" />
+</shape>
\ 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 (file)
index 0000000..7caf90a
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <FrameLayout
+        android:translationX="50dp"
+        android:translationY="50dp"
+        android:translationZ="30dp"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        android:background="@drawable/round_rect_background">
+        <View
+            android:id="@+id/clickable1"
+            android:layout_width="100dp"
+            android:layout_height="100dp"
+            android:background="?android:attr/selectableItemBackground"/>
+        <View
+            android:id="@+id/clickable2"
+            android:translationX="50dp"
+            android:translationY="10dp"
+            android:layout_width="150dp"
+            android:layout_height="100dp"
+            android:background="?android:attr/selectableItemBackground"/>
+    </FrameLayout>
+</LinearLayout>
\ 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 (file)
index 0000000..2ae960b
--- /dev/null
@@ -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);
+    }
+}