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

Commit 2b61fecb authored by Yein Jo's avatar Yein Jo
Browse files

Add sparkle noise type in the TurbulenceNoiseShader.

- Pulled out the color turbulence & sparkle as functions.
- Removed background color uniform and combined with in_src, which does
  the same thing.
- Added screen color that is used for the new shader type.
- Updated the tests with the animator rule.
- Marked Flag as NA since this specific change is not guarded with the
  refactor flag.

Bug: 282007590
Test: LoadingEffectTest
Test: TurbulenceNoiseShaderTest
Test: UMO, Wallpaper picker, and AI wallpaper. Ensure they look the
same.
Flag: NA

Change-Id: I98156cb3b4473479a0e132cd09b6cff20c28d502
parent 09d78e79
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -181,6 +181,11 @@ private constructor(
        turbulenceNoiseShader.setColor(newColor)
    }

    /** Updates the noise color that's screen blended on top. */
    fun updateScreenColor(newColor: Int) {
        turbulenceNoiseShader.setScreenColor(newColor)
    }

    /**
     * Retrieves the noise offset x, y, z values. This is useful for replaying the animation
     * smoothly from the last animation, by passing in the last values to the next animation.
@@ -322,7 +327,10 @@ private constructor(
    private fun draw() {
        paintCallback?.onDraw(paint!!)
        renderEffectCallback?.onDraw(
            RenderEffect.createRuntimeShaderEffect(turbulenceNoiseShader, "in_src")
            RenderEffect.createRuntimeShaderEffect(
                turbulenceNoiseShader,
                TurbulenceNoiseShader.BACKGROUND_UNIFORM
            )
        )
    }

+3 −3
Original line number Diff line number Diff line
@@ -52,7 +52,7 @@ data class TurbulenceNoiseAnimationConfig(
    /** Color of the effect. */
    val color: Int = DEFAULT_COLOR,
    /** Background color of the effect. */
    val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR,
    val screenColor: Int = DEFAULT_SCREEN_COLOR,
    val width: Float = 0f,
    val height: Float = 0f,
    val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS,
