From 735738c4ddf3229caa5f6e634bf591953ac29944 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Mon, 3 Dec 2012 12:34:51 -0800 Subject: [PATCH] Preliminary Support for region clipping Region clipping, using Canvas.clipPath or Canvas.clipRegion, requires a stencil buffer to be always present. In addition, extra wiring is required in JNI and display lists. This change only adds the necessary JNI/C++ APIs and some extra plumbing to start the real work on properly supporting region clipping. A new debug define called DEBUG_CLIP_REGIONS can be used to draw the current clip region. It is off by default, as is region clipping. The default implementation of clipPath() and clipRegion(), now in native, mimics the previous Dalvik implementation to prevent regressions. Change-Id: I7903e7cfd7412b9b9b622566d4dbfce7bdcec00c --- core/java/android/view/GLES20Canvas.java | 28 +++----- core/java/android/view/HardwareRenderer.java | 37 +++++++--- core/jni/android_view_GLES20Canvas.cpp | 12 ++++ libs/hwui/Debug.h | 3 + libs/hwui/DisplayListRenderer.cpp | 68 +++++++++++++++---- libs/hwui/DisplayListRenderer.h | 36 ++++++++++ libs/hwui/OpenGLRenderer.cpp | 50 +++++++++++--- libs/hwui/OpenGLRenderer.h | 19 +++++- libs/hwui/Snapshot.cpp | 2 + .../android/test/hwui/ClipRegionActivity.java | 10 +-- 10 files changed, 206 insertions(+), 59 deletions(-) diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 7739ff74f2009..e0cf3b28e0bab 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -433,20 +433,16 @@ class GLES20Canvas extends HardwareCanvas { @Override public boolean clipPath(Path path) { - // TODO: Implement - path.computeBounds(mPathBounds, true); - return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top, - mPathBounds.right, mPathBounds.bottom, Region.Op.INTERSECT.nativeInt); + return nClipPath(mRenderer, path.mNativePath, Region.Op.INTERSECT.nativeInt); } @Override public boolean clipPath(Path path, Region.Op op) { - // TODO: Implement - path.computeBounds(mPathBounds, true); - return nClipRect(mRenderer, mPathBounds.left, mPathBounds.top, - mPathBounds.right, mPathBounds.bottom, op.nativeInt); + return nClipPath(mRenderer, path.mNativePath, op.nativeInt); } + private static native boolean nClipPath(int renderer, int path, int op); + @Override public boolean clipRect(float left, float top, float right, float bottom) { return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt); @@ -465,8 +461,8 @@ class GLES20Canvas extends HardwareCanvas { return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt); } - private static native boolean nClipRect(int renderer, int left, int top, int right, int bottom, - int op); + private static native boolean nClipRect(int renderer, int left, int top, + int right, int bottom, int op); @Override public boolean clipRect(Rect rect) { @@ -492,20 +488,16 @@ class GLES20Canvas extends HardwareCanvas { @Override public boolean clipRegion(Region region) { - // TODO: Implement - region.getBounds(mClipBounds); - return nClipRect(mRenderer, mClipBounds.left, mClipBounds.top, - mClipBounds.right, mClipBounds.bottom, Region.Op.INTERSECT.nativeInt); + return nClipRegion(mRenderer, region.mNativeRegion, Region.Op.INTERSECT.nativeInt); } @Override public boolean clipRegion(Region region, Region.Op op) { - // TODO: Implement - region.getBounds(mClipBounds); - return nClipRect(mRenderer, mClipBounds.left, mClipBounds.top, - mClipBounds.right, mClipBounds.bottom, op.nativeInt); + return nClipRegion(mRenderer, region.mNativeRegion, op.nativeInt); } + private static native boolean nClipRegion(int renderer, int region, int op); + @Override public boolean getClipBounds(Rect bounds) { return nGetClipBounds(mRenderer, bounds); diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 7ecdcbefc88ad..0d45bbc20ccaf 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,6 @@ * limitations under the License. */ - package android.view; import android.content.ComponentCallbacks2; @@ -173,6 +172,17 @@ public abstract class HardwareRenderer { */ public static final String DEBUG_SHOW_OVERDRAW_PROPERTY = "debug.hwui.show_overdraw"; + /** + * Turn on to allow region clipping (see + * {@link android.graphics.Canvas#clipPath(android.graphics.Path)} and + * {@link android.graphics.Canvas#clipRegion(android.graphics.Region)}. + * + * When this option is turned on a stencil buffer is always required. + * If this option is off a stencil buffer is only created when the overdraw + * debugging mode is turned on. + */ + private static final boolean REGION_CLIPPING_ENABLED = false; + /** * A process can set this flag to false to prevent the use of hardware * rendering. @@ -876,10 +886,12 @@ public abstract class HardwareRenderer { changed = true; mShowOverdraw = value; - if (surface != null && isEnabled()) { - if (validate()) { - sEglConfig = loadEglConfig(); - invalidate(surface); + if (!REGION_CLIPPING_ENABLED) { + if (surface != null && isEnabled()) { + if (validate()) { + sEglConfig = loadEglConfig(); + invalidate(surface); + } } } } @@ -1752,6 +1764,11 @@ public abstract class HardwareRenderer { @Override int[] getConfig(boolean dirtyRegions) { + //noinspection PointlessBooleanExpression + final int stencilSize = mShowOverdraw || REGION_CLIPPING_ENABLED ? + GLES20Canvas.getStencilSize() : 0; + final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + return new int[] { EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, @@ -1760,14 +1777,12 @@ public abstract class HardwareRenderer { EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 0, EGL_CONFIG_CAVEAT, EGL_NONE, - // TODO: Find a better way to choose the stencil size - EGL_STENCIL_SIZE, mShowOverdraw ? GLES20Canvas.getStencilSize() : 0, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT | - (dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0), + EGL_STENCIL_SIZE, stencilSize, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, EGL_NONE }; } - + @Override void initCaches() { GLES20Canvas.initCaches(); diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index 7d886da535f31..de14826c92853 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -275,6 +275,16 @@ static bool android_view_GLES20Canvas_clipRect(JNIEnv* env, jobject clazz, return renderer->clipRect(float(left), float(top), float(right), float(bottom), op); } +static bool android_view_GLES20Canvas_clipPath(JNIEnv* env, jobject clazz, + OpenGLRenderer* renderer, SkPath* path, SkRegion::Op op) { + return renderer->clipPath(path, op); +} + +static bool android_view_GLES20Canvas_clipRegion(JNIEnv* env, jobject clazz, + OpenGLRenderer* renderer, SkRegion* region, SkRegion::Op op) { + return renderer->clipRegion(region, op); +} + static bool android_view_GLES20Canvas_getClipBounds(JNIEnv* env, jobject clazz, OpenGLRenderer* renderer, jobject rect) { const android::uirenderer::Rect& bounds(renderer->getClipBounds()); @@ -961,6 +971,8 @@ static JNINativeMethod gMethods[] = { { "nQuickReject", "(IFFFFI)Z", (void*) android_view_GLES20Canvas_quickReject }, { "nClipRect", "(IFFFFI)Z", (void*) android_view_GLES20Canvas_clipRectF }, { "nClipRect", "(IIIIII)Z", (void*) android_view_GLES20Canvas_clipRect }, + { "nClipPath", "(III)Z", (void*) android_view_GLES20Canvas_clipPath }, + { "nClipRegion", "(III)Z", (void*) android_view_GLES20Canvas_clipRegion }, { "nTranslate", "(IFF)V", (void*) android_view_GLES20Canvas_translate }, { "nRotate", "(IF)V", (void*) android_view_GLES20Canvas_rotate }, diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h index 6795ac3205d18..dfc4e25cc085c 100644 --- a/libs/hwui/Debug.h +++ b/libs/hwui/Debug.h @@ -35,6 +35,9 @@ // Turn on to enable layers debugging when rendered as regions #define DEBUG_LAYERS_AS_REGIONS 0 +// Turn on to enable debugging when the clip is not a rect +#define DEBUG_CLIP_REGIONS 0 + // Turn on to display debug info about vertex/fragment shaders #define DEBUG_PROGRAMS 0 diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 06574cd55cf3b..f0c9ce4748906 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -44,6 +44,8 @@ const char* DisplayList::OP_NAMES[] = { "SetMatrix", "ConcatMatrix", "ClipRect", + "ClipPath", + "ClipRegion", "DrawDisplayList", "DrawLayer", "DrawBitmap", @@ -166,6 +168,10 @@ void DisplayList::clearResources() { delete mPaints.itemAt(i); } + for (size_t i = 0; i < mRegions.size(); i++) { + delete mRegions.itemAt(i); + } + for (size_t i = 0; i < mPaths.size(); i++) { SkPath* path = mPaths.itemAt(i); caches.pathCache.remove(path); @@ -182,6 +188,7 @@ void DisplayList::clearResources() { mShaders.clear(); mSourcePaths.clear(); mPaints.clear(); + mRegions.clear(); mPaths.clear(); mMatrices.clear(); mLayers.clear(); @@ -259,20 +266,10 @@ void DisplayList::initFromDisplayListRenderer(const DisplayListRenderer& recorde caches.resourceCache.unlock(); - const Vector& paints = recorder.getPaints(); - for (size_t i = 0; i < paints.size(); i++) { - mPaints.add(paints.itemAt(i)); - } - - const Vector& paths = recorder.getPaths(); - for (size_t i = 0; i < paths.size(); i++) { - mPaths.add(paths.itemAt(i)); - } - - const Vector& matrices = recorder.getMatrices(); - for (size_t i = 0; i < matrices.size(); i++) { - mMatrices.add(matrices.itemAt(i)); - } + mPaints.appendVector(recorder.getPaints()); + mRegions.appendVector(recorder.getRegions()); + mPaths.appendVector(recorder.getPaths()); + mMatrices.appendVector(recorder.getMatrices()); } void DisplayList::init() { @@ -429,6 +426,18 @@ void DisplayList::output(OpenGLRenderer& renderer, uint32_t level) { f1, f2, f3, f4, regionOp); } break; + case ClipPath: { + SkPath* path = getPath(); + int regionOp = getInt(); + ALOGD("%s%s %d", (char*) indent, OP_NAMES[op], regionOp); + } + break; + case ClipRegion: { + SkRegion* region = getRegion(); + int regionOp = getInt(); + ALOGD("%s%s %d", (char*) indent, OP_NAMES[op], regionOp); + } + break; case DrawDisplayList: { DisplayList* displayList = getDisplayList(); int32_t flags = getInt(); @@ -1031,6 +1040,20 @@ status_t DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flag renderer.clipRect(f1, f2, f3, f4, (SkRegion::Op) regionOp); } break; + case ClipPath: { + SkPath* path = getPath(); + int32_t regionOp = getInt(); + DISPLAY_LIST_LOGD("%s%s %d", (char*) indent, OP_NAMES[op], regionOp); + renderer.clipPath(path, (SkRegion::Op) regionOp); + } + break; + case ClipRegion: { + SkRegion* region = getRegion(); + int32_t regionOp = getInt(); + DISPLAY_LIST_LOGD("%s%s %d", (char*) indent, OP_NAMES[op], regionOp); + renderer.clipRegion(region, (SkRegion::Op) regionOp); + } + break; case DrawDisplayList: { DisplayList* displayList = getDisplayList(); int32_t flags = getInt(); @@ -1415,6 +1438,9 @@ void DisplayListRenderer::reset() { mPaints.clear(); mPaintMap.clear(); + mRegions.clear(); + mRegionMap.clear(); + mPaths.clear(); mPathMap.clear(); @@ -1571,6 +1597,20 @@ bool DisplayListRenderer::clipRect(float left, float top, float right, float bot return OpenGLRenderer::clipRect(left, top, right, bottom, op); } +bool DisplayListRenderer::clipPath(SkPath* path, SkRegion::Op op) { + addOp(DisplayList::ClipPath); + addPath(path); + addInt(op); + return OpenGLRenderer::clipPath(path, op); +} + +bool DisplayListRenderer::clipRegion(SkRegion* region, SkRegion::Op op) { + addOp(DisplayList::ClipRegion); + addRegion(region); + addInt(op); + return OpenGLRenderer::clipRegion(region, op); +} + status_t DisplayListRenderer::drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags, uint32_t level) { // dirty is an out parameter and should not be recorded, diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index fb0175303b589..f55f1f2fa91e1 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -85,6 +85,8 @@ public: SetMatrix, ConcatMatrix, ClipRect, + ClipPath, + ClipRegion, // Drawing operations DrawDisplayList, DrawLayer, @@ -457,6 +459,10 @@ private: return (SkPath*) getInt(); } + SkRegion* getRegion() { + return (SkRegion*) getInt(); + } + SkPaint* getPaint(OpenGLRenderer& renderer) { return renderer.filterPaint((SkPaint*) getInt()); } @@ -496,6 +502,7 @@ private: Vector mPaints; Vector mPaths; SortedVector mSourcePaths; + Vector mRegions; Vector mMatrices; Vector mShaders; Vector mLayers; @@ -577,6 +584,8 @@ public: virtual void concatMatrix(SkMatrix* matrix); virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op); + virtual bool clipPath(SkPath* path, SkRegion::Op op); + virtual bool clipRegion(SkRegion* region, SkRegion::Op op); virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags, uint32_t level = 0); @@ -657,6 +666,10 @@ public: return mSourcePaths; } + const Vector& getRegions() const { + return mRegions; + } + const Vector& getLayers() const { return mLayers; } @@ -802,6 +815,26 @@ private: return paintCopy; } + inline SkRegion* addRegion(SkRegion* region) { + if (!region) { + addInt((int) NULL); + return region; + } + + SkRegion* regionCopy = mRegionMap.valueFor(region); + // TODO: Add generation ID to SkRegion + if (regionCopy == NULL) { + regionCopy = new SkRegion(*region); + // replaceValueFor() performs an add if the entry doesn't exist + mRegionMap.replaceValueFor(region, regionCopy); + mRegions.add(regionCopy); + } + + addInt((int) regionCopy); + + return regionCopy; + } + inline void addDisplayList(DisplayList* displayList) { // TODO: To be safe, the display list should be ref-counted in the // resources cache, but we rely on the caller (UI toolkit) to @@ -876,6 +909,9 @@ private: SortedVector mSourcePaths; + Vector mRegions; + DefaultKeyedVector mRegionMap; + Vector mShaders; DefaultKeyedVector mShaderMap; diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 8cda72989c707..bb1edbb6e2303 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1288,10 +1288,38 @@ bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, bool clipped = mSnapshot->clip(left, top, right, bottom, op); if (clipped) { dirtyClip(); +#if DEBUG_CLIP_REGIONS + if (!isDeferred() && mSnapshot->clipRegion && !mSnapshot->clipRegion->isRect()) { + int count = 0; + Vector 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); +} + Rect* OpenGLRenderer::getClipRect() { return mSnapshot->clipRect; } @@ -3046,6 +3074,19 @@ status_t OpenGLRenderer::drawRects(const float* rects, int count, SkPaint* paint return DrawGlInfo::kStatusDone; } + int color = paint->getColor(); + // If a shader is set, preserve only the alpha + if (mShader) { + color |= 0x00ffffff; + } + SkXfermode::Mode mode = getXfermode(paint->getXfermode()); + + return drawColorRects(rects, count, color, mode); +} + +status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color, + SkXfermode::Mode mode, bool ignoreTransform) { + float left = FLT_MAX; float top = FLT_MAX; float right = FLT_MIN; @@ -3081,13 +3122,6 @@ status_t OpenGLRenderer::drawRects(const float* rects, int count, SkPaint* paint if (count == 0) return DrawGlInfo::kStatusDone; - int color = paint->getColor(); - // If a shader is set, preserve only the alpha - if (mShader) { - color |= 0x00ffffff; - } - SkXfermode::Mode mode = getXfermode(paint->getXfermode()); - setupDraw(); setupDrawNoTexture(); setupDrawColor(color, ((color >> 24) & 0xFF) * mSnapshot->alpha); @@ -3096,7 +3130,7 @@ status_t OpenGLRenderer::drawRects(const float* rects, int count, SkPaint* paint setupDrawBlending(mode); setupDrawProgram(); setupDrawDirtyRegionsDisabled(); - setupDrawModelView(0.0f, 0.0f, 1.0f, 1.0f); + setupDrawModelView(0.0f, 0.0f, 1.0f, 1.0f, ignoreTransform, true); setupDrawColorUniforms(); setupDrawShaderUniforms(); setupDrawColorFilterUniforms(); diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 5520edbcb1d58..f07325f966d8a 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -164,6 +164,8 @@ public: ANDROID_API bool quickReject(float left, float top, float right, float bottom); bool quickRejectNoScissor(float left, float top, float right, float bottom); virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op); + virtual bool clipPath(SkPath* path, SkRegion::Op op); + virtual bool clipRegion(SkRegion* region, SkRegion::Op op); virtual Rect* getClipRect(); virtual status_t drawDisplayList(DisplayList* displayList, Rect& dirty, int32_t flags, @@ -498,7 +500,8 @@ private: /** * Draws a colored rectangle with the specified color. The specified coordinates - * are transformed by the current snapshot's transform matrix. + * are transformed by the current snapshot's transform matrix unless specified + * otherwise. * * @param left The left coordinate of the rectangle * @param top The top coordinate of the rectangle @@ -511,6 +514,20 @@ private: void drawColorRect(float left, float top, float right, float bottom, int color, SkXfermode::Mode mode, bool ignoreTransform = false); + /** + * Draws a series of colored rectangles with the specified color. The specified + * coordinates are transformed by the current snapshot's transform matrix unless + * specified otherwise. + * + * @param rects A list of rectangles, 4 floats (left, top, right, bottom) + * per rectangle + * @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 + */ + status_t drawColorRects(const float* rects, int count, int color, + SkXfermode::Mode mode, bool ignoreTransform = false); + /** * Draws the shape represented by the specified path texture. * This method invokes drawPathTexture() but takes into account diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index fbc84554dcdfc..d947299114c1f 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -130,6 +130,7 @@ bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) { switch (op) { case SkRegion::kIntersect_Op: { if (CC_UNLIKELY(clipRegion)) { + ensureClipRegion(); clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kIntersect_Op); } else { clipped = clipRect->intersect(r); @@ -142,6 +143,7 @@ bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) { } case SkRegion::kUnion_Op: { if (CC_UNLIKELY(clipRegion)) { + ensureClipRegion(); clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kUnion_Op); } else { clipped = clipRect->unionWith(r); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegionActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegionActivity.java index b2a508b0cc16c..d5daa5f673d90 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegionActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ClipRegionActivity.java @@ -18,13 +18,7 @@ package com.android.test.hwui; import android.app.Activity; import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; import android.graphics.Region; import android.os.Bundle; import android.view.View; @@ -50,8 +44,10 @@ public class ClipRegionActivity extends Activity { canvas.save(); canvas.clipRect(100.0f, 100.0f, getWidth() - 100.0f, getHeight() - 100.0f, Region.Op.DIFFERENCE); - canvas.drawARGB(255, 255, 0, 0); + canvas.drawARGB(128, 255, 0, 0); canvas.restore(); + + invalidate(); } } }