diff options
82 files changed, 492 insertions, 2475 deletions
diff --git a/Android.mk b/Android.mk index e352bc70d085..1f3210f7af6c 100644 --- a/Android.mk +++ b/Android.mk @@ -288,7 +288,6 @@ LOCAL_SRC_FILES += \ core/java/android/view/accessibility/IAccessibilityManagerClient.aidl \ core/java/android/view/IApplicationToken.aidl \ core/java/android/view/IAppTransitionAnimationSpecsFuture.aidl \ - core/java/android/view/IAssetAtlas.aidl \ core/java/android/view/IDockedStackListener.aidl \ core/java/android/view/IGraphicsStats.aidl \ core/java/android/view/IInputFilter.aidl \ diff --git a/compiled-classes-phone b/compiled-classes-phone index 221d68739366..621430671eb4 100644 --- a/compiled-classes-phone +++ b/compiled-classes-phone @@ -1168,14 +1168,6 @@ android.drm.DrmManagerClient$OnEventListener android.drm.DrmManagerClient$OnInfoListener android.drm.DrmOutputStream android.drm.DrmSupportInfo -android.graphics.Atlas -android.graphics.Atlas$Entry -android.graphics.Atlas$Policy -android.graphics.Atlas$SlicePolicy -android.graphics.Atlas$SlicePolicy$Cell -android.graphics.Atlas$SlicePolicy$MinAreaSplitDecision -android.graphics.Atlas$SlicePolicy$SplitDecision -android.graphics.Atlas$Type android.graphics.Bitmap android.graphics.Bitmap$1 android.graphics.Bitmap$CompressFormat @@ -4264,9 +4256,6 @@ android.view.IAppTransitionAnimationSpecsFuture$Stub android.view.IAppTransitionAnimationSpecsFuture$Stub$Proxy android.view.IApplicationToken android.view.IApplicationToken$Stub -android.view.IAssetAtlas -android.view.IAssetAtlas$Stub -android.view.IAssetAtlas$Stub$Proxy android.view.IDockedStackListener android.view.IDockedStackListener$Stub android.view.IDockedStackListener$Stub$Proxy diff --git a/core/java/android/view/IAssetAtlas.aidl b/core/java/android/view/IAssetAtlas.aidl deleted file mode 100644 index edce05970b9a..000000000000 --- a/core/java/android/view/IAssetAtlas.aidl +++ /dev/null @@ -1,54 +0,0 @@ -/** - * 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 { - /** - * Indicates whether the atlas is compatible with the specified - * parent process id. If the atlas' ppid does not match, this - * method will return false. - */ - boolean isCompatible(int ppid); - - /** - * 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: - * long0: SkBitmap*, the native bitmap object - * long1: x position - * long2: y position - * long3: rotated, 1 if the bitmap must be rotated, 0 otherwise - */ - long[] getMap(); -} diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index ce390a239d2c..f71459e426dc 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -916,7 +916,6 @@ public final class ThreadedRenderer { mInitialized = true; initSched(context, renderProxy); initGraphicsStats(context, renderProxy); - initAssetAtlas(context, renderProxy); } private static void initSched(Context context, long renderProxy) { @@ -944,32 +943,6 @@ public final class ThreadedRenderer { Log.w(LOG_TAG, "Could not acquire gfx stats buffer", t); } } - - private static void initAssetAtlas(Context context, long renderProxy) { - IBinder binder = ServiceManager.getService("assetatlas"); - if (binder == null) return; - - IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder); - try { - if (atlas.isCompatible(android.os.Process.myPpid())) { - GraphicBuffer buffer = atlas.getBuffer(); - if (buffer != null) { - long[] map = atlas.getMap(); - if (map != null) { - nSetAtlas(renderProxy, buffer, map); - } - // If IAssetAtlas is not the same class as the IBinder - // we are using a remote service and we can safely - // destroy the graphic buffer - if (atlas.getClass() != binder.getClass()) { - buffer.destroy(); - } - } - } - } catch (RemoteException e) { - Log.w(LOG_TAG, "Could not acquire atlas", e); - } - } } void addFrameMetricsObserver(FrameMetricsObserver observer) { @@ -984,7 +957,6 @@ public final class ThreadedRenderer { static native void setupShadersDiskCache(String cacheFile); - private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map); private static native void nSetProcessStatsBuffer(long nativeProxy, int fd); private static native int nGetRenderThreadTid(long nativeProxy); diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 4c6350ba9ad6..a7b0fe9ad869 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -19,6 +19,14 @@ ifneq ($(ENABLE_CPUSETS),) LOCAL_CFLAGS += -DENABLE_CPUSETS endif +# TODO: Linear blending should be enabled by default, but we are +# TODO: making it an opt-in while it's a work in progress +# TODO: The final test should be: +# TODO: ifneq ($(TARGET_ENABLE_LINEAR_BLENDING),false) +ifeq ($(TARGET_ENABLE_LINEAR_BLENDING),true) + hwui_cflags += -DANDROID_ENABLE_LINEAR_BLENDING +endif + LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES LOCAL_CFLAGS += -DU_USING_ICU_NAMESPACE=0 diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index e79732100e53..9cfe1537a86a 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -693,7 +693,8 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, } SkBitmap bitmap; - bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType)); + bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType, + GraphicsJNI::defaultColorSpace())); PixelRef* nativeBitmap = GraphicsJNI::allocateHeapPixelRef(&bitmap, NULL); if (!nativeBitmap) { @@ -794,7 +795,8 @@ static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle, // Otherwise respect the premultiplied request. alphaType = requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; } - bitmap->pixelRef()->reconfigure(SkImageInfo::Make(width, height, colorType, alphaType)); + bitmap->pixelRef()->reconfigure(SkImageInfo::Make(width, height, colorType, alphaType, + sk_sp<SkColorSpace>(bitmap->info().colorSpace()))); } // These must match the int values in Bitmap.java @@ -927,6 +929,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { const bool isMutable = p->readInt32() != 0; const SkColorType colorType = (SkColorType)p->readInt32(); const SkAlphaType alphaType = (SkAlphaType)p->readInt32(); + const bool isSRGB = p->readInt32() != 0; const int width = p->readInt32(); const int height = p->readInt32(); const int rowBytes = p->readInt32(); @@ -943,7 +946,8 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { std::unique_ptr<SkBitmap> bitmap(new SkBitmap); - if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType), rowBytes)) { + if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, + isSRGB ? SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named) : nullptr), rowBytes)) { return NULL; } @@ -1058,9 +1062,13 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, auto androidBitmap = reinterpret_cast<Bitmap*>(bitmapHandle); androidBitmap->getSkBitmap(&bitmap); + sk_sp<SkColorSpace> sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + bool isSRGB = bitmap.colorSpace() == sRGB.get(); + p->writeInt32(isMutable); p->writeInt32(bitmap.colorType()); p->writeInt32(bitmap.alphaType()); + p->writeInt32(isSRGB); // TODO: We should write the color space (b/32072280) p->writeInt32(bitmap.width()); p->writeInt32(bitmap.height()); p->writeInt32(bitmap.rowBytes()); @@ -1273,7 +1281,9 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, reinterpret_cast<Bitmap*>(bm1Handle)->getSkBitmap(&bm1); if (bm0.width() != bm1.width() || bm0.height() != bm1.height() || - bm0.colorType() != bm1.colorType()) { + bm0.colorType() != bm1.colorType() || + bm0.alphaType() != bm1.alphaType() || + bm0.colorSpace() != bm1.colorSpace()) { return JNI_FALSE; } @@ -1326,13 +1336,6 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, return JNI_TRUE; } -static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) { - LocalScopedBitmap bitmap(bitmapHandle); - SkPixelRef* pixelRef = bitmap->pixelRef(); - SkSafeRef(pixelRef); - return reinterpret_cast<jlong>(pixelRef); -} - static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapPtr) { LocalScopedBitmap bitmapHandle(bitmapPtr); if (!bitmapHandle.valid()) return; @@ -1402,7 +1405,6 @@ static const JNINativeMethod gBitmapMethods[] = { { "nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsFromBuffer }, { "nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs }, - { "nativeRefPixelRef", "(J)J", (void*)Bitmap_refPixelRef }, { "nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw }, { "nativeGetAllocationByteCount", "(J)I", (void*)Bitmap_getAllocationByteCount }, }; diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 5bb02746bf3a..5c24585cb182 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -385,11 +385,8 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // Set the alpha type for the decode. SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied); - // Enable legacy behavior to avoid any gamma correction. Android's assets are - // adjusted to expect a non-gamma correct premultiply. - sk_sp<SkColorSpace> colorSpace = nullptr; - const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType, - alphaType, colorSpace); + const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), + decodeColorType, alphaType, GraphicsJNI::defaultColorSpace()); SkImageInfo bitmapInfo = decodeInfo; if (decodeColorType == kGray_8_SkColorType) { @@ -413,7 +410,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // Use SkAndroidCodec to perform the decode. SkAndroidCodec::AndroidOptions codecOptions; - codecOptions.fZeroInitialized = decodeAllocator == &defaultAllocator ? + codecOptions.fZeroInitialized = decodeAllocator == &defaultAllocator ? SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized; codecOptions.fColorPtr = colorPtr; codecOptions.fColorCount = colorCount; @@ -452,8 +449,10 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding jobject ninePatchInsets = NULL; if (peeker.mHasInsets) { ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID, - peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3], - peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3], + peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], + peeker.mOpticalInsets[2], peeker.mOpticalInsets[3], + peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], + peeker.mOutlineInsets[2], peeker.mOutlineInsets[3], peeker.mOutlineRadius, peeker.mOutlineAlpha, scale); if (ninePatchInsets == NULL) { return nullObjectReturn("nine patch insets == null"); @@ -495,11 +494,11 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding } SkPaint paint; - // kSrc_Mode instructs us to overwrite the unininitialized pixels in + // kSrc_Mode instructs us to overwrite the uninitialized pixels in // outputBitmap. Otherwise we would blend by default, which is not // what we want. paint.setXfermodeMode(SkXfermode::kSrc_Mode); - paint.setFilterQuality(kLow_SkFilterQuality); + paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering SkCanvas canvas(outputBitmap); canvas.scale(sx, sy); diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 8a55052376f7..82b70fc683dc 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -587,6 +587,14 @@ android::PixelRef* GraphicsJNI::mapAshmemPixelRef(JNIEnv* env, SkBitmap* bitmap, return wrapper; } +sk_sp<SkColorSpace> GraphicsJNI::defaultColorSpace() { +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + return SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); +#else + return nullptr; +#endif +} + /////////////////////////////////////////////////////////////////////////////// bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { mStorage.reset(GraphicsJNI::allocateHeapPixelRef(bitmap, ctable)); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index ceda3cd28ad4..34c4efb3362e 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -10,6 +10,7 @@ #include "SkMallocPixelRef.h" #include "SkPoint.h" #include "SkRect.h" +#include "SkColorSpace.h" #include <jni.h> #include <hwui/Canvas.h> @@ -93,6 +94,8 @@ public: static bool SetPixels(JNIEnv* env, jintArray colors, int srcOffset, int srcStride, int x, int y, int width, int height, const SkBitmap& dstBitmap); + + static sk_sp<SkColorSpace> defaultColorSpace(); }; class HeapAllocator : public SkBRDAllocator { diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index 87f754d261ff..43f7ca5c1237 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -442,9 +442,8 @@ static void drawBitmapArray(JNIEnv* env, jobject, jlong canvasHandle, jboolean hasAlpha, jlong paintHandle) { // Note: If hasAlpha is false, kRGB_565_SkColorType will be used, which will // correct the alphaType to kOpaque_SkAlphaType. - SkImageInfo info = SkImageInfo::Make(width, height, - hasAlpha ? kN32_SkColorType : kRGB_565_SkColorType, - kPremul_SkAlphaType); + SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, + GraphicsJNI::defaultColorSpace()); SkBitmap bitmap; bitmap.setInfo(info); if (!GraphicsJNI::allocatePixels(env, &bitmap, NULL)) { diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp index 59c337b63f1d..17437318470d 100644 --- a/core/jni/android_view_GraphicBuffer.cpp +++ b/core/jni/android_view_GraphicBuffer.cpp @@ -182,7 +182,8 @@ static jboolean android_view_GraphicBuffer_lockCanvas(JNIEnv* env, jobject, SkBitmap bitmap; bitmap.setInfo(SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(), convertPixelFormat(buffer->getPixelFormat()), - kPremul_SkAlphaType), + kPremul_SkAlphaType, + GraphicsJNI::defaultColorSpace()), bytesCount); if (buffer->getWidth() > 0 && buffer->getHeight() > 0) { diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 3f2b924f225f..b6c81cf8738a 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -318,11 +318,11 @@ static jlong nativeLockCanvas(JNIEnv* env, jclass clazz, return 0; } - SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height, convertPixelFormat(outBuffer.format), - outBuffer.format == PIXEL_FORMAT_RGBX_8888 ? - kOpaque_SkAlphaType : kPremul_SkAlphaType); + outBuffer.format == PIXEL_FORMAT_RGBX_8888 + ? kOpaque_SkAlphaType : kPremul_SkAlphaType, + GraphicsJNI::defaultColorSpace()); SkBitmap bitmap; ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format); diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 5d53b3f8bdce..65f12ac1907a 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -175,7 +175,9 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, } SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(), screenshot->getHeight(), - colorType, alphaType); + colorType, + alphaType, + GraphicsJNI::defaultColorSpace()); const size_t rowBytes = screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat()); diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp index e185281dad8a..268aec57032d 100644 --- a/core/jni/android_view_TextureView.cpp +++ b/core/jni/android_view_TextureView.cpp @@ -90,7 +90,8 @@ static inline SkImageInfo convertPixelFormat(const ANativeWindow_Buffer& buffer) default: break; } - return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType); + return SkImageInfo::Make(buffer.width, buffer.height, colorType, alphaType, + GraphicsJNI::defaultColorSpace()); } /** diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 309bb2f7b0b9..14dcb3febe6f 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -613,21 +613,6 @@ static jboolean android_view_ThreadedRenderer_supportsOpenGL(JNIEnv* env, jobjec return atoi(prop) > 0 ? JNI_TRUE : JNI_FALSE; } -static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz, - jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) { - sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer); - jsize len = env->GetArrayLength(atlasMapArray); - if (len <= 0) { - ALOGW("Failed to initialize atlas, invalid map length: %d", len); - return; - } - int64_t* map = new int64_t[len]; - env->GetLongArrayRegion(atlasMapArray, 0, len, map); - - RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); - proxy->setTextureAtlas(buffer, map, len); -} - static void android_view_ThreadedRenderer_setProcessStatsBuffer(JNIEnv* env, jobject clazz, jlong proxyPtr, jint fd) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); @@ -955,7 +940,6 @@ const char* const kClassPathName = "android/view/ThreadedRenderer"; static const JNINativeMethod gMethods[] = { { "nSupportsOpenGL", "()Z", (void*) android_view_ThreadedRenderer_supportsOpenGL }, - { "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V", (void*) android_view_ThreadedRenderer_setAtlas }, { "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer }, { "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid }, { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode }, diff --git a/graphics/java/android/graphics/Atlas.java b/graphics/java/android/graphics/Atlas.java deleted file mode 100644 index e0a534583b67..000000000000 --- a/graphics/java/android/graphics/Atlas.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * 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 { - /** - * WARNING: These flag values are part of the on-disk configuration information, - * do not change their values. - */ - - /** DELETED: FLAG_ROTATION = 0x01 */ - - /** - * 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. - */ - 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; - } - - 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 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) { - 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 - */ - private boolean insert(Cell cell, Cell prev, int width, int height, Entry entry) { - if (cell.width < width || cell.height < height) { - 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; - - return true; - } - } -} diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 7ce750d6e000..f7082920a018 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1654,16 +1654,6 @@ public final class Bitmap implements Parcelable { nativePrepareToDraw(mNativePtr); } - /** - * Refs the underlying SkPixelRef and returns a pointer to it. - * - * @hide - * */ - public final long refSkPixelRef() { - checkRecycled("Can't refSkPixelRef on a recycled bitmap!"); - return nativeRefPixelRef(mNativePtr); - } - //////////// native methods private static native Bitmap nativeCreate(int[] colors, int offset, @@ -1720,7 +1710,6 @@ public final class Bitmap implements Parcelable { private static native boolean nativeHasMipMap(long nativeBitmap); private static native void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap); private static native boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1); - private static native long nativeRefPixelRef(long nativeBitmap); private static native void nativePrepareToDraw(long nativeBitmap); private static native int nativeGetAllocationByteCount(long nativeBitmap); } diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index df107f5ce3ef..6deeb0d9af9a 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -49,7 +49,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.util.Collection; /** * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a @@ -957,14 +956,6 @@ public class BitmapDrawable extends Drawable { } @Override - public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { - if (isAtlasable(mBitmap) && atlasList.add(mBitmap)) { - return mBitmap.getWidth() * mBitmap.getHeight(); - } - return 0; - } - - @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 10f0dda5a976..6ddc2d7e192f 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -58,7 +58,6 @@ import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.Arrays; -import java.util.Collection; /** * A Drawable is a general abstraction for "something that can be drawn." Most @@ -1367,19 +1366,6 @@ public abstract class Drawable { public abstract @Config int getChangingConfigurations(); /** - * @return Total pixel count - * @hide - */ - public int addAtlasableBitmaps(@NonNull Collection<Bitmap> atlasList) { - return 0; - } - - /** @hide */ - protected final boolean isAtlasable(@Nullable Bitmap bitmap) { - return bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888; - } - - /** * Return whether this constant state can have a theme applied. */ public boolean canApplyTheme() { diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index c7a3c75f3545..abdc2b91c2d9 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -21,7 +21,6 @@ import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; @@ -35,8 +34,6 @@ import android.util.LayoutDirection; import android.util.SparseArray; import android.view.View; -import java.util.Collection; - /** * A helper class that contains several {@link Drawable}s and selects which one to use. * @@ -1194,19 +1191,6 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return true; } - /** @hide */ - @Override - public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { - final int N = mNumChildren; - int pixelCount = 0; - for (int i = 0; i < N; i++) { - final ConstantState state = getChild(i).getConstantState(); - if (state != null) { - pixelCount += state.addAtlasableBitmaps(atlasList); - } - } - return pixelCount; - } } protected void setConstantState(DrawableContainerState state) { diff --git a/graphics/java/android/graphics/drawable/DrawableWrapper.java b/graphics/java/android/graphics/drawable/DrawableWrapper.java index 5abfc54bfce3..5887939b05a4 100644 --- a/graphics/java/android/graphics/drawable/DrawableWrapper.java +++ b/graphics/java/android/graphics/drawable/DrawableWrapper.java @@ -28,7 +28,6 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; @@ -41,7 +40,6 @@ import android.util.DisplayMetrics; import android.view.View; import java.io.IOException; -import java.util.Collection; /** * Drawable container with only one child element. @@ -508,15 +506,6 @@ public abstract class DrawableWrapper extends Drawable implements Drawable.Callb } @Override - public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { - final Drawable.ConstantState state = mDrawableState; - if (state != null) { - return state.addAtlasableBitmaps(atlasList); - } - return 0; - } - - @Override public Drawable newDrawable() { return newDrawable(null); } diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index c30c4c2f02d6..e09fea5af3ea 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -23,7 +23,6 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; -import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Outline; @@ -42,7 +41,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.util.Collection; /** * A Drawable that manages an array of other Drawables. These are drawn in array @@ -2128,22 +2126,6 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mHaveIsStateful = false; } - @Override - public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { - final ChildDrawable[] array = mChildren; - final int N = mNum; - int pixelCount = 0; - for (int i = 0; i < N; i++) { - final Drawable dr = array[i].mDrawable; - if (dr != null) { - final ConstantState state = dr.getConstantState(); - if (state != null) { - pixelCount += state.addAtlasableBitmaps(atlasList); - } - } - } - return pixelCount; - } } } diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index d96238521f94..c7183d9e8f50 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -49,7 +49,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.InputStream; -import java.util.Collection; /** * @@ -633,15 +632,6 @@ public class NinePatchDrawable extends Drawable { } @Override - public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { - final Bitmap bitmap = mNinePatch.getBitmap(); - if (isAtlasable(bitmap) && atlasList.add(bitmap)) { - return bitmap.getWidth() * bitmap.getHeight(); - } - return 0; - } - - @Override public Drawable newDrawable() { return new NinePatchDrawable(this, null); } diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 81a183126cce..2d9b764f5504 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -45,7 +45,6 @@ hwui_src_files := \ AnimationContext.cpp \ Animator.cpp \ AnimatorManager.cpp \ - AssetAtlas.cpp \ BakedOpDispatcher.cpp \ BakedOpRenderer.cpp \ BakedOpState.cpp \ @@ -56,7 +55,6 @@ hwui_src_files := \ DeferredLayerUpdater.cpp \ DeviceInfo.cpp \ DisplayList.cpp \ - Dither.cpp \ Extensions.cpp \ FboCache.cpp \ FontRenderer.cpp \ @@ -130,6 +128,14 @@ ifeq ($(TARGET_USES_HWC2),true) hwui_cflags += -DUSE_HWC2 endif +# TODO: Linear blending should be enabled by default, but we are +# TODO: making it an opt-in while it's a work in progress +# TODO: The final test should be: +# TODO: ifneq ($(TARGET_ENABLE_LINEAR_BLENDING),false) +ifeq ($(TARGET_ENABLE_LINEAR_BLENDING),true) + hwui_cflags += -DANDROID_ENABLE_LINEAR_BLENDING +endif + # GCC false-positives on this warning, and since we -Werror that's # a problem hwui_cflags += -Wno-free-nonheap-object @@ -143,7 +149,6 @@ ifeq (true, $(BUGREPORT_FONT_CACHE_USAGE)) hwui_cflags += -DBUGREPORT_FONT_CACHE_USAGE endif - ifndef HWUI_COMPILE_SYMBOLS hwui_cflags += -fvisibility=hidden endif diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp deleted file mode 100644 index e2e7037202b8..000000000000 --- a/libs/hwui/AssetAtlas.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 "AssetAtlas.h" -#include "Caches.h" -#include "Image.h" - -#include <GLES2/gl2ext.h> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Lifecycle -/////////////////////////////////////////////////////////////////////////////// - -void AssetAtlas::init(const sp<GraphicBuffer>& buffer, int64_t* map, int count) { - if (mImage) { - return; - } - - ATRACE_NAME("AssetAtlas::init"); - - mImage = new Image(buffer); - if (mImage->getTexture()) { - if (!mTexture) { - Caches& caches = Caches::getInstance(); - mTexture = new Texture(caches); - mTexture->wrap(mImage->getTexture(), - buffer->getWidth(), buffer->getHeight(), GL_RGBA); - createEntries(caches, map, count); - } - } else { - ALOGW("Could not create atlas image"); - terminate(); - } -} - -void AssetAtlas::terminate() { - delete mImage; - mImage = nullptr; - delete mTexture; - mTexture = nullptr; - mEntries.clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Entries -/////////////////////////////////////////////////////////////////////////////// - -AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const { - auto result = mEntries.find(pixelRef); - return result != mEntries.end() ? result->second.get() : nullptr; -} - -Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const { - auto result = mEntries.find(pixelRef); - return result != mEntries.end() ? result->second->texture : nullptr; -} - -/** - * Delegates changes to wrapping and filtering to the base atlas texture - * instead of applying the changes to the virtual textures. - */ -struct DelegateTexture: public Texture { - DelegateTexture(Caches& caches, Texture* delegate) - : Texture(caches), mDelegate(delegate) { } - - virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, - bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override { - mDelegate->setWrapST(wrapS, wrapT, bindTexture, force, renderTarget); - } - - virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, - bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override { - mDelegate->setFilterMinMag(min, mag, bindTexture, force, renderTarget); - } - -private: - Texture* const mDelegate; -}; // struct DelegateTexture - -void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { - const float width = float(mTexture->width()); - const float height = float(mTexture->height()); - - for (int i = 0; i < count; ) { - SkPixelRef* pixelRef = reinterpret_cast<SkPixelRef*>(map[i++]); - // NOTE: We're converting from 64 bit signed values to 32 bit - // signed values. This is guaranteed to be safe because the "x" - // and "y" coordinate values are guaranteed to be representable - // with 32 bits. The array is 64 bits wide so that it can carry - // pointers on 64 bit architectures. - const int x = static_cast<int>(map[i++]); - const int y = static_cast<int>(map[i++]); - - // Bitmaps should never be null, we're just extra paranoid - if (!pixelRef) continue; - - const UvMapper mapper( - x / width, (x + pixelRef->info().width()) / width, - y / height, (y + pixelRef->info().height()) / height); - - Texture* texture = new DelegateTexture(caches, mTexture); - texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType()); - texture->wrap(mTexture->id(), pixelRef->info().width(), - pixelRef->info().height(), mTexture->format()); - - std::unique_ptr<Entry> entry(new Entry(pixelRef, texture, mapper, *this)); - texture->uvMapper = &entry->uvMapper; - - mEntries.emplace(entry->pixelRef, std::move(entry)); - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h deleted file mode 100644 index b32e51851b94..000000000000 --- a/libs/hwui/AssetAtlas.h +++ /dev/null @@ -1,174 +0,0 @@ -/* - * 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 "Texture.h" -#include "UvMapper.h" - -#include <cutils/compiler.h> -#include <GLES2/gl2.h> -#include <ui/GraphicBuffer.h> -#include <SkBitmap.h> - -#include <memory> -#include <unordered_map> - -namespace android { -namespace uirenderer { - -class Caches; -class Image; - -/** - * 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 texture and uvMapper of a PixelRef in the - * atlas - */ - class Entry { - public: - /* - * A "virtual texture" object that represents the texture - * this entry belongs to. This texture should never be - * modified. - */ - Texture* texture; - - /** - * Maps texture coordinates in the [0..1] range into the - * correct range to sample this entry from the atlas. - */ - const UvMapper uvMapper; - - /** - * Unique identifier used to merge bitmaps and 9-patches stored - * in the atlas. - */ - const void* getMergeId() const { - return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey; - } - - ~Entry() { - delete texture; - } - - private: - /** - * The pixel ref that generated this atlas entry. - */ - SkPixelRef* pixelRef; - - /** - * Atlas this entry belongs to. - */ - const AssetAtlas& atlas; - - Entry(SkPixelRef* pixelRef, Texture* texture, const UvMapper& mapper, - const AssetAtlas& atlas) - : texture(texture) - , uvMapper(mapper) - , pixelRef(pixelRef) - , atlas(atlas) { - } - - friend class AssetAtlas; - }; - - AssetAtlas(): mTexture(nullptr), mImage(nullptr), - mBlendKey(true), mOpaqueKey(false) { } - ~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 - * - * This method returns immediately if the atlas is already - * initialized. To re-initialize the atlas, you must - * first call terminate(). - */ - ANDROID_API void init(const sp<GraphicBuffer>& buffer, int64_t* 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. - */ - void terminate(); - - /** - * Returns the width of this atlas in pixels. - * Can return 0 if the atlas is not initialized. - */ - uint32_t getWidth() const { - return mTexture ? mTexture->width() : 0; - } - - /** - * Returns the height of this atlas in pixels. - * Can return 0 if the atlas is not initialized. - */ - uint32_t getHeight() const { - return mTexture ? mTexture->height() : 0; - } - - /** - * Returns the OpenGL name of the texture backing this atlas. - * Can return 0 if the atlas is not initialized. - */ - GLuint getTexture() const { - return mTexture ? mTexture->id() : 0; - } - - /** - * Returns the entry in the atlas associated with the specified - * pixelRef. If the pixelRef is not in the atlas, return NULL. - */ - Entry* getEntry(const SkPixelRef* pixelRef) const; - - /** - * Returns the texture for the atlas entry associated with the - * specified pixelRef. If the pixelRef is not in the atlas, return NULL. - */ - Texture* getEntryTexture(const SkPixelRef* pixelRef) const; - -private: - void createEntries(Caches& caches, int64_t* map, int count); - - Texture* mTexture; - Image* mImage; - - const bool mBlendKey; - const bool mOpaqueKey; - - std::unordered_map<const SkPixelRef*, std::unique_ptr<Entry>> mEntries; -}; // class AssetAtlas - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_ASSET_ATLAS_H diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp index 8b3f1722dddf..699503991e02 100644 --- a/libs/hwui/BakedOpDispatcher.cpp +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -35,11 +35,11 @@ namespace android { namespace uirenderer { -static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) { - vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top }; - vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top }; - vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom }; - vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom }; +static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds) { + vertices[0] = { bounds.left, bounds.top, 0, 0 }; + vertices[1] = { bounds.right, bounds.top, 1, 0 }; + vertices[2] = { bounds.left, bounds.bottom, 0, 1 }; + vertices[3] = { bounds.right, bounds.bottom, 1, 1 }; } void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, @@ -48,16 +48,11 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, const BakedOpState& firstState = *(opList.states[0]); const SkBitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap; - AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef()); - Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap); + Texture* texture = renderer.caches().textureCache.get(bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); TextureVertex vertices[opList.count * 4]; - Rect texCoords(0, 0, 1, 1); - if (entry) { - entry->uvMapper.map(texCoords); - } for (size_t i = 0; i < opList.count; i++) { const BakedOpState& state = *(opList.states[i]); TextureVertex* rectVerts = &vertices[i * 4]; @@ -69,7 +64,7 @@ void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, // pure translate, so snap (same behavior as onBitmapOp) opBounds.snapToPixelBoundaries(); } - storeTexturedRect(rectVerts, opBounds, texCoords); + storeTexturedRect(rectVerts, opBounds); renderer.dirtyRenderTarget(opBounds); } @@ -92,8 +87,6 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, const MergedBakedOpList& opList) { const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op)); const BakedOpState& firstState = *(opList.states[0]); - AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry( - firstOp.bitmap->pixelRef()); // Batches will usually contain a small number of items so it's // worth performing a first iteration to count the exact number @@ -105,7 +98,7 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, // TODO: cache mesh lookups const Patch* opMesh = renderer.caches().patchCache.get( - entry, op.bitmap->width(), op.bitmap->height(), + op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); totalVertices += opMesh->verticesCount; } @@ -126,7 +119,7 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, // TODO: cache mesh lookups const Patch* opMesh = renderer.caches().patchCache.get( - entry, op.bitmap->width(), op.bitmap->height(), + op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); @@ -172,7 +165,7 @@ void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, } - Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap); + Texture* texture = renderer.caches().textureCache.get(firstOp.bitmap); if (!texture) return; const AutoTexture autoCleanup(texture); @@ -442,7 +435,12 @@ void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op } void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) { - const static UvMapper defaultUvMapper; + Texture* texture = renderer.caches().textureCache.get(op.bitmap); + if (!texture) { + return; + } + const AutoTexture autoCleanup(texture); + const uint32_t elementCount = op.meshWidth * op.meshHeight * 6; std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]); @@ -457,9 +455,6 @@ void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMe colors = tempColors.get(); } - Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef()); - const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper); - for (int32_t y = 0; y < op.meshHeight; y++) { for (int32_t x = 0; x < op.meshWidth; x++) { uint32_t i = (y * (op.meshWidth + 1) + x) * 2; @@ -469,8 +464,6 @@ void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMe float v1 = float(y) / op.meshHeight; float v2 = float(y + 1) / op.meshHeight; - mapper.map(u1, v1, u2, v2); - int ax = i + (op.meshWidth + 1) * 2; int ay = ax + 1; int bx = i; @@ -491,14 +484,6 @@ void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMe } } - if (!texture) { - texture = renderer.caches().textureCache.get(op.bitmap); - if (!texture) { - return; - } - } - const AutoTexture autoCleanup(texture); - /* * TODO: handle alpha_8 textures correctly by applying paint color, but *not* * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh. @@ -599,12 +584,11 @@ void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op, } // TODO: avoid redoing the below work each frame: - AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef()); const Patch* mesh = renderer.caches().patchCache.get( - entry, op.bitmap->width(), op.bitmap->height(), + op.bitmap->width(), op.bitmap->height(), op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); - Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap); + Texture* texture = renderer.caches().textureCache.get(op.bitmap); if (CC_LIKELY(texture)) { const AutoTexture autoCleanup(texture); Glop glop; diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index 6db345ac0006..ac7a600af85f 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -182,11 +182,7 @@ void BakedOpRenderer::clearColorBuffer(const Rect& rect) { } Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) { - Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef()); - if (!texture) { - return mCaches.textureCache.get(bitmap); - } - return texture; + return mCaches.textureCache.get(bitmap); } void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* paint) { diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 741cdccaa778..b463e45fb39b 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -53,7 +53,6 @@ Caches::Caches(RenderState& renderState) : gradientCache(mExtensions) , patchCache(renderState) , programCache(mExtensions) - , dither(*this) , mRenderState(&renderState) , mInitialized(false) { INIT_LOGD("Creating OpenGL renderer caches"); @@ -238,7 +237,6 @@ void Caches::flush(FlushMode mode) { gradientCache.clear(); fontRenderer.clear(); fboCache.clear(); - dither.clear(); // fall through case FlushMode::Moderate: fontRenderer.flush(); diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 344ee71a2cc5..7c2e78c7d3b8 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -16,8 +16,6 @@ #pragma once -#include "AssetAtlas.h" -#include "Dither.h" #include "Extensions.h" #include "FboCache.h" #include "GammaFontRenderer.h" @@ -130,6 +128,15 @@ public: TextureVertex* getRegionMesh(); /** + * Returns the GL RGBA internal format to use for the current device + * If the device supports linear blending and needSRGB is true, + * this function returns GL_SRGB8_ALPHA8, otherwise it returns GL_RGBA + */ + constexpr GLint rgbaInternalFormat(bool needSRGB = true) const { + return extensions().hasSRGB() && needSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA; + } + + /** * Displays the memory usage of each cache and the total sum. */ void dumpMemoryUsage(); @@ -157,8 +164,6 @@ public: TaskManager tasks; - Dither dither; - bool gpuPixelBuffersEnabled; // Debug methods @@ -169,7 +174,7 @@ public: void setProgram(const ProgramDescription& description); void setProgram(Program* program); - Extensions& extensions() { return mExtensions; } + const Extensions& extensions() const { return mExtensions; } Program& program() { return *mProgram; } PixelBufferState& pixelBufferState() { return *mPixelBufferState; } TextureState& textureState() { return *mTextureState; } diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp deleted file mode 100644 index ec2013e27401..000000000000 --- a/libs/hwui/Dither.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2012 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 "Caches.h" -#include "Dither.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Lifecycle -/////////////////////////////////////////////////////////////////////////////// - -Dither::Dither(Caches& caches) - : mCaches(caches) - , mInitialized(false) - , mDitherTexture(0) { -} - -void Dither::bindDitherTexture() { - if (!mInitialized) { - glGenTextures(1, &mDitherTexture); - mCaches.textureState().bindTexture(mDitherTexture); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - - if (mCaches.extensions().hasFloatTextures()) { - // We use a R16F texture, let's remap the alpha channel to the - // red channel to avoid changing the shader sampling code on GL ES 3.0+ - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_RED); - - float dither = 1.0f / (255.0f * DITHER_KERNEL_SIZE * DITHER_KERNEL_SIZE); - const GLfloat pattern[] = { - 0 * dither, 8 * dither, 2 * dither, 10 * dither, - 12 * dither, 4 * dither, 14 * dither, 6 * dither, - 3 * dither, 11 * dither, 1 * dither, 9 * dither, - 15 * dither, 7 * dither, 13 * dither, 5 * dither - }; - - glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, - GL_RED, GL_FLOAT, &pattern); - } else { - const uint8_t pattern[] = { - 0, 8, 2, 10, - 12, 4, 14, 6, - 3, 11, 1, 9, - 15, 7, 13, 5 - }; - - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, - GL_ALPHA, GL_UNSIGNED_BYTE, &pattern); - } - - mInitialized = true; - } else { - mCaches.textureState().bindTexture(mDitherTexture); - } -} - -void Dither::clear() { - if (mInitialized) { - mCaches.textureState().deleteTexture(mDitherTexture); - mInitialized = false; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Program management -/////////////////////////////////////////////////////////////////////////////// - -void Dither::setupProgram(Program& program, GLuint* textureUnit) { - GLuint textureSlot = (*textureUnit)++; - mCaches.textureState().activateTexture(textureSlot); - - bindDitherTexture(); - - glUniform1i(program.getUniform("ditherSampler"), textureSlot); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Dither.h b/libs/hwui/Dither.h deleted file mode 100644 index 6af3e8384472..000000000000 --- a/libs/hwui/Dither.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2012 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_DITHER_H -#define ANDROID_HWUI_DITHER_H - -#include <GLES3/gl3.h> - -namespace android { -namespace uirenderer { - -class Caches; -class Extensions; -class Program; - -// Must be a power of two -#define DITHER_KERNEL_SIZE 4 -// These must not use the .0f notation as they are used from GLSL -#define DITHER_KERNEL_SIZE_INV (1.0 / 4.0) -#define DITHER_KERNEL_SIZE_INV_SQUARE (1.0 / 16.0) - -/** - * Handles dithering for programs. - */ -class Dither { -public: - explicit Dither(Caches& caches); - - void clear(); - void setupProgram(Program& program, GLuint* textureUnit); - -private: - void bindDitherTexture(); - - Caches& mCaches; - bool mInitialized; - GLuint mDitherTexture; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_DITHER_H diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp index 02caaa49e99c..4dc7536d60bc 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -22,6 +22,7 @@ #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> + #include <utils/Log.h> namespace android { @@ -44,6 +45,14 @@ Extensions::Extensions() { mHas1BitStencil = extensions.has("GL_OES_stencil1"); mHas4BitStencil = extensions.has("GL_OES_stencil4"); mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage"); + mHasSRGB = extensions.has("GL_EXT_sRGB"); + mHasSRGBWriteControl = extensions.has("GL_EXT_sRGB_write_control"); + + // If linear blending is enabled, the device must have ES3.0 and GL_EXT_sRGB_write_control +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + assert(mVersionMajor >= 3 || mHasSRGB); + assert(mHasSRGBWriteControl); +#endif const char* version = (const char*) glGetString(GL_VERSION); diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index 67cc747015e0..cc73c2317720 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -43,6 +43,8 @@ public: inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; } inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; } inline bool hasFloatTextures() const { return mVersionMajor >= 3; } + inline bool hasSRGB() const { return mVersionMajor >= 3 || mHasSRGB; } + inline bool hasSRGBWriteControl() const { return hasSRGB() && mHasSRGBWriteControl; } inline int getMajorGlVersion() const { return mVersionMajor; } inline int getMinorGlVersion() const { return mVersionMinor; } @@ -55,6 +57,8 @@ private: bool mHas1BitStencil; bool mHas4BitStencil; bool mHasUnpackSubImage; + bool mHasSRGB; + bool mHasSRGBWriteControl; int mVersionMajor; int mVersionMinor; diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h index 9a39ec28aa3d..6d19b7ccdd79 100644 --- a/libs/hwui/FloatColor.h +++ b/libs/hwui/FloatColor.h @@ -16,6 +16,7 @@ #ifndef FLOATCOLOR_H #define FLOATCOLOR_H +#include "utils/Color.h" #include "utils/Macros.h" #include "utils/MathUtils.h" @@ -25,11 +26,13 @@ namespace android { namespace uirenderer { struct FloatColor { + // "color" is a gamma-encoded sRGB color + // After calling this method, the color is stored as a pre-multiplied linear color void set(uint32_t color) { a = ((color >> 24) & 0xff) / 255.0f; - r = a * ((color >> 16) & 0xff) / 255.0f; - g = a * ((color >> 8) & 0xff) / 255.0f; - b = a * ((color ) & 0xff) / 255.0f; + r = a * EOCF_sRGB(((color >> 16) & 0xff) / 255.0f); + g = a * EOCF_sRGB(((color >> 8) & 0xff) / 255.0f); + b = a * EOCF_sRGB(((color ) & 0xff) / 255.0f); } bool isNotBlack() { diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 9b60dfcb5867..effc65ea967f 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -60,11 +60,17 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { } int transformFlags = pureTranslate ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None; +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + bool gammaCorrection = true; +#else + bool gammaCorrection = false; +#endif Glop glop; GlopBuilder(renderer->renderState(), renderer->caches(), &glop) .setRoundRectClipState(bakedState->roundRectClipState) .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount()) .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha) + .setGammaCorrection(gammaCorrection) .setTransform(bakedState->computedState.transform, transformFlags) .setModelViewIdentityEmptyBounds() .build(); @@ -287,24 +293,23 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp // Copy the glyph image, taking the mask format into account switch (format) { case SkMask::kA8_Format: { - uint32_t cacheX = 0, bX = 0, cacheY = 0, bY = 0; uint32_t row = (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE; // write leading border line memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); // write glyph data if (mGammaTable) { - for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { + for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { row = cacheY * cacheWidth; cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; - for (cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { + for (uint32_t cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { uint8_t tempCol = bitmapBuffer[bY + bX]; cacheBuffer[row + cacheX] = mGammaTable[tempCol]; } cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; } } else { - for (cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { + for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { row = cacheY * cacheWidth; memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth); cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp index be4fdac5f800..17ad0e36fa90 100644 --- a/libs/hwui/FrameBuilder.cpp +++ b/libs/hwui/FrameBuilder.cpp @@ -612,7 +612,6 @@ void FrameBuilder::deferBitmapOp(const BitmapOp& op) { && op.bitmap->colorType() != kAlpha_8_SkColorType && hasMergeableClip(*bakedState)) { mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID()); - // TODO: AssetAtlas in mergeId currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId); } else { currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); @@ -687,7 +686,6 @@ void FrameBuilder::deferPatchOp(const PatchOp& op) { && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode && hasMergeableClip(*bakedState)) { mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID()); - // TODO: AssetAtlas in mergeId // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId); diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp index 96cac86386b5..8aff0a24b4f0 100644 --- a/libs/hwui/GammaFontRenderer.cpp +++ b/libs/hwui/GammaFontRenderer.cpp @@ -24,12 +24,13 @@ namespace uirenderer { GammaFontRenderer::GammaFontRenderer() { INIT_LOGD("Creating lookup gamma font renderer"); +#ifndef ANDROID_ENABLE_LINEAR_BLENDING // Compute the gamma tables const float gamma = 1.0f / Properties::textGamma; - for (uint32_t i = 0; i <= 255; i++) { mGammaTable[i] = uint8_t((float)::floor(pow(i / 255.0f, gamma) * 255.0f + 0.5f)); } +#endif } void GammaFontRenderer::endPrecaching() { diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h index bd27a1a72060..c9cf69bc7d9f 100644 --- a/libs/hwui/GammaFontRenderer.h +++ b/libs/hwui/GammaFontRenderer.h @@ -18,11 +18,6 @@ #define ANDROID_HWUI_GAMMA_FONT_RENDERER_H #include "FontRenderer.h" -#include "Program.h" - -#include <SkPaint.h> - -#include <utils/String8.h> namespace android { namespace uirenderer { @@ -43,7 +38,11 @@ public: FontRenderer& getFontRenderer() { if (!mRenderer) { - mRenderer.reset(new FontRenderer(&mGammaTable[0])); + const uint8_t* table = nullptr; +#ifndef ANDROID_ENABLE_LINEAR_BLENDING + table = &mGammaTable[0]; +#endif + mRenderer.reset(new FontRenderer(table)); } return *mRenderer; } @@ -64,7 +63,9 @@ public: private: std::unique_ptr<FontRenderer> mRenderer; +#ifndef ANDROID_ENABLE_LINEAR_BLENDING uint8_t mGammaTable[256]; +#endif }; }; // namespace uirenderer diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index 1091736ab9c3..ff88d020030c 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -223,16 +223,16 @@ void GlopBuilder::setFill(int color, float alphaScale, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage, const SkShader* shader, const SkColorFilter* colorFilter) { if (mode != SkXfermode::kClear_Mode) { - float alpha = (SkColorGetA(color) / 255.0f) * alphaScale; if (!shader) { - float colorScale = alpha / 255.0f; - mOutGlop->fill.color = { - colorScale * SkColorGetR(color), - colorScale * SkColorGetG(color), - colorScale * SkColorGetB(color), - alpha - }; + FloatColor c; + c.set(color); + c.r *= alphaScale; + c.g *= alphaScale; + c.b *= alphaScale; + c.a *= alphaScale; + mOutGlop->fill.color = c; } else { + float alpha = (SkColorGetA(color) / 255.0f) * alphaScale; mOutGlop->fill.color = { 1, 1, 1, alpha }; } } else { @@ -276,15 +276,7 @@ void GlopBuilder::setFill(int color, float alphaScale, if (colorFilter->asColorMode(&color, &mode)) { mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Blend; mDescription.colorMode = mode; - - const float alpha = SkColorGetA(color) / 255.0f; - float colorScale = alpha / 255.0f; - mOutGlop->fill.filter.color = { - colorScale * SkColorGetR(color), - colorScale * SkColorGetG(color), - colorScale * SkColorGetB(color), - alpha, - }; + mOutGlop->fill.filter.color.set(color); } else if (colorFilter->asColorMatrix(srcColorMatrix)) { mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Matrix; @@ -297,10 +289,10 @@ void GlopBuilder::setFill(int color, float alphaScale, // Skia uses the range [0..255] for the addition vector, but we need // the [0..1] range to apply the vector in GLSL float* colorVector = mOutGlop->fill.filter.matrix.vector; - colorVector[0] = srcColorMatrix[4] / 255.0f; - colorVector[1] = srcColorMatrix[9] / 255.0f; - colorVector[2] = srcColorMatrix[14] / 255.0f; - colorVector[3] = srcColorMatrix[19] / 255.0f; + colorVector[0] = EOCF_sRGB(srcColorMatrix[4] / 255.0f); + colorVector[1] = EOCF_sRGB(srcColorMatrix[9] / 255.0f); + colorVector[2] = EOCF_sRGB(srcColorMatrix[14] / 255.0f); + colorVector[3] = EOCF_sRGB(srcColorMatrix[19] / 255.0f); } else { LOG_ALWAYS_FATAL("unsupported ColorFilter"); } @@ -481,6 +473,13 @@ GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& text return *this; } +GlopBuilder& GlopBuilder::setGammaCorrection(bool enabled) { + REQUIRE_STAGES(kFillStage); + + mDescription.hasGammaCorrection = enabled; + return *this; +} + //////////////////////////////////////////////////////////////////////////////// // Transform //////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h index 11524615074e..1f3b53abdb29 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -105,6 +105,8 @@ public: GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState); + GlopBuilder& setGammaCorrection(bool enabled); + void build(); static void dump(const Glop& glop); diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index c8f5e9435594..8573ab078aaf 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -67,7 +67,8 @@ GradientCache::GradientCache(Extensions& extensions) , mSize(0) , mMaxSize(Properties::gradientCacheSize) , mUseFloatTexture(extensions.hasFloatTextures()) - , mHasNpot(extensions.hasNPot()){ + , mHasNpot(extensions.hasNPot()) + , mHasSRGB(extensions.hasSRGB()) { glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); mCache.setOnEntryRemovedListener(this); @@ -176,71 +177,50 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, size_t GradientCache::bytesPerPixel() const { // We use 4 channels (RGBA) - return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t)); -} - -void GradientCache::splitToBytes(uint32_t inColor, GradientColor& outColor) const { - outColor.r = (inColor >> 16) & 0xff; - outColor.g = (inColor >> 8) & 0xff; - outColor.b = (inColor >> 0) & 0xff; - outColor.a = (inColor >> 24) & 0xff; + return 4 * (mUseFloatTexture ? /* fp16 */ 2 : sizeof(uint8_t)); } -void GradientCache::splitToFloats(uint32_t inColor, GradientColor& outColor) const { - outColor.r = ((inColor >> 16) & 0xff) / 255.0f; - outColor.g = ((inColor >> 8) & 0xff) / 255.0f; - outColor.b = ((inColor >> 0) & 0xff) / 255.0f; - outColor.a = ((inColor >> 24) & 0xff) / 255.0f; +size_t GradientCache::sourceBytesPerPixel() const { + // We use 4 channels (RGBA) and upload from floats (not half floats) + return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t)); } -void GradientCache::mixBytes(GradientColor& start, GradientColor& end, float amount, +void GradientCache::mixBytes(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const { float oppAmount = 1.0f - amount; - const float alpha = start.a * oppAmount + end.a * amount; - const float a = alpha / 255.0f; - - *dst++ = uint8_t(a * (start.r * oppAmount + end.r * amount)); - *dst++ = uint8_t(a * (start.g * oppAmount + end.g * amount)); - *dst++ = uint8_t(a * (start.b * oppAmount + end.b * amount)); - *dst++ = uint8_t(alpha); + *dst++ = uint8_t(OECF_sRGB((start.r * oppAmount + end.r * amount) * 255.0f)); + *dst++ = uint8_t(OECF_sRGB((start.g * oppAmount + end.g * amount) * 255.0f)); + *dst++ = uint8_t(OECF_sRGB((start.b * oppAmount + end.b * amount) * 255.0f)); + *dst++ = uint8_t( (start.a * oppAmount + end.a * amount) * 255.0f); } -void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float amount, +void GradientCache::mixFloats(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const { float oppAmount = 1.0f - amount; - const float a = start.a * oppAmount + end.a * amount; - float* d = (float*) dst; - *d++ = a * (start.r * oppAmount + end.r * amount); - *d++ = a * (start.g * oppAmount + end.g * amount); - *d++ = a * (start.b * oppAmount + end.b * amount); - *d++ = a; - + *d++ = start.r * oppAmount + end.r * amount; + *d++ = start.g * oppAmount + end.g * amount; + *d++ = start.b * oppAmount + end.b * amount; + *d++ = start.a * oppAmount + end.a * amount; dst += 4 * sizeof(float); } void GradientCache::generateTexture(uint32_t* colors, float* positions, const uint32_t width, const uint32_t height, Texture* texture) { - const GLsizei rowBytes = width * bytesPerPixel(); + const GLsizei rowBytes = width * sourceBytesPerPixel(); uint8_t pixels[rowBytes * height]; - static ChannelSplitter gSplitters[] = { - &android::uirenderer::GradientCache::splitToBytes, - &android::uirenderer::GradientCache::splitToFloats, - }; - ChannelSplitter split = gSplitters[mUseFloatTexture]; - static ChannelMixer gMixers[] = { - &android::uirenderer::GradientCache::mixBytes, - &android::uirenderer::GradientCache::mixFloats, + &android::uirenderer::GradientCache::mixBytes, // colors are stored gamma-encoded + &android::uirenderer::GradientCache::mixFloats, // colors are stored in linear }; ChannelMixer mix = gMixers[mUseFloatTexture]; - GradientColor start; - (this->*split)(colors[0], start); + FloatColor start; + start.set(colors[0]); - GradientColor end; - (this->*split)(colors[1], end); + FloatColor end; + end.set(colors[1]); int currentPos = 1; float startPos = positions[0]; @@ -255,7 +235,7 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, currentPos++; - (this->*split)(colors[currentPos], end); + end.set(colors[currentPos]); distance = positions[currentPos] - startPos; } @@ -266,10 +246,10 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, memcpy(pixels + rowBytes, pixels, rowBytes); if (mUseFloatTexture) { - // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels); } else { - texture->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + GLint internalFormat = mHasSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA; + texture->upload(internalFormat, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); } texture->setFilter(GL_LINEAR); diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h index 49be19ab1c81..3fd8e80dd64b 100644 --- a/libs/hwui/GradientCache.h +++ b/libs/hwui/GradientCache.h @@ -26,6 +26,8 @@ #include <utils/LruCache.h> #include <utils/Mutex.h> +#include "FloatColor.h" + namespace android { namespace uirenderer { @@ -150,25 +152,13 @@ private: void getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info); size_t bytesPerPixel() const; + size_t sourceBytesPerPixel() const; - struct GradientColor { - float r; - float g; - float b; - float a; - }; - - typedef void (GradientCache::*ChannelSplitter)(uint32_t inColor, - GradientColor& outColor) const; - - void splitToBytes(uint32_t inColor, GradientColor& outColor) const; - void splitToFloats(uint32_t inColor, GradientColor& outColor) const; - - typedef void (GradientCache::*ChannelMixer)(GradientColor& start, GradientColor& end, + typedef void (GradientCache::*ChannelMixer)(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const; - void mixBytes(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const; - void mixFloats(GradientColor& start, GradientColor& end, float amount, uint8_t*& dst) const; + void mixBytes(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const; + void mixFloats(FloatColor& start, FloatColor& end, float amount, uint8_t*& dst) const; LruCache<GradientCacheEntry, Texture*> mCache; @@ -178,6 +168,7 @@ private: GLint mMaxTextureSize; bool mUseFloatTexture; bool mHasNpot; + bool mHasSRGB; mutable Mutex mLock; }; // class GradientCache diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index c688a96aa8cd..01650ef5745f 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -75,7 +75,7 @@ public: } void setSize(uint32_t width, uint32_t height) { - texture.updateSize(width, height, texture.format()); + texture.updateSize(width, height, texture.internalFormat(), texture.format()); } inline void setBlend(bool blend) { diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index a6c281dc9839..52c62cc34670 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -225,17 +225,15 @@ void PatchCache::setupMesh(Patch* newMesh) { static const UvMapper sIdentity; -const Patch* PatchCache::get(const AssetAtlas::Entry* entry, - const uint32_t bitmapWidth, const uint32_t bitmapHeight, +const Patch* PatchCache::get( const uint32_t bitmapWidth, const uint32_t bitmapHeight, const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) { const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch); const Patch* mesh = mCache.get(description); if (!mesh) { - const UvMapper& mapper = entry ? entry->uvMapper : sIdentity; Patch* newMesh = new Patch(bitmapWidth, bitmapHeight, - pixelWidth, pixelHeight, mapper, patch); + pixelWidth, pixelHeight, sIdentity, patch); if (newMesh->vertices) { setupMesh(newMesh); diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 6e6a730ffe2b..0624c355332c 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -22,7 +22,6 @@ #include <androidfw/ResourceTypes.h> -#include "AssetAtlas.h" #include "Debug.h" #include "utils/Pair.h" @@ -54,8 +53,7 @@ public: explicit PatchCache(RenderState& renderState); ~PatchCache(); - const Patch* get(const AssetAtlas::Entry* entry, - const uint32_t bitmapWidth, const uint32_t bitmapHeight, + const Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight, const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch); void clear(); diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index e5200a516777..f5beb62a84ed 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -85,6 +85,8 @@ namespace uirenderer { #define PROGRAM_HAS_DEBUG_HIGHLIGHT 42 #define PROGRAM_HAS_ROUND_RECT_CLIP 43 +#define PROGRAM_HAS_GAMMA_CORRECTION 44 + /////////////////////////////////////////////////////////////////////////////// // Types /////////////////////////////////////////////////////////////////////////////// @@ -158,6 +160,8 @@ struct ProgramDescription { bool hasDebugHighlight; bool hasRoundRectClip; + bool hasGammaCorrection; + /** * Resets this description. All fields are reset back to the default * values they hold after building a new instance. @@ -196,6 +200,8 @@ struct ProgramDescription { hasDebugHighlight = false; hasRoundRectClip = false; + + hasGammaCorrection = false; } /** @@ -262,6 +268,7 @@ struct ProgramDescription { if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS; if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT; if (hasRoundRectClip) key |= programid(0x1) << PROGRAM_HAS_ROUND_RECT_CLIP; + if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION; return key; } diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 59225e108ac7..315c60eccb3e 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -17,8 +17,8 @@ #include <utils/String8.h> #include "Caches.h" -#include "Dither.h" #include "ProgramCache.h" +#include "Properties.h" namespace android { namespace uirenderer { @@ -69,22 +69,16 @@ const char* gVS_Header_Varyings_HasBitmap = "varying highp vec2 outBitmapTexCoords;\n"; const char* gVS_Header_Varyings_HasGradient[6] = { // Linear - "varying highp vec2 linear;\n" - "varying vec2 ditherTexCoords;\n", - "varying float linear;\n" - "varying vec2 ditherTexCoords;\n", + "varying highp vec2 linear;\n", + "varying float linear;\n", // Circular - "varying highp vec2 circular;\n" - "varying vec2 ditherTexCoords;\n", - "varying highp vec2 circular;\n" - "varying vec2 ditherTexCoords;\n", + "varying highp vec2 circular;\n", + "varying highp vec2 circular;\n", // Sweep - "varying highp vec2 sweep;\n" - "varying vec2 ditherTexCoords;\n", - "varying highp vec2 sweep;\n" - "varying vec2 ditherTexCoords;\n", + "varying highp vec2 sweep;\n", + "varying highp vec2 sweep;\n", }; const char* gVS_Header_Varyings_HasRoundRectClip = "varying highp vec2 roundRectPos;\n"; @@ -98,22 +92,16 @@ const char* gVS_Main_OutTransformedTexCoords = " outTexCoords = (mainTextureTransform * vec4(texCoords, 0.0, 1.0)).xy;\n"; const char* gVS_Main_OutGradient[6] = { // Linear - " linear = vec2((screenSpace * position).x, 0.5);\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", - " linear = (screenSpace * position).x;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", + " linear = vec2((screenSpace * position).x, 0.5);\n", + " linear = (screenSpace * position).x;\n", // Circular - " circular = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", - " circular = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", + " circular = (screenSpace * position).xy;\n", + " circular = (screenSpace * position).xy;\n", // Sweep + " sweep = (screenSpace * position).xy;\n", " sweep = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", - " sweep = (screenSpace * position).xy;\n" - " ditherTexCoords = (transform * position).xy * " STR(DITHER_KERNEL_SIZE_INV) ";\n", }; const char* gVS_Main_OutBitmapTexCoords = " outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n"; @@ -147,12 +135,11 @@ const char* gFS_Uniforms_TextureSampler = "uniform sampler2D baseSampler;\n"; const char* gFS_Uniforms_ExternalTextureSampler = "uniform samplerExternalOES baseSampler;\n"; -const char* gFS_Uniforms_Dither = - "uniform sampler2D ditherSampler;"; const char* gFS_Uniforms_GradientSampler[2] = { - "%s\n" + "uniform vec2 screenSize;\n" "uniform sampler2D gradientSampler;\n", - "%s\n" + + "uniform vec2 screenSize;\n" "uniform vec4 startColor;\n" "uniform vec4 endColor;\n" }; @@ -172,18 +159,51 @@ const char* gFS_Uniforms_HasRoundRectClip = "uniform vec4 roundRectInnerRectLTRB;\n" "uniform float roundRectRadius;\n"; +// Dithering must be done in the quantization space +// When we are writing to an sRGB framebuffer, we must do the following: +// EOCF(OECF(color) + dither) +// We approximate the transfer functions with gamma 2.0 to avoid branches and pow() +// The dithering pattern is generated with a triangle noise generator in the range [-0.5,1.5[ +// TODO: Handle linear fp16 render targets +const char* gFS_Dither_Functions = + "\nmediump float triangleNoise(const highp vec2 n) {\n" + " highp vec2 p = fract(n * vec2(5.3987, 5.4421));\n" + " p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));\n" + " highp float xy = p.x * p.y;\n" + " return fract(xy * 95.4307) + fract(xy * 75.04961) - 0.5;\n" + "}\n"; +const char* gFS_Dither_Preamble[2] = { + // Linear framebuffer + "\nvec4 dither(const vec4 color) {\n" + " return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);" + "}\n", + // sRGB framebuffer + "\nvec4 dither(const vec4 color) {\n" + " vec3 dithered = sqrt(color.rgb) + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0);\n" + " return vec4(dithered * dithered, color.a);\n" + "}\n" +}; + +// Uses luminance coefficients from Rec.709 to choose the appropriate gamma +// The gamma() function assumes that bright text will be displayed on a dark +// background and that dark text will be displayed on bright background +// The gamma coefficient is chosen to thicken or thin the text accordingly +// The dot product used to compute the luminance could be approximated with +// a simple max(color.r, color.g, color.b) +const char* gFS_Gamma_Preamble = + "\n#define GAMMA (%.2f)\n" + "#define GAMMA_INV (%.2f)\n" + "\nfloat gamma(float a, const vec3 color) {\n" + " float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722));\n" + " return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA);\n" + "}\n"; + const char* gFS_Main = "\nvoid main(void) {\n" - " lowp vec4 fragColor;\n"; + " vec4 fragColor;\n"; -const char* gFS_Main_Dither[2] = { - // ES 2.0 - "texture2D(ditherSampler, ditherTexCoords).a * " STR(DITHER_KERNEL_SIZE_INV_SQUARE), - // ES 3.0 - "texture2D(ditherSampler, ditherTexCoords).a" -}; -const char* gFS_Main_AddDitherToGradient = - " gradientColor += %s;\n"; +const char* gFS_Main_AddDither = + " fragColor = dither(fragColor);\n"; // Fast cases const char* gFS_Fast_SingleColor = @@ -202,24 +222,32 @@ const char* gFS_Fast_SingleA8Texture = "\nvoid main(void) {\n" " gl_FragColor = texture2D(baseSampler, outTexCoords);\n" "}\n\n"; +const char* gFS_Fast_SingleA8Texture_ApplyGamma = + "\nvoid main(void) {\n" + " gl_FragColor = vec4(0.0, 0.0, 0.0, pow(texture2D(baseSampler, outTexCoords).a, GAMMA));\n" + "}\n\n"; const char* gFS_Fast_SingleModulateA8Texture = "\nvoid main(void) {\n" " gl_FragColor = color * texture2D(baseSampler, outTexCoords).a;\n" "}\n\n"; +const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma = + "\nvoid main(void) {\n" + " gl_FragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n" + "}\n\n"; const char* gFS_Fast_SingleGradient[2] = { "\nvoid main(void) {\n" - " gl_FragColor = %s + texture2D(gradientSampler, linear);\n" + " gl_FragColor = dither(texture2D(gradientSampler, linear));\n" "}\n\n", "\nvoid main(void) {\n" - " gl_FragColor = %s + mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n" + " gl_FragColor = dither(mix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n" "}\n\n", }; const char* gFS_Fast_SingleModulateGradient[2] = { "\nvoid main(void) {\n" - " gl_FragColor = %s + color.a * texture2D(gradientSampler, linear);\n" + " gl_FragColor = dither(color.a * texture2D(gradientSampler, linear));\n" "}\n\n", "\nvoid main(void) {\n" - " gl_FragColor = %s + color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n" + " gl_FragColor = dither(color.a * mix(startColor, endColor, clamp(linear, 0.0, 1.0)));\n" "}\n\n" }; @@ -239,11 +267,13 @@ const char* gFS_Main_FetchTexture[2] = { // Modulate " fragColor = color * texture2D(baseSampler, outTexCoords);\n" }; -const char* gFS_Main_FetchA8Texture[2] = { +const char* gFS_Main_FetchA8Texture[4] = { // Don't modulate " fragColor = texture2D(baseSampler, outTexCoords);\n", + " fragColor = texture2D(baseSampler, outTexCoords);\n", // Modulate " fragColor = color * texture2D(baseSampler, outTexCoords).a;\n", + " fragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n", }; const char* gFS_Main_FetchGradient[6] = { // Linear @@ -271,29 +301,38 @@ const char* gFS_Main_BlendShadersBG = " fragColor = blendShaders(gradientColor, bitmapColor)"; const char* gFS_Main_BlendShadersGB = " fragColor = blendShaders(bitmapColor, gradientColor)"; -const char* gFS_Main_BlendShaders_Modulate[3] = { +const char* gFS_Main_BlendShaders_Modulate[6] = { // Don't modulate ";\n", + ";\n", // Modulate " * color.a;\n", + " * color.a;\n", // Modulate with alpha 8 texture " * texture2D(baseSampler, outTexCoords).a;\n", + " * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n", }; -const char* gFS_Main_GradientShader_Modulate[3] = { +const char* gFS_Main_GradientShader_Modulate[6] = { // Don't modulate " fragColor = gradientColor;\n", + " fragColor = gradientColor;\n", // Modulate " fragColor = gradientColor * color.a;\n", + " fragColor = gradientColor * color.a;\n", // Modulate with alpha 8 texture " fragColor = gradientColor * texture2D(baseSampler, outTexCoords).a;\n", + " fragColor = gradientColor * gamma(texture2D(baseSampler, outTexCoords).a, gradientColor.rgb);\n", }; -const char* gFS_Main_BitmapShader_Modulate[3] = { +const char* gFS_Main_BitmapShader_Modulate[6] = { // Don't modulate " fragColor = bitmapColor;\n", + " fragColor = bitmapColor;\n", // Modulate " fragColor = bitmapColor * color.a;\n", + " fragColor = bitmapColor * color.a;\n", // Modulate with alpha 8 texture " fragColor = bitmapColor * texture2D(baseSampler, outTexCoords).a;\n", + " fragColor = bitmapColor * gamma(texture2D(baseSampler, outTexCoords).a, bitmapColor.rgb);\n", }; const char* gFS_Main_FragColor = " gl_FragColor = fragColor;\n"; @@ -385,7 +424,8 @@ const char* gBlendOps[18] = { /////////////////////////////////////////////////////////////////////////////// ProgramCache::ProgramCache(Extensions& extensions) - : mHasES3(extensions.getMajorGlVersion() >= 3) { + : mHasES3(extensions.getMajorGlVersion() >= 3) + , mHasSRGB(extensions.hasSRGB()) { } ProgramCache::~ProgramCache() { @@ -518,6 +558,7 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description static bool shaderOp(const ProgramDescription& description, String8& shader, const int modulateOp, const char** snippets) { int op = description.hasAlpha8Texture ? MODULATE_OP_MODULATE_A8 : modulateOp; + op = op * 2 + description.hasGammaCorrection; shader.append(snippets[op]); return description.hasAlpha8Texture; } @@ -570,13 +611,16 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.append(gFS_Uniforms_ExternalTextureSampler); } if (description.hasGradient) { - shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient], - gFS_Uniforms_Dither); + shader.append(gFS_Uniforms_GradientSampler[description.isSimpleGradient]); } if (description.hasRoundRectClip) { shader.append(gFS_Uniforms_HasRoundRectClip); } + if (description.hasGammaCorrection) { + shader.appendFormat(gFS_Gamma_Preamble, Properties::textGamma, 1.0f / Properties::textGamma); + } + // Optimization for common cases if (!description.hasVertexAlpha && !blendFramebuffer @@ -607,18 +651,26 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti fast = true; } else if (singleA8Texture) { if (!description.modulate) { - shader.append(gFS_Fast_SingleA8Texture); + if (description.hasGammaCorrection) { + shader.append(gFS_Fast_SingleA8Texture_ApplyGamma); + } else { + shader.append(gFS_Fast_SingleA8Texture); + } } else { - shader.append(gFS_Fast_SingleModulateA8Texture); + if (description.hasGammaCorrection) { + shader.append(gFS_Fast_SingleModulateA8Texture_ApplyGamma); + } else { + shader.append(gFS_Fast_SingleModulateA8Texture); + } } fast = true; } else if (singleGradient) { + shader.append(gFS_Dither_Functions); + shader.append(gFS_Dither_Preamble[mHasSRGB]); if (!description.modulate) { - shader.appendFormat(gFS_Fast_SingleGradient[description.isSimpleGradient], - gFS_Main_Dither[mHasES3]); + shader.append(gFS_Fast_SingleGradient[description.isSimpleGradient]); } else { - shader.appendFormat(gFS_Fast_SingleModulateGradient[description.isSimpleGradient], - gFS_Main_Dither[mHasES3]); + shader.append(gFS_Fast_SingleModulateGradient[description.isSimpleGradient]); } fast = true; } @@ -652,6 +704,10 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.isBitmapNpot) { generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT); } + if (description.hasGradient) { + shader.append(gFS_Dither_Functions); + shader.append(gFS_Dither_Preamble[mHasSRGB]); + } // Begin the shader shader.append(gFS_Main); { @@ -659,7 +715,8 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.hasTexture || description.hasExternalTexture) { if (description.hasAlpha8Texture) { if (!description.hasGradient && !description.hasBitmap) { - shader.append(gFS_Main_FetchA8Texture[modulateOp]); + shader.append( + gFS_Main_FetchA8Texture[modulateOp * 2 + description.hasGammaCorrection]); } } else { shader.append(gFS_Main_FetchTexture[modulateOp]); @@ -671,7 +728,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti } if (description.hasGradient) { shader.append(gFS_Main_FetchGradient[gradientIndex(description)]); - shader.appendFormat(gFS_Main_AddDitherToGradient, gFS_Main_Dither[mHasES3]); } if (description.hasBitmap) { if (!description.isBitmapNpot) { @@ -715,6 +771,10 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti } } + if (description.hasGradient) { + shader.append(gFS_Main_AddDither); + } + // Output the fragment if (!blendFramebuffer) { shader.append(gFS_Main_FragColor); diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h index 9ac885b665e7..292ecdfdc2b6 100644 --- a/libs/hwui/ProgramCache.h +++ b/libs/hwui/ProgramCache.h @@ -59,6 +59,7 @@ private: std::map<programid, std::unique_ptr<Program>> mCache; const bool mHasES3; + const bool mHasSRGB; }; // class ProgramCache }; // namespace uirenderer diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index eedc9e7d5333..92a49d28c408 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -203,7 +203,7 @@ enum DebugLevel { #define PROPERTY_TEXT_LARGE_CACHE_WIDTH "ro.hwui.text_large_cache_width" #define PROPERTY_TEXT_LARGE_CACHE_HEIGHT "ro.hwui.text_large_cache_height" -// Gamma (>= 1.0, <= 10.0) +// Gamma (>= 1.0, <= 3.0) #define PROPERTY_TEXT_GAMMA "hwui.text_gamma" /////////////////////////////////////////////////////////////////////////////// @@ -222,7 +222,7 @@ enum DebugLevel { #define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f -#define DEFAULT_TEXT_GAMMA 1.4f +#define DEFAULT_TEXT_GAMMA 1.45f // Match design tools // cap to 256 to limite paths in the path cache #define DEFAULT_PATH_TEXTURE_CAP 256 diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp index 6ba0ab59a88c..2a03e6a3ebc5 100644 --- a/libs/hwui/PropertyValuesHolder.cpp +++ b/libs/hwui/PropertyValuesHolder.cpp @@ -16,6 +16,7 @@ #include "PropertyValuesHolder.h" +#include "utils/Color.h" #include "utils/VectorDrawableUtils.h" #include <utils/Log.h> @@ -25,18 +26,26 @@ namespace uirenderer { using namespace VectorDrawable; -inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) { - return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction); +inline constexpr float lerp(float fromValue, float toValue, float fraction) { + return float (fromValue * (1 - fraction) + toValue * fraction); +} + +inline constexpr float linearize(U8CPU component) { + return EOCF_sRGB(component / 255.0f); } // TODO: Add a test for this void ColorEvaluator::evaluate(SkColor* outColor, const SkColor& fromColor, const SkColor& toColor, float fraction) const { - U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction); - U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction); - U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction); - U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction); - *outColor = SkColorSetARGB(alpha, red, green, blue); + float a = lerp(SkColorGetA(fromColor) / 255.0f, SkColorGetA(toColor) / 255.0f, fraction); + float r = lerp(linearize(SkColorGetR(fromColor)), linearize(SkColorGetR(toColor)), fraction); + float g = lerp(linearize(SkColorGetG(fromColor)), linearize(SkColorGetG(toColor)), fraction); + float b = lerp(linearize(SkColorGetB(fromColor)), linearize(SkColorGetB(toColor)), fraction); + *outColor = SkColorSetARGB( + (U8CPU) roundf(a * 255.0f), + (U8CPU) roundf(OECF_sRGB(r) * 255.0f), + (U8CPU) roundf(OECF_sRGB(g) * 255.0f), + (U8CPU) roundf(OECF_sRGB(b) * 255.0f)); } void PathEvaluator::evaluate(PathData* out, diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp index ddca122788d1..22c6dfc6b55a 100644 --- a/libs/hwui/Readback.cpp +++ b/libs/hwui/Readback.cpp @@ -197,7 +197,7 @@ CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread, Texture sourceTexture(caches); sourceTexture.wrap(sourceTexId, - sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */); + sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0, 0 /* total lie */); CopyResult copyResult = copyTextureInto(caches, renderThread.renderState(), sourceTexture, texTransform, srcRect, bitmap); diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index a65c22c3a555..ebc41b18ed8c 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -216,7 +216,6 @@ struct BitmapOp : RecordedOp { : SUPER(BitmapOp) , bitmap(bitmap) {} const SkBitmap* bitmap; - // TODO: asset atlas/texture id lookup? }; struct BitmapMeshOp : RecordedOp { diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 6f4a6839be4e..9838df2f6013 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -177,11 +177,12 @@ bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 mode outData->endColor.set(gradInfo.fColors[1]); } - outData->ditherSampler = (*textureUnit)++; return true; } -void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data) { +void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data, + const GLsizei width, const GLsizei height) { + if (CC_UNLIKELY(data.gradientTexture)) { caches.textureState().activateTexture(data.gradientSampler); bindTexture(&caches, data.gradientTexture, data.wrapST, data.wrapST); @@ -191,10 +192,7 @@ void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& dat bindUniformColor(caches.program().getUniform("endColor"), data.endColor); } - // TODO: remove sampler slot incrementing from dither.setupProgram, - // since this assignment of slots is done at store, not apply time - GLuint ditherSampler = data.ditherSampler; - caches.dither.setupProgram(caches.program(), &ditherSampler); + glUniform2f(caches.program().getUniform("screenSize"), 1.0f / width, 1.0f / height); glUniformMatrix4fv(caches.program().getUniform("screenSpace"), 1, GL_FALSE, &data.screenSpace.data[0]); } @@ -208,13 +206,7 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model return false; } - /* - * Bypass the AssetAtlas, since those textures: - * 1) require UV mapping, which isn't implemented in matrix computation below - * 2) can't handle REPEAT simply - * 3) are safe to upload here (outside of sync stage), since they're static - */ - outData->bitmapTexture = caches.textureCache.getAndBypassAtlas(&bitmap); + outData->bitmapTexture = caches.textureCache.get(&bitmap); if (!outData->bitmapTexture) return false; outData->bitmapSampler = (*textureUnit)++; @@ -388,11 +380,12 @@ void SkiaShader::store(Caches& caches, const SkShader& shader, const Matrix4& mo outData->skiaShaderType = kNone_SkiaShaderType; } -void SkiaShader::apply(Caches& caches, const SkiaShaderData& data) { +void SkiaShader::apply(Caches& caches, const SkiaShaderData& data, + const GLsizei width, const GLsizei height) { if (!data.skiaShaderType) return; if (data.skiaShaderType & kGradient_SkiaShaderType) { - applyGradient(caches, data.gradientData); + applyGradient(caches, data.gradientData, width, height); } if (data.skiaShaderType & kBitmap_SkiaShaderType) { applyBitmap(caches, data.bitmapData); diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h index 884196d9fc62..5854289f49a7 100644 --- a/libs/hwui/SkiaShader.h +++ b/libs/hwui/SkiaShader.h @@ -62,7 +62,6 @@ struct SkiaShaderData { } bitmapData; struct GradientShaderData { Matrix4 screenSpace; - GLuint ditherSampler; // simple gradient FloatColor startColor; @@ -72,7 +71,6 @@ struct SkiaShaderData { Texture* gradientTexture; GLuint gradientSampler; GLenum wrapST; - } gradientData; struct LayerShaderData { Layer* layer; @@ -90,7 +88,8 @@ public: static void store(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, GLuint* textureUnit, ProgramDescription* description, SkiaShaderData* outData); - static void apply(Caches& caches, const SkiaShaderData& data); + static void apply(Caches& caches, const SkiaShaderData& data, + const GLsizei width, const GLsizei height); }; }; // namespace uirenderer diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 4f49a3518be0..908f57265906 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -26,19 +26,23 @@ namespace android { namespace uirenderer { +// Number of bytes used by a texture in the given format static int bytesPerPixel(GLint glFormat) { switch (glFormat) { // The wrapped-texture case, usually means a SurfaceTexture case 0: return 0; + case GL_LUMINANCE: case GL_ALPHA: return 1; + case GL_SRGB8: case GL_RGB: return 3; + case GL_SRGB8_ALPHA8: case GL_RGBA: return 4; case GL_RGBA16F: - return 16; + return 8; default: LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat); } @@ -83,14 +87,16 @@ void Texture::deleteTexture() { mId = 0; } -bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) { - if (mWidth == width && mHeight == height && mFormat == format) { +bool Texture::updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) { + if (mWidth == width && mHeight == height && + mFormat == format && mInternalFormat == internalFormat) { return false; } mWidth = width; mHeight = height; mFormat = format; - notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat)); + mInternalFormat = internalFormat; + notifySizeChanged(mWidth * mHeight * bytesPerPixel(internalFormat)); return true; } @@ -101,10 +107,10 @@ void Texture::resetCachedParams() { mMagFilter = GL_LINEAR; } -void Texture::upload(GLint internalformat, uint32_t width, uint32_t height, +void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height, GLenum format, GLenum type, const void* pixels) { GL_CHECKPOINT(MODERATE); - bool needsAlloc = updateSize(width, height, internalformat); + bool needsAlloc = updateSize(width, height, internalFormat, format); if (!mId) { glGenTextures(1, &mId); needsAlloc = true; @@ -112,17 +118,17 @@ void Texture::upload(GLint internalformat, uint32_t width, uint32_t height, } mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId); if (needsAlloc) { - glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, pixels); } else if (pixels) { - glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, + glTexSubImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, pixels); } GL_CHECKPOINT(MODERATE); } -static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp, - GLsizei width, GLsizei height, const GLvoid * data) { +static void uploadToTexture(bool resize, GLint internalFormat, GLenum format, GLenum type, + GLsizei stride, GLsizei bpp, GLsizei width, GLsizei height, const GLvoid * data) { const bool useStride = stride != width && Caches::getInstance().extensions().hasUnpackRowLength(); @@ -132,7 +138,7 @@ static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei str } if (resize) { - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, data); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); } @@ -156,7 +162,7 @@ static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei str } if (resize) { - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, temp); } else { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp); } @@ -166,31 +172,44 @@ static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei str } static void uploadSkBitmapToTexture(const SkBitmap& bitmap, - bool resize, GLenum format, GLenum type) { - uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(), - bitmap.width(), bitmap.height(), bitmap.getPixels()); + bool resize, GLint internalFormat, GLenum format, GLenum type) { + uploadToTexture(resize, internalFormat, format, type, bitmap.rowBytesAsPixels(), + bitmap.bytesPerPixel(), bitmap.width(), bitmap.height(), bitmap.getPixels()); } -static void colorTypeToGlFormatAndType(SkColorType colorType, - GLint* outFormat, GLint* outType) { +static void colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType, + bool needSRGB, GLint* outInternalFormat, GLint* outFormat, GLint* outType) { switch (colorType) { case kAlpha_8_SkColorType: *outFormat = GL_ALPHA; + *outInternalFormat = GL_ALPHA; *outType = GL_UNSIGNED_BYTE; break; case kRGB_565_SkColorType: - *outFormat = GL_RGB; - *outType = GL_UNSIGNED_SHORT_5_6_5; + if (needSRGB) { + // We would ideally use a GL_RGB/GL_SRGB8 texture but the + // intermediate Skia bitmap needs to be ARGB_8888 + *outFormat = GL_RGBA; + *outInternalFormat = caches.rgbaInternalFormat(); + *outType = GL_UNSIGNED_BYTE; + } else { + *outFormat = GL_RGB; + *outInternalFormat = GL_RGB; + *outType = GL_UNSIGNED_SHORT_5_6_5; + } break; // ARGB_4444 and Index_8 are both upconverted to RGBA_8888 case kARGB_4444_SkColorType: case kIndex_8_SkColorType: case kN32_SkColorType: *outFormat = GL_RGBA; + *outInternalFormat = caches.rgbaInternalFormat(needSRGB); *outType = GL_UNSIGNED_BYTE; break; case kGray_8_SkColorType: + // TODO: Handle sRGB *outFormat = GL_LUMINANCE; + *outInternalFormat = GL_LUMINANCE; *outType = GL_UNSIGNED_BYTE; break; default: @@ -224,29 +243,36 @@ void Texture::upload(const SkBitmap& bitmap) { setDefaultParams = true; } - GLint format, type; - colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type); + sk_sp<SkColorSpace> sRGB = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + bool needSRGB = bitmap.colorSpace() == sRGB.get(); - if (updateSize(bitmap.width(), bitmap.height(), format)) { + GLint internalFormat, format, type; + colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB, &internalFormat, &format, &type); + + if (updateSize(bitmap.width(), bitmap.height(), internalFormat, format)) { needsAlloc = true; } blend = !bitmap.isOpaque(); mCaches.textureState().bindTexture(mId); + // TODO: Handle sRGB gray bitmaps + bool hasSRGB = mCaches.extensions().hasSRGB(); if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType - || bitmap.colorType() == kIndex_8_SkColorType)) { + || bitmap.colorType() == kIndex_8_SkColorType + || (bitmap.colorType() == kRGB_565_SkColorType && hasSRGB && needSRGB))) { + SkBitmap rgbaBitmap; - rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight, - bitmap.alphaType())); + rgbaBitmap.allocPixels(SkImageInfo::MakeN32( + mWidth, mHeight, bitmap.alphaType(), hasSRGB ? sRGB : nullptr)); rgbaBitmap.eraseColor(0); SkCanvas canvas(rgbaBitmap); canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); - uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type); + uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, internalFormat, format, type); } else { - uploadSkBitmapToTexture(bitmap, needsAlloc, format, type); + uploadSkBitmapToTexture(bitmap, needsAlloc, internalFormat, format, type); } if (canMipMap) { @@ -262,11 +288,12 @@ void Texture::upload(const SkBitmap& bitmap) { } } -void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) { +void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format) { mId = id; mWidth = width; mHeight = height; mFormat = format; + mInternalFormat = internalFormat; // We're wrapping an existing texture, so don't double count this memory notifySizeChanged(0); } diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index b72742f45654..aa8a6d3fae82 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -69,8 +69,8 @@ public: * * The image data is undefined after calling this. */ - void resize(uint32_t width, uint32_t height, GLint format) { - upload(format, width, height, format, GL_UNSIGNED_BYTE, nullptr); + void resize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) { + upload(internalFormat, width, height, format, GL_UNSIGNED_BYTE, nullptr); } /** @@ -85,13 +85,13 @@ public: /** * Basically glTexImage2D/glTexSubImage2D. */ - void upload(GLint internalformat, uint32_t width, uint32_t height, + void upload(GLint internalFormat, uint32_t width, uint32_t height, GLenum format, GLenum type, const void* pixels); /** * Wraps an existing texture. */ - void wrap(GLuint id, uint32_t width, uint32_t height, GLint format); + void wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format); GLuint id() const { return mId; @@ -109,6 +109,10 @@ public: return mFormat; } + GLint internalFormat() const { + return mInternalFormat; + } + /** * Generation of the backing bitmap, */ @@ -148,13 +152,14 @@ private: friend class Layer; // Returns true if the size changed, false if it was the same - bool updateSize(uint32_t width, uint32_t height, GLint format); + bool updateSize(uint32_t width, uint32_t height, GLint internalFormat, GLint format); void resetCachedParams(); GLuint mId = 0; uint32_t mWidth = 0; uint32_t mHeight = 0; GLint mFormat = 0; + GLint mInternalFormat = 0; /* See GLES spec section 3.8.14 * "In the initial state, the value assigned to TEXTURE_MIN_FILTER is diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index 523924af5ef1..5ccdbda67e74 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -18,7 +18,6 @@ #include <utils/Mutex.h> -#include "AssetAtlas.h" #include "Caches.h" #include "Texture.h" #include "TextureCache.h" @@ -36,8 +35,7 @@ TextureCache::TextureCache() : mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity) , mSize(0) , mMaxSize(Properties::textureCacheSize) - , mFlushRate(Properties::textureCacheFlushRate) - , mAssetAtlas(nullptr) { + , mFlushRate(Properties::textureCacheFlushRate) { mCache.setOnEntryRemovedListener(this); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); @@ -84,10 +82,6 @@ void TextureCache::operator()(uint32_t&, Texture*& texture) { // Caching /////////////////////////////////////////////////////////////////////////////// -void TextureCache::setAssetAtlas(AssetAtlas* assetAtlas) { - mAssetAtlas = assetAtlas; -} - void TextureCache::resetMarkInUse(void* ownerToken) { LruCache<uint32_t, Texture*>::Iterator iter(mCache); while (iter.next()) { @@ -108,14 +102,7 @@ bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) { // Returns a prepared Texture* that either is already in the cache or can fit // in the cache (and is thus added to the cache) -Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) { - if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) { - AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef()); - if (CC_UNLIKELY(entry)) { - return entry->texture; - } - } - +Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) { Texture* texture = mCache.get(bitmap->pixelRef()->getStableID()); if (!texture) { @@ -160,7 +147,7 @@ Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType a } bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap) { - Texture* texture = getCachedTexture(bitmap, AtlasUsageType::Use); + Texture* texture = getCachedTexture(bitmap); if (texture) { texture->isInUse = ownerToken; } @@ -168,11 +155,11 @@ bool TextureCache::prefetchAndMarkInUse(void* ownerToken, const SkBitmap* bitmap } bool TextureCache::prefetch(const SkBitmap* bitmap) { - return getCachedTexture(bitmap, AtlasUsageType::Use); + return getCachedTexture(bitmap); } -Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) { - Texture* texture = getCachedTexture(bitmap, atlasUsageType); +Texture* TextureCache::get(const SkBitmap* bitmap) { + Texture* texture = getCachedTexture(bitmap); if (!texture) { if (!canMakeTextureFromBitmap(bitmap)) { diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h index 0a61b6b1a522..88ef7711e844 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -47,8 +47,6 @@ class Texture; // Classes /////////////////////////////////////////////////////////////////////////////// -class AssetAtlas; - /** * A simple LRU texture cache. The cache has a maximum size expressed in bytes. * Any texture added to the cache causing the cache to grow beyond the maximum @@ -85,20 +83,10 @@ public: bool prefetch(const SkBitmap* bitmap); /** - * Returns the texture associated with the specified bitmap from either within the cache, or - * the AssetAtlas. If the texture cannot be found in the cache, a new texture is generated. - */ - Texture* get(const SkBitmap* bitmap) { - return get(bitmap, AtlasUsageType::Use); - } - - /** - * Returns the texture associated with the specified bitmap. If the texture cannot be found in - * the cache, a new texture is generated, even if it resides in the AssetAtlas. + * Returns the texture associated with the specified bitmap from within the cache. + * If the texture cannot be found in the cache, a new texture is generated. */ - Texture* getAndBypassAtlas(const SkBitmap* bitmap) { - return get(bitmap, AtlasUsageType::Bypass); - } + Texture* get(const SkBitmap* bitmap); /** * Removes the texture associated with the specified pixelRef. This is meant @@ -130,18 +118,10 @@ public: */ void flush(); - void setAssetAtlas(AssetAtlas* assetAtlas); - private: - enum class AtlasUsageType { - Use, - Bypass, - }; - bool canMakeTextureFromBitmap(const SkBitmap* bitmap); - Texture* get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType); - Texture* getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType); + Texture* getCachedTexture(const SkBitmap* bitmap); LruCache<uint32_t, Texture*> mCache; @@ -155,8 +135,6 @@ private: std::vector<uint32_t> mGarbage; mutable Mutex mLock; - - AssetAtlas* mAssetAtlas; }; // class TextureCache }; // namespace uirenderer diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 2b7994139641..4e5b9ad2f0a3 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -561,8 +561,12 @@ void Tree::updateBitmapCache(SkBitmap* outCache, bool useStagingData) { bool Tree::allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height) { if (!canReuseBitmap(*outCache, width, height)) { - SkImageInfo info = SkImageInfo::Make(width, height, - kN32_SkColorType, kPremul_SkAlphaType); +#ifndef ANDROID_ENABLE_LINEAR_BLENDING + sk_sp<SkColorSpace> colorSpace = nullptr; +#else + sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); +#endif + SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType, colorSpace); outCache->setInfo(info); // TODO: Count the bitmap cache against app's java heap outCache->allocPixels(info); diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h index c1bf980658b2..db982ad0c8f4 100644 --- a/libs/hwui/Vertex.h +++ b/libs/hwui/Vertex.h @@ -19,6 +19,7 @@ #include "Vector.h" +#include "FloatColor.h" #include "utils/Macros.h" namespace android { @@ -76,21 +77,19 @@ struct TextureVertex { REQUIRE_COMPATIBLE_LAYOUT(TextureVertex); /** - * Simple structure to describe a vertex with a position, texture UV and ARGB color. + * Simple structure to describe a vertex with a position, texture UV and an + * sRGB color with alpha. The color is stored pre-multiplied in linear space. */ struct ColorTextureVertex { float x, y; float u, v; - float r, g, b, a; + float r, g, b, a; // pre-multiplied linear static inline void set(ColorTextureVertex* vertex, float x, float y, - float u, float v, int color) { - - float a = ((color >> 24) & 0xff) / 255.0f; - float r = a * ((color >> 16) & 0xff) / 255.0f; - float g = a * ((color >> 8) & 0xff) / 255.0f; - float b = a * ((color) & 0xff) / 255.0f; - *vertex = { x, y, u, v, r, g, b, a }; + float u, float v, uint32_t color) { + FloatColor c; + c.set(color); + *vertex = { x, y, u, v, c.r, c.g, c.b, c.a }; } }; // struct ColorTextureVertex diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index 49e9f65582ae..e2844ad0649a 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -180,7 +180,12 @@ void CacheTexture::allocatePixelBuffer() { mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight()); } - mTexture.resize(mWidth, mHeight, mFormat); + GLint internalFormat = mFormat; + if (mFormat == GL_RGBA) { + internalFormat = mCaches.rgbaInternalFormat(); + } + + mTexture.resize(mWidth, mHeight, internalFormat, mFormat); mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST); mTexture.setWrap(GL_CLAMP_TO_EDGE); } diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp index 10a26e08f897..a9bbb273dbb5 100644 --- a/libs/hwui/renderstate/OffscreenBufferPool.cpp +++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp @@ -22,6 +22,7 @@ #include "utils/FatVector.h" #include "utils/TraceUtils.h" +#include <utils/Color.h> #include <utils/Log.h> #include <GLES2/gl2.h> @@ -44,7 +45,7 @@ OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches, uint32_t height = computeIdealDimension(viewportHeight); ATRACE_FORMAT("Allocate %ux%u HW Layer", width, height); caches.textureState().activateTexture(0); - texture.resize(width, height, GL_RGBA); + texture.resize(width, height, caches.rgbaInternalFormat(), GL_RGBA); texture.blend = true; texture.setWrap(GL_CLAMP_TO_EDGE); // not setting filter on texture, since it's set when drawing, based on transform diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index ee4619d2c222..84ab3f31e7ab 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -52,7 +52,6 @@ void RenderState::onGLContextCreated() { mCaches = &Caches::createInstance(*this); } mCaches->init(); - mCaches->textureCache.setAssetAtlas(&mAssetAtlas); } static void layerLostGlContext(Layer* layer) { @@ -64,7 +63,6 @@ void RenderState::onGLContextDestroyed() { // TODO: reset all cached state in state objects std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext); - mAssetAtlas.terminate(); mCaches->terminate(); @@ -147,9 +145,17 @@ void RenderState::interruptForFunctorInvoke() { meshState().resetVertexPointers(); meshState().disableTexCoordsVertexArray(); debugOverdraw(false, false); + // TODO: We need a way to know whether the functor is sRGB aware (b/32072673) + if (mCaches->extensions().hasSRGBWriteControl()) { + glDisable(GL_FRAMEBUFFER_SRGB_EXT); + } } void RenderState::resumeFromFunctorInvoke() { + if (mCaches->extensions().hasSRGBWriteControl()) { + glEnable(GL_FRAMEBUFFER_SRGB_EXT); + } + glViewport(0, 0, mViewportWidth, mViewportHeight); glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); debugOverdraw(false, false); @@ -308,7 +314,7 @@ void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) { glVertexAttribPointer(alphaLocation, 1, GL_FLOAT, GL_FALSE, vertices.stride, alphaCoords); } // Shader uniforms - SkiaShader::apply(*mCaches, fill.skiaShaderData); + SkiaShader::apply(*mCaches, fill.skiaShaderData, mViewportWidth, mViewportHeight); GL_CHECKPOINT(MODERATE); Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ? diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 9e0fb121be65..3d119dc9e290 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -16,7 +16,6 @@ #ifndef RENDERSTATE_H #define RENDERSTATE_H -#include "AssetAtlas.h" #include "Caches.h" #include "Glop.h" #include "renderstate/Blend.h" @@ -92,7 +91,6 @@ public: void render(const Glop& glop, const Matrix4& orthoMatrix); - AssetAtlas& assetAtlas() { return mAssetAtlas; } Blend& blend() { return *mBlend; } MeshState& meshState() { return *mMeshState; } Scissor& scissor() { return *mScissor; } @@ -120,7 +118,6 @@ private: OffscreenBufferPool mLayerPool; - AssetAtlas mAssetAtlas; std::set<Layer*> mActiveLayers; std::set<renderthread::CanvasContext*> mRegisteredContexts; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 0f2d55bc209d..fe0f56ad1332 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -545,11 +545,6 @@ DeferredLayerUpdater* CanvasContext::createTextureLayer() { return mRenderPipeline->createTextureLayer(); } -void CanvasContext::setTextureAtlas(RenderThread& thread, - const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize) { - thread.eglManager().setTextureAtlas(buffer, map, mapSize); -} - void CanvasContext::dumpFrames(int fd) { FILE* file = fdopen(fd, "a"); fprintf(file, "\n\n---PROFILEDATA---\n"); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 652cddd968bb..41b658e083da 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -119,9 +119,6 @@ public: DeferredLayerUpdater* createTextureLayer(); - ANDROID_API static void setTextureAtlas(RenderThread& thread, - const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize); - void stopDrawing(); void notifyFramePending(); diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 86731c9581be..beda0455c145 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -91,9 +91,7 @@ EglManager::EglManager(RenderThread& thread) , mEglConfig(nullptr) , mEglContext(EGL_NO_CONTEXT) , mPBufferSurface(EGL_NO_SURFACE) - , mCurrentSurface(EGL_NO_SURFACE) - , mAtlasMap(nullptr) - , mAtlasMapSize(0) { + , mCurrentSurface(EGL_NO_SURFACE) { } void EglManager::initialize() { @@ -128,7 +126,6 @@ void EglManager::initialize() { makeCurrent(mPBufferSurface); DeviceInfo::initialize(); mRenderThread.renderState().onGLContextCreated(); - initAtlas(); } void EglManager::initExtensions() { @@ -191,32 +188,6 @@ void EglManager::createContext() { "Failed to create context, error = %s", egl_error_str()); } -void EglManager::setTextureAtlas(const sp<GraphicBuffer>& buffer, - int64_t* map, size_t mapSize) { - - // Already initialized - if (mAtlasBuffer.get()) { - ALOGW("Multiple calls to setTextureAtlas!"); - delete map; - return; - } - - mAtlasBuffer = buffer; - mAtlasMap = map; - mAtlasMapSize = mapSize; - - if (hasEglContext()) { - initAtlas(); - } -} - -void EglManager::initAtlas() { - if (mAtlasBuffer.get()) { - mRenderThread.renderState().assetAtlas().init(mAtlasBuffer, - mAtlasMap, mAtlasMapSize); - } -} - void EglManager::createPBufferSurface() { LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY, "usePBufferSurface() called on uninitialized GlobalContext!"); @@ -229,7 +200,16 @@ void EglManager::createPBufferSurface() { EGLSurface EglManager::createSurface(EGLNativeWindowType window) { initialize(); - EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, nullptr); + + EGLint attribs[] = { +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR, + EGL_COLORSPACE, EGL_COLORSPACE_sRGB, +#endif + EGL_NONE + }; + + EGLSurface surface = eglCreateWindowSurface(mEglDisplay, mEglConfig, window, attribs); LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE, "Failed to create EGLSurface for window %p, eglErr = %s", (void*) window, egl_error_str()); diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 41047fecf960..ba4a3e1c5192 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -79,8 +79,6 @@ public: // Returns true iff the surface is now preserving buffers. bool setPreserveBuffer(EGLSurface surface, bool preserve); - void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t mapSize); - void fence(); private: @@ -94,7 +92,6 @@ private: void createPBufferSurface(); void loadConfig(); void createContext(); - void initAtlas(); EGLint queryBufferAge(EGLSurface surface); RenderThread& mRenderThread; @@ -106,10 +103,6 @@ private: EGLSurface mCurrentSurface; - sp<GraphicBuffer> mAtlasBuffer; - int64_t* mAtlasMap; - size_t mAtlasMapSize; - enum class SwapBehavior { Discard, Preserved, diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index dcbc980763ec..c2ed8643c0ad 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -480,23 +480,6 @@ void RenderProxy::dumpGraphicsMemory(int fd) { staticPostAndWait(task); } -CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, - size_t size) { - CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size); - args->buffer->decStrong(nullptr); - return nullptr; -} - -void RenderProxy::setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size) { - SETUP_TASK(setTextureAtlas); - args->thread = &mRenderThread; - args->buffer = buffer.get(); - args->buffer->incStrong(nullptr); - args->map = map; - args->size = size; - post(task); -} - CREATE_BRIDGE2(setProcessStatsBuffer, RenderThread* thread, int fd) { args->thread->jankTracker().switchStorageToAshmem(args->fd); close(args->fd); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index d4aaea6d7280..50a6f64fe5ca 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -112,7 +112,6 @@ public: uint32_t frameTimePercentile(int p); ANDROID_API static void dumpGraphicsMemory(int fd); - ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size); ANDROID_API void setProcessStatsBuffer(int fd); ANDROID_API int getRenderThreadTid(); diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 78e9bc475826..51c0a05fb6d7 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -124,8 +124,9 @@ public: static SkBitmap createSkBitmap(int width, int height, SkColorType colorType = kN32_SkColorType) { SkBitmap bitmap; + sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); SkImageInfo info = SkImageInfo::Make(width, height, - colorType, kPremul_SkAlphaType); + colorType, kPremul_SkAlphaType, colorSpace); bitmap.setInfo(info); bitmap.allocPixels(info); return bitmap; diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp index a30ada0df453..5cab04d26c2a 100644 --- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp +++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp @@ -18,6 +18,7 @@ #include <gtest/gtest.h> #include <SkColorMatrixFilter.h> +#include <SkColorSpace.h> #include <SkImagePriv.h> #include <SkShader.h> @@ -82,3 +83,9 @@ TEST(SkiaBehavior, porterDuffCreateIsCached) { paint.setXfermodeMode(SkXfermode::kOverlay_Mode); ASSERT_EQ(expected, paint.getXfermode()); } + +TEST(SkiaBehavior, srgbColorSpaceIsSingleton) { + sk_sp<SkColorSpace> sRGB1 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + sk_sp<SkColorSpace> sRGB2 = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); + ASSERT_EQ(sRGB1.get(), sRGB2.get()); +} diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index b5157f401438..c8f8c7071075 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -16,6 +16,8 @@ #ifndef COLOR_H #define COLOR_H +#include <math.h> + #include <SkColor.h> namespace android { @@ -80,6 +82,28 @@ namespace uirenderer { }; static constexpr int BrightColorsCount = sizeof(BrightColors) / sizeof(Color::Color); + // Opto-electronic conversion function for the sRGB color space + // Takes a gamma-encoded sRGB value and converts it to a linear sRGB value + static constexpr float OECF_sRGB(float linear) { +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + // IEC 61966-2-1:1999 + return linear <= 0.0031308f ? + linear * 12.92f : (powf(linear, 1.0f / 2.4f) * 1.055f) - 0.055f; +#else + return linear; +#endif + } + + // Electro-optical conversion function for the sRGB color space + // Takes a linear sRGB value and converts it to a gamma-encoded sRGB value + static constexpr float EOCF_sRGB(float srgb) { +#ifdef ANDROID_ENABLE_LINEAR_BLENDING + // IEC 61966-2-1:1999 + return srgb <= 0.04045f ? srgb / 12.92f : powf((srgb + 0.055f) / 1.055f, 2.4f); +#else + return srgb; +#endif + } } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp index b879f781bce1..624d20763384 100644 --- a/libs/hwui/utils/TestWindowContext.cpp +++ b/libs/hwui/utils/TestWindowContext.cpp @@ -110,9 +110,10 @@ public: } bool capturePixels(SkBitmap* bmp) { + sk_sp<SkColorSpace> colorSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); SkImageInfo destinationConfig = SkImageInfo::Make(mSize.width(), mSize.height(), - kRGBA_8888_SkColorType, kPremul_SkAlphaType); + kRGBA_8888_SkColorType, kPremul_SkAlphaType, colorSpace); bmp->allocPixels(destinationConfig); android_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED, mSize.width() * mSize.height() * 4); diff --git a/preloaded-classes b/preloaded-classes index 42f290e22b3f..5ddd08b8cddf 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -2077,9 +2077,6 @@ android.view.Gravity android.view.HandlerActionQueue android.view.HandlerActionQueue$HandlerAction android.view.HardwareLayer -android.view.IAssetAtlas -android.view.IAssetAtlas$Stub -android.view.IAssetAtlas$Stub$Proxy android.view.IGraphicsStats android.view.IGraphicsStats$Stub android.view.IGraphicsStats$Stub$Proxy diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java deleted file mode 100644 index b0f60482df1d..000000000000 --- a/services/core/java/com/android/server/AssetAtlasService.java +++ /dev/null @@ -1,717 +0,0 @@ -/* - * 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.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -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 = "AssetAtlas"; - - // 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 = 512; - // 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 = 3; - - // 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: - // long0: SkBitmap*, the native bitmap object - // long1: x position - // long2: y position - private long[] 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); - - Collection<Bitmap> bitmaps = new HashSet<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++) { - try { - totalPixelCount += drawables.valueAt(i).addAtlasableBitmaps(bitmaps); - } catch (Throwable t) { - Log.e("AssetAtlas", "Failed to fetch preloaded drawable state", t); - throw t; - } - } - - ArrayList<Bitmap> sortedBitmaps = new ArrayList<Bitmap>(bitmaps); - // Our algorithms perform better when the bitmaps are first sorted - // The comparator will sort the bitmap by width first, then by height - Collections.sort(sortedBitmaps, 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(sortedBitmaps, 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, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING); - 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 systemRunning() { - } - - /** - * The renderer does all the work: - */ - private class Renderer implements Runnable { - private final ArrayList<Bitmap> mBitmaps; - private final int mPixelCount; - - 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. - * - * @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 Bitmap atlasBitmap = Bitmap.createBitmap( - buffer.getWidth(), buffer.getHeight(), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(atlasBitmap); - - final Atlas.Entry entry = new Atlas.Entry(); - - mAtlasMap = new long[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT]; - long[] atlasMap = mAtlasMap; - int mapIndex = 0; - - boolean result = false; - 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); - canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); - canvas.restore(); - atlasMap[mapIndex++] = bitmap.refSkPixelRef(); - atlasMap[mapIndex++] = entry.x; - atlasMap[mapIndex++] = entry.y; - } - } - - final long endRender = System.nanoTime(); - releaseCanvas(canvas, atlasBitmap); - result = nUploadAtlas(buffer, atlasBitmap); - atlasBitmap.recycle(); - 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)); - } - - return result; - } - - /** - * 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, Bitmap atlasBitmap) { - canvas.setBitmap(null); - if (DEBUG_ATLAS_TEXTURE) { - - File systemDirectory = new File(Environment.getDataDirectory(), "system"); - File dataFile = new File(systemDirectory, "atlas.png"); - - try { - FileOutputStream out = new FileOutputStream(dataFile); - atlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); - out.close(); - } catch (FileNotFoundException e) { - // Ignore - } catch (IOException e) { - // Ignore - } - } - } - } - - private static native boolean nUploadAtlas(GraphicBuffer buffer, Bitmap bitmap); - - @Override - public boolean isCompatible(int ppid) { - return ppid == android.os.Process.myPpid(); - } - - @Override - public GraphicBuffer getBuffer() throws RemoteException { - return mAtlasReady.get() ? mBuffer : null; - } - - @Override - public long[] 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 + (cpuCount - 1) * STEP; - int end = MAX_SIZE; - 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(); - } - - boolean isAllWorkerFinished; - try { - isAllWorkerFinished = signal.await(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - Log.w(LOG_TAG, "Could not complete configuration computation"); - return null; - } - - if (!isAllWorkerFinished) { - // We have to abort here, otherwise the async updates on "results" would crash the - // sort later. - Log.w(LOG_TAG, "Could not complete configuration computation before timeout."); - 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 (out of %d) in %.2fs", - results.size(), 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 (int width = mEnd; width > mStart; width -= mStep) { - for (int height = MAX_SIZE; height > MIN_SIZE; height -= STEP) { - // If the atlas is not big enough, skip it - if (width * height <= mThreshold) continue; - - boolean packSuccess = false; - - for (Atlas.Type type : Atlas.Type.values()) { - final int count = packBitmaps(type, width, height, entry); - if (count > 0) { - mResults.add(new WorkerResult(type, width, height, count)); - if (count == mBitmaps.size()) { - // If we were able to pack everything let's stop here - // Changing the type further won't make things better - packSuccess = true; - break; - } - } - } - - // If we were not able to pack everything let's stop here - // Decreasing the height further won't make things better - if (!packSuccess) { - 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/core/jni/Android.mk b/services/core/jni/Android.mk index 9459517caed7..a65dd99bf640 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -7,7 +7,6 @@ LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \ - $(LOCAL_REL_DIR)/com_android_server_AssetAtlasService.cpp \ $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \ $(LOCAL_REL_DIR)/com_android_server_ConsumerIrService.cpp \ $(LOCAL_REL_DIR)/com_android_server_HardwarePropertiesManagerService.cpp \ diff --git a/services/core/jni/com_android_server_AssetAtlasService.cpp b/services/core/jni/com_android_server_AssetAtlasService.cpp deleted file mode 100644 index d004e3022230..000000000000 --- a/services/core/jni/com_android_server_AssetAtlasService.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * 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/graphics/GraphicsJNI.h" - -#include <android_view_GraphicBuffer.h> -#include <cutils/log.h> - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -#include <EGL/egl.h> -#include <EGL/eglext.h> - -// Disable warnings for Skia. -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#include <SkCanvas.h> -#include <SkBitmap.h> -#pragma GCC diagnostic pop - -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 - -// ---------------------------------------------------------------------------- -// Canvas management -// ---------------------------------------------------------------------------- - -#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, jobject bitmapHandle) { - - SkBitmap bitmap; - GraphicsJNI::getSkBitmap(env, bitmapHandle, &bitmap); - SkAutoLockPixels alp(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 JNI_FALSE; - - EGLint major; - EGLint minor; - if (!eglInitialize(display, &major, &minor)) { - ALOGW("Could not initialize EGL"); - return JNI_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 JNI_FALSE; - } - if (configCount <= 0) { - ALOGW("Could not find EGL configuration"); - eglReleaseThread(); - eglTerminate(display); - return JNI_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(JNI_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(JNI_FALSE); - } - - if (!eglMakeCurrent(display, surface, surface, context)) { - ALOGW("Could not change current EGL context"); - CLEANUP_GL_AND_RETURN(JNI_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(JNI_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(JNI_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(JNI_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(JNI_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(JNI_FALSE); - } - - CLEANUP_GL_AND_RETURN(JNI_TRUE); - } - - return JNI_FALSE; -} - -// ---------------------------------------------------------------------------- -// JNI Glue -// ---------------------------------------------------------------------------- - -#define FIND_CLASS(var, className) \ - var = env->FindClass(className); \ - LOG_FATAL_IF(! (var), "Unable to find class " className); - -#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 = "com/android/server/AssetAtlasService"; - -static const JNINativeMethod gMethods[] = { - { "nUploadAtlas", "(Landroid/view/GraphicBuffer;Landroid/graphics/Bitmap;)Z", - (void*) com_android_server_AssetAtlasService_upload }, -}; - -int register_android_server_AssetAtlasService(JNIEnv* env) { - return jniRegisterNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)); -} - -}; diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 327019d6bbd5..d69c37f0afea 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -21,7 +21,6 @@ namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); -int register_android_server_AssetAtlasService(JNIEnv* env); int register_android_server_BatteryStatsService(JNIEnv* env); int register_android_server_ConsumerIrService(JNIEnv *env); int register_android_server_InputApplicationHandle(JNIEnv* env); @@ -76,7 +75,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_location_GnssLocationProvider(env); register_android_server_location_FlpHardwareProvider(env); register_android_server_connectivity_Vpn(env); - register_android_server_AssetAtlasService(env); register_android_server_ConsumerIrService(env); register_android_server_BatteryStatsService(env); register_android_server_hdmi_HdmiCecController(env); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 769b5ee92997..deb52383fb6b 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -747,7 +747,6 @@ public final class SystemServer { LocationManagerService location = null; CountryDetectorService countryDetector = null; ILockSettings lockSettings = null; - AssetAtlasService atlas = null; MediaRouterService mediaRouter = null; // Bring up services needed for UI. @@ -1235,17 +1234,6 @@ public final class SystemServer { traceEnd(); } - if (!disableNonCoreServices && ZygoteInit.PRELOAD_RESOURCES) { - traceBeginAndSlog("StartAssetAtlasService"); - try { - atlas = new AssetAtlasService(context); - ServiceManager.addService(AssetAtlasService.ASSET_ATLAS_SERVICE, atlas); - } catch (Throwable e) { - reportWtf("starting AssetAtlasService", e); - } - traceEnd(); - } - if (!disableNonCoreServices) { traceBeginAndSlog("AddGraphicsStatsService"); ServiceManager.addService(GraphicsStatsService.GRAPHICS_STATS_SERVICE, @@ -1465,7 +1453,6 @@ public final class SystemServer { final CountryDetectorService countryDetectorF = countryDetector; final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater; final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService; - final AssetAtlasService atlasF = atlas; final InputManagerService inputManagerF = inputManager; final TelephonyRegistry telephonyRegistryF = telephonyRegistry; final MediaRouterService mediaRouterF = mediaRouter; @@ -1583,13 +1570,6 @@ public final class SystemServer { reportWtf("Notifying CommonTimeManagementService running", e); } traceEnd(); - traceBeginAndSlog("MakeAtlasServiceReady"); - try { - if (atlasF != null) atlasF.systemRunning(); - } catch (Throwable e) { - reportWtf("Notifying AssetAtlasService running", e); - } - traceEnd(); traceBeginAndSlog("MakeInputManagerServiceReady"); try { // TODO(BT) Pass parameter to input manager diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index bcfe3bffae17..090cee81bf95 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -601,14 +601,6 @@ public final class Bitmap_Delegate { return Arrays.equals(argb1, argb2); } - // Only used by AssetAtlasService, which we don't care about. - @LayoutlibDelegate - /*package*/ static long nativeRefPixelRef(long nativeBitmap) { - // Hack: This is called by Bitmap.refSkPixelRef() and LayoutLib uses that method to get - // the native pointer from a Bitmap. So, we return nativeBitmap here. - return nativeBitmap; - } - // ---- Private delegate/helper methods ---- private Bitmap_Delegate(BufferedImage image, Config config) { |