@@ -72,7 +72,7 @@ data class TurbulenceNoiseAnimationConfig(
     */
    val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS,
    /** Whether to flip the luma mask. */
    val shouldInverseNoiseLuminosity: Boolean = false
    val shouldInverseNoiseLuminosity: Boolean = false,
) {
    companion object {
        const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -83,7 +83,7 @@ data class TurbulenceNoiseAnimationConfig(
        const val DEFAULT_COLOR = Color.WHITE
        const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f
        const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f
        const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
        const val DEFAULT_SCREEN_COLOR = Color.BLACK
        private val random = Random()
    }
}
+110 −25
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.systemui.surfaceeffects.turbulencenoise

import android.graphics.RuntimeShader
import com.android.systemui.surfaceeffects.shaders.SolidColorShader
import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
import java.lang.Float.max

@@ -28,9 +29,11 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
    RuntimeShader(getShader(baseType)) {
    // language=AGSL
    companion object {
        /** Uniform name for the background buffer (e.g. image, solid color, etc.). */
        const val BACKGROUND_UNIFORM = "in_src"
        private const val UNIFORMS =
            """
            uniform shader in_src; // Needed to support RenderEffect.
            uniform shader ${BACKGROUND_UNIFORM};
            uniform float in_gridNum;
            uniform vec3 in_noiseMove;
            uniform vec2 in_size;
@@ -41,7 +44,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
            uniform half in_lumaMatteBlendFactor;
            uniform half in_lumaMatteOverallBrightness;
            layout(color) uniform vec4 in_color;
            layout(color) uniform vec4 in_backgroundColor;
            layout(color) uniform vec4 in_screenColor;
        """

        private const val SIMPLEX_SHADER =
@@ -50,22 +53,20 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
                vec2 uv = p / in_size.xy;
                uv.x *= in_aspectRatio;

                // Compute turbulence effect with the uv distorted with simplex noise.
                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
                // Bring it to [0, 1] range.
                float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5;
                luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
                        * in_opacity;
                vec3 mask = maskLuminosity(in_color.rgb, luma);
                vec3 color = in_backgroundColor.rgb + mask * 0.6;
                vec3 color = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma);

                // Blend the result with the background color.
                color = in_src.eval(p).rgb + color * 0.6;

                // Add dither with triangle distribution to avoid color banding. Dither in the
                // shader here as we are in gamma space.
                float dither = triangleNoise(p * in_pixelDensity) / 255.;
                color += dither.rrr;

                // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
                // multiply rgb with a to get the correct result.
                color = (color + dither.rrr) * in_opacity;
                return vec4(color, in_opacity);
                // Return the pre-multiplied alpha result, i.e. [R*A, G*A, B*A, A].
                return vec4(color * in_opacity, in_opacity);
            }
        """

@@ -76,32 +77,105 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
                uv.x *= in_aspectRatio;

                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
                // Bring it to [0, 1] range.
                float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5;
                luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
                        * in_opacity;
                vec3 mask = maskLuminosity(in_color.rgb, luma);
                vec3 color = in_backgroundColor.rgb + mask * 0.6;
                vec3 color = getColorTurbulenceMask(simplex3d_fractal(noiseP) * in_inverseLuma);

                // Blend the result with the background color.
                color = in_src.eval(p).rgb + color * 0.6;

                // Skip dithering.
                return vec4(color * in_opacity, in_opacity);
            }
        """

        /**
         * This effect has two layers: color turbulence effect with sparkles on top.
         * 1. Gets the luma matte using Simplex noise.
         * 2. Generate a colored turbulence layer with the luma matte.
         * 3. Generate a colored sparkle layer with the same luma matter.
         * 4. Apply a screen color to the background image.
         * 5. Composite the previous result with the color turbulence.
         * 6. Composite the latest result with the sparkles.
         */
        private const val SIMPLEX_SPARKLE_SHADER =
            """
            vec4 main(vec2 p) {
                vec2 uv = p / in_size.xy;
                uv.x *= in_aspectRatio;

                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
                // Luma is used for both color and sparkle masks.
                float luma = simplex3d(noiseP) * in_inverseLuma;

                // Get color layer (color mask with in_color applied)
                vec3 colorLayer = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma);
                float dither = triangleNoise(p * in_pixelDensity) / 255.;
                colorLayer += dither.rrr;

                // Get sparkle layer (sparkle mask with particles & in_color applied)
                vec3 sparkleLayer = getSparkleTurbulenceMask(luma, p);

                // Composite with the background.
                half4 bgColor = in_src.eval(p);
                half sparkleOpacity = smoothstep(0, 0.75, in_opacity);

                half3 effect = screen(bgColor.rgb, in_screenColor.rgb);
                effect = screen(effect, colorLayer * 0.22);
                effect += sparkleLayer * sparkleOpacity;

                return mix(bgColor, vec4(effect, 1.), in_opacity);
            }
        """

        private const val COMMON_FUNCTIONS =
            /**
             * Below two functions generate turbulence layers (color or sparkles applied) with the
             * given luma matte. They both return a mask with in_color applied.
             */
            """
            vec3 getColorTurbulenceMask(float luma) {
                // Bring it to [0, 1] range.
                luma = luma * 0.5 + 0.5;

                half colorLuma =
                    saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
                    * in_opacity;
                vec3 colorLayer = maskLuminosity(in_color.rgb, colorLuma);

                return colorLayer;
            }

            vec3 getSparkleTurbulenceMask(float luma, vec2 p) {
                half lumaIntensity = 1.75;
                half lumaBrightness = -1.3;
                half sparkleLuma = max(luma * lumaIntensity + lumaBrightness, 0.);

                float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_noiseMove.z);
                vec3 sparkleLayer = maskLuminosity(in_color.rgb * sparkle, sparkleLuma);

                return sparkleLayer;
            }
        """
        private const val SIMPLEX_NOISE_SHADER =
            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER
            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SHADER
        private const val FRACTAL_NOISE_SHADER =
            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER
        // TODO (b/282007590): Add NOISE_WITH_SPARKLE
            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + FRACTAL_SHADER
        private const val SPARKLE_NOISE_SHADER =
            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SPARKLE_SHADER

        enum class Type {
            /** Effect with a simple color noise turbulence. */
            SIMPLEX_NOISE,
            /** Effect with a simple color noise turbulence, with fractal. */
            SIMPLEX_NOISE_FRACTAL,
            /** Effect with color & sparkle turbulence with screen color layer. */
            SIMPLEX_NOISE_SPARKLE
        }

        fun getShader(type: Type): String {
            return when (type) {
                Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER
                Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER
                Type.SIMPLEX_NOISE_SPARKLE -> SPARKLE_NOISE_SHADER
            }
        }
    }
@@ -111,7 +185,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
        setGridCount(config.gridCount)
        setPixelDensity(config.pixelDensity)
        setColor(config.color)
        setBackgroundColor(config.backgroundColor)
        setScreenColor(config.screenColor)
        setSize(config.width, config.height)
        setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
        setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity)
@@ -137,9 +211,20 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
        setColorUniform("in_color", color)
    }

    /** Sets the background color of the effect. Alpha is ignored. */
    /**
     * Sets the color that is used for blending on top of the background color/image. Only relevant
     * to [Type.SIMPLEX_NOISE_SPARKLE].
     */
    fun setScreenColor(color: Int) {
        setColorUniform("in_screenColor", color)
    }

    /**
     * Sets the background color of the effect. Alpha is ignored. If you are using [RenderEffect],
     * no need to call this function since the background image of the View will be used.
     */
    fun setBackgroundColor(color: Int) {
        setColorUniform("in_backgroundColor", color)
        setInputShader(BACKGROUND_UNIFORM, SolidColorShader(color))
    }

    /**
@@ -163,7 +248,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) :
     *
     * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting
     *   this a lower number removes variations. I.e. the turbulence noise will look more blended.
     *   Expected input range is [0, 1]. more dimmed.
     *   Expected input range is [0, 1].
     * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise.
     *   Expected input range is [0, 1].
     *
+1 −1
Original line number Diff line number Diff line
@@ -1306,7 +1306,7 @@ public class MediaControlPanel {
                TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
                // Color will be correctly updated in ColorSchemeTransition.
                /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
                /* backgroundColor= */ Color.BLACK,
                /* screenColor= */ Color.BLACK,
                width,
                height,
                TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
+40 −50
Original line number Diff line number Diff line
@@ -19,7 +19,9 @@ package com.android.systemui.surfaceeffects.loadingeffect
import android.graphics.Paint
import android.graphics.RenderEffect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.animation.AnimatorTestRule
import com.android.systemui.model.SysUiStateTest
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN
@@ -31,18 +33,17 @@ import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion
import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig
import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class LoadingEffectTest : SysUiStateTest() {

    private val fakeSystemClock = FakeSystemClock()
    private val fakeExecutor = FakeExecutor(fakeSystemClock)
    @get:Rule val animatorTestRule = AnimatorTestRule(this)

    @Test
    fun play_paintCallback_triggersDrawCallback() {
@@ -61,15 +62,13 @@ class LoadingEffectTest : SysUiStateTest() {
                animationStateChangedCallback = null
            )

        fakeExecutor.execute {
        assertThat(paintFromCallback).isNull()

        loadingEffect.play()
            fakeSystemClock.advanceTime(500L)
        animatorTestRule.advanceTimeBy(500L)

        assertThat(paintFromCallback).isNotNull()
    }
    }

    @Test
    fun play_renderEffectCallback_triggersDrawCallback() {
@@ -88,25 +87,22 @@ class LoadingEffectTest : SysUiStateTest() {
                animationStateChangedCallback = null
            )

        fakeExecutor.execute {
        assertThat(renderEffectFromCallback).isNull()

        loadingEffect.play()
            fakeSystemClock.advanceTime(500L)
        animatorTestRule.advanceTimeBy(500L)

        assertThat(renderEffectFromCallback).isNotNull()
    }
    }

    @Test
    fun play_animationStateChangesInOrder() {
        val config = TurbulenceNoiseAnimationConfig()
        val expectedStates = arrayOf(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING)
        val actualStates = mutableListOf(NOT_PLAYING)
        val states = mutableListOf(NOT_PLAYING)
        val stateChangedCallback =
            object : AnimationStateChangedCallback {
                override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
                    actualStates.add(newState)
                    states.add(newState)
                }
            }
        val drawCallback =
@@ -121,16 +117,15 @@ class LoadingEffectTest : SysUiStateTest() {
                stateChangedCallback
            )

        val timeToAdvance =
            config.easeInDuration + config.maxDuration + config.easeOutDuration + 100

        fakeExecutor.execute {
        loadingEffect.play()

            fakeSystemClock.advanceTime(timeToAdvance.toLong())
        // Execute all the animators by advancing each duration with some buffer.
        animatorTestRule.advanceTimeBy(config.easeInDuration.toLong())
        animatorTestRule.advanceTimeBy(config.maxDuration.toLong())
        animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong())
        animatorTestRule.advanceTimeBy(500)

            assertThat(actualStates).isEqualTo(expectedStates)
        }
        assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING)
    }

    @Test
@@ -157,7 +152,6 @@ class LoadingEffectTest : SysUiStateTest() {
                stateChangedCallback
            )

        fakeExecutor.execute {
        assertThat(numPlay).isEqualTo(0)

        loadingEffect.play()
@@ -168,7 +162,6 @@ class LoadingEffectTest : SysUiStateTest() {

        assertThat(numPlay).isEqualTo(1)
    }
    }

    @Test
    fun finish_finishesLoadingEffect() {
@@ -181,7 +174,7 @@ class LoadingEffectTest : SysUiStateTest() {
        val stateChangedCallback =
            object : AnimationStateChangedCallback {
                override fun onStateChanged(oldState: AnimationState, newState: AnimationState) {
                    if (oldState == MAIN && newState == NOT_PLAYING) {
                    if (oldState == EASE_OUT && newState == NOT_PLAYING) {
                        isFinished = true
                    }
                }
@@ -194,19 +187,18 @@ class LoadingEffectTest : SysUiStateTest() {
                stateChangedCallback
            )

        fakeExecutor.execute {
        assertThat(isFinished).isFalse()

        loadingEffect.play()
            fakeSystemClock.advanceTime(config.easeInDuration.toLong() + 500L)
        animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L)

        assertThat(isFinished).isFalse()

        loadingEffect.finish()
        animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L)

        assertThat(isFinished).isTrue()
    }
    }

    @Test
    fun finish_notMainState_hasNoEffect() {
@@ -232,14 +224,12 @@ class LoadingEffectTest : SysUiStateTest() {
                stateChangedCallback
            )

        fakeExecutor.execute {
        assertThat(isFinished).isFalse()

        loadingEffect.finish()

        assertThat(isFinished).isFalse()
    }
    }

    @Test
    fun getNoiseOffset_returnsNoiseOffset() {
Loading