diff options
51 files changed, 3285 insertions, 541 deletions
@@ -161,6 +161,7 @@ LOCAL_SRC_FILES += \ core/java/android/view/accessibility/IAccessibilityManager.aidl \ core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \ core/java/android/view/IApplicationToken.aidl \ + core/java/android/view/IAssetAtlas.aidl \ core/java/android/view/IMagnificationCallbacks.aidl \ core/java/android/view/IInputFilter.aidl \ core/java/android/view/IInputFilterHost.aidl \ diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 42f4faf..c7976c3 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -16,8 +16,6 @@ package android.content.res; -import android.os.Trace; -import android.view.View; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -30,6 +28,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable.ConstantState; import android.os.Build; import android.os.Bundle; +import android.os.Trace; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -1985,6 +1984,13 @@ public class Resources { } } + /** + * @hide + */ + public LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() { + return sPreloadedDrawables[0]; + } + private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying, int resourceId, String name) { // We allow preloading of resources even if they vary by font scale (which diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 2ec9a7d..21a03d8 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -21,6 +21,7 @@ import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.DrawFilter; import android.graphics.Matrix; +import android.graphics.NinePatch; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Path; @@ -314,21 +315,21 @@ class GLES20Canvas extends HardwareCanvas { * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_LAYERS = 0; + static final int FLUSH_CACHES_LAYERS = 0; /** * Must match Caches::FlushMode values * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_MODERATE = 1; + static final int FLUSH_CACHES_MODERATE = 1; /** * Must match Caches::FlushMode values * * @see #flushCaches(int) */ - public static final int FLUSH_CACHES_FULL = 2; + static final int FLUSH_CACHES_FULL = 2; /** * Flush caches to reclaim as much memory as possible. The amount of memory @@ -338,10 +339,8 @@ class GLES20Canvas extends HardwareCanvas { * {@link #FLUSH_CACHES_FULL}. * * @param level Hint about the amount of memory to reclaim - * - * @hide */ - public static void flushCaches(int level) { + static void flushCaches(int level) { nFlushCaches(level); } @@ -353,21 +352,28 @@ class GLES20Canvas extends HardwareCanvas { * * @hide */ - public static void terminateCaches() { + static void terminateCaches() { nTerminateCaches(); } private static native void nTerminateCaches(); - /** - * @hide - */ - public static void initCaches() { - nInitCaches(); + static boolean initCaches() { + return nInitCaches(); } - private static native void nInitCaches(); - + private static native boolean nInitCaches(); + + /////////////////////////////////////////////////////////////////////////// + // Atlas + /////////////////////////////////////////////////////////////////////////// + + static void initAtlas(GraphicBuffer buffer, int[] map) { + nInitAtlas(buffer, map, map.length); + } + + private static native void nInitAtlas(GraphicBuffer buffer, int[] map, int count); + /////////////////////////////////////////////////////////////////////////// // Display list /////////////////////////////////////////////////////////////////////////// @@ -718,20 +724,21 @@ class GLES20Canvas extends HardwareCanvas { } @Override - public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { + public void drawPatch(NinePatch patch, RectF dst, Paint paint) { + Bitmap bitmap = patch.getBitmap(); if (bitmap.isRecycled()) throw new IllegalArgumentException("Cannot draw recycled bitmaps"); // Shaders are ignored when drawing patches int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawPatch(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, chunks, + nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mChunk, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } finally { if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier); } } - private static native void nDrawPatch(int renderer, int bitmap, byte[] buffer, byte[] chunks, + private static native void nDrawPatch(int renderer, int bitmap, byte[] chunks, float left, float top, float right, float bottom, int paint); @Override @@ -741,14 +748,14 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, nativePaint); + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } - private static native void nDrawBitmap( - int renderer, int bitmap, byte[] buffer, float left, float top, int paint); + private static native void nDrawBitmap(int renderer, int bitmap, + float left, float top, int paint); @Override public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) { @@ -757,15 +764,13 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, - matrix.native_instance, nativePaint); + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, matrix.native_instance, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } - private static native void nDrawBitmap(int renderer, int bitmap, byte[] buff, - int matrix, int paint); + private static native void nDrawBitmap(int renderer, int bitmap, int matrix, int paint); @Override public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) { @@ -787,7 +792,7 @@ class GLES20Canvas extends HardwareCanvas { bottom = src.bottom; } - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom, + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); @@ -814,14 +819,14 @@ class GLES20Canvas extends HardwareCanvas { bottom = src.bottom; } - nDrawBitmap(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, left, top, right, bottom, + nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom, dst.left, dst.top, dst.right, dst.bottom, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } - private static native void nDrawBitmap(int renderer, int bitmap, byte[] buffer, + private static native void nDrawBitmap(int renderer, int bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float left, float top, float right, float bottom, int paint); @@ -891,14 +896,14 @@ class GLES20Canvas extends HardwareCanvas { int modifiers = paint != null ? setupModifiers(bitmap, paint) : MODIFIER_NONE; try { final int nativePaint = paint == null ? 0 : paint.mNativePaint; - nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, bitmap.mBuffer, meshWidth, meshHeight, + nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, meshWidth, meshHeight, verts, vertOffset, colors, colorOffset, nativePaint); } finally { if (modifiers != MODIFIER_NONE) nResetModifiers(mRenderer, modifiers); } } - private static native void nDrawBitmapMesh(int renderer, int bitmap, byte[] buffer, + private static native void nDrawBitmapMesh(int renderer, int bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, int paint); diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index 3272504..d367267 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -18,6 +18,7 @@ package android.view; import android.graphics.Bitmap; import android.graphics.Matrix; +import android.graphics.NinePatch; import java.util.ArrayList; @@ -29,7 +30,8 @@ class GLES20DisplayList extends DisplayList { // alive as long as the DisplayList is alive. The Bitmap and DisplayList lists // are populated by the GLES20RecordingCanvas during appropriate drawing calls and are // cleared at the start of a new drawing frame or when the view is detached from the window. - final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(5); + final ArrayList<Bitmap> mBitmaps = new ArrayList<Bitmap>(10); + final ArrayList<NinePatch> mNinePatches = new ArrayList<NinePatch>(10); final ArrayList<DisplayList> mChildDisplayLists = new ArrayList<DisplayList>(); private GLES20RecordingCanvas mCanvas; @@ -83,7 +85,12 @@ class GLES20DisplayList extends DisplayList { } mValid = false; + clearReferences(); + } + + void clearReferences() { mBitmaps.clear(); + mNinePatches.clear(); mChildDisplayLists.clear(); } diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java index 7da2451..ec059d5 100644 --- a/core/java/android/view/GLES20RecordingCanvas.java +++ b/core/java/android/view/GLES20RecordingCanvas.java @@ -19,6 +19,7 @@ package android.view; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Matrix; +import android.graphics.NinePatch; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; @@ -62,8 +63,7 @@ class GLES20RecordingCanvas extends GLES20Canvas { } void start() { - mDisplayList.mBitmaps.clear(); - mDisplayList.mChildDisplayLists.clear(); + mDisplayList.clearReferences(); } int end(int nativeDisplayList) { @@ -80,9 +80,10 @@ class GLES20RecordingCanvas extends GLES20Canvas { } @Override - public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { - super.drawPatch(bitmap, chunks, dst, paint); - mDisplayList.mBitmaps.add(bitmap); + public void drawPatch(NinePatch patch, RectF dst, Paint paint) { + super.drawPatch(patch, dst, paint); + mDisplayList.mBitmaps.add(patch.getBitmap()); + mDisplayList.mNinePatches.add(patch); // Shaders in the Paint are ignored when drawing a Bitmap } diff --git a/core/java/android/view/GraphicBuffer.aidl b/core/java/android/view/GraphicBuffer.aidl new file mode 100644 index 0000000..6dc6bed --- /dev/null +++ b/core/java/android/view/GraphicBuffer.aidl @@ -0,0 +1,19 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +parcelable GraphicBuffer; diff --git a/core/java/android/view/GraphicBuffer.java b/core/java/android/view/GraphicBuffer.java new file mode 100644 index 0000000..b4576f3 --- /dev/null +++ b/core/java/android/view/GraphicBuffer.java @@ -0,0 +1,229 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Simple wrapper for the native GraphicBuffer class. + * + * @hide + */ +@SuppressWarnings("UnusedDeclaration") +public class GraphicBuffer implements Parcelable { + // Note: keep usage flags in sync with GraphicBuffer.h and gralloc.h + public static final int USAGE_SW_READ_NEVER = 0x0; + public static final int USAGE_SW_READ_RARELY = 0x2; + public static final int USAGE_SW_READ_OFTEN = 0x3; + public static final int USAGE_SW_READ_MASK = 0xF; + + public static final int USAGE_SW_WRITE_NEVER = 0x0; + public static final int USAGE_SW_WRITE_RARELY = 0x20; + public static final int USAGE_SW_WRITE_OFTEN = 0x30; + public static final int USAGE_SW_WRITE_MASK = 0xF0; + + public static final int USAGE_SOFTWARE_MASK = USAGE_SW_READ_MASK | USAGE_SW_WRITE_MASK; + + public static final int USAGE_PROTECTED = 0x4000; + + public static final int USAGE_HW_TEXTURE = 0x100; + public static final int USAGE_HW_RENDER = 0x200; + public static final int USAGE_HW_2D = 0x400; + public static final int USAGE_HW_COMPOSER = 0x800; + public static final int USAGE_HW_VIDEO_ENCODER = 0x10000; + public static final int USAGE_HW_MASK = 0x71F00; + + private final int mWidth; + private final int mHeight; + private final int mFormat; + private final int mUsage; + // Note: do not rename, this field is used by native code + private final int mNativeObject; + + // These two fields are only used by lock/unlockCanvas() + private Canvas mCanvas; + private int mSaveCount; + + /** + * Creates new <code>GraphicBuffer</code> instance. This method will return null + * if the buffer cannot be created. + * + * @param width The width in pixels of the buffer + * @param height The height in pixels of the buffer + * @param format The format of each pixel as specified in {@link PixelFormat} + * @param usage Hint indicating how the buffer will be used + * + * @return A <code>GraphicBuffer</code> instance or null + */ + public static GraphicBuffer create(int width, int height, int format, int usage) { + int nativeObject = nCreateGraphicBuffer(width, height, format, usage); + if (nativeObject != 0) { + return new GraphicBuffer(width, height, format, usage, nativeObject); + } + return null; + } + + /** + * Private use only. See {@link #create(int, int, int, int)}. + */ + private GraphicBuffer(int width, int height, int format, int usage, int nativeObject) { + mWidth = width; + mHeight = height; + mFormat = format; + mUsage = usage; + mNativeObject = nativeObject; + } + + /** + * Returns the width of this buffer in pixels. + */ + public int getWidth() { + return mWidth; + } + + /** + * Returns the height of this buffer in pixels. + */ + public int getHeight() { + return mHeight; + } + + /** + * Returns the pixel format of this buffer. The pixel format must be one of + * the formats defined in {@link PixelFormat}. + */ + public int getFormat() { + return mFormat; + } + + /** + * Returns the usage hint set on this buffer. + */ + public int getUsage() { + return mUsage; + } + + /** + * <p>Start editing the pixels in the buffer. A null is returned if the buffer + * cannot be locked for editing.</p> + * + * <p>The content of the buffer is preserved between unlockCanvas() + * and lockCanvas().</p> + * + * @return A Canvas used to draw into the buffer, or null. + * + * @see #lockCanvas(android.graphics.Rect) + * @see #unlockCanvasAndPost(android.graphics.Canvas) + */ + public Canvas lockCanvas() { + return lockCanvas(null); + } + + /** + * Just like {@link #lockCanvas()} but allows specification of a dirty + * rectangle. + * + * @param dirty Area of the buffer that may be modified. + + * @return A Canvas used to draw into the surface or null + * + * @see #lockCanvas() + * @see #unlockCanvasAndPost(android.graphics.Canvas) + */ + public Canvas lockCanvas(Rect dirty) { + if (mCanvas == null) { + mCanvas = new Canvas(); + } + + if (nLockCanvas(mNativeObject, mCanvas, dirty)) { + mSaveCount = mCanvas.save(); + return mCanvas; + } + + return null; + } + + /** + * Finish editing pixels in the buffer. + * + * @param canvas The Canvas previously returned by lockCanvas() + * + * @see #lockCanvas() + * @see #lockCanvas(android.graphics.Rect) + */ + public void unlockCanvasAndPost(Canvas canvas) { + if (mCanvas != null && canvas == mCanvas) { + canvas.restoreToCount(mSaveCount); + mSaveCount = 0; + + nUnlockCanvasAndPost(mNativeObject, mCanvas); + } + } + + @Override + protected void finalize() throws Throwable { + try { + nDestroyGraphicBuffer(mNativeObject); + } finally { + super.finalize(); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeInt(mFormat); + dest.writeInt(mUsage); + nWriteGraphicBufferToParcel(mNativeObject, dest); + } + + public static final Parcelable.Creator<GraphicBuffer> CREATOR = + new Parcelable.Creator<GraphicBuffer>() { + public GraphicBuffer createFromParcel(Parcel in) { + int width = in.readInt(); + int height = in.readInt(); + int format = in.readInt(); + int usage = in.readInt(); + int nativeObject = nReadGraphicBufferFromParcel(in); + if (nativeObject != 0) { + return new GraphicBuffer(width, height, format, usage, nativeObject); + } + return null; + } + + public GraphicBuffer[] newArray(int size) { + return new GraphicBuffer[size]; + } + }; + + private static native int nCreateGraphicBuffer(int width, int height, int format, int usage); + private static native void nDestroyGraphicBuffer(int nativeObject); + private static native void nWriteGraphicBufferToParcel(int nativeObject, Parcel dest); + private static native int nReadGraphicBufferFromParcel(Parcel in); + private static native boolean nLockCanvas(int nativeObject, Canvas canvas, Rect dirty); + private static native boolean nUnlockCanvasAndPost(int nativeObject, Canvas canvas); +} diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 8308459..0632e1d 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -24,7 +24,10 @@ import android.opengl.EGL14; import android.opengl.GLUtils; import android.opengl.ManagedEGLContext; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; @@ -968,7 +971,7 @@ public abstract class HardwareRenderer { if (fallback) { // we'll try again if it was context lost setRequested(false); - Log.w(LOG_TAG, "Mountain View, we've had a problem here. " + Log.w(LOG_TAG, "Mountain View, we've had a problem here. " + "Switching back to software rendering."); } } @@ -976,7 +979,7 @@ public abstract class HardwareRenderer { @Override boolean initialize(Surface surface) throws Surface.OutOfResourcesException { if (isRequested() && !isEnabled()) { - initializeEgl(); + boolean contextCreated = initializeEgl(); mGl = createEglSurface(surface); mDestroyed = false; @@ -991,6 +994,10 @@ public abstract class HardwareRenderer { mCanvas.setName(mName); } setEnabled(true); + + if (contextCreated) { + initAtlas(); + } } return mCanvas != null; @@ -1010,7 +1017,7 @@ public abstract class HardwareRenderer { abstract int[] getConfig(boolean dirtyRegions); - void initializeEgl() { + boolean initializeEgl() { synchronized (sEglLock) { if (sEgl == null && sEglConfig == null) { sEgl = (EGL10) EGLContext.getEGL(); @@ -1043,7 +1050,10 @@ public abstract class HardwareRenderer { if (mEglContext == null) { mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); sEglContextStorage.set(createManagedContext(mEglContext)); + return true; } + + return false; } private EGLConfig loadEglConfig() { @@ -1181,6 +1191,7 @@ public abstract class HardwareRenderer { } abstract void initCaches(); + abstract void initAtlas(); EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; @@ -1193,6 +1204,7 @@ public abstract class HardwareRenderer { "Could not create an EGL context. eglCreateContext failed with error: " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } + return context; } @@ -1788,7 +1800,27 @@ public abstract class HardwareRenderer { @Override void initCaches() { - GLES20Canvas.initCaches(); + if (GLES20Canvas.initCaches()) { + // Caches were (re)initialized, rebind atlas + initAtlas(); + } + } + + @Override + void initAtlas() { + IBinder binder = ServiceManager.getService("assetatlas"); + IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); + try { + GraphicBuffer buffer = atlas.getBuffer(); + if (buffer != null) { + int[] map = atlas.getMap(); + if (map != null) { + GLES20Canvas.initAtlas(buffer, map); + } + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Could not acquire atlas", e); + } } @Override diff --git a/core/java/android/view/IAssetAtlas.aidl b/core/java/android/view/IAssetAtlas.aidl new file mode 100644 index 0000000..2595179 --- /dev/null +++ b/core/java/android/view/IAssetAtlas.aidl @@ -0,0 +1,47 @@ +/** + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.view.GraphicBuffer; + +/** + * Programming interface to the system assets atlas. This atlas, when + * present, holds preloaded drawable in a single, shareable graphics + * buffer. This allows multiple processes to share the same data to + * save up on memory. + * + * @hide + */ +interface IAssetAtlas { + /** + * Returns the atlas buffer (texture) or null if the atlas is + * not available yet. + */ + GraphicBuffer getBuffer(); + + /** + * Returns the map of the bitmaps stored in the atlas or null + * if the atlas is not available yet. + * + * Each bitmap is represented by several entries in the array: + * int0: SkBitmap*, the native bitmap object + * int1: x position + * int2: y position + * int3: rotated, 1 if the bitmap must be rotated, 0 otherwise + */ + int[] getMap(); +} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f34d390..47c40d2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -23,7 +23,6 @@ import android.content.ClipDescription; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; @@ -108,8 +107,6 @@ public final class ViewRootImpl implements ViewParent, private static final boolean DEBUG_FPS = false; private static final boolean DEBUG_INPUT_PROCESSING = false || LOCAL_LOGV; - private static final boolean USE_RENDER_THREAD = false; - /** * Set this system property to true to force the view hierarchy to render * at 60 Hz. This can be used to measure the potential framerate. @@ -131,10 +128,6 @@ public final class ViewRootImpl implements ViewParent, static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList<ComponentCallbacks>(); - private static boolean sUseRenderThread = false; - private static boolean sRenderThreadQueried = false; - private static final Object[] sRenderThreadQueryLock = new Object[0]; - final Context mContext; final IWindowSession mWindowSession; final Display mDisplay; @@ -375,35 +368,6 @@ public final class ViewRootImpl implements ViewParent, loadSystemProperties(); } - /** - * @return True if the application requests the use of a separate render thread, - * false otherwise - */ - private static boolean isRenderThreadRequested(Context context) { - if (USE_RENDER_THREAD) { - synchronized (sRenderThreadQueryLock) { - if (!sRenderThreadQueried) { - final PackageManager packageManager = context.getPackageManager(); - final String packageName = context.getApplicationInfo().packageName; - try { - ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, - PackageManager.GET_META_DATA); - if (applicationInfo.metaData != null) { - sUseRenderThread = applicationInfo.metaData.getBoolean( - "android.graphics.renderThread", false); - } - } catch (PackageManager.NameNotFoundException e) { - } finally { - sRenderThreadQueried = true; - } - } - return sUseRenderThread; - } - } else { - return false; - } - } - public static void addFirstDrawHandler(Runnable callback) { synchronized (sFirstDrawHandlers) { if (!sFirstDrawComplete) { @@ -481,7 +445,7 @@ public final class ViewRootImpl implements ViewParent, // If the application owns the surface, don't enable hardware acceleration if (mSurfaceHolder == null) { - enableHardwareAcceleration(mView.getContext(), attrs); + enableHardwareAcceleration(attrs); } boolean restore = false; @@ -689,7 +653,7 @@ public final class ViewRootImpl implements ViewParent, } } - private void enableHardwareAcceleration(Context context, WindowManager.LayoutParams attrs) { + private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) { mAttachInfo.mHardwareAccelerated = false; mAttachInfo.mHardwareAccelerationRequested = false; @@ -729,11 +693,6 @@ public final class ViewRootImpl implements ViewParent, return; } - final boolean renderThread = isRenderThreadRequested(context); - if (renderThread) { - Log.i(HardwareRenderer.LOG_TAG, "Render threat initiated"); - } - if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.destroy(true); } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index edc0baf..d0d3508 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -50,6 +50,7 @@ LOCAL_SRC_FILES:= \ android_view_KeyEvent.cpp \ android_view_KeyCharacterMap.cpp \ android_view_HardwareRenderer.cpp \ + android_view_GraphicBuffer.cpp \ android_view_GLES20DisplayList.cpp \ android_view_GLES20Canvas.cpp \ android_view_MotionEvent.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 53fde48..e243ed7 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -116,6 +116,7 @@ extern int register_android_graphics_SurfaceTexture(JNIEnv* env); extern int register_android_graphics_Xfermode(JNIEnv* env); extern int register_android_graphics_PixelFormat(JNIEnv* env); extern int register_android_view_DisplayEventReceiver(JNIEnv* env); +extern int register_android_view_GraphicBuffer(JNIEnv* env); extern int register_android_view_GLES20DisplayList(JNIEnv* env); extern int register_android_view_GLES20Canvas(JNIEnv* env); extern int register_android_view_HardwareRenderer(JNIEnv* env); @@ -1111,6 +1112,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_nio_utils), REG_JNI(register_android_graphics_PixelFormat), REG_JNI(register_android_graphics_Graphics), + REG_JNI(register_android_view_GraphicBuffer), REG_JNI(register_android_view_GLES20DisplayList), REG_JNI(register_android_view_GLES20Canvas), REG_JNI(register_android_view_HardwareRenderer), diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index b87fe27..c8fa290 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -16,18 +16,19 @@ #define LOG_TAG "OpenGLRenderer" -#include <EGL/egl.h> - #include "jni.h" #include "GraphicsJNI.h" #include <nativehelper/JNIHelp.h> +#include "android_view_GraphicBuffer.h" + #include <android_runtime/AndroidRuntime.h> #include <android_runtime/android_graphics_SurfaceTexture.h> -#include <gui/GLConsumer.h> #include <androidfw/ResourceTypes.h> +#include <gui/GLConsumer.h> + #include <private/hwui/DrawGlInfo.h> #include <cutils/properties.h> @@ -99,10 +100,11 @@ static void android_view_GLES20Canvas_flushCaches(JNIEnv* env, jobject clazz, } } -static void android_view_GLES20Canvas_initCaches(JNIEnv* env, jobject clazz) { +static bool android_view_GLES20Canvas_initCaches(JNIEnv* env, jobject clazz) { if (Caches::hasInstance()) { - Caches::getInstance().init(); + return Caches::getInstance().init(); } + return false; } static void android_view_GLES20Canvas_terminateCaches(JNIEnv* env, jobject clazz) { @@ -112,6 +114,21 @@ static void android_view_GLES20Canvas_terminateCaches(JNIEnv* env, jobject clazz } // ---------------------------------------------------------------------------- +// Caching +// ---------------------------------------------------------------------------- + +static void android_view_GLES20Canvas_initAtlas(JNIEnv* env, jobject clazz, + jobject graphicBuffer, jintArray atlasMapArray, jint count) { + + sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer); + jint* atlasMap = env->GetIntArrayElements(atlasMapArray, NULL); + + Caches::getInstance().assetAtlas.init(buffer, atlasMap, count); + + env->ReleaseIntArrayElements(atlasMapArray, atlasMap, 0); +} + +// ---------------------------------------------------------------------------- // Constructors // ---------------------------------------------------------------------------- @@ -350,31 +367,20 @@ static void android_view_GLES20Canvas_concatMatrix(JNIEnv* env, jobject clazz, // ---------------------------------------------------------------------------- static void android_view_GLES20Canvas_drawBitmap(JNIEnv* env, jobject clazz, - OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer, - jfloat left, jfloat top, SkPaint* paint) { - // This object allows the renderer to allocate a global JNI ref to the buffer object. - JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - + OpenGLRenderer* renderer, SkBitmap* bitmap, jfloat left, jfloat top, SkPaint* paint) { renderer->drawBitmap(bitmap, left, top, paint); } static void android_view_GLES20Canvas_drawBitmapRect(JNIEnv* env, jobject clazz, - OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer, + OpenGLRenderer* renderer, SkBitmap* bitmap, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, float dstBottom, SkPaint* paint) { - // This object allows the renderer to allocate a global JNI ref to the buffer object. - JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - renderer->drawBitmap(bitmap, srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom, paint); } static void android_view_GLES20Canvas_drawBitmapMatrix(JNIEnv* env, jobject clazz, - OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer, SkMatrix* matrix, - SkPaint* paint) { - // This object allows the renderer to allocate a global JNI ref to the buffer object. - JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - + OpenGLRenderer* renderer, SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint) { renderer->drawBitmap(bitmap, matrix, paint); } @@ -404,12 +410,8 @@ static void android_view_GLES20Canvas_drawBitmapData(JNIEnv* env, jobject clazz, } static void android_view_GLES20Canvas_drawBitmapMesh(JNIEnv* env, jobject clazz, - OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer, - jint meshWidth, jint meshHeight, jfloatArray vertices, jint offset, - jintArray colors, jint colorOffset, SkPaint* paint) { - // This object allows the renderer to allocate a global JNI ref to the buffer object. - JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - + OpenGLRenderer* renderer, SkBitmap* bitmap, jint meshWidth, jint meshHeight, + jfloatArray vertices, jint offset, jintArray colors, jint colorOffset, SkPaint* paint) { jfloat* verticesArray = vertices ? env->GetFloatArrayElements(vertices, NULL) + offset : NULL; jint* colorsArray = colors ? env->GetIntArrayElements(colors, NULL) + colorOffset : NULL; @@ -420,18 +422,13 @@ static void android_view_GLES20Canvas_drawBitmapMesh(JNIEnv* env, jobject clazz, } static void android_view_GLES20Canvas_drawPatch(JNIEnv* env, jobject clazz, - OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray buffer, jbyteArray chunks, + OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray chunks, float left, float top, float right, float bottom, SkPaint* paint) { - // This object allows the renderer to allocate a global JNI ref to the buffer object. - JavaHeapBitmapRef bitmapRef(env, bitmap, buffer); - jbyte* storage = env->GetByteArrayElements(chunks, NULL); Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(storage); Res_png_9patch::deserialize(patch); - renderer->drawPatch(bitmap, &patch->xDivs[0], &patch->yDivs[0], - &patch->colors[0], patch->numXDivs, patch->numYDivs, patch->numColors, - left, top, right, bottom, paint); + renderer->drawPatch(bitmap, patch, left, top, right, bottom, paint); env->ReleaseByteArrayElements(chunks, storage, 0); } @@ -932,9 +929,12 @@ static JNINativeMethod gMethods[] = { #ifdef USE_OPENGL_RENDERER { "nFlushCaches", "(I)V", (void*) android_view_GLES20Canvas_flushCaches }, - { "nInitCaches", "()V", (void*) android_view_GLES20Canvas_initCaches }, + { "nInitCaches", "()Z", (void*) android_view_GLES20Canvas_initCaches }, { "nTerminateCaches", "()V", (void*) android_view_GLES20Canvas_terminateCaches }, + { "nInitAtlas", "(Landroid/view/GraphicBuffer;[II)V", + (void*) android_view_GLES20Canvas_initAtlas }, + { "nCreateRenderer", "()I", (void*) android_view_GLES20Canvas_createRenderer }, { "nDestroyRenderer", "(I)V", (void*) android_view_GLES20Canvas_destroyRenderer }, { "nSetViewport", "(III)V", (void*) android_view_GLES20Canvas_setViewport }, @@ -977,14 +977,14 @@ static JNINativeMethod gMethods[] = { { "nGetMatrix", "(II)V", (void*) android_view_GLES20Canvas_getMatrix }, { "nConcatMatrix", "(II)V", (void*) android_view_GLES20Canvas_concatMatrix }, - { "nDrawBitmap", "(II[BFFI)V", (void*) android_view_GLES20Canvas_drawBitmap }, - { "nDrawBitmap", "(II[BFFFFFFFFI)V",(void*) android_view_GLES20Canvas_drawBitmapRect }, - { "nDrawBitmap", "(II[BII)V", (void*) android_view_GLES20Canvas_drawBitmapMatrix }, + { "nDrawBitmap", "(IIFFI)V", (void*) android_view_GLES20Canvas_drawBitmap }, + { "nDrawBitmap", "(IIFFFFFFFFI)V", (void*) android_view_GLES20Canvas_drawBitmapRect }, + { "nDrawBitmap", "(IIII)V", (void*) android_view_GLES20Canvas_drawBitmapMatrix }, { "nDrawBitmap", "(I[IIIFFIIZI)V", (void*) android_view_GLES20Canvas_drawBitmapData }, - { "nDrawBitmapMesh", "(II[BII[FI[III)V",(void*) android_view_GLES20Canvas_drawBitmapMesh }, + { "nDrawBitmapMesh", "(IIII[FI[III)V", (void*) android_view_GLES20Canvas_drawBitmapMesh }, - { "nDrawPatch", "(II[B[BFFFFI)V", (void*) android_view_GLES20Canvas_drawPatch }, + { "nDrawPatch", "(II[BFFFFI)V", (void*) android_view_GLES20Canvas_drawPatch }, { "nDrawColor", "(III)V", (void*) android_view_GLES20Canvas_drawColor }, { "nDrawRect", "(IFFFFI)V", (void*) android_view_GLES20Canvas_drawRect }, diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp new file mode 100644 index 0000000..d68c0b2 --- /dev/null +++ b/core/jni/android_view_GraphicBuffer.cpp @@ -0,0 +1,331 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "GraphicBuffer" + +#include "jni.h" +#include "JNIHelp.h" + +#include "android_os_Parcel.h" +#include "android_view_GraphicBuffer.h" + +#include <android_runtime/AndroidRuntime.h> + +#include <binder/Parcel.h> + +#include <ui/GraphicBuffer.h> +#include <ui/PixelFormat.h> + +#include <gui/IGraphicBufferAlloc.h> +#include <gui/ISurfaceComposer.h> + +#include <SkCanvas.h> +#include <SkBitmap.h> + +#include <private/gui/ComposerService.h> + +namespace android { + +// ---------------------------------------------------------------------------- +// Defines +// ---------------------------------------------------------------------------- + +// Debug +#define DEBUG_GRAPHIC_BUFFER 0 + +// Debug +#if DEBUG_GRAPHIC_BUFFER + #define GB_LOGD(...) ALOGD(__VA_ARGS__) + #define GB_LOGW(...) ALOGW(__VA_ARGS__) +#else + #define GB_LOGD(...) + #define GB_LOGW(...) +#endif + +#define LOCK_CANVAS_USAGE GraphicBuffer::USAGE_SW_READ_OFTEN | GraphicBuffer::USAGE_SW_WRITE_OFTEN + +// ---------------------------------------------------------------------------- +// JNI Helpers +// ---------------------------------------------------------------------------- + +static struct { + jfieldID mNativeObject; +} gGraphicBufferClassInfo; + +static struct { + jmethodID set; + jfieldID left; + jfieldID top; + jfieldID right; + jfieldID bottom; +} gRectClassInfo; + +static struct { + jfieldID mFinalizer; + jfieldID mNativeCanvas; + jfieldID mSurfaceFormat; +} gCanvasClassInfo; + +static struct { + jfieldID mNativeCanvas; +} gCanvasFinalizerClassInfo; + +#define GET_INT(object, field) \ + env->GetIntField(object, field) + +#define SET_INT(object, field, value) \ + env->SetIntField(object, field, value) + +#define INVOKEV(object, method, ...) \ + env->CallVoidMethod(object, method, __VA_ARGS__) + +// ---------------------------------------------------------------------------- +// Types +// ---------------------------------------------------------------------------- + +class GraphicBufferWrapper { +public: + GraphicBufferWrapper(const sp<GraphicBuffer>& buffer): buffer(buffer) { + } + + sp<GraphicBuffer> buffer; +}; + +// ---------------------------------------------------------------------------- +// GraphicBuffer lifecycle +// ---------------------------------------------------------------------------- + +static GraphicBufferWrapper* android_view_GraphiceBuffer_create(JNIEnv* env, jobject clazz, + jint width, jint height, jint format, jint usage) { + + sp<ISurfaceComposer> composer(ComposerService::getComposerService()); + sp<IGraphicBufferAlloc> alloc(composer->createGraphicBufferAlloc()); + if (alloc == NULL) { + GB_LOGW("createGraphicBufferAlloc() failed in GraphicBuffer.create()"); + return NULL; + } + + status_t error; + sp<GraphicBuffer> buffer(alloc->createGraphicBuffer(width, height, format, usage, &error)); + if (buffer == NULL) { + GB_LOGW("createGraphicBuffer() failed in GraphicBuffer.create()"); + return NULL; + } + + return new GraphicBufferWrapper(buffer); +} + +static void android_view_GraphiceBuffer_destroy(JNIEnv* env, jobject clazz, + GraphicBufferWrapper* wrapper) { + delete wrapper; +} + +// ---------------------------------------------------------------------------- +// Canvas management +// ---------------------------------------------------------------------------- + +static inline void swapCanvasPtr(JNIEnv* env, jobject canvasObj, SkCanvas* newCanvas) { + jobject canvasFinalizerObj = env->GetObjectField(canvasObj, gCanvasClassInfo.mFinalizer); + SkCanvas* previousCanvas = reinterpret_cast<SkCanvas*>( + GET_INT(canvasObj, gCanvasClassInfo.mNativeCanvas)); + SET_INT(canvasObj, gCanvasClassInfo.mNativeCanvas, (int) newCanvas); + SET_INT(canvasFinalizerObj, gCanvasFinalizerClassInfo.mNativeCanvas, (int) newCanvas); + SkSafeUnref(previousCanvas); +} + +static inline SkBitmap::Config convertPixelFormat(int32_t format) { + switch (format) { + case PIXEL_FORMAT_RGBA_8888: + return SkBitmap::kARGB_8888_Config; + case PIXEL_FORMAT_RGBX_8888: + return SkBitmap::kARGB_8888_Config; + case PIXEL_FORMAT_RGB_565: + return SkBitmap::kRGB_565_Config; + default: + return SkBitmap::kNo_Config; + } +} + +static jboolean android_view_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, + GraphicBufferWrapper* wrapper, jobject canvas, jobject dirtyRect) { + + if (!wrapper) { + return false; + } + + sp<GraphicBuffer> buffer(wrapper->buffer); + + Rect rect; + if (dirtyRect) { + rect.left = GET_INT(dirtyRect, gRectClassInfo.left); + rect.top = GET_INT(dirtyRect, gRectClassInfo.top); + rect.right = GET_INT(dirtyRect, gRectClassInfo.right); + rect.bottom = GET_INT(dirtyRect, gRectClassInfo.bottom); + } else { + rect.set(Rect(buffer->getWidth(), buffer->getHeight())); + } + + void* bits = NULL; + status_t status = buffer->lock(LOCK_CANVAS_USAGE, rect, &bits); + + if (status) return false; + if (!bits) { + buffer->unlock(); + return false; + } + + ssize_t bytesCount = buffer->getStride() * bytesPerPixel(buffer->getPixelFormat()); + + SkBitmap bitmap; + bitmap.setConfig(convertPixelFormat(buffer->getPixelFormat()), + buffer->getWidth(), buffer->getHeight(), bytesCount); + + if (buffer->getWidth() > 0 && buffer->getHeight() > 0) { + bitmap.setPixels(bits); + } else { + bitmap.setPixels(NULL); + } + + SET_INT(canvas, gCanvasClassInfo.mSurfaceFormat, buffer->getPixelFormat()); + + SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap)); + swapCanvasPtr(env, canvas, nativeCanvas); + + SkRect clipRect; + clipRect.set(rect.left, rect.top, rect.right, rect.bottom); + nativeCanvas->clipRect(clipRect); + + if (dirtyRect) { + INVOKEV(dirtyRect, gRectClassInfo.set, + int(rect.left), int(rect.top), int(rect.right), int(rect.bottom)); + } + + return true; +} + +static jboolean android_view_GraphicBuffer_unlockCanvasAndPost(JNIEnv* env, jobject, + GraphicBufferWrapper* wrapper, jobject canvas) { + + SkCanvas* nativeCanvas = SkNEW(SkCanvas); + swapCanvasPtr(env, canvas, nativeCanvas); + + if (wrapper) { + status_t status = wrapper->buffer->unlock(); + return status == 0; + } + + return false; +} + +// ---------------------------------------------------------------------------- +// Serialization +// ---------------------------------------------------------------------------- + +static void android_view_GraphiceBuffer_write(JNIEnv* env, jobject clazz, + GraphicBufferWrapper* wrapper, jobject dest) { + Parcel* parcel = parcelForJavaObject(env, dest); + if (parcel) { + parcel->write(*wrapper->buffer); + } +} + +static GraphicBufferWrapper* android_view_GraphiceBuffer_read(JNIEnv* env, jobject clazz, + jobject in) { + + Parcel* parcel = parcelForJavaObject(env, in); + if (parcel) { + sp<GraphicBuffer> buffer = new GraphicBuffer(); + parcel->read(*buffer); + return new GraphicBufferWrapper(buffer); + } + + return NULL; +} + +// ---------------------------------------------------------------------------- +// External helpers +// ---------------------------------------------------------------------------- + +sp<GraphicBuffer> graphicBufferForJavaObject(JNIEnv* env, jobject obj) { + if (obj) { + jint nativeObject = env->GetIntField(obj, gGraphicBufferClassInfo.mNativeObject); + GraphicBufferWrapper* wrapper = (GraphicBufferWrapper*) nativeObject; + if (wrapper != NULL) { + sp<GraphicBuffer> buffer(wrapper->buffer); + return buffer; + } + } + return NULL; +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(!var, "Unable to find method " methodName); + +const char* const kClassPathName = "android/view/GraphicBuffer"; + +static JNINativeMethod gMethods[] = { + { "nCreateGraphicBuffer", "(IIII)I", (void*) android_view_GraphiceBuffer_create }, + { "nDestroyGraphicBuffer", "(I)V", (void*) android_view_GraphiceBuffer_destroy }, + + { "nWriteGraphicBufferToParcel", "(ILandroid/os/Parcel;)V", + (void*) android_view_GraphiceBuffer_write }, + { "nReadGraphicBufferFromParcel", "(Landroid/os/Parcel;)I", + (void*) android_view_GraphiceBuffer_read }, + + { "nLockCanvas", "(ILandroid/graphics/Canvas;Landroid/graphics/Rect;)Z", + (void*) android_view_GraphicBuffer_lockCanvas }, + { "nUnlockCanvasAndPost", "(ILandroid/graphics/Canvas;)Z", + (void*) android_view_GraphicBuffer_unlockCanvasAndPost }, +}; + +int register_android_view_GraphicBuffer(JNIEnv* env) { + jclass clazz; + FIND_CLASS(clazz, "android/view/GraphicBuffer"); + GET_FIELD_ID(gGraphicBufferClassInfo.mNativeObject, clazz, "mNativeObject", "I"); + + FIND_CLASS(clazz, "android/graphics/Rect"); + GET_METHOD_ID(gRectClassInfo.set, clazz, "set", "(IIII)V"); + GET_FIELD_ID(gRectClassInfo.left, clazz, "left", "I"); + GET_FIELD_ID(gRectClassInfo.top, clazz, "top", "I"); + GET_FIELD_ID(gRectClassInfo.right, clazz, "right", "I"); + GET_FIELD_ID(gRectClassInfo.bottom, clazz, "bottom", "I"); + + FIND_CLASS(clazz, "android/graphics/Canvas"); + GET_FIELD_ID(gCanvasClassInfo.mFinalizer, clazz, "mFinalizer", + "Landroid/graphics/Canvas$CanvasFinalizer;"); + GET_FIELD_ID(gCanvasClassInfo.mNativeCanvas, clazz, "mNativeCanvas", "I"); + GET_FIELD_ID(gCanvasClassInfo.mSurfaceFormat, clazz, "mSurfaceFormat", "I"); + + FIND_CLASS(clazz, "android/graphics/Canvas$CanvasFinalizer"); + GET_FIELD_ID(gCanvasFinalizerClassInfo.mNativeCanvas, clazz, "mNativeCanvas", "I"); + + return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +}; diff --git a/core/jni/android_view_GraphicBuffer.h b/core/jni/android_view_GraphicBuffer.h new file mode 100644 index 0000000..509587c --- /dev/null +++ b/core/jni/android_view_GraphicBuffer.h @@ -0,0 +1,27 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ui/GraphicBuffer.h> + +#include "jni.h" + +namespace android { + +// This function does not perform any type checking, the specified +// object must be an instance of android.view.GraphicBuffer +extern sp<GraphicBuffer> graphicBufferForJavaObject(JNIEnv* env, jobject obj); + +} diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index a1985bc..d515696 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -126,12 +126,12 @@ static void android_view_TextureView_destroyNativeWindow(JNIEnv* env, jobject te } static inline void swapCanvasPtr(JNIEnv* env, jobject canvasObj, SkCanvas* newCanvas) { - jobject canvasFinalizerObj = env->GetObjectField(canvasObj, gCanvasClassInfo.mFinalizer); - SkCanvas* previousCanvas = reinterpret_cast<SkCanvas*>( + jobject canvasFinalizerObj = env->GetObjectField(canvasObj, gCanvasClassInfo.mFinalizer); + SkCanvas* previousCanvas = reinterpret_cast<SkCanvas*>( env->GetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas)); - env->SetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas, (int)newCanvas); - env->SetIntField(canvasFinalizerObj, gCanvasFinalizerClassInfo.mNativeCanvas, (int)newCanvas); - SkSafeUnref(previousCanvas); + env->SetIntField(canvasObj, gCanvasClassInfo.mNativeCanvas, (int)newCanvas); + env->SetIntField(canvasFinalizerObj, gCanvasFinalizerClassInfo.mNativeCanvas, (int)newCanvas); + SkSafeUnref(previousCanvas); } static jboolean android_view_TextureView_lockCanvas(JNIEnv* env, jobject, diff --git a/graphics/java/android/graphics/Atlas.java b/graphics/java/android/graphics/Atlas.java new file mode 100644 index 0000000..39a5a53 --- /dev/null +++ b/graphics/java/android/graphics/Atlas.java @@ -0,0 +1,441 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +/** + * @hide + */ +public class Atlas { + /** + * This flag indicates whether the packing algorithm will attempt + * to rotate entries to make them fit better in the atlas. + */ + public static final int FLAG_ALLOW_ROTATIONS = 0x1; + /** + * This flag indicates whether the packing algorithm should leave + * an empty 1 pixel wide border around each bitmap. This border can + * be useful if the content of the atlas will be used in OpenGL using + * bilinear filtering. + */ + public static final int FLAG_ADD_PADDING = 0x2; + /** + * Default flags: allow rotations and add padding. + */ + public static final int FLAG_DEFAULTS = FLAG_ADD_PADDING; + + /** + * Each type defines a different packing algorithm that can + * be used by an {@link Atlas}. The best algorithm to use + * will depend on the source dataset and the dimensions of + * the atlas. + */ + public enum Type { + SliceMinArea, + SliceMaxArea, + SliceShortAxis, + SliceLongAxis + } + + /** + * Represents a bitmap packed in the atlas. Each entry has a location in + * pixels in the atlas and a rotation flag. If the entry was rotated, the + * bitmap must be rotated by 90 degrees (in either direction as long as + * the origin remains the same) before being rendered into the atlas. + */ + public static class Entry { + /** + * Location, in pixels, of the bitmap on the X axis in the atlas. + */ + public int x; + /** + * Location, in pixels, of the bitmap on the Y axis in the atlas. + */ + public int y; + + /** + * If true, the bitmap must be rotated 90 degrees in the atlas. + */ + public boolean rotated; + } + + private final Policy mPolicy; + + /** + * Creates a new atlas with the specified algorithm and dimensions + * in pixels. Calling this constructor is equivalent to calling + * {@link #Atlas(Atlas.Type, int, int, int)} with {@link #FLAG_DEFAULTS}. + * + * @param type The algorithm to use to pack rectangles in the atlas + * @param width The width of the atlas in pixels + * @param height The height of the atlas in pixels + * + * @see #Atlas(Atlas.Type, int, int, int) + */ + public Atlas(Type type, int width, int height) { + this(type, width, height, FLAG_DEFAULTS); + } + + /** + * Creates a new atlas with the specified algorithm and dimensions + * in pixels. A set of flags can also be specified to control the + * behavior of the atlas. + * + * @param type The algorithm to use to pack rectangles in the atlas + * @param width The width of the atlas in pixels + * @param height The height of the atlas in pixels + * @param flags Optional flags to control the behavior of the atlas: + * {@link #FLAG_ADD_PADDING}, {@link #FLAG_ALLOW_ROTATIONS} + * + * @see #Atlas(Atlas.Type, int, int) + */ + public Atlas(Type type, int width, int height, int flags) { + mPolicy = findPolicy(type, width, height, flags); + } + + /** + * Packs a rectangle of the specified dimensions in this atlas. + * + * @param width The width of the rectangle to pack in the atlas + * @param height The height of the rectangle to pack in the atlas + * + * @return An {@link Entry} instance if the rectangle was packed in + * the atlas, or null if the rectangle could not fit + * + * @see #pack(int, int, Atlas.Entry) + */ + public Entry pack(int width, int height) { + return pack(width, height, null); + } + + /** + * Packs a rectangle of the specified dimensions in this atlas. + * + * @param width The width of the rectangle to pack in the atlas + * @param height The height of the rectangle to pack in the atlas + * @param entry Out parameter that will be filled in with the location + * and attributes of the packed rectangle, can be null + * + * @return An {@link Entry} instance if the rectangle was packed in + * the atlas, or null if the rectangle could not fit + * + * @see #pack(int, int) + */ + public Entry pack(int width, int height, Entry entry) { + if (entry == null) entry = new Entry(); + return mPolicy.pack(width, height, entry); + } + + private static Policy findPolicy(Type type, int width, int height, int flags) { + switch (type) { + case SliceMinArea: + return new SlicePolicy(width, height, flags, + new SlicePolicy.MinAreaSplitDecision()); + case SliceMaxArea: + return new SlicePolicy(width, height, flags, + new SlicePolicy.MaxAreaSplitDecision()); + case SliceShortAxis: + return new SlicePolicy(width, height, flags, + new SlicePolicy.ShorterFreeAxisSplitDecision()); + case SliceLongAxis: + return new SlicePolicy(width, height, flags, + new SlicePolicy.LongerFreeAxisSplitDecision()); + } + return null; + } + + /** + * A policy defines how the atlas performs the packing operation. + */ + private static abstract class Policy { + abstract Entry pack(int width, int height, Entry entry); + } + + /** + * The Slice algorightm divides the remaining empty space either + * horizontally or vertically after a bitmap is placed in the atlas. + * + * NOTE: the algorithm is explained below using a tree but is + * implemented using a linked list instead for performance reasons. + * + * The algorithm starts with a single empty cell covering the entire + * atlas: + * + * ----------------------- + * | | + * | | + * | | + * | Empty space | + * | (C0) | + * | | + * | | + * | | + * ----------------------- + * + * The tree of cells looks like this: + * + * N0(free) + * + * The algorithm then places a bitmap B1, if possible: + * + * ----------------------- + * | | | + * | B1 | | + * | | | + * |-------- | + * | | + * | | + * | | + * | | + * ----------------------- + * + * After placing a bitmap in an empty cell, the algorithm splits + * the remaining space in two new empty cells. The split can occur + * vertically or horizontally (this is controlled by the "split + * decision" parameter of the algorithm.) + * + * Here is for the instance the result of a vertical split: + * + * ----------------------- + * | | | + * | B1 | | + * | | | + * |--------| C2 | + * | | | + * | | | + * | C1 | | + * | | | + * ----------------------- + * + * The cells tree now looks like this: + * + * C0(occupied) + * / \ + * / \ + * / \ + * / \ + * C1(free) C2(free) + * + * For each bitmap to place in the atlas, the Slice algorithm + * will visit the free cells until it finds one where a bitmap can + * fit. It will then split the now occupied cell and proceed onto + * the next bitmap. + */ + private static class SlicePolicy extends Policy { + private final Cell mRoot = new Cell(); + + private final SplitDecision mSplitDecision; + + private final boolean mAllowRotation; + private final int mPadding; + + /** + * A cell represents a sub-rectangle of the atlas. A cell is + * a node in a linked list representing the available free + * space in the atlas. + */ + private static class Cell { + int x; + int y; + + int width; + int height; + + Cell next; + + @Override + public String toString() { + return String.format("cell[x=%d y=%d width=%d height=%d", x, y, width, height); + } + } + + SlicePolicy(int width, int height, int flags, SplitDecision splitDecision) { + mAllowRotation = (flags & FLAG_ALLOW_ROTATIONS) != 0; + mPadding = (flags & FLAG_ADD_PADDING) != 0 ? 1 : 0; + + // The entire atlas is empty at first, minus padding + Cell first = new Cell(); + first.x = first.y = mPadding; + first.width = width - 2 * mPadding; + first.height = height - 2 * mPadding; + + mRoot.next = first; + mSplitDecision = splitDecision; + } + + @Override + Entry pack(int width, int height, Entry entry) { + Cell cell = mRoot.next; + Cell prev = mRoot; + + while (cell != null) { + if (insert(cell, prev, width, height, entry)) { + return entry; + } + + prev = cell; + cell = cell.next; + } + + return null; + } + + /** + * Defines how the remaining empty space should be split up: + * vertically or horizontally. + */ + private static interface SplitDecision { + /** + * Returns true if the remaining space defined by + * <code>freeWidth</code> and <code>freeHeight</code> + * should be split horizontally. + * + * @param freeWidth The rectWidth of the free space after packing a rectangle + * @param freeHeight The rectHeight of the free space after packing a rectangle + * @param rectWidth The rectWidth of the rectangle that was packed in a cell + * @param rectHeight The rectHeight of the rectangle that was packed in a cell + */ + boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight); + } + + // Splits the free area horizontally to minimize the horizontal section area + private static class MinAreaSplitDecision implements SplitDecision { + @Override + public boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight) { + return rectWidth * freeHeight > freeWidth * rectHeight; + } + } + + // Splits the free area horizontally to maximize the horizontal section area + private static class MaxAreaSplitDecision implements SplitDecision { + @Override + public boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight) { + return rectWidth * freeHeight <= freeWidth * rectHeight; + } + } + + // Splits the free area horizontally if the horizontal axis is shorter + private static class ShorterFreeAxisSplitDecision implements SplitDecision { + @Override + public boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight) { + return freeWidth <= freeHeight; + } + } + + // Splits the free area horizontally if the vertical axis is shorter + private static class LongerFreeAxisSplitDecision implements SplitDecision { + @Override + public boolean splitHorizontal(int freeWidth, int freeHeight, + int rectWidth, int rectHeight) { + return freeWidth > freeHeight; + } + } + + /** + * Attempts to pack a rectangle of specified dimensions in the available + * empty space. + * + * @param cell The cell representing free space in which to pack the rectangle + * @param prev The previous cell in the free space linked list + * @param width The width of the rectangle to pack + * @param height The height of the rectangle to pack + * @param entry Stores the location of the packged rectangle, if it fits + * + * @return True if the rectangle was packed in the atlas, false otherwise + */ + @SuppressWarnings("SuspiciousNameCombination") + private boolean insert(Cell cell, Cell prev, int width, int height, Entry entry) { + boolean rotated = false; + + // If the rectangle doesn't fit we'll try to rotate it + // if possible before giving up + if (cell.width < width || cell.height < height) { + if (mAllowRotation) { + if (cell.width < height || cell.height < width) { + return false; + } + + // Rotate the rectangle + int temp = width; + width = height; + height = temp; + rotated = true; + } else { + return false; + } + } + + // Remaining free space after packing the rectangle + int deltaWidth = cell.width - width; + int deltaHeight = cell.height - height; + + // Split the remaining free space into two new cells + Cell first = new Cell(); + Cell second = new Cell(); + + first.x = cell.x + width + mPadding; + first.y = cell.y; + first.width = deltaWidth - mPadding; + + second.x = cell.x; + second.y = cell.y + height + mPadding; + second.height = deltaHeight - mPadding; + + if (mSplitDecision.splitHorizontal(deltaWidth, deltaHeight, + width, height)) { + first.height = height; + second.width = cell.width; + } else { + first.height = cell.height; + second.width = width; + + // The order of the cells matters for efficient packing + // We want to give priority to the cell chosen by the + // split decision heuristic + Cell temp = first; + first = second; + second = temp; + } + + // Remove degenerate cases to keep the free list as small as possible + if (first.width > 0 && first.height > 0) { + prev.next = first; + prev = first; + } + + if (second.width > 0 && second.height > 0) { + prev.next = second; + second.next = cell.next; + } else { + prev.next = cell.next; + } + + // The cell is now completely removed from the free list + cell.next = null; + + // Return the location and rotation of the packed rectangle + entry.x = cell.x; + entry.y = cell.y; + entry.rotated = rotated; + + return true; + } + } +} diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 89abdef..d26b5a2 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -45,12 +45,9 @@ public final class Bitmap implements Parcelable { /** * Backing buffer for the Bitmap. - * Made public for quick access from drawing methods -- do NOT modify - * from outside this class. - * - * @hide */ - public byte[] mBuffer; + @SuppressWarnings("UnusedDeclaration") // native code only + private byte[] mBuffer; @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) // Keep to finalize native resources private final BitmapFinalizer mFinalizer; @@ -701,12 +698,10 @@ public final class Bitmap implements Parcelable { if (config == Config.ARGB_8888 && !hasAlpha) { nativeErase(bm.mNativeBitmap, 0xff000000); nativeSetHasAlpha(bm.mNativeBitmap, hasAlpha); - } else { - // No need to initialize it to zeroes; it is backed by a VM byte array - // which is by definition preinitialized to all zeroes. - // - //nativeErase(bm.mNativeBitmap, 0); } + // No need to initialize the bitmap to zeroes with other configs; + // it is backed by a VM byte array which is by definition preinitialized + // to all zeroes. return bm; } diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index cc7f23f..58e7525 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1058,14 +1058,13 @@ public class Canvas { * * Note: Only supported by hardware accelerated canvas at the moment. * - * @param bitmap The bitmap to draw as an N-patch - * @param chunks The patches information (matches the native struct Res_png_9patch) + * @param patch The ninepatch object to render * @param dst The destination rectangle. * @param paint The paint to draw the bitmap with. may be null * * @hide */ - public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) { + public void drawPatch(NinePatch patch, RectF dst, Paint paint) { } /** diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index 6de4d84..81e9d84 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -36,7 +36,10 @@ package android.graphics; */ public class NinePatch { private final Bitmap mBitmap; - private final byte[] mChunk; + /** + * @hide + */ + public final byte[] mChunk; private Paint mPaint; private String mSrcName; // Useful for debugging private final RectF mRect = new RectF(); @@ -72,6 +75,13 @@ public class NinePatch { public void setPaint(Paint p) { mPaint = p; } + + /** + * @hide + */ + public Bitmap getBitmap() { + return mBitmap; + } /** * Draw a bitmap of nine patches. @@ -86,7 +96,7 @@ public class NinePatch { mPaint != null ? mPaint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity); } else { - canvas.drawPatch(mBitmap, mChunk, location, mPaint); + canvas.drawPatch(this, location, mPaint); } } @@ -104,7 +114,7 @@ public class NinePatch { canvas.mDensity, mBitmap.mDensity); } else { mRect.set(location); - canvas.drawPatch(mBitmap, mChunk, mRect, mPaint); + canvas.drawPatch(this, mRect, mPaint); } } @@ -122,7 +132,7 @@ public class NinePatch { canvas.mDensity, mBitmap.mDensity); } else { mRect.set(location); - canvas.drawPatch(mBitmap, mChunk, mRect, paint); + canvas.drawPatch(this, mRect, paint); } } diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index 7578110..8689261 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -584,6 +584,11 @@ public class BitmapDrawable extends Drawable { } @Override + public Bitmap getBitmap() { + return mBitmap; + } + + @Override public Drawable newDrawable() { return new BitmapDrawable(this, null); } diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 66f7a5e..6d236d9 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -996,6 +996,13 @@ public abstract class Drawable { * this drawable (and thus require completely reloading it). */ public abstract int getChangingConfigurations(); + + /** + * @hide + */ + public Bitmap getBitmap() { + return null; + } } /** diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 8429e24..b83815b 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -403,7 +403,7 @@ public class NinePatchDrawable extends Drawable { return this; } - private final static class NinePatchState extends ConstantState { + final static class NinePatchState extends ConstantState { final NinePatch mNinePatch; final Rect mPadding; final Insets mOpticalInsets; @@ -439,6 +439,11 @@ public class NinePatchDrawable extends Drawable { } @Override + public Bitmap getBitmap() { + return mNinePatch.getBitmap(); + } + + @Override public Drawable newDrawable() { return new NinePatchDrawable(this, null); } diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 281f9a5..3433d0e 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -10,6 +10,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) thread/TaskManager.cpp \ font/CacheTexture.cpp \ font/Font.cpp \ + AssetAtlas.cpp \ FontRenderer.cpp \ GammaFontRenderer.cpp \ Caches.cpp \ @@ -54,9 +55,9 @@ ifeq ($(USE_OPENGL_RENDERER),true) external/skia/src/ports \ external/skia/include/utils - LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DGL_GLEXT_PROTOTYPES + LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES LOCAL_MODULE_CLASS := SHARED_LIBRARIES - LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libGLESv2 libskia libui + LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui LOCAL_MODULE := libhwui LOCAL_MODULE_TAGS := optional diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp new file mode 100644 index 0000000..cf8cc97 --- /dev/null +++ b/libs/hwui/AssetAtlas.cpp @@ -0,0 +1,121 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "OpenGLRenderer" + +#include "AssetAtlas.h" + +#include <GLES2/gl2ext.h> + +#include <utils/Log.h> + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// Lifecycle +/////////////////////////////////////////////////////////////////////////////// + +void AssetAtlas::init(sp<GraphicBuffer> buffer, int* map, int count) { + if (mImage != EGL_NO_IMAGE_KHR) { + return; + } + + // Create the EGLImage object that maps the GraphicBuffer + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer(); + EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; + + mImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); + + if (mImage == EGL_NO_IMAGE_KHR) { + ALOGW("Error creating atlas image (%#x)", eglGetError()); + return; + } + + // Create a 2D texture to sample from the EGLImage + glGenTextures(1, &mTexture); + glBindTexture(GL_TEXTURE_2D, mTexture); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, mImage); + + mWidth = buffer->getWidth(); + mHeight = buffer->getHeight(); + + createEntries(map, count); +} + +void AssetAtlas::terminate() { + if (mImage != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), mImage); + mImage = EGL_NO_IMAGE_KHR; + + glDeleteTextures(1, &mTexture); + mTexture = 0; + + for (size_t i = 0; i < mEntries.size(); i++) { + delete mEntries.valueAt(i); + } + mEntries.clear(); + + mWidth = mHeight = 0; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Entries +/////////////////////////////////////////////////////////////////////////////// + +AssetAtlas::Entry* AssetAtlas::getEntry(SkBitmap* const bitmap) const { + ssize_t index = mEntries.indexOfKey(bitmap); + return index >= 0 ? mEntries.valueAt(index) : NULL; +} + +Texture* AssetAtlas::getEntryTexture(SkBitmap* const bitmap) const { + ssize_t index = mEntries.indexOfKey(bitmap); + return index >= 0 ? &mEntries.valueAt(index)->texture : NULL; +} + +/** + * TODO: This method does not take the rotation flag into account + */ +void AssetAtlas::createEntries(int* map, int count) { + for (int i = 0; i < count; ) { + SkBitmap* bitmap = (SkBitmap*) map[i++]; + int x = map[i++]; + int y = map[i++]; + bool rotated = map[i++] > 0; + + // Bitmaps should never be null, we're just extra paranoid + if (!bitmap) continue; + + const UvMapper mapper( + x / (float) mWidth, (x + bitmap->width()) / (float) mWidth, + y / (float) mHeight, (y + bitmap->height()) / (float) mHeight); + + Entry* entry = new Entry(bitmap, x, y, rotated, mapper, *this); + entry->texture.id = mTexture; + entry->texture.blend = !bitmap->isOpaque(); + entry->texture.width = bitmap->width(); + entry->texture.height = bitmap->height(); + entry->texture.uvMapper = &entry->uvMapper; + + mEntries.add(entry->bitmap, entry); + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h new file mode 100644 index 0000000..4ede716 --- /dev/null +++ b/libs/hwui/AssetAtlas.h @@ -0,0 +1,171 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_ASSET_ATLAS_H +#define ANDROID_HWUI_ASSET_ATLAS_H + +#include <GLES2/gl2.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <ui/GraphicBuffer.h> + +#include <utils/KeyedVector.h> + +#include <cutils/compiler.h> + +#include <SkBitmap.h> + +#include "Texture.h" +#include "UvMapper.h" + +namespace android { +namespace uirenderer { + +/** + * An asset atlas holds a collection of framework bitmaps in a single OpenGL + * texture. Each bitmap is associated with a location, defined in pixels, + * inside the atlas. The atlas is generated by the framework and bound as + * an external texture using the EGLImageKHR extension. + */ +class AssetAtlas { +public: + /** + * Entry representing the position and rotation of a + * bitmap inside the atlas. + */ + struct Entry { + /** + * The bitmap that generated this atlas entry. + */ + SkBitmap* bitmap; + + /** + * Location of the bitmap inside the atlas, in pixels. + */ + int x; + int y; + + /** + * If set, the bitmap is rotated 90 degrees (clockwise) + * inside the atlas. + */ + bool rotated; + + /** + * Maps texture coordinates in the [0..1] range into the + * correct range to sample this entry from the atlas. + */ + const UvMapper uvMapper; + + /** + * Atlas this entry belongs to. + */ + const AssetAtlas& atlas; + + /* + * A "virtual texture" object that represents the texture + * this entry belongs to. This texture should never be + * modified. + */ + Texture texture; + + private: + Entry(SkBitmap* bitmap, int x, int y, bool rotated, + const UvMapper& mapper, const AssetAtlas& atlas): + bitmap(bitmap), x(x), y(y), rotated(rotated), uvMapper(mapper), atlas(atlas) { } + + friend class AssetAtlas; + }; + + AssetAtlas(): mWidth(0), mHeight(0), mTexture(0), mImage(EGL_NO_IMAGE_KHR) { } + ~AssetAtlas() { terminate(); } + + /** + * Initializes the atlas with the specified buffer and + * map. The buffer is a gralloc'd texture that will be + * used as an EGLImage. The map is a list of SkBitmap* + * and their (x, y) positions as well as their rotation + * flags. + * + * This method returns immediately if the atlas is already + * initialized. To re-initialize the atlas, you must + * first call terminate(). + */ + ANDROID_API void init(sp<GraphicBuffer> buffer, int* map, int count); + + /** + * Destroys the atlas texture. This object can be + * re-initialized after calling this method. + * + * After calling this method, the width, height + * and texture are set to 0. + */ + ANDROID_API void terminate(); + + /** + * Returns the width of this atlas in pixels. + * Can return 0 if the atlas is not initialized. + */ + uint32_t getWidth() const { + return mWidth; + } + + /** + * Returns the height of this atlas in pixels. + * Can return 0 if the atlas is not initialized. + */ + uint32_t getHeight() const { + return mHeight; + } + + /** + * Returns the OpenGL name of the texture backing this atlas. + * Can return 0 if the atlas is not initialized. + */ + GLuint getTexture() const { + return mTexture; + } + + /** + * Returns the entry in the atlas associated with the specified + * bitmap. If the bitmap is not in the atlas, return NULL. + */ + Entry* getEntry(SkBitmap* const bitmap) const; + + /** + * Returns the texture for the atlas entry associated with the + * specified bitmap. If the bitmap is not in the atlas, return NULL. + */ + Texture* getEntryTexture(SkBitmap* const bitmap) const; + +private: + void createEntries(int* map, int count); + + uint32_t mWidth; + uint32_t mHeight; + + GLuint mTexture; + EGLImageKHR mImage; + + KeyedVector<SkBitmap*, Entry*> mEntries; +}; // class AssetAtlas + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_ASSET_ATLAS_H diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index a381a68..c60848c 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -58,8 +58,8 @@ Caches::Caches(): Singleton<Caches>(), mExtensions(Extensions::getInstance()), m ALOGD("Enabling debug mode %d", mDebugLevel); } -void Caches::init() { - if (mInitialized) return; +bool Caches::init() { + if (mInitialized) return false; glGenBuffers(1, &meshBuffer); glBindBuffer(GL_ARRAY_BUFFER, meshBuffer); @@ -82,6 +82,7 @@ void Caches::init() { mTextureUnit = 0; mRegionMesh = NULL; + mMeshIndices = 0; blend = false; lastSrcMode = GL_ZERO; @@ -94,7 +95,11 @@ void Caches::init() { debugOverdraw = false; debugStencilClip = kStencilHide; + patchCache.init(*this); + mInitialized = true; + + return true; } void Caches::initFont() { @@ -191,8 +196,9 @@ void Caches::terminate() { glDeleteBuffers(1, &meshBuffer); mCurrentBuffer = 0; - glDeleteBuffers(1, &mRegionMeshIndices); + glDeleteBuffers(1, &mMeshIndices); delete[] mRegionMesh; + mMeshIndices = 0; mRegionMesh = NULL; fboCache.clear(); @@ -200,6 +206,10 @@ void Caches::terminate() { programCache.clear(); currentProgram = NULL; + assetAtlas.terminate(); + + patchCache.clear(); + mInitialized = false; } @@ -227,6 +237,8 @@ void Caches::dumpMemoryUsage(String8 &log) { pathCache.getSize(), pathCache.getMaxSize()); log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(), dropShadowCache.getMaxSize()); + log.appendFormat(" PatchCache %8d / %8d\n", + patchCache.getSize(), patchCache.getMaxSize()); for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) { const uint32_t size = fontRenderer->getFontRendererSize(i); log.appendFormat(" FontRenderer %d %8d / %8d\n", i, size, size); @@ -234,8 +246,6 @@ void Caches::dumpMemoryUsage(String8 &log) { log.appendFormat("Other:\n"); log.appendFormat(" FboCache %8d / %8d\n", fboCache.getSize(), fboCache.getMaxSize()); - log.appendFormat(" PatchCache %8d / %8d\n", - patchCache.getSize(), patchCache.getMaxSize()); uint32_t total = 0; total += textureCache.getSize(); @@ -244,6 +254,7 @@ void Caches::dumpMemoryUsage(String8 &log) { total += gradientCache.getSize(); total += pathCache.getSize(); total += dropShadowCache.getSize(); + total += patchCache.getSize(); for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) { total += fontRenderer->getFontRendererSize(i); } @@ -357,6 +368,32 @@ bool Caches::bindIndicesBuffer(const GLuint buffer) { return false; } +bool Caches::bindIndicesBuffer() { + if (!mMeshIndices) { + uint16_t* regionIndices = new uint16_t[REGION_MESH_QUAD_COUNT * 6]; + for (int i = 0; i < REGION_MESH_QUAD_COUNT; i++) { + uint16_t quad = i * 4; + int index = i * 6; + regionIndices[index ] = quad; // top-left + regionIndices[index + 1] = quad + 1; // top-right + regionIndices[index + 2] = quad + 2; // bottom-left + regionIndices[index + 3] = quad + 2; // bottom-left + regionIndices[index + 4] = quad + 1; // top-right + regionIndices[index + 5] = quad + 3; // bottom-right + } + + glGenBuffers(1, &mMeshIndices); + bool force = bindIndicesBuffer(mMeshIndices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, REGION_MESH_QUAD_COUNT * 6 * sizeof(uint16_t), + regionIndices, GL_STATIC_DRAW); + + delete[] regionIndices; + return force; + } + + return bindIndicesBuffer(mMeshIndices); +} + bool Caches::unbindIndicesBuffer() { if (mCurrentIndicesBuffer) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); @@ -546,27 +583,6 @@ TextureVertex* Caches::getRegionMesh() { // Create the mesh, 2 triangles and 4 vertices per rectangle in the region if (!mRegionMesh) { mRegionMesh = new TextureVertex[REGION_MESH_QUAD_COUNT * 4]; - - uint16_t* regionIndices = new uint16_t[REGION_MESH_QUAD_COUNT * 6]; - for (int i = 0; i < REGION_MESH_QUAD_COUNT; i++) { - uint16_t quad = i * 4; - int index = i * 6; - regionIndices[index ] = quad; // top-left - regionIndices[index + 1] = quad + 1; // top-right - regionIndices[index + 2] = quad + 2; // bottom-left - regionIndices[index + 3] = quad + 2; // bottom-left - regionIndices[index + 4] = quad + 1; // top-right - regionIndices[index + 5] = quad + 3; // bottom-right - } - - glGenBuffers(1, &mRegionMeshIndices); - bindIndicesBuffer(mRegionMeshIndices); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, REGION_MESH_QUAD_COUNT * 6 * sizeof(uint16_t), - regionIndices, GL_STATIC_DRAW); - - delete[] regionIndices; - } else { - bindIndicesBuffer(mRegionMeshIndices); } return mRegionMesh; diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 91b938b..18aeeab 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -21,13 +21,18 @@ #define LOG_TAG "OpenGLRenderer" #endif +#include <GLES3/gl3.h> + +#include <utils/KeyedVector.h> #include <utils/Singleton.h> +#include <utils/Vector.h> #include <cutils/compiler.h> #include "thread/TaskProcessor.h" #include "thread/TaskManager.h" +#include "AssetAtlas.h" #include "FontRenderer.h" #include "GammaFontRenderer.h" #include "TextureCache.h" @@ -113,7 +118,7 @@ public: /** * Initialize caches. */ - void init(); + bool init(); /** * Initialize global system properties. @@ -172,6 +177,11 @@ public: */ bool unbindMeshBuffer(); + /** + * Binds a global indices buffer that can draw up to + * REGION_MESH_QUAD_COUNT quads. + */ + bool bindIndicesBuffer(); bool bindIndicesBuffer(const GLuint buffer); bool unbindIndicesBuffer(); @@ -290,6 +300,8 @@ public: Dither dither; Stencil stencil; + AssetAtlas assetAtlas; + // Debug methods PFNGLINSERTEVENTMARKEREXTPROC eventMark; PFNGLPUSHGROUPMARKEREXTPROC startMark; @@ -336,7 +348,9 @@ private: // Used to render layers TextureVertex* mRegionMesh; - GLuint mRegionMeshIndices; + + // Global index buffer + GLuint mMeshIndices; mutable Mutex mGarbageLock; Vector<Layer*> mLayerGarbage; diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h index 790c4f4..786f12a 100644 --- a/libs/hwui/Debug.h +++ b/libs/hwui/Debug.h @@ -53,8 +53,6 @@ // Turn on to display debug info about 9patch objects #define DEBUG_PATCHES 0 -// Turn on to "explode" 9patch objects -#define DEBUG_EXPLODE_PATCHES 0 // Turn on to display vertex and tex coords data about 9patch objects // This flag requires DEBUG_PATCHES to be turned on #define DEBUG_PATCHES_VERTICES 0 diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index a0290e3..990372e 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -26,8 +26,10 @@ #include <private/hwui/DrawGlInfo.h> #include "OpenGLRenderer.h" +#include "AssetAtlas.h" #include "DeferredDisplayList.h" #include "DisplayListRenderer.h" +#include "UvMapper.h" #include "utils/LinearAllocator.h" #define CRASH() do { \ @@ -721,7 +723,6 @@ private: int mSetBits; }; - /////////////////////////////////////////////////////////////////////////////// // DRAW OPERATIONS - these are operations that can draw to the canvas's device /////////////////////////////////////////////////////////////////////////////// @@ -729,9 +730,16 @@ private: class DrawBitmapOp : public DrawBoundedOp { public: DrawBitmapOp(SkBitmap* bitmap, float left, float top, SkPaint* paint) - : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), - paint), - mBitmap(bitmap) {} + : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), paint), + mBitmap(bitmap), mAtlasEntry(NULL) { + } + + DrawBitmapOp(SkBitmap* bitmap, float left, float top, SkPaint* paint, + const AssetAtlas::Entry* entry) + : DrawBoundedOp(left, top, left + bitmap->width(), top + bitmap->height(), paint), + mBitmap(bitmap), mAtlasEntry(entry) { + if (entry) mUvMapper = entry->uvMapper; + } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { return renderer.drawBitmap(mBitmap, mLocalBounds.left, mLocalBounds.top, @@ -749,14 +757,14 @@ public: TextureVertex vertices[6 * ops.size()]; TextureVertex* vertex = &vertices[0]; - // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op, and allowing - // them to be merged in getBatchId() - const Rect texCoords(0, 0, 1, 1); - - const float width = mBitmap->width(); - const float height = mBitmap->height(); + // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op, + // and allowing them to be merged in getBatchId() for (unsigned int i = 0; i < ops.size(); i++) { const Rect& opBounds = ops[i]->state.mBounds; + + Rect texCoords(0, 0, 1, 1); + ((DrawBitmapOp*) ops[i])->mUvMapper.map(texCoords); + SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, top); SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top); SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom); @@ -777,7 +785,7 @@ public: virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { *batchId = DeferredDisplayList::kOpBatch_Bitmap; - *mergeId = (mergeid_t)mBitmap; + *mergeId = mAtlasEntry ? (mergeid_t) &mAtlasEntry->atlas : (mergeid_t) mBitmap; // don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in // MergingDrawBatch::canMergeWith @@ -787,6 +795,8 @@ public: const SkBitmap* bitmap() { return mBitmap; } protected: SkBitmap* mBitmap; + const AssetAtlas::Entry* mAtlasEntry; + UvMapper mUvMapper; }; class DrawBitmapMatrixOp : public DrawBoundedOp { @@ -904,20 +914,16 @@ private: class DrawPatchOp : public DrawBoundedOp { public: - DrawPatchOp(SkBitmap* bitmap, const int32_t* xDivs, - const int32_t* yDivs, const uint32_t* colors, uint32_t width, uint32_t height, - int8_t numColors, float left, float top, float right, float bottom, - int alpha, SkXfermode::Mode mode) + DrawPatchOp(SkBitmap* bitmap, Res_png_9patch* patch, + float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode) : DrawBoundedOp(left, top, right, bottom, 0), - mBitmap(bitmap), mxDivs(xDivs), myDivs(yDivs), - mColors(colors), mxDivsCount(width), myDivsCount(height), - mNumColors(numColors), mAlpha(alpha), mMode(mode) {}; + mBitmap(bitmap), mPatch(patch), mAlpha(alpha), mMode(mode) { + mEntry = Caches::getInstance().assetAtlas.getEntry(bitmap); + }; virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) { // NOTE: not calling the virtual method, which takes a paint - return renderer.drawPatch(mBitmap, mxDivs, myDivs, mColors, - mxDivsCount, myDivsCount, mNumColors, - mLocalBounds.left, mLocalBounds.top, + return renderer.drawPatch(mBitmap, mPatch, mEntry, mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom, mAlpha, mMode); } @@ -929,20 +935,16 @@ public: virtual bool onDefer(OpenGLRenderer& renderer, int* batchId, mergeid_t* mergeId) { *batchId = DeferredDisplayList::kOpBatch_Patch; - *mergeId = (mergeid_t)mBitmap; + *mergeId = (mergeid_t) mBitmap; return true; } private: SkBitmap* mBitmap; - const int32_t* mxDivs; - const int32_t* myDivs; - const uint32_t* mColors; - uint32_t mxDivsCount; - uint32_t myDivsCount; - int8_t mNumColors; + Res_png_9patch* mPatch; int mAlpha; SkXfermode::Mode mMode; + AssetAtlas::Entry* mEntry; }; class DrawColorOp : public DrawOp { diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 876c38a..bfd4086 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -257,7 +257,8 @@ status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top bitmap = refBitmap(bitmap); paint = refPaint(paint); - addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint)); + const AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap); + addDrawOp(new (alloc()) DrawBitmapOp(bitmap, left, top, paint, entry)); return DrawGlInfo::kStatusDone; } @@ -281,7 +282,8 @@ status_t DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float srcLeft, float (srcBottom - srcTop == dstBottom - dstTop) && (srcRight - srcLeft == dstRight - dstLeft)) { // transform simple rect to rect drawing case into position bitmap ops, since they merge - addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint)); + const AssetAtlas::Entry* entry = mCaches.assetAtlas.getEntry(bitmap); + addDrawOp(new (alloc()) DrawBitmapOp(bitmap, dstLeft, dstTop, paint, entry)); return DrawGlInfo::kStatusDone; } @@ -313,20 +315,15 @@ status_t DisplayListRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, in return DrawGlInfo::kStatusDone; } -status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, - const int32_t* yDivs, const uint32_t* colors, uint32_t width, uint32_t height, - int8_t numColors, float left, float top, float right, float bottom, SkPaint* paint) { +status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, + float left, float top, float right, float bottom, SkPaint* paint) { int alpha; SkXfermode::Mode mode; OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode); bitmap = refBitmap(bitmap); - xDivs = refBuffer<int>(xDivs, width); - yDivs = refBuffer<int>(yDivs, height); - colors = refBuffer<uint32_t>(colors, numColors); - addDrawOp(new (alloc()) DrawPatchOp(bitmap, xDivs, yDivs, colors, width, height, numColors, - left, top, right, bottom, alpha, mode)); + addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, alpha, mode)); return DrawGlInfo::kStatusDone; } diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 75abad6..db08921 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -103,8 +103,7 @@ public: virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint); virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight, float* vertices, int* colors, SkPaint* paint); - virtual status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, + virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top, float right, float bottom, SkPaint* paint); virtual status_t drawColor(int color, SkXfermode::Mode mode); virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint); diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index 54a3987..a3f7c44 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -33,9 +33,6 @@ namespace uirenderer { class Extensions: public Singleton<Extensions> { public: - Extensions(); - ~Extensions(); - inline bool hasNPot() const { return mHasNPot; } inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; } @@ -53,6 +50,9 @@ public: void dump() const; private: + Extensions(); + ~Extensions(); + friend class Singleton<Extensions>; SortedVector<String8> mExtensionList; diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index a4f9860..025e9f8 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1873,7 +1873,7 @@ void OpenGLRenderer::setupDrawTextureTransformUniforms(mat4& transform) { void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) { bool force = false; - if (!vertices) { + if (!vertices || vbo) { force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo); } else { force = mCaches.unbindMeshBuffer(); @@ -1904,8 +1904,18 @@ void OpenGLRenderer::setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* mCaches.unbindIndicesBuffer(); } -void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords) { - bool force = mCaches.unbindMeshBuffer(); +void OpenGLRenderer::setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo) { + bool force = false; + // If vbo is != 0 we want to treat the vertices parameter as an offset inside + // a VBO. However, if vertices is set to NULL and vbo == 0 then we want to + // use the default VBO found in Caches + if (!vertices || vbo) { + force = mCaches.bindMeshBuffer(vbo == 0 ? mCaches.meshBuffer : vbo); + } else { + force = mCaches.unbindMeshBuffer(); + } + mCaches.bindIndicesBuffer(); + mCaches.bindPositionVertexPointer(force, vertices); if (mCaches.currentProgram->texCoords >= 0) { mCaches.bindTexCoordsVertexPointer(force, texCoords); @@ -1980,9 +1990,11 @@ void OpenGLRenderer::drawAlphaBitmap(Texture* texture, float left, float top, Sk texture->setFilter(FILTER(paint), true); } + // No need to check for a UV mapper on the texture object, only ARGB_8888 + // bitmaps get packed in the atlas drawAlpha8TextureMesh(x, y, x + texture->width, y + texture->height, texture->id, - paint != NULL, color, alpha, mode, (GLvoid*) NULL, - (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform); + paint != NULL, color, alpha, mode, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, + GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform); } status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices, @@ -1992,8 +2004,9 @@ status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureV mCaches.setScissorEnabled(mScissorOptimizationDisabled); mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; + const AutoTexture autoCleanup(texture); int alpha; @@ -2030,7 +2043,7 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, SkP } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); @@ -2053,7 +2066,7 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); @@ -2116,6 +2129,10 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes cleanupColors = true; } + mCaches.activeTexture(0); + Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap); + const UvMapper& mapper(getMapper(texture)); + for (int32_t y = 0; y < meshHeight; y++) { for (int32_t x = 0; x < meshWidth; x++) { uint32_t i = (y * (meshWidth + 1) + x) * 2; @@ -2125,6 +2142,8 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes float v1 = float(y) / meshHeight; float v2 = float(y + 1) / meshHeight; + mapper.map(u1, v1, u2, v2); + int ax = i + (meshWidth + 1) * 2; int ay = ax + 1; int bx = i; @@ -2154,11 +2173,12 @@ status_t OpenGLRenderer::drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int mes return DrawGlInfo::kStatusDone; } - mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); if (!texture) { - if (cleanupColors) delete[] colors; - return DrawGlInfo::kStatusDone; + texture = mCaches.textureCache.get(bitmap); + if (!texture) { + if (cleanupColors) delete[] colors; + return DrawGlInfo::kStatusDone; + } } const AutoTexture autoCleanup(texture); @@ -2211,17 +2231,19 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, } mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = getTexture(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); const float width = texture->width; const float height = texture->height; - const float u1 = fmax(0.0f, srcLeft / width); - const float v1 = fmax(0.0f, srcTop / height); - const float u2 = fmin(1.0f, srcRight / width); - const float v2 = fmin(1.0f, srcBottom / height); + float u1 = fmax(0.0f, srcLeft / width); + float v1 = fmax(0.0f, srcTop / height); + float u2 = fmin(1.0f, srcRight / width); + float v2 = fmin(1.0f, srcBottom / height); + + getMapper(texture).map(u1, v1, u2, v2); mCaches.unbindMeshBuffer(); resetDrawTextureTexCoords(u1, v1, u2, v2); @@ -2292,34 +2314,32 @@ status_t OpenGLRenderer::drawBitmap(SkBitmap* bitmap, return DrawGlInfo::kStatusDrew; } -status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, +status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top, float right, float bottom, SkPaint* paint) { int alpha; SkXfermode::Mode mode; getAlphaAndMode(paint, &alpha, &mode); - return drawPatch(bitmap, xDivs, yDivs, colors, width, height, numColors, + return drawPatch(bitmap, patch, mCaches.assetAtlas.getEntry(bitmap), left, top, right, bottom, alpha, mode); } -status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, - float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode) { +status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, + AssetAtlas::Entry* entry, float left, float top, float right, float bottom, + int alpha, SkXfermode::Mode mode) { if (quickReject(left, top, right, bottom)) { return DrawGlInfo::kStatusDone; } - alpha *= mSnapshot->alpha; - - const Patch* mesh = mCaches.patchCache.get(bitmap->width(), bitmap->height(), - right - left, bottom - top, xDivs, yDivs, colors, width, height, numColors); + const Patch* mesh = mCaches.patchCache.get(entry, bitmap->width(), bitmap->height(), + right - left, bottom - top, patch); if (CC_LIKELY(mesh && mesh->verticesCount > 0)) { mCaches.activeTexture(0); - Texture* texture = mCaches.textureCache.get(bitmap); + Texture* texture = entry ? &entry->texture : mCaches.textureCache.get(bitmap); if (!texture) return DrawGlInfo::kStatusDone; const AutoTexture autoCleanup(texture); + texture->setWrap(GL_CLAMP_TO_EDGE, true); texture->setFilter(GL_LINEAR, true); @@ -2342,19 +2362,23 @@ status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const } } + alpha *= mSnapshot->alpha; + if (CC_LIKELY(pureTranslate)) { const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f); const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f); - drawTextureMesh(x, y, x + right - left, y + bottom - top, texture->id, alpha / 255.0f, - mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset, - GL_TRIANGLES, mesh->verticesCount, false, true, mesh->meshBuffer, - true, !mesh->hasEmptyQuads); + right = x + right - left; + bottom = y + bottom - top; + drawIndexedTextureMesh(x, y, right, bottom, texture->id, alpha / 255.0f, + mode, texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset, + GL_TRIANGLES, mesh->indexCount, false, true, + mCaches.patchCache.getMeshBuffer(), true, !mesh->hasEmptyQuads); } else { - drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, - mode, texture->blend, (GLvoid*) 0, (GLvoid*) gMeshTextureOffset, - GL_TRIANGLES, mesh->verticesCount, false, false, mesh->meshBuffer, - true, !mesh->hasEmptyQuads); + drawIndexedTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, + mode, texture->blend, (GLvoid*) mesh->offset, (GLvoid*) mesh->textureOffset, + GL_TRIANGLES, mesh->indexCount, false, false, + mCaches.patchCache.getMeshBuffer(), true, !mesh->hasEmptyQuads); } } @@ -3196,6 +3220,14 @@ SkPaint* OpenGLRenderer::filterPaint(SkPaint* paint) { // Drawing implementation /////////////////////////////////////////////////////////////////////////////// +Texture* OpenGLRenderer::getTexture(SkBitmap* bitmap) { + Texture* texture = mCaches.assetAtlas.getEntryTexture(bitmap); + if (!texture) { + return mCaches.textureCache.get(bitmap); + } + return texture; +} + void OpenGLRenderer::drawPathTexture(const PathTexture* texture, float x, float y, SkPaint* paint) { if (quickReject(x, y, x + texture->width, y + texture->height)) { @@ -3389,19 +3421,35 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b texture->setWrap(GL_CLAMP_TO_EDGE, true); + GLvoid* vertices = (GLvoid*) NULL; + GLvoid* texCoords = (GLvoid*) gMeshTextureOffset; + + if (texture->uvMapper) { + vertices = &mMeshVertices[0].position[0]; + texCoords = &mMeshVertices[0].texture[0]; + + Rect uvs(0.0f, 0.0f, 1.0f, 1.0f); + texture->uvMapper->map(uvs); + + resetDrawTextureTexCoords(uvs.left, uvs.top, uvs.right, uvs.bottom); + } + if (CC_LIKELY(currentTransform().isPureTranslate())) { const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f); const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f); texture->setFilter(GL_NEAREST, true); drawTextureMesh(x, y, x + texture->width, y + texture->height, texture->id, - alpha / 255.0f, mode, texture->blend, (GLvoid*) NULL, - (GLvoid*) gMeshTextureOffset, GL_TRIANGLE_STRIP, gMeshCount, false, true); + alpha / 255.0f, mode, texture->blend, vertices, texCoords, + GL_TRIANGLE_STRIP, gMeshCount, false, true); } else { texture->setFilter(FILTER(paint), true); drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode, - texture->blend, (GLvoid*) NULL, (GLvoid*) gMeshTextureOffset, - GL_TRIANGLE_STRIP, gMeshCount); + texture->blend, vertices, texCoords, GL_TRIANGLE_STRIP, gMeshCount); + } + + if (texture->uvMapper) { + resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f); } } @@ -3438,6 +3486,33 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b finishDrawTexture(); } +void OpenGLRenderer::drawIndexedTextureMesh(float left, float top, float right, float bottom, + GLuint texture, float alpha, SkXfermode::Mode mode, bool blend, + GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, + bool swapSrcDst, bool ignoreTransform, GLuint vbo, bool ignoreScale, bool dirty) { + + setupDraw(); + setupDrawWithTexture(); + setupDrawColor(alpha, alpha, alpha, alpha); + setupDrawColorFilter(); + setupDrawBlending(blend, mode, swapSrcDst); + setupDrawProgram(); + if (!dirty) setupDrawDirtyRegionsDisabled(); + if (!ignoreScale) { + setupDrawModelView(left, top, right, bottom, ignoreTransform); + } else { + setupDrawModelViewTranslate(left, top, right, bottom, ignoreTransform); + } + setupDrawTexture(texture); + setupDrawPureColorUniforms(); + setupDrawColorFilterUniforms(); + setupDrawMeshIndices(vertices, texCoords, vbo); + + glDrawElements(drawMode, elementsCount, GL_UNSIGNED_SHORT, NULL); + + finishDrawTexture(); +} + void OpenGLRenderer::drawAlpha8TextureMesh(float left, float top, float right, float bottom, GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode, GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index a0ad888..640c7db 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -34,6 +34,8 @@ #include <cutils/compiler.h> +#include <androidfw/ResourceTypes.h> + #include "Debug.h" #include "Extensions.h" #include "Matrix.h" @@ -43,6 +45,7 @@ #include "Vertex.h" #include "SkiaShader.h" #include "SkiaColorFilter.h" +#include "UvMapper.h" #include "Caches.h" namespace android { @@ -78,7 +81,8 @@ enum DrawOpMode { }; struct DeferredDisplayState { - Rect mBounds; // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped. + // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped + Rect mBounds; // the below are set and used by the OpenGLRenderer at record and deferred playback bool mClipValid; @@ -248,11 +252,9 @@ public: virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint); virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight, float* vertices, int* colors, SkPaint* paint); - virtual status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, + virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, float left, float top, float right, float bottom, SkPaint* paint); - status_t drawPatch(SkBitmap* bitmap, const int32_t* xDivs, const int32_t* yDivs, - const uint32_t* colors, uint32_t width, uint32_t height, int8_t numColors, + status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch, AssetAtlas::Entry* entry, float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode); virtual status_t drawColor(int color, SkXfermode::Mode mode); virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint); @@ -798,6 +800,12 @@ private: bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0, bool ignoreScale = false, bool dirty = true); + void drawIndexedTextureMesh(float left, float top, float right, float bottom, GLuint texture, + float alpha, SkXfermode::Mode mode, bool blend, + GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, + bool swapSrcDst = false, bool ignoreTransform = false, GLuint vbo = 0, + bool ignoreScale = false, bool dirty = true); + void drawAlpha8TextureMesh(float left, float top, float right, float bottom, GLuint texture, bool hasColor, int color, int alpha, SkXfermode::Mode mode, GLvoid* vertices, GLvoid* texCoords, GLenum drawMode, GLsizei elementsCount, @@ -943,7 +951,7 @@ private: void setupDrawTextGammaUniforms(); void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords = NULL, GLuint vbo = 0); void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords, GLvoid* colors); - void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords); + void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords, GLuint vbo = 0); void setupDrawVertices(GLvoid* vertices); void finishDrawTexture(); void accountForClear(SkXfermode::Mode mode); @@ -985,6 +993,17 @@ private: return *mSnapshot->transform; } + inline const UvMapper& getMapper(const Texture* texture) { + return texture && texture->uvMapper ? *texture->uvMapper : mUvMapper; + } + + /** + * Returns a texture object for the specified bitmap. The texture can + * come from the texture cache or an atlas. If this method returns + * NULL, the texture could not be found and/or allocated. + */ + Texture* getTexture(SkBitmap* bitmap); + // Dimensions of the drawing surface int mWidth, mHeight; @@ -1010,6 +1029,9 @@ private: // Used to draw textured quads TextureVertex mMeshVertices[4]; + // Default UV mapper + const UvMapper mUvMapper; + // shader, filters, and shadow DrawModifiers mDrawModifiers; SkPaint mFilteredPaint; diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp index 45c619e..6b0734a 100644 --- a/libs/hwui/Patch.cpp +++ b/libs/hwui/Patch.cpp @@ -20,9 +20,10 @@ #include <utils/Log.h> -#include "Patch.h" #include "Caches.h" +#include "Patch.h" #include "Properties.h" +#include "UvMapper.h" namespace android { namespace uirenderer { @@ -31,90 +32,61 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -Patch::Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads): - mXCount(xCount), mYCount(yCount), mEmptyQuads(emptyQuads) { - // Initialized with the maximum number of vertices we will need - // 2 triangles per patch, 3 vertices per triangle - uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 2 * 3; - mVertices = new TextureVertex[maxVertices]; - mAllocatedVerticesCount = 0; - - verticesCount = 0; - hasEmptyQuads = emptyQuads > 0; - - mColorKey = 0; - mXDivs = new int32_t[mXCount]; - mYDivs = new int32_t[mYCount]; - - PATCH_LOGD(" patch: xCount = %d, yCount = %d, emptyQuads = %d, max vertices = %d", - xCount, yCount, emptyQuads, maxVertices); - - glGenBuffers(1, &meshBuffer); +Patch::Patch(): verticesCount(0), indexCount(0), hasEmptyQuads(false) { } Patch::~Patch() { - delete[] mVertices; - delete[] mXDivs; - delete[] mYDivs; - glDeleteBuffers(1, &meshBuffer); } /////////////////////////////////////////////////////////////////////////////// -// Patch management +// Vertices management /////////////////////////////////////////////////////////////////////////////// -void Patch::copy(const int32_t* xDivs, const int32_t* yDivs) { - memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t)); - memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t)); +uint32_t Patch::getSize() const { + return verticesCount * sizeof(TextureVertex); } -void Patch::updateColorKey(const uint32_t colorKey) { - mColorKey = colorKey; +TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight, + float left, float top, float right, float bottom, const Res_png_9patch* patch) { + UvMapper mapper; + return createMesh(bitmapWidth, bitmapHeight, left, top, right, bottom, mapper, patch); } -bool Patch::matches(const int32_t* xDivs, const int32_t* yDivs, - const uint32_t colorKey, const int8_t emptyQuads) { +TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight, + float left, float top, float right, float bottom, + const UvMapper& mapper, const Res_png_9patch* patch) { - bool matches = true; + const uint32_t* colors = &patch->colors[0]; + const int8_t numColors = patch->numColors; - if (mEmptyQuads != emptyQuads) { - mEmptyQuads = emptyQuads; - hasEmptyQuads = emptyQuads > 0; - matches = false; - } - - if (mColorKey != colorKey) { - updateColorKey(colorKey); - matches = false; - } - - if (memcmp(mXDivs, xDivs, mXCount * sizeof(int32_t))) { - memcpy(mXDivs, xDivs, mXCount * sizeof(int32_t)); - matches = false; + mColorKey = 0; + int8_t emptyQuads = 0; + + if (uint8_t(numColors) < sizeof(uint32_t) * 4) { + for (int8_t i = 0; i < numColors; i++) { + if (colors[i] == 0x0) { + emptyQuads++; + mColorKey |= 0x1 << i; + } + } } - if (memcmp(mYDivs, yDivs, mYCount * sizeof(int32_t))) { - memcpy(mYDivs, yDivs, mYCount * sizeof(int32_t)); - matches = false; - } + hasEmptyQuads = emptyQuads > 0; - return matches; -} + uint32_t xCount = patch->numXDivs; + uint32_t yCount = patch->numYDivs; -/////////////////////////////////////////////////////////////////////////////// -// Vertices management -/////////////////////////////////////////////////////////////////////////////// + uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 4; + if (maxVertices == 0) return NULL; -void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, - float left, float top, float right, float bottom) { - if (hasEmptyQuads) quads.clear(); + TextureVertex* vertices = new TextureVertex[maxVertices]; + TextureVertex* vertex = vertices; - // Reset the vertices count here, we will count exactly how many - // vertices we actually need when generating the quads - verticesCount = 0; + const int32_t* xDivs = patch->xDivs; + const int32_t* yDivs = patch->yDivs; - const uint32_t xStretchCount = (mXCount + 1) >> 1; - const uint32_t yStretchCount = (mYCount + 1) >> 1; + const uint32_t xStretchCount = (xCount + 1) >> 1; + const uint32_t yStretchCount = (yCount + 1) >> 1; float stretchX = 0.0f; float stretchY = 0.0f; @@ -124,8 +96,8 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, if (xStretchCount > 0) { uint32_t stretchSize = 0; - for (uint32_t i = 1; i < mXCount; i += 2) { - stretchSize += mXDivs[i] - mXDivs[i - 1]; + for (uint32_t i = 1; i < xCount; i += 2) { + stretchSize += xDivs[i] - xDivs[i - 1]; } const float xStretchTex = stretchSize; const float fixed = bitmapWidth - stretchSize; @@ -136,8 +108,8 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, if (yStretchCount > 0) { uint32_t stretchSize = 0; - for (uint32_t i = 1; i < mYCount; i += 2) { - stretchSize += mYDivs[i] - mYDivs[i - 1]; + for (uint32_t i = 1; i < yCount; i += 2) { + stretchSize += yDivs[i] - yDivs[i - 1]; } const float yStretchTex = stretchSize; const float fixed = bitmapHeight - stretchSize; @@ -146,7 +118,6 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(bottom - top, 0.0f) / fixed, 1.0f); } - TextureVertex* vertex = mVertices; uint32_t quadCount = 0; float previousStepY = 0.0f; @@ -155,8 +126,10 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, float y2 = 0.0f; float v1 = 0.0f; - for (uint32_t i = 0; i < mYCount; i++) { - float stepY = mYDivs[i]; + mUvMapper = mapper; + + for (uint32_t i = 0; i < yCount; i++) { + float stepY = yDivs[i]; const float segment = stepY - previousStepY; if (i & 1) { @@ -170,15 +143,8 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, v1 += vOffset / bitmapHeight; if (stepY > 0.0f) { -#if DEBUG_EXPLODE_PATCHES - y1 += i * EXPLODE_GAP; - y2 += i * EXPLODE_GAP; -#endif - generateRow(vertex, y1, y2, v1, v2, stretchX, rescaleX, right - left, - bitmapWidth, quadCount); -#if DEBUG_EXPLODE_PATCHES - y2 -= i * EXPLODE_GAP; -#endif + generateRow(xDivs, xCount, vertex, y1, y2, v1, v2, stretchX, rescaleX, + right - left, bitmapWidth, quadCount); } y1 = y2; @@ -189,33 +155,16 @@ void Patch::updateVertices(const float bitmapWidth, const float bitmapHeight, if (previousStepY != bitmapHeight) { y2 = bottom - top; -#if DEBUG_EXPLODE_PATCHES - y1 += mYCount * EXPLODE_GAP; - y2 += mYCount * EXPLODE_GAP; -#endif - generateRow(vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, right - left, - bitmapWidth, quadCount); - } - - if (verticesCount > 0) { - Caches& caches = Caches::getInstance(); - caches.bindMeshBuffer(meshBuffer); - if (mAllocatedVerticesCount < verticesCount) { - glBufferData(GL_ARRAY_BUFFER, sizeof(TextureVertex) * verticesCount, - mVertices, GL_DYNAMIC_DRAW); - mAllocatedVerticesCount = verticesCount; - } else { - glBufferSubData(GL_ARRAY_BUFFER, 0, - sizeof(TextureVertex) * verticesCount, mVertices); - } - caches.resetVertexPointers(); + generateRow(xDivs, xCount, vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, + right - left, bitmapWidth, quadCount); } - PATCH_LOGD(" patch: new vertices count = %d", verticesCount); + return vertices; } -void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, float v2, - float stretchX, float rescaleX, float width, float bitmapWidth, uint32_t& quadCount) { +void Patch::generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex, + float y1, float y2, float v1, float v2, float stretchX, float rescaleX, + float width, float bitmapWidth, uint32_t& quadCount) { float previousStepX = 0.0f; float x1 = 0.0f; @@ -223,8 +172,8 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl float u1 = 0.0f; // Generate the row quad by quad - for (uint32_t i = 0; i < mXCount; i++) { - float stepX = mXDivs[i]; + for (uint32_t i = 0; i < xCount; i++) { + float stepX = xDivs[i]; const float segment = stepX - previousStepX; if (i & 1) { @@ -238,14 +187,7 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl u1 += uOffset / bitmapWidth; if (stepX > 0.0f) { -#if DEBUG_EXPLODE_PATCHES - x1 += i * EXPLODE_GAP; - x2 += i * EXPLODE_GAP; -#endif generateQuad(vertex, x1, y1, x2, y2, u1, v1, u2, v2, quadCount); -#if DEBUG_EXPLODE_PATCHES - x2 -= i * EXPLODE_GAP; -#endif } x1 = x2; @@ -256,10 +198,6 @@ void Patch::generateRow(TextureVertex*& vertex, float y1, float y2, float v1, fl if (previousStepX != bitmapWidth) { x2 = width; -#if DEBUG_EXPLODE_PATCHES - x1 += mXCount * EXPLODE_GAP; - x2 += mXCount * EXPLODE_GAP; -#endif generateQuad(vertex, x1, y1, x2, y2, u1, v1, 1.0f, v2, quadCount); } } @@ -290,18 +228,15 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f quads.add(bounds); } - // Left triangle + mUvMapper.map(u1, v1, u2, v2); + TextureVertex::set(vertex++, x1, y1, u1, v1); TextureVertex::set(vertex++, x2, y1, u2, v1); TextureVertex::set(vertex++, x1, y2, u1, v2); - - // Right triangle - TextureVertex::set(vertex++, x1, y2, u1, v2); - TextureVertex::set(vertex++, x2, y1, u2, v1); TextureVertex::set(vertex++, x2, y2, u2, v2); - // A quad is made of 2 triangles, 6 vertices - verticesCount += 6; + verticesCount += 4; + indexCount += 6; #if DEBUG_PATCHES_VERTICES PATCH_LOGD(" quad %d", oldQuadCount); diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h index ee7bf70..448cf60 100644 --- a/libs/hwui/Patch.h +++ b/libs/hwui/Patch.h @@ -23,62 +23,52 @@ #include <utils/Vector.h> +#include <androidfw/ResourceTypes.h> + #include "Rect.h" +#include "UvMapper.h" #include "Vertex.h" namespace android { namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -#define EXPLODE_GAP 4 - -/////////////////////////////////////////////////////////////////////////////// // 9-patch structures /////////////////////////////////////////////////////////////////////////////// -/** - * An OpenGL patch. This contains an array of vertices and an array of - * indices to render the vertices. - */ struct Patch { - Patch(const uint32_t xCount, const uint32_t yCount, const int8_t emptyQuads); + Patch(); ~Patch(); - void updateVertices(const float bitmapWidth, const float bitmapHeight, - float left, float top, float right, float bottom); - - void updateColorKey(const uint32_t colorKey); - void copy(const int32_t* xDivs, const int32_t* yDivs); - bool matches(const int32_t* xDivs, const int32_t* yDivs, - const uint32_t colorKey, const int8_t emptyQuads); + /** + * Returns the size of this patch's mesh in bytes. + */ + uint32_t getSize() const; - GLuint meshBuffer; uint32_t verticesCount; + uint32_t indexCount; bool hasEmptyQuads; Vector<Rect> quads; -private: - TextureVertex* mVertices; - uint32_t mAllocatedVerticesCount; - - int32_t* mXDivs; - int32_t* mYDivs; - uint32_t mColorKey; + GLintptr offset; + GLintptr textureOffset; - uint32_t mXCount; - uint32_t mYCount; - int8_t mEmptyQuads; + TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight, + float left, float top, float right, float bottom, + const Res_png_9patch* patch); + TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight, + float left, float top, float right, float bottom, + const UvMapper& mapper, const Res_png_9patch* patch); - void generateRow(TextureVertex*& vertex, float y1, float y2, - float v1, float v2, float stretchX, float rescaleX, +private: + void generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex, + float y1, float y2, float v1, float v2, float stretchX, float rescaleX, float width, float bitmapWidth, uint32_t& quadCount); - void generateQuad(TextureVertex*& vertex, - float x1, float y1, float x2, float y2, - float u1, float v1, float u2, float v2, - uint32_t& quadCount); + void generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, float y2, + float u1, float v1, float u2, float v2, uint32_t& quadCount); + + uint32_t mColorKey; + UvMapper mUvMapper; }; // struct Patch }; // namespace uirenderer diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index 62e38d3..5fa75e9 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -16,8 +16,10 @@ #define LOG_TAG "OpenGLRenderer" +#include <utils/JenkinsHash.h> #include <utils/Log.h> +#include "Caches.h" #include "PatchCache.h" #include "Properties.h" @@ -28,107 +30,107 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -PatchCache::PatchCache(): mMaxEntries(DEFAULT_PATCH_CACHE_SIZE) { -} - -PatchCache::PatchCache(uint32_t maxEntries): mMaxEntries(maxEntries) { +PatchCache::PatchCache(): mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity) { + char property[PROPERTY_VALUE_MAX]; + if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, NULL) > 0) { + INIT_LOGD(" Setting patch cache size to %skB", property); + mMaxSize = KB(atoi(property)); + } else { + INIT_LOGD(" Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE); + mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE); + } + mSize = 0; } PatchCache::~PatchCache() { clear(); } +void PatchCache::init(Caches& caches) { + glGenBuffers(1, &mMeshBuffer); + caches.bindMeshBuffer(mMeshBuffer); + caches.resetVertexPointers(); + + glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW); +} + /////////////////////////////////////////////////////////////////////////////// // Caching /////////////////////////////////////////////////////////////////////////////// -int PatchCache::PatchDescription::compare( - const PatchCache::PatchDescription& lhs, const PatchCache::PatchDescription& rhs) { - int deltaInt = lhs.bitmapWidth - rhs.bitmapWidth; - if (deltaInt != 0) return deltaInt; - - deltaInt = lhs.bitmapHeight - rhs.bitmapHeight; - if (deltaInt != 0) return deltaInt; - - if (lhs.pixelWidth < rhs.pixelWidth) return -1; - if (lhs.pixelWidth > rhs.pixelWidth) return +1; - - if (lhs.pixelHeight < rhs.pixelHeight) return -1; - if (lhs.pixelHeight > rhs.pixelHeight) return +1; - - deltaInt = lhs.xCount - rhs.xCount; - if (deltaInt != 0) return deltaInt; - - deltaInt = lhs.yCount - rhs.yCount; - if (deltaInt != 0) return deltaInt; - - deltaInt = lhs.emptyCount - rhs.emptyCount; - if (deltaInt != 0) return deltaInt; - - deltaInt = lhs.colorKey - rhs.colorKey; - if (deltaInt != 0) return deltaInt; +hash_t PatchCache::PatchDescription::hash() const { + uint32_t hash = JenkinsHashMix(0, android::hash_type(mPatch)); + hash = JenkinsHashMix(hash, mBitmapWidth); + hash = JenkinsHashMix(hash, mBitmapHeight); + hash = JenkinsHashMix(hash, mPixelWidth); + hash = JenkinsHashMix(hash, mPixelHeight); + return JenkinsHashWhiten(hash); +} - return 0; +int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs, + const PatchCache::PatchDescription& rhs) { + return memcmp(&lhs, &rhs, sizeof(PatchDescription)); } void PatchCache::clear() { - size_t count = mCache.size(); - for (size_t i = 0; i < count; i++) { - delete mCache.valueAt(i); + glDeleteBuffers(1, &mMeshBuffer); + clearCache(); + mSize = 0; +} + +void PatchCache::clearCache() { + LruCache<PatchDescription, Patch*>::Iterator i(mCache); + while (i.next()) { + ALOGD("Delete %p", i.value()); + delete i.value(); } mCache.clear(); } -Patch* PatchCache::get(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, - const int32_t* xDivs, const int32_t* yDivs, const uint32_t* colors, - const uint32_t width, const uint32_t height, const int8_t numColors) { +const Patch* PatchCache::get(const AssetAtlas::Entry* entry, + const uint32_t bitmapWidth, const uint32_t bitmapHeight, + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) { - int8_t transparentQuads = 0; - uint32_t colorKey = 0; + const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch); + const Patch* mesh = mCache.get(description); - if (uint8_t(numColors) < sizeof(uint32_t) * 4) { - for (int8_t i = 0; i < numColors; i++) { - if (colors[i] == 0x0) { - transparentQuads++; - colorKey |= 0x1 << i; - } + if (!mesh) { + Patch* newMesh = new Patch(); + TextureVertex* vertices; + + if (entry) { + vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, + 0.0f, 0.0f, pixelWidth, pixelHeight, entry->uvMapper, patch); + } else { + vertices = newMesh->createMesh(bitmapWidth, bitmapHeight, + 0.0f, 0.0f, pixelWidth, pixelHeight, patch); } - } - // If the 9patch is made of only transparent quads - if (transparentQuads == int8_t((width + 1) * (height + 1))) { - return NULL; - } + if (vertices) { + Caches& caches = Caches::getInstance(); + caches.bindMeshBuffer(mMeshBuffer); + caches.resetVertexPointers(); + + // TODO: Simply remove the oldest items until we have enough room + // This will require to keep a list of free blocks in the VBO + uint32_t size = newMesh->getSize(); + if (mSize + size > mMaxSize) { + clearCache(); + glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW); + mSize = 0; + } - const PatchDescription description(bitmapWidth, bitmapHeight, - pixelWidth, pixelHeight, width, height, transparentQuads, colorKey); + newMesh->offset = (GLintptr) mSize; + newMesh->textureOffset = newMesh->offset + gMeshTextureOffset; + mSize += size; - ssize_t index = mCache.indexOfKey(description); - Patch* mesh = NULL; - if (index >= 0) { - mesh = mCache.valueAt(index); - } + glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices); - if (!mesh) { - PATCH_LOGD("New patch mesh " - "xCount=%d yCount=%d, w=%.2f h=%.2f, bw=%.2f bh=%.2f", - width, height, pixelWidth, pixelHeight, bitmapWidth, bitmapHeight); - - mesh = new Patch(width, height, transparentQuads); - mesh->updateColorKey(colorKey); - mesh->copy(xDivs, yDivs); - mesh->updateVertices(bitmapWidth, bitmapHeight, 0.0f, 0.0f, pixelWidth, pixelHeight); - - if (mCache.size() >= mMaxEntries) { - delete mCache.valueAt(mCache.size() - 1); - mCache.removeItemsAt(mCache.size() - 1, 1); + delete[] vertices; } - mCache.add(description, mesh); - } else if (!mesh->matches(xDivs, yDivs, colorKey, transparentQuads)) { - PATCH_LOGD("Patch mesh does not match, refreshing vertices"); - mesh->updateVertices(bitmapWidth, bitmapHeight, 0.0f, 0.0f, pixelWidth, pixelHeight); + mCache.put(description, newMesh); + return newMesh; } return mesh; diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 0822cba..129a0dc 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -17,8 +17,13 @@ #ifndef ANDROID_HWUI_PATCH_CACHE_H #define ANDROID_HWUI_PATCH_CACHE_H -#include <utils/KeyedVector.h> +#include <GLES2/gl2.h> +#include <utils/LruCache.h> + +#include <androidfw/ResourceTypes.h> + +#include "AssetAtlas.h" #include "Debug.h" #include "Patch.h" @@ -40,45 +45,47 @@ namespace uirenderer { // Cache /////////////////////////////////////////////////////////////////////////////// +class Caches; + class PatchCache { public: PatchCache(); - PatchCache(uint32_t maxCapacity); ~PatchCache(); + void init(Caches& caches); - Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, - const int32_t* xDivs, const int32_t* yDivs, const uint32_t* colors, - const uint32_t width, const uint32_t height, const int8_t numColors); + const Patch* get(const AssetAtlas::Entry* entry, + const uint32_t bitmapWidth, const uint32_t bitmapHeight, + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch); void clear(); uint32_t getSize() const { - return mCache.size(); + return mSize; } uint32_t getMaxSize() const { - return mMaxEntries; + return mMaxSize; + } + + GLuint getMeshBuffer() const { + return mMeshBuffer; } private: - /** - * Description of a patch. - */ + void clearCache(); + struct PatchDescription { - PatchDescription(): bitmapWidth(0), bitmapHeight(0), pixelWidth(0), pixelHeight(0), - xCount(0), yCount(0), emptyCount(0), colorKey(0) { + PatchDescription(): mPatch(NULL), mBitmapWidth(0), mBitmapHeight(0), + mPixelWidth(0), mPixelHeight(0) { } PatchDescription(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, - const uint32_t xCount, const uint32_t yCount, - const int8_t emptyCount, const uint32_t colorKey): - bitmapWidth(bitmapWidth), bitmapHeight(bitmapHeight), - pixelWidth(pixelWidth), pixelHeight(pixelHeight), - xCount(xCount), yCount(yCount), - emptyCount(emptyCount), colorKey(colorKey) { + const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch): + mPatch(patch), mBitmapWidth(bitmapWidth), mBitmapHeight(bitmapHeight), + mPixelWidth(pixelWidth), mPixelHeight(pixelHeight) { } + hash_t hash() const; + static int compare(const PatchDescription& lhs, const PatchDescription& rhs); bool operator==(const PatchDescription& other) const { @@ -99,21 +106,24 @@ private: return PatchDescription::compare(lhs, rhs); } + friend inline hash_t hash_type(const PatchDescription& entry) { + return entry.hash(); + } + private: - uint32_t bitmapWidth; - uint32_t bitmapHeight; - float pixelWidth; - float pixelHeight; - uint32_t xCount; - uint32_t yCount; - int8_t emptyCount; - uint32_t colorKey; + const Res_png_9patch* mPatch; + uint32_t mBitmapWidth; + uint32_t mBitmapHeight; + float mPixelWidth; + float mPixelHeight; }; // struct PatchDescription - uint32_t mMaxEntries; - KeyedVector<PatchDescription, Patch*> mCache; + uint32_t mMaxSize; + uint32_t mSize; + LruCache<PatchDescription, Patch*> mCache; + GLuint mMeshBuffer; }; // class PatchCache }; // namespace uirenderer diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp index 14a2376..c127d68 100644 --- a/libs/hwui/Program.cpp +++ b/libs/hwui/Program.cpp @@ -15,6 +15,9 @@ */ #define LOG_TAG "OpenGLRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include <utils/Trace.h> #include "Program.h" @@ -25,7 +28,6 @@ namespace uirenderer { // Base program /////////////////////////////////////////////////////////////////////////////// -// TODO: Program instance should be created from a factory method Program::Program(const ProgramDescription& description, const char* vertex, const char* fragment) { mInitialized = false; mHasColorUniform = false; @@ -50,7 +52,9 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons texCoords = -1; } + ATRACE_BEGIN("linkProgram"); glLinkProgram(mProgramId); + ATRACE_END(); GLint status; glGetProgramiv(mProgramId, GL_LINK_STATUS, &status); @@ -87,6 +91,9 @@ Program::Program(const ProgramDescription& description, const char* vertex, cons Program::~Program() { if (mInitialized) { + // This would ideally happen after linking the program + // but Tegra drivers, especially when perfhud is enabled, + // sometimes crash if we do so glDetachShader(mProgramId, mVertexShader); glDetachShader(mProgramId, mFragmentShader); @@ -132,6 +139,8 @@ int Program::getUniform(const char* name) { } GLuint Program::buildShader(const char* source, GLenum type) { + ATRACE_CALL(); + GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, 0); glCompileShader(shader); @@ -153,20 +162,24 @@ GLuint Program::buildShader(const char* source, GLenum type) { void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix, const mat4& transformMatrix, bool offset) { - mat4 p(projectionMatrix); - if (offset) { - // offset screenspace xy by an amount that compensates for typical precision - // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted - // up and to the left. - // This offset value is based on an assumption that some hardware may use as - // little as 12.4 precision, so we offset by slightly more than 1/16. - p.translate(.065, .065, 0); + if (projectionMatrix != mProjection) { + if (CC_LIKELY(!offset)) { + glUniformMatrix4fv(projection, 1, GL_FALSE, &projectionMatrix.data[0]); + } else { + mat4 p(projectionMatrix); + // offset screenspace xy by an amount that compensates for typical precision + // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted + // up and to the left. + // This offset value is based on an assumption that some hardware may use as + // little as 12.4 precision, so we offset by slightly more than 1/16. + p.translate(.065, .065, 0); + glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]); + } + mProjection = projectionMatrix; } mat4 t(transformMatrix); t.multiply(modelViewMatrix); - - glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]); glUniformMatrix4fv(transform, 1, GL_FALSE, &t.data[0]); } diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index e8b6d47..a252209 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -430,10 +430,13 @@ private: bool mUse; bool mInitialized; + // Uniforms caching bool mHasColorUniform; int mColorUniform; bool mHasSampler; + + mat4 mProjection; }; // class Program }; // namespace uirenderer diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 6eea00c..87ac845 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -133,6 +133,7 @@ enum DebugLevel { #define PROPERTY_RENDER_BUFFER_CACHE_SIZE "ro.hwui.r_buffer_cache_size" #define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size" #define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size" +#define PROPERTY_PATCH_CACHE_SIZE "ro.hwui.patch_cache_size" #define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size" #define PROPERTY_FBO_CACHE_SIZE "ro.hwui.fbo_cache_size" @@ -178,7 +179,7 @@ enum DebugLevel { #define DEFAULT_LAYER_CACHE_SIZE 16.0f #define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f #define DEFAULT_PATH_CACHE_SIZE 10.0f -#define DEFAULT_PATCH_CACHE_SIZE 512 +#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f #define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f #define DEFAULT_FBO_CACHE_SIZE 16 @@ -195,6 +196,8 @@ enum DebugLevel { // Converts a number of mega-bytes into bytes #define MB(s) s * 1024 * 1024 +// Converts a number of kilo-bytes into bytes +#define KB(s) s * 1024 static DebugLevel readDebugLevel() { char property[PROPERTY_VALUE_MAX]; diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index 8d88bdc..dd39cae 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -22,6 +22,8 @@ namespace android { namespace uirenderer { +class UvMapper; + /** * Represents an OpenGL texture. */ @@ -42,6 +44,8 @@ struct Texture { firstWrap = true; id = 0; + + uvMapper = NULL; } void setWrap(GLenum wrap, bool bindTexture = false, bool force = false, @@ -125,6 +129,11 @@ struct Texture { */ bool mipMap; + /** + * Optional, pointer to a texture coordinates mapper. + */ + const UvMapper* uvMapper; + private: /** * Last wrap modes set on this texture. Defaults to GL_CLAMP_TO_EDGE. diff --git a/libs/hwui/UvMapper.h b/libs/hwui/UvMapper.h new file mode 100644 index 0000000..70428d2 --- /dev/null +++ b/libs/hwui/UvMapper.h @@ -0,0 +1,133 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_UV_MAPPER_H +#define ANDROID_HWUI_UV_MAPPER_H + +#include "Rect.h" + +namespace android { +namespace uirenderer { + +/** + * This class can be used to map UV coordinates from the [0..1] + * range to other arbitrary ranges. All the methods below assume + * that the input values lie in the [0..1] range already. + */ +class UvMapper { +public: + /** + * Using this constructor is equivalent to not using any mapping at all. + * UV coordinates in the [0..1] range remain in the [0..1] range. + */ + UvMapper(): mIdentity(true), mMinU(0.0f), mMaxU(1.0f), mMinV(0.0f), mMaxV(1.0f) { + } + + /** + * Creates a new mapper with the specified ranges for U and V coordinates. + * The parameter minU must be < maxU and minV must be < maxV. + */ + UvMapper(float minU, float maxU, float minV, float maxV): + mMinU(minU), mMaxU(maxU), mMinV(minV), mMaxV(maxV) { + checkIdentity(); + } + + /** + * Returns true if calling the map*() methods has no effect (that is, + * texture coordinates remain in the 0..1 range.) + */ + bool isIdentity() const { + return mIdentity; + } + + /** + * Changes the U and V mapping ranges. + * The parameter minU must be < maxU and minV must be < maxV. + */ + void setMapping(float minU, float maxU, float minV, float maxV) { + mMinU = minU; + mMaxU = maxU; + mMinV = minV; + mMaxV = maxV; + checkIdentity(); + } + + /** + * Maps a single value in the U range. + */ + void mapU(float& u) const { + if (!mIdentity) u = lerp(mMinU, mMaxU, u); + } + + /** + * Maps a single value in the V range. + */ + void mapV(float& v) const { + if (!mIdentity) v = lerp(mMinV, mMaxV, v); + } + + /** + * Maps the specified rectangle in place. This method assumes: + * - left = min. U + * - top = min. V + * - right = max. U + * - bottom = max. V + */ + void map(Rect& texCoords) const { + if (!mIdentity) { + texCoords.left = lerp(mMinU, mMaxU, texCoords.left); + texCoords.right = lerp(mMinU, mMaxU, texCoords.right); + texCoords.top = lerp(mMinV, mMaxV, texCoords.top); + texCoords.bottom = lerp(mMinV, mMaxV, texCoords.bottom); + } + } + + /** + * Maps the specified UV coordinates in place. + */ + void map(float& u1, float& v1, float& u2, float& v2) const { + if (!mIdentity) { + u1 = lerp(mMinU, mMaxU, u1); + u2 = lerp(mMinU, mMaxU, u2); + v1 = lerp(mMinV, mMaxV, v1); + v2 = lerp(mMinV, mMaxV, v2); + } + } + + void dump() const { + ALOGD("mapper[minU=%.2f maxU=%.2f minV=%.2f maxV=%.2f]", mMinU, mMaxU, mMinV, mMaxV); + } + +private: + static float lerp(float start, float stop, float amount) { + return start + (stop - start) * amount; + } + + void checkIdentity() { + mIdentity = mMinU == 0.0f && mMaxU == 1.0f && mMinV == 0.0f && mMaxV == 1.0f; + } + + bool mIdentity; + float mMinU; + float mMaxU; + float mMinV; + float mMaxV; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_UV_MAPPER_H diff --git a/services/java/com/android/server/AssetAtlasService.java b/services/java/com/android/server/AssetAtlasService.java new file mode 100644 index 0000000..b18be1c --- /dev/null +++ b/services/java/com/android/server/AssetAtlasService.java @@ -0,0 +1,730 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Atlas; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.Drawable; +import android.os.Environment; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; +import android.util.LongSparseArray; +import android.view.GraphicBuffer; +import android.view.IAssetAtlas; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This service is responsible for packing preloaded bitmaps into a single + * atlas texture. The resulting texture can be shared across processes to + * reduce overall memory usage. + * + * @hide + */ +public class AssetAtlasService extends IAssetAtlas.Stub { + /** + * Name of the <code>AssetAtlasService</code>. + */ + public static final String ASSET_ATLAS_SERVICE = "assetatlas"; + + private static final String LOG_TAG = "Atlas"; + + // Turns debug logs on/off. Debug logs are kept to a minimum and should + // remain on to diagnose issues + private static final boolean DEBUG_ATLAS = true; + + // When set to true the content of the atlas will be saved to disk + // in /data/system/atlas.png. The shared GraphicBuffer may be empty + private static final boolean DEBUG_ATLAS_TEXTURE = false; + + // Minimum size in pixels to consider for the resulting texture + private static final int MIN_SIZE = 768; + // Maximum size in pixels to consider for the resulting texture + private static final int MAX_SIZE = 2048; + // Increment in number of pixels between size variants when looking + // for the best texture dimensions + private static final int STEP = 64; + + // This percentage of the total number of pixels represents the minimum + // number of pixels we want to be able to pack in the atlas + private static final float PACKING_THRESHOLD = 0.8f; + + // Defines the number of int fields used to represent a single entry + // in the atlas map. This number defines the size of the array returned + // by the getMap(). See the mAtlasMap field for more information + private static final int ATLAS_MAP_ENTRY_FIELD_COUNT = 4; + + // Specifies how our GraphicBuffer will be used. To get proper swizzling + // the buffer will be written to using OpenGL (from JNI) so we can leave + // the software flag set to "never" + private static final int GRAPHIC_BUFFER_USAGE = GraphicBuffer.USAGE_SW_READ_NEVER | + GraphicBuffer.USAGE_SW_WRITE_NEVER | GraphicBuffer.USAGE_HW_TEXTURE; + + // This boolean is set to true if an atlas was successfully + // computed and rendered + private final AtomicBoolean mAtlasReady = new AtomicBoolean(false); + + private final Context mContext; + + // Version name of the current build, used to identify changes to assets list + private final String mVersionName; + + // Holds the atlas' data. This buffer can be mapped to + // OpenGL using an EGLImage + private GraphicBuffer mBuffer; + + // Describes how bitmaps are placed in the atlas. Each bitmap is + // represented by several entries in the array: + // int0: SkBitmap*, the native bitmap object + // int1: x position + // int2: y position + // int3: rotated, 1 if the bitmap must be rotated, 0 otherwise + // NOTE: This will need to be handled differently to support 64 bit pointers + private int[] mAtlasMap; + + /** + * Creates a new service. Upon creating, the service will gather the list of + * assets to consider for packing into the atlas and spawn a new thread to + * start the packing work. + * + * @param context The context giving access to preloaded resources + */ + public AssetAtlasService(Context context) { + mContext = context; + mVersionName = queryVersionName(context); + + ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(300); + int totalPixelCount = 0; + + // We only care about drawables that hold bitmaps + final Resources resources = context.getResources(); + final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables(); + + final int count = drawables.size(); + for (int i = 0; i < count; i++) { + final Bitmap bitmap = drawables.valueAt(i).getBitmap(); + if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) { + bitmaps.add(bitmap); + totalPixelCount += bitmap.getWidth() * bitmap.getHeight(); + } + } + + // Our algorithms perform better when the bitmaps are first sorted + // The comparator will sort the bitmap by width first, then by height + Collections.sort(bitmaps, new Comparator<Bitmap>() { + @Override + public int compare(Bitmap b1, Bitmap b2) { + if (b1.getWidth() == b2.getWidth()) { + return b2.getHeight() - b1.getHeight(); + } + return b2.getWidth() - b1.getWidth(); + } + }); + + // Kick off the packing work on a worker thread + new Thread(new Renderer(bitmaps, totalPixelCount)).start(); + } + + /** + * Queries the version name stored in framework's AndroidManifest. + * The version name can be used to identify possible changes to + * framework resources. + * + * @see #getBuildIdentifier(String) + */ + private static String queryVersionName(Context context) { + try { + String packageName = context.getPackageName(); + PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); + return info.versionName; + } catch (PackageManager.NameNotFoundException e) { + Log.w(LOG_TAG, "Could not get package info", e); + } + return null; + } + + /** + * Callback invoked by the server thread to indicate we can now run + * 3rd party code. + */ + public void systemReady() { + } + + /** + * The renderer does all the work: + */ + private class Renderer implements Runnable { + private final ArrayList<Bitmap> mBitmaps; + private final int mPixelCount; + + private int mNativeBitmap; + + // Used for debugging only + private Bitmap mAtlasBitmap; + + Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) { + mBitmaps = bitmaps; + mPixelCount = pixelCount; + } + + /** + * 1. On first boot or after every update, brute-force through all the + * possible atlas configurations and look for the best one (maximimize + * number of packed assets and minimize texture size) + * a. If a best configuration was computed, write it out to disk for + * future use + * 2. Read best configuration from disk + * 3. Compute the packing using the best configuration + * 4. Allocate a GraphicBuffer + * 5. Render assets in the buffer + */ + @Override + public void run() { + Configuration config = chooseConfiguration(mBitmaps, mPixelCount, mVersionName); + if (DEBUG_ATLAS) Log.d(LOG_TAG, "Loaded configuration: " + config); + + if (config != null) { + mBuffer = GraphicBuffer.create(config.width, config.height, + PixelFormat.RGBA_8888, GRAPHIC_BUFFER_USAGE); + + if (mBuffer != null) { + Atlas atlas = new Atlas(config.type, config.width, config.height, config.flags); + if (renderAtlas(mBuffer, atlas, config.count)) { + mAtlasReady.set(true); + } + } + } + } + + /** + * Renders a list of bitmaps into the atlas. The position of each bitmap + * was decided by the packing algorithm and will be honored by this + * method. If need be this method will also rotate bitmaps. + * + * @param buffer The buffer to render the atlas entries into + * @param atlas The atlas to pack the bitmaps into + * @param packCount The number of bitmaps that will be packed in the atlas + * + * @return true if the atlas was rendered, false otherwise + */ + @SuppressWarnings("MismatchedReadAndWriteOfArray") + private boolean renderAtlas(GraphicBuffer buffer, Atlas atlas, int packCount) { + // Use a Source blend mode to improve performance, the target bitmap + // will be zero'd out so there's no need to waste time applying blending + final Paint paint = new Paint(); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + + // We always render the atlas into a bitmap. This bitmap is then + // uploaded into the GraphicBuffer using OpenGL to swizzle the content + final Canvas canvas = acquireCanvas(buffer.getWidth(), buffer.getHeight()); + if (canvas == null) return false; + + final Atlas.Entry entry = new Atlas.Entry(); + + mAtlasMap = new int[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT]; + int[] atlasMap = mAtlasMap; + int mapIndex = 0; + + boolean result = false; + try { + final long startRender = System.nanoTime(); + final int count = mBitmaps.size(); + + for (int i = 0; i < count; i++) { + final Bitmap bitmap = mBitmaps.get(i); + if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) { + // We have more bitmaps to pack than the current configuration + // says, we were most likely not able to detect a change in the + // list of preloaded drawables, abort and delete the configuration + if (mapIndex >= mAtlasMap.length) { + deleteDataFile(); + break; + } + + canvas.save(); + canvas.translate(entry.x, entry.y); + if (entry.rotated) { + canvas.translate(bitmap.getHeight(), 0.0f); + canvas.rotate(90.0f); + } + canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); + canvas.restore(); + + atlasMap[mapIndex++] = bitmap.mNativeBitmap; + atlasMap[mapIndex++] = entry.x; + atlasMap[mapIndex++] = entry.y; + atlasMap[mapIndex++] = entry.rotated ? 1 : 0; + } + } + + final long endRender = System.nanoTime(); + if (mNativeBitmap != 0) { + result = nUploadAtlas(buffer, mNativeBitmap); + } + + final long endUpload = System.nanoTime(); + if (DEBUG_ATLAS) { + float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f; + float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f; + Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)", + renderDuration + uploadDuration, renderDuration, uploadDuration)); + } + + } finally { + releaseCanvas(canvas); + } + + return result; + } + + /** + * Returns a Canvas for the specified buffer. If {@link #DEBUG_ATLAS_TEXTURE} + * is turned on, the returned Canvas will render into a local bitmap that + * will then be saved out to disk for debugging purposes. + * @param width + * @param height + */ + private Canvas acquireCanvas(int width, int height) { + if (DEBUG_ATLAS_TEXTURE) { + mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + return new Canvas(mAtlasBitmap); + } else { + Canvas canvas = new Canvas(); + mNativeBitmap = nAcquireAtlasCanvas(canvas, width, height); + return canvas; + } + } + + /** + * Releases the canvas used to render into the buffer. Calling this method + * will release any resource previously acquired. If {@link #DEBUG_ATLAS_TEXTURE} + * is turend on, calling this method will write the content of the atlas + * to disk in /data/system/atlas.png for debugging. + */ + private void releaseCanvas(Canvas canvas) { + if (DEBUG_ATLAS_TEXTURE) { + canvas.setBitmap(null); + + File systemDirectory = new File(Environment.getDataDirectory(), "system"); + File dataFile = new File(systemDirectory, "atlas.png"); + + try { + FileOutputStream out = new FileOutputStream(dataFile); + mAtlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + out.close(); + } catch (FileNotFoundException e) { + // Ignore + } catch (IOException e) { + // Ignore + } + + mAtlasBitmap.recycle(); + mAtlasBitmap = null; + } else { + nReleaseAtlasCanvas(canvas, mNativeBitmap); + } + } + } + + private static native int nAcquireAtlasCanvas(Canvas canvas, int width, int height); + private static native void nReleaseAtlasCanvas(Canvas canvas, int bitmap); + private static native boolean nUploadAtlas(GraphicBuffer buffer, int bitmap); + + @Override + public GraphicBuffer getBuffer() throws RemoteException { + return mAtlasReady.get() ? mBuffer : null; + } + + @Override + public int[] getMap() throws RemoteException { + return mAtlasReady.get() ? mAtlasMap : null; + } + + /** + * Finds the best atlas configuration to pack the list of supplied bitmaps. + * This method takes advantage of multi-core systems by spawning a number + * of threads equal to the number of available cores. + */ + private static Configuration computeBestConfiguration( + ArrayList<Bitmap> bitmaps, int pixelCount) { + if (DEBUG_ATLAS) Log.d(LOG_TAG, "Computing best atlas configuration..."); + + long begin = System.nanoTime(); + List<WorkerResult> results = Collections.synchronizedList(new ArrayList<WorkerResult>()); + + // Don't bother with an extra thread if there's only one processor + int cpuCount = Runtime.getRuntime().availableProcessors(); + if (cpuCount == 1) { + new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run(); + } else { + int start = MIN_SIZE; + int end = MAX_SIZE - (cpuCount - 1) * STEP; + int step = STEP * cpuCount; + + final CountDownLatch signal = new CountDownLatch(cpuCount); + + for (int i = 0; i < cpuCount; i++, start += STEP, end += STEP) { + ComputeWorker worker = new ComputeWorker(start, end, step, + bitmaps, pixelCount, results, signal); + new Thread(worker, "Atlas Worker #" + (i + 1)).start(); + } + + try { + signal.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.w(LOG_TAG, "Could not complete configuration computation"); + return null; + } + } + + // Maximize the number of packed bitmaps, minimize the texture size + Collections.sort(results, new Comparator<WorkerResult>() { + @Override + public int compare(WorkerResult r1, WorkerResult r2) { + int delta = r2.count - r1.count; + if (delta != 0) return delta; + return r1.width * r1.height - r2.width * r2.height; + } + }); + + if (DEBUG_ATLAS) { + float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f; + Log.d(LOG_TAG, String.format("Found best atlas configuration in %.2fs", delay)); + } + + WorkerResult result = results.get(0); + return new Configuration(result.type, result.width, result.height, result.count); + } + + /** + * Returns the path to the file containing the best computed + * atlas configuration. + */ + private static File getDataFile() { + File systemDirectory = new File(Environment.getDataDirectory(), "system"); + return new File(systemDirectory, "framework_atlas.config"); + } + + private static void deleteDataFile() { + Log.w(LOG_TAG, "Current configuration inconsistent with assets list"); + if (!getDataFile().delete()) { + Log.w(LOG_TAG, "Could not delete the current configuration"); + } + } + + private File getFrameworkResourcesFile() { + return new File(mContext.getApplicationInfo().sourceDir); + } + + /** + * Returns the best known atlas configuration. This method will either + * read the configuration from disk or start a brute-force search + * and save the result out to disk. + */ + private Configuration chooseConfiguration(ArrayList<Bitmap> bitmaps, int pixelCount, + String versionName) { + Configuration config = null; + + final File dataFile = getDataFile(); + if (dataFile.exists()) { + config = readConfiguration(dataFile, versionName); + } + + if (config == null) { + config = computeBestConfiguration(bitmaps, pixelCount); + if (config != null) writeConfiguration(config, dataFile, versionName); + } + + return config; + } + + /** + * Writes the specified atlas configuration to the specified file. + */ + private void writeConfiguration(Configuration config, File file, String versionName) { + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); + writer.write(getBuildIdentifier(versionName)); + writer.newLine(); + writer.write(config.type.toString()); + writer.newLine(); + writer.write(String.valueOf(config.width)); + writer.newLine(); + writer.write(String.valueOf(config.height)); + writer.newLine(); + writer.write(String.valueOf(config.count)); + writer.newLine(); + writer.write(String.valueOf(config.flags)); + writer.newLine(); + } catch (FileNotFoundException e) { + Log.w(LOG_TAG, "Could not write " + file, e); + } catch (IOException e) { + Log.w(LOG_TAG, "Could not write " + file, e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + + /** + * Reads an atlas configuration from the specified file. This method + * returns null if an error occurs or if the configuration is invalid. + */ + private Configuration readConfiguration(File file, String versionName) { + BufferedReader reader = null; + Configuration config = null; + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + + if (checkBuildIdentifier(reader, versionName)) { + Atlas.Type type = Atlas.Type.valueOf(reader.readLine()); + int width = readInt(reader, MIN_SIZE, MAX_SIZE); + int height = readInt(reader, MIN_SIZE, MAX_SIZE); + int count = readInt(reader, 0, Integer.MAX_VALUE); + int flags = readInt(reader, Integer.MIN_VALUE, Integer.MAX_VALUE); + + config = new Configuration(type, width, height, count, flags); + } + } catch (IllegalArgumentException e) { + Log.w(LOG_TAG, "Invalid parameter value in " + file, e); + } catch (FileNotFoundException e) { + Log.w(LOG_TAG, "Could not read " + file, e); + } catch (IOException e) { + Log.w(LOG_TAG, "Could not read " + file, e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + // Ignore + } + } + } + return config; + } + + private static int readInt(BufferedReader reader, int min, int max) throws IOException { + return Math.max(min, Math.min(max, Integer.parseInt(reader.readLine()))); + } + + /** + * Compares the next line in the specified buffered reader to the current + * build identifier. Returns whether the two values are equal. + * + * @see #getBuildIdentifier(String) + */ + private boolean checkBuildIdentifier(BufferedReader reader, String versionName) + throws IOException { + String deviceBuildId = getBuildIdentifier(versionName); + String buildId = reader.readLine(); + return deviceBuildId.equals(buildId); + } + + /** + * Returns an identifier for the current build that can be used to detect + * likely changes to framework resources. The build identifier is made of + * several distinct values: + * + * build fingerprint/framework version name/file size of framework resources apk + * + * Only the build fingerprint should be necessary on user builds but + * the other values are useful to detect changes on eng builds during + * development. + * + * This identifier does not attempt to be exact: a new identifier does not + * necessarily mean the preloaded drawables have changed. It is important + * however that whenever the list of preloaded drawables changes, this + * identifier changes as well. + * + * @see #checkBuildIdentifier(java.io.BufferedReader, String) + */ + private String getBuildIdentifier(String versionName) { + return SystemProperties.get("ro.build.fingerprint", "") + '/' + versionName + '/' + + String.valueOf(getFrameworkResourcesFile().length()); + } + + /** + * Atlas configuration. Specifies the algorithm, dimensions and flags to use. + */ + private static class Configuration { + final Atlas.Type type; + final int width; + final int height; + final int count; + final int flags; + + Configuration(Atlas.Type type, int width, int height, int count) { + this(type, width, height, count, Atlas.FLAG_DEFAULTS); + } + + Configuration(Atlas.Type type, int width, int height, int count, int flags) { + this.type = type; + this.width = width; + this.height = height; + this.count = count; + this.flags = flags; + } + + @Override + public String toString() { + return type.toString() + " (" + width + "x" + height + ") flags=0x" + + Integer.toHexString(flags) + " count=" + count; + } + } + + /** + * Used during the brute-force search to gather information about each + * variant of the packing algorithm. + */ + private static class WorkerResult { + Atlas.Type type; + int width; + int height; + int count; + + WorkerResult(Atlas.Type type, int width, int height, int count) { + this.type = type; + this.width = width; + this.height = height; + this.count = count; + } + + @Override + public String toString() { + return String.format("%s %dx%d", type.toString(), width, height); + } + } + + /** + * A compute worker will try a finite number of variations of the packing + * algorithms and save the results in a supplied list. + */ + private static class ComputeWorker implements Runnable { + private final int mStart; + private final int mEnd; + private final int mStep; + private final List<Bitmap> mBitmaps; + private final List<WorkerResult> mResults; + private final CountDownLatch mSignal; + private final int mThreshold; + + /** + * Creates a new compute worker to brute-force through a range of + * packing algorithms variants. + * + * @param start The minimum texture width to try + * @param end The maximum texture width to try + * @param step The number of pixels to increment the texture width by at each step + * @param bitmaps The list of bitmaps to pack in the atlas + * @param pixelCount The total number of pixels occupied by the list of bitmaps + * @param results The list of results in which to save the brute-force search results + * @param signal Latch to decrement when this worker is done, may be null + */ + ComputeWorker(int start, int end, int step, List<Bitmap> bitmaps, int pixelCount, + List<WorkerResult> results, CountDownLatch signal) { + mStart = start; + mEnd = end; + mStep = step; + mBitmaps = bitmaps; + mResults = results; + mSignal = signal; + + // Minimum number of pixels we want to be able to pack + int threshold = (int) (pixelCount * PACKING_THRESHOLD); + // Make sure we can find at least one configuration + while (threshold > MAX_SIZE * MAX_SIZE) { + threshold >>= 1; + } + mThreshold = threshold; + } + + @Override + public void run() { + if (DEBUG_ATLAS) Log.d(LOG_TAG, "Running " + Thread.currentThread().getName()); + + Atlas.Entry entry = new Atlas.Entry(); + for (Atlas.Type type : Atlas.Type.values()) { + for (int width = mStart; width < mEnd; width += mStep) { + for (int height = MIN_SIZE; height < MAX_SIZE; height += STEP) { + // If the atlas is not big enough, skip it + if (width * height <= mThreshold) continue; + + final int count = packBitmaps(type, width, height, entry); + if (count > 0) { + mResults.add(new WorkerResult(type, width, height, count)); + // If we were able to pack everything let's stop here + // Increasing the height further won't make things better + if (count == mBitmaps.size()) { + break; + } + } + } + } + } + + if (mSignal != null) { + mSignal.countDown(); + } + } + + private int packBitmaps(Atlas.Type type, int width, int height, Atlas.Entry entry) { + int total = 0; + Atlas atlas = new Atlas(type, width, height); + + final int count = mBitmaps.size(); + for (int i = 0; i < count; i++) { + final Bitmap bitmap = mBitmaps.get(i); + if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) { + total++; + } + } + + return total; + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 92f72ba..25818e4 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -373,6 +373,7 @@ class ServerThread extends Thread { TextServicesManagerService tsms = null; LockSettingsService lockSettings = null; DreamManagerService dreamy = null; + AssetAtlasService atlas = null; // Bring up services needed for UI. if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { @@ -796,6 +797,16 @@ class ServerThread extends Thread { } } + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Assets Atlas Service"); + atlas = new AssetAtlasService(context); + ServiceManager.addService(AssetAtlasService.ASSET_ATLAS_SERVICE, atlas); + } catch (Throwable e) { + reportWtf("starting AssetAtlasService", e); + } + } + try { Slog.i(TAG, "IdleMaintenanceService"); new IdleMaintenanceService(context); @@ -910,6 +921,7 @@ class ServerThread extends Thread { final TextServicesManagerService textServiceManagerServiceF = tsms; final StatusBarManagerService statusBarF = statusBar; final DreamManagerService dreamyF = dreamy; + final AssetAtlasService atlasF = atlas; final InputManagerService inputManagerF = inputManager; final TelephonyRegistry telephonyRegistryF = telephonyRegistry; @@ -1036,6 +1048,11 @@ class ServerThread extends Thread { reportWtf("making DreamManagerService ready", e); } try { + if (atlasF != null) atlasF.systemReady(); + } catch (Throwable e) { + reportWtf("making AssetAtlasService ready", e); + } + try { // TODO(BT) Pass parameter to input manager if (inputManagerF != null) inputManagerF.systemReady(); } catch (Throwable e) { diff --git a/services/jni/Android.mk b/services/jni/Android.mk index b313d48..e416676 100644 --- a/services/jni/Android.mk +++ b/services/jni/Android.mk @@ -3,6 +3,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ com_android_server_AlarmManagerService.cpp \ + com_android_server_AssetAtlasService.cpp \ com_android_server_BatteryService.cpp \ com_android_server_input_InputApplicationHandle.cpp \ com_android_server_input_InputManagerService.cpp \ @@ -43,7 +44,11 @@ LOCAL_SHARED_LIBRARIES := \ libskia \ libgui \ libusbhost \ - libsuspend + libsuspend \ + libEGL \ + libGLESv2 + +LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES ifeq ($(WITH_MALLOC_LEAK_CHECK),true) LOCAL_CFLAGS += -DMALLOC_LEAK_CHECK diff --git a/services/jni/com_android_server_AssetAtlasService.cpp b/services/jni/com_android_server_AssetAtlasService.cpp new file mode 100644 index 0000000..62e950f --- /dev/null +++ b/services/jni/com_android_server_AssetAtlasService.cpp @@ -0,0 +1,271 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AssetAtlasService" + +#include "jni.h" +#include "JNIHelp.h" + +#include <android_view_GraphicBuffer.h> + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include <SkCanvas.h> +#include <SkBitmap.h> + +namespace android { + +// ---------------------------------------------------------------------------- +// Defines +// ---------------------------------------------------------------------------- + +// Defines how long to wait for the GPU when uploading the atlas +// This timeout is defined in nanoseconds (see EGL_KHR_fence_sync extension) +#define FENCE_TIMEOUT 2000000000 + +// ---------------------------------------------------------------------------- +// JNI Helpers +// ---------------------------------------------------------------------------- + +static struct { + jfieldID mFinalizer; + jfieldID mNativeCanvas; +} gCanvasClassInfo; + +static struct { + jfieldID mNativeCanvas; +} gCanvasFinalizerClassInfo; + +#define GET_INT(object, field) \ + env->GetIntField(object, field) + +#define SET_INT(object, field, value) \ + env->SetIntField(object, field, value) + +// ---------------------------------------------------------------------------- +// Canvas management +// ---------------------------------------------------------------------------- + +static inline void swapCanvasPtr(JNIEnv* env, jobject canvasObj, SkCanvas* newCanvas) { + jobject canvasFinalizerObj = env->GetObjectField(canvasObj, gCanvasClassInfo.mFinalizer); + SkCanvas* previousCanvas = reinterpret_cast<SkCanvas*>( + GET_INT(canvasObj, gCanvasClassInfo.mNativeCanvas)); + SET_INT(canvasObj, gCanvasClassInfo.mNativeCanvas, (int) newCanvas); + SET_INT(canvasFinalizerObj, gCanvasFinalizerClassInfo.mNativeCanvas, (int) newCanvas); + SkSafeUnref(previousCanvas); +} + +static SkBitmap* com_android_server_AssetAtlasService_acquireCanvas(JNIEnv* env, jobject, + jobject canvas, jint width, jint height) { + + SkBitmap* bitmap = new SkBitmap; + bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height); + bitmap->allocPixels(); + bitmap->eraseColor(0); + + SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (*bitmap)); + swapCanvasPtr(env, canvas, nativeCanvas); + + return bitmap; +} + +static void com_android_server_AssetAtlasService_releaseCanvas(JNIEnv* env, jobject, + jobject canvas, SkBitmap* bitmap) { + + SkCanvas* nativeCanvas = SkNEW(SkCanvas); + swapCanvasPtr(env, canvas, nativeCanvas); + + delete bitmap; +} + +#define CLEANUP_GL_AND_RETURN(result) \ + if (fence != EGL_NO_SYNC_KHR) eglDestroySyncKHR(display, fence); \ + if (image) eglDestroyImageKHR(display, image); \ + if (texture) glDeleteTextures(1, &texture); \ + if (surface != EGL_NO_SURFACE) eglDestroySurface(display, surface); \ + if (context != EGL_NO_CONTEXT) eglDestroyContext(display, context); \ + eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); \ + eglReleaseThread(); \ + eglTerminate(display); \ + return result; + +static jboolean com_android_server_AssetAtlasService_upload(JNIEnv* env, jobject, + jobject graphicBuffer, SkBitmap* bitmap) { + + // The goal of this method is to copy the bitmap into the GraphicBuffer + // using the GPU to swizzle the texture content + sp<GraphicBuffer> buffer(graphicBufferForJavaObject(env, graphicBuffer)); + + if (buffer != NULL) { + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) return false; + + EGLint major; + EGLint minor; + if (!eglInitialize(display, &major, &minor)) { + ALOGW("Could not initialize EGL"); + return false; + } + + // We're going to use a 1x1 pbuffer surface later on + // The configuration doesn't really matter for what we're trying to do + EGLint configAttrs[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 0, + EGL_DEPTH_SIZE, 0, + EGL_STENCIL_SIZE, 0, + EGL_NONE + }; + EGLConfig configs[1]; + EGLint configCount; + if (!eglChooseConfig(display, configAttrs, configs, 1, &configCount)) { + ALOGW("Could not select EGL configuration"); + eglReleaseThread(); + eglTerminate(display); + return false; + } + if (configCount <= 0) { + ALOGW("Could not find EGL configuration"); + eglReleaseThread(); + eglTerminate(display); + return false; + } + + // These objects are initialized below but the default "null" + // values are used to cleanup properly at any point in the + // initialization sequence + GLuint texture = 0; + EGLImageKHR image = EGL_NO_IMAGE_KHR; + EGLSurface surface = EGL_NO_SURFACE; + EGLSyncKHR fence = EGL_NO_SYNC_KHR; + + EGLint attrs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + EGLContext context = eglCreateContext(display, configs[0], EGL_NO_CONTEXT, attrs); + if (context == EGL_NO_CONTEXT) { + ALOGW("Could not create EGL context"); + CLEANUP_GL_AND_RETURN(false); + } + + // Create the 1x1 pbuffer + EGLint surfaceAttrs[] = { EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE }; + surface = eglCreatePbufferSurface(display, configs[0], surfaceAttrs); + if (surface == EGL_NO_SURFACE) { + ALOGW("Could not create EGL surface"); + CLEANUP_GL_AND_RETURN(false); + } + + if (!eglMakeCurrent(display, surface, surface, context)) { + ALOGW("Could not change current EGL context"); + CLEANUP_GL_AND_RETURN(false); + } + + // We use an EGLImage to access the content of the GraphicBuffer + // The EGL image is later bound to a 2D texture + EGLClientBuffer clientBuffer = (EGLClientBuffer) buffer->getNativeBuffer(); + EGLint imageAttrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; + image = eglCreateImageKHR(display, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, clientBuffer, imageAttrs); + if (image == EGL_NO_IMAGE_KHR) { + ALOGW("Could not create EGL image"); + CLEANUP_GL_AND_RETURN(false); + } + + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); + if (glGetError() != GL_NO_ERROR) { + ALOGW("Could not create/bind texture"); + CLEANUP_GL_AND_RETURN(false); + } + + // Upload the content of the bitmap in the GraphicBuffer + glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap->bytesPerPixel()); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap->width(), bitmap->height(), + GL_RGBA, GL_UNSIGNED_BYTE, bitmap->getPixels()); + if (glGetError() != GL_NO_ERROR) { + ALOGW("Could not upload to texture"); + CLEANUP_GL_AND_RETURN(false); + } + + // The fence is used to wait for the texture upload to finish + // properly. We cannot rely on glFlush() and glFinish() as + // some drivers completely ignore these API calls + fence = eglCreateSyncKHR(display, EGL_SYNC_FENCE_KHR, NULL); + if (fence == EGL_NO_SYNC_KHR) { + ALOGW("Could not create sync fence %#x", eglGetError()); + CLEANUP_GL_AND_RETURN(false); + } + + // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a + // pipeline flush (similar to what a glFlush() would do.) + EGLint waitStatus = eglClientWaitSyncKHR(display, fence, + EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT); + if (waitStatus != EGL_CONDITION_SATISFIED_KHR) { + ALOGW("Failed to wait for the fence %#x", eglGetError()); + CLEANUP_GL_AND_RETURN(false); + } + + CLEANUP_GL_AND_RETURN(true); + } + + return false; +} + +// ---------------------------------------------------------------------------- +// JNI Glue +// ---------------------------------------------------------------------------- + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +const char* const kClassPathName = "com/android/server/AssetAtlasService"; + +static JNINativeMethod gMethods[] = { + { "nAcquireAtlasCanvas", "(Landroid/graphics/Canvas;II)I", + (void*) com_android_server_AssetAtlasService_acquireCanvas }, + { "nReleaseAtlasCanvas", "(Landroid/graphics/Canvas;I)V", + (void*) com_android_server_AssetAtlasService_releaseCanvas }, + { "nUploadAtlas", "(Landroid/view/GraphicBuffer;I)Z", + (void*) com_android_server_AssetAtlasService_upload }, +}; + +int register_android_server_AssetAtlasService(JNIEnv* env) { + jclass clazz; + + FIND_CLASS(clazz, "android/graphics/Canvas"); + GET_FIELD_ID(gCanvasClassInfo.mFinalizer, clazz, "mFinalizer", + "Landroid/graphics/Canvas$CanvasFinalizer;"); + GET_FIELD_ID(gCanvasClassInfo.mNativeCanvas, clazz, "mNativeCanvas", "I"); + + FIND_CLASS(clazz, "android/graphics/Canvas$CanvasFinalizer"); + GET_FIELD_ID(gCanvasFinalizerClassInfo.mNativeCanvas, clazz, "mNativeCanvas", "I"); + + return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); +} + +}; diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp index 423ebd1..bb679aa 100644 --- a/services/jni/onload.cpp +++ b/services/jni/onload.cpp @@ -34,6 +34,7 @@ int register_android_server_VibratorService(JNIEnv* env); int register_android_server_SystemServer(JNIEnv* env); int register_android_server_location_GpsLocationProvider(JNIEnv* env); int register_android_server_connectivity_Vpn(JNIEnv* env); +int register_android_server_AssetAtlasService(JNIEnv* env); }; using namespace android; @@ -63,6 +64,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) register_android_server_SystemServer(env); register_android_server_location_GpsLocationProvider(env); register_android_server_connectivity_Vpn(env); + register_android_server_AssetAtlasService(env); return JNI_VERSION_1_4; } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 46a539e..bdd8aa6 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -41,6 +41,16 @@ </activity> <activity + android:name="AssetsAtlasActivity" + android:label="Atlas/Framework" + android:theme="@android:style/Theme.Holo.Light"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.android.test.hwui.TEST" /> + </intent-filter> + </activity> + + <activity android:name="ScaledTextActivity" android:label="Text/Scaled" android:theme="@android:style/Theme.Holo.Light"> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java new file mode 100644 index 0000000..df7e3bb --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AssetsAtlasActivity.java @@ -0,0 +1,66 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.View; +import com.android.internal.R; + +@SuppressWarnings({"UnusedDeclaration"}) +public class AssetsAtlasActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(new BitmapsView(this)); + } + + static class BitmapsView extends View { + private final Bitmap mBitmap; + + BitmapsView(Context c) { + super(c); + + Drawable d = c.getResources().getDrawable(R.drawable.text_select_handle_left); + mBitmap = ((BitmapDrawable) d).getBitmap(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + final Matrix matrix = new Matrix(); + matrix.setScale(0.5f, 0.5f); + + final Rect src = new Rect(0, 0, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2); + final Rect dst = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); + + canvas.drawBitmap(mBitmap, 0.0f, 0.0f, null); + canvas.translate(0.0f, mBitmap.getHeight()); + canvas.drawBitmap(mBitmap, matrix, null); + canvas.translate(0.0f, mBitmap.getHeight()); + canvas.drawBitmap(mBitmap, src, dst, null); + } + } +} |