diff options
| author | 2024-03-18 17:24:56 +0000 | |
|---|---|---|
| committer | 2024-03-18 17:24:56 +0000 | |
| commit | 5fea62d53cc2987236e3fd40486c4bfa06ffb77e (patch) | |
| tree | 1a8ccec54e61506f671e459aad6aaa07913afb30 | |
| parent | f308af1bf7ad3a7c90f9199fe05d7cc34cac3b25 (diff) | |
| parent | 2b61fecb0c95d5ebabb691d0ca61ed195653c37e (diff) | |
Merge "Add sparkle noise type in the TurbulenceNoiseShader." into main
6 files changed, 169 insertions, 80 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt index abe1e3de8eea..1c763e8c6108 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt @@ -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 + ) ) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt index 59354c843447..ba8f1ace0214 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt @@ -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() } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt index 8dd90a8ffe9f..025c8b9dce04 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt @@ -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]. * diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index 26c63f31fa46..899b9ed103cd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt index 7c36a85243a2..7a83cfe852d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt @@ -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,14 +62,12 @@ class LoadingEffectTest : SysUiStateTest() { animationStateChangedCallback = null ) - fakeExecutor.execute { - assertThat(paintFromCallback).isNull() + assertThat(paintFromCallback).isNull() - loadingEffect.play() - fakeSystemClock.advanceTime(500L) + loadingEffect.play() + animatorTestRule.advanceTimeBy(500L) - assertThat(paintFromCallback).isNotNull() - } + assertThat(paintFromCallback).isNotNull() } @Test @@ -88,25 +87,22 @@ class LoadingEffectTest : SysUiStateTest() { animationStateChangedCallback = null ) - fakeExecutor.execute { - assertThat(renderEffectFromCallback).isNull() + assertThat(renderEffectFromCallback).isNull() - loadingEffect.play() - fakeSystemClock.advanceTime(500L) + loadingEffect.play() + animatorTestRule.advanceTimeBy(500L) - assertThat(renderEffectFromCallback).isNotNull() - } + 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 + loadingEffect.play() - fakeExecutor.execute { - loadingEffect.play() + // 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) - fakeSystemClock.advanceTime(timeToAdvance.toLong()) - - assertThat(actualStates).isEqualTo(expectedStates) - } + assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING) } @Test @@ -157,17 +152,15 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) - fakeExecutor.execute { - assertThat(numPlay).isEqualTo(0) + assertThat(numPlay).isEqualTo(0) - loadingEffect.play() - loadingEffect.play() - loadingEffect.play() - loadingEffect.play() - loadingEffect.play() + loadingEffect.play() + loadingEffect.play() + loadingEffect.play() + loadingEffect.play() + loadingEffect.play() - assertThat(numPlay).isEqualTo(1) - } + assertThat(numPlay).isEqualTo(1) } @Test @@ -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,18 +187,17 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) - fakeExecutor.execute { - assertThat(isFinished).isFalse() + assertThat(isFinished).isFalse() - loadingEffect.play() - fakeSystemClock.advanceTime(config.easeInDuration.toLong() + 500L) + loadingEffect.play() + animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L) - assertThat(isFinished).isFalse() + assertThat(isFinished).isFalse() - loadingEffect.finish() + loadingEffect.finish() + animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L) - assertThat(isFinished).isTrue() - } + assertThat(isFinished).isTrue() } @Test @@ -232,13 +224,11 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) - fakeExecutor.execute { - assertThat(isFinished).isFalse() + assertThat(isFinished).isFalse() - loadingEffect.finish() + loadingEffect.finish() - assertThat(isFinished).isFalse() - } + assertThat(isFinished).isFalse() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt index 549280a809e2..e62ca645d772 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShaderTest.kt @@ -20,6 +20,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE_FRACTAL +import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE_SPARKLE import org.junit.Test import org.junit.runner.RunWith @@ -38,4 +39,9 @@ class TurbulenceNoiseShaderTest : SysuiTestCase() { fun compilesFractalNoise() { turbulenceNoiseShader = TurbulenceNoiseShader(baseType = SIMPLEX_NOISE_FRACTAL) } + + @Test + fun compilesSparkleNoise() { + turbulenceNoiseShader = TurbulenceNoiseShader(baseType = SIMPLEX_NOISE_SPARKLE) + } } |