Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a1d8861 authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Some optimizations for icon shape renderer

> Moving rounded-rect optimization from Launcher3 to icon lib
> Falling back to a alpha-mask based renderer which performs better than path renderer
> Maintaing a static cache of default icon shapes so that multiple icon factory do not create duplicate shapes

Bug: 427523903
Flag: EXEMPT refactor
Test: atest RoundRectEstimatorTest
      GPU compaision when forcing bitmap renderer: https://dashboards.corp.google.com/_1cacd766_cd2b_4f59_966a_cae97246d69f?p=left_id:I95900010419074669&p=right_id:I07800010417390147&fb=metric_key:in:perfetto_android_gpu-com.android.launcher3-mem_avg,perfetto_android_gpu-com.android.launcher3-mem_max,perfetto_android_gpu-com.android.launcher3-mem_min&fh=eJxdzMEKwjAQhOF3mXOvovQNPOdYisR0wGA2hmT3UMR3b0JvHvdf5lu-iBtmCLXG8Hhzx4TwSSb5v-pe2FtjYtB-b169Kwz3c--bVQqztiG8fNXxWHC9XfLTihDrBLGk0Z3CrNXY2ahpuPitB0lJL7I.
Change-Id: I45af7a0188c1f6d9c7acb6512ebd66e330be5099
parent 7389cfa1
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -5,12 +5,22 @@ plugins {

android {
    namespace = "com.android.launcher3.icons"

    defaultConfig {
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        testApplicationId = "com.android.launcher3.icons.tests"
    }

    sourceSets {
        named("main") {
            java.setSrcDirs(listOf("src", "src_full_lib"))
            manifest.srcFile("AndroidManifest.xml")
            res.setSrcDirs(listOf("res"))
        }

        named("androidTest") {
            java.setSrcDirs(listOf("tests/src"))
        }
    }
}

@@ -18,4 +28,7 @@ dependencies {
    implementation("androidx.core:core")
    api(project(":NexusLauncher:Flags"))
    api(project(":frameworks:base:packages:SystemUI:SystemUISharedFlags"))

    androidTestImplementation(libs.androidx.test.rules)
    androidTestImplementation(libs.androidx.junit)
}
+24 −10
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.launcher3.util.UserIconInfo
import com.android.launcher3.util.UserIconInfo.TYPE_MAIN
import com.android.launcher3.util.UserIconInfo.TYPE_WORK
import com.android.systemui.shared.Flags.extendibleThemeManager
import java.lang.ref.WeakReference
import kotlin.annotation.AnnotationRetention.SOURCE
import kotlin.math.ceil
import kotlin.math.max
@@ -59,7 +60,6 @@ constructor(
    @JvmField val iconBitmapSize: Int,
    private val drawFullBleedIcons: Boolean = false,
    val themeController: IconThemeController? = null,
    private val defaultShapeRenderer: ShapeRenderer = DefaultRenderer
) : AutoCloseable {

    private val cachedUserInfo = SparseArray<UserIconInfo>()
@@ -67,15 +67,8 @@ constructor(
    private val shadowGenerator: ShadowGenerator by lazy { ShadowGenerator(iconBitmapSize) }

    /** Default IconShape for when custom shape is not needed */
    val defaultIconShape: IconShape by lazy {
        generateIconShape(
            iconBitmapSize,
            AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null)
                .apply { setBounds(0, 0, iconBitmapSize, iconBitmapSize) }
                .iconMask,
            defaultShapeRenderer
        )
    }
    val defaultIconShape: IconShape by
        lazy(LazyThreadSafetyMode.NONE) { getDefaultIconShape(iconBitmapSize) }

    @Suppress("deprecation")
    fun createIconBitmap(iconRes: ShortcutIconResource): BitmapInfo? {
@@ -477,5 +470,26 @@ constructor(
        fun getBadgeSizeForIconSize(iconSize: Int): Int {
            return (ICON_BADGE_SCALE * iconSize).toInt()
        }

        /** Cache of default icon shape keyed to the path size */
        private val defaultIconShapeCache = SparseArray<WeakReference<IconShape>>()

        private fun getDefaultIconShape(size: Int): IconShape {
            synchronized(defaultIconShapeCache) {
                val cachedShape = defaultIconShapeCache[size]?.get()
                if (cachedShape != null) return cachedShape

                val generatedShape =
                    generateIconShape(
                        size,
                        AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null)
                            .apply { setBounds(0, 0, size, size) }
                            .iconMask,
                    )

                defaultIconShapeCache[size] = WeakReference(generatedShape)
                return generatedShape
            }
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -136,7 +136,7 @@ constructor(

        canvas.resizeToContentSize(bounds, iconShape.pathSize.toFloat()) {
            paint.shader = shader
            iconShape.shapeRenderer.render(iconShape.path, canvas, paint)
            iconShape.shapeRenderer.render(canvas, paint)
            paint.shader = null
        }
    }
+13 −19
Original line number Diff line number Diff line
@@ -32,13 +32,14 @@ import android.graphics.Paint
import android.graphics.Path
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Region
import android.graphics.RegionIterator
import android.util.Log
import androidx.annotation.ColorInt
import androidx.core.graphics.ColorUtils.compositeColors
import com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR
import com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR
import com.android.launcher3.icons.ShapeRenderer.AlphaMaskRenderer
import com.android.launcher3.icons.ShapeRenderer.CircleRenderer
import com.android.launcher3.icons.ShapeRenderer.RoundedRectRenderer
import java.io.ByteArrayOutputStream
import java.io.IOException
import kotlin.math.ceil
@@ -94,17 +95,6 @@ object GraphicsUtils {
     */
    @JvmStatic fun getExpectedBitmapSize(bitmap: Bitmap): Int = bitmap.width * bitmap.height * 4

    @JvmStatic
    fun getArea(r: Region): Int {
        val itr = RegionIterator(r)
        var area = 0
        val tempRect = Rect()
        while (itr.next(tempRect)) {
            area += tempRect.width() * tempRect.height()
        }
        return area
    }

    /** Utility method to track new bitmap creation */
    @JvmStatic fun noteNewBitmapCreated() = sOnNewBitmapRunnable.run()

@@ -165,11 +155,7 @@ object GraphicsUtils {
     * [size]]
     */
    @JvmStatic
    fun generateIconShape(
        size: Int,
        shapePath: Path,
        shapeRenderer: ShapeRenderer = DefaultRenderer
    ): IconShape {
    fun generateIconShape(size: Int, shapePath: Path): IconShape {
        // Generate shadow layer:
        // Based on adaptive icon drawing in BaseIconFactory
        val offset =
@@ -186,11 +172,19 @@ object GraphicsUtils {
                    ShadowGenerator(size).addPathShadow(drawnPath, canvas)
                }
            }

        val roundRectEstimation = RoundRectEstimator.estimateRadius(shapePath, size.toFloat())
        return IconShape(
            pathSize = size,
            path = shapePath,
            shadowLayer = shadowLayer,
            shapeRenderer = shapeRenderer
            shapeRenderer =
                when {
                    roundRectEstimation >= 1f -> CircleRenderer(size.toFloat() / 2)
                    roundRectEstimation >= 0f ->
                        RoundedRectRenderer(size.toFloat(), roundRectEstimation * size / 2)
                    else -> AlphaMaskRenderer(shapePath, size)
                },
        )
    }

+2 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.graphics.Color
import android.graphics.Path
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.ColorDrawable
import com.android.launcher3.icons.ShapeRenderer.PathRenderer

data class IconShape(
    /** Size that [path] should be scaled to. */
@@ -31,7 +32,7 @@ data class IconShape(
    /** Shadow layer to draw behind icon. Should use the same shape and scale as [path] */
    @JvmField val shadowLayer: Bitmap,
    /** Renderer for customizing how shapes are drawn to canvas */
    @JvmField val shapeRenderer: ShapeRenderer = DefaultRenderer
    @JvmField val shapeRenderer: ShapeRenderer = PathRenderer(path),
) {
    companion object {
        private const val DEFAULT_PATH_SIZE = 100
Loading