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

Commit e2f828a6 authored by Sherry Zhou's avatar Sherry Zhou
Browse files

optimize snow effects

Avoid generating accumulated snow outline frame buffer when only intensity
changes, instead we precalculate a gradient outline with max thickness,
and we do smoothstep to filter out the real thickness by intensity,
it can reduce GPU battery ussage from ~1700mW to ~500mW

Remove horizontal movement of snow flaks, get ~390mW
Skip generating snow behind foreground, get ~350mW
Skip blending color out of accumulation mask, get ~330mW
Reduce snow flakes from 10 layers to 7 layers, get ~240mW

Test: manual test for the visual effects, measure GPU battery usage
Bug: 379336782
Flag: NONE, only magic portrait

Change-Id: I2145a7c596cb2672d32f3acbb924f2e19f7f9706
parent 621c3b11
Loading
Loading
Loading
Loading
+3 −31
Original line number Diff line number Diff line
@@ -58,8 +58,8 @@ Snow generateSnow(

    /* Grid. */
    // Increase the last number to make each layer more separate from the previous one.
    float depth = 0.65 + layerIndex * 0.37;
    float speedAdj = 1. + layerIndex * 0.15;
    float depth = 0.65 + layerIndex * 0.555;
    float speedAdj = 1. + layerIndex * 0.225;
    float layerR = idGenerator(layerIndex);
    snowGridSize *= depth;
    time += layerR * 58.3;
@@ -116,37 +116,9 @@ Snow generateSnow(
        farLayerFadeOut;

    /* Cell snow flake. */
    // Horizontal movement: Wiggle (Adjust the wiggle speed based on the distance).
    float wiggleSpeed = map(
        normalizedLayerIndex,
        0.2,
        0.7,
        closestSnowLayerWiggleSpeed,
        farthestSnowLayerWiggleSpeed);
    // Adjust wiggle based on layer number (0 = closer to screen => we want less movement).
    float wiggleAmp = 0.6 + 0.4 * smoothstep(0.5, 2.5, layerIndex);
    // Define the start based on the cell id.
    float horizontalStartAmp = 0.5;
    // Add the wiggle (equation decided by testing in Grapher).
    float horizontalWiggle = wiggle(
        // Current uv position.
        uv.y
        // Adjustment so the shape is not skewed.
        - cellUv.y / snowGridSize.y
        // variation based on cell ID.
        + cellId * 2.1,
        wiggleSpeed * speedAdj);

    // Add the start and wiggle and make that when we are closer to the edge, we don't wiggle much
    // (so the drop doesn't go outside it's cell).
    horizontalWiggle = horizontalStartAmp * wiggleAmp * horizontalWiggle;

    // Calculate main cell drop.
    float snowFlakePosUncorrected = (cellUv.x - horizontalWiggle);

    // Calculate snow flake.
    vec2 snowFlakeShape = vec2(0.28, 0.26);
    vec2 snowFlakePos = vec2(snowFlakePosUncorrected, cellUv.y * cellAspectRatio);
    vec2 snowFlakePos = vec2(cellUv.x, cellUv.y * cellAspectRatio);
    snowFlakePos -= vec2(
            0.,
            (uv.y - 0.5 / screenAspectRatio)  - cellUv.y / snowGridSize.y
+1 −3
Original line number Diff line number Diff line
@@ -37,10 +37,8 @@ vec4 main(float2 fragCoord) {
    float dY = (aN - aS) * 0.5;
    dY = max(dY, 0.0);

    float accumulatedSnow = smoothstep(0.1, 1.8, dY * 5.0);
    vec4 color = vec4(0., 0., 0., 1.);
    color.r = accumulatedSnow;
    color.r = dY * 10.0;
    color.g = random(uv);
    color.b = variation;
    return color;
}
+29 −26
Original line number Diff line number Diff line
@@ -35,8 +35,8 @@ const vec4 snowColor = vec4(1., 1., 1., 0.95);
const vec4 bgdTint = vec4(0.8, 0.8, 0.8, 0.07);

// Indices of the different snow layers.
const float farthestSnowLayerIndex = 9;
const float midSnowLayerIndex = 3;
const float farthestSnowLayerIndex = 6;
const float midSnowLayerIndex = 2;
const float closestSnowLayerIndex = 0;

vec4 main(float2 fragCoord) {
@@ -52,7 +52,6 @@ vec4 main(float2 fragCoord) {

    // Apply transform matrix to fragCoord
    float2 adjustedUv = transformPoint(transformMatrixBitmap, fragCoord);

    // Calculate uv for snow based on transformed coordinates
    float2 uv = transformPoint(transformMatrixWeather, fragCoord) / screenSize;
    float2 uvAdjusted = vec2(uv.x, uv.y / screenAspectRatio);
@@ -74,6 +73,7 @@ vec4 main(float2 fragCoord) {
    color.rgb = normalBlendNotPremultiplied(color.rgb, bgdTint.rgb, bgdTint.a);

    // 2. Generate snow layers behind the subject.
    if (colorForeground.a == 0) {
        for (float i = farthestSnowLayerIndex; i > midSnowLayerIndex; i--) {
            Snow snow = generateSnow(
                uv,
@@ -87,6 +87,7 @@ vec4 main(float2 fragCoord) {
            color.rgb =
                normalBlendNotPremultiplied(color.rgb, snowColor.rgb, snowColor.a * snow.flakeMask);
        }
    }

    // 3. Add the foreground layer. Any effect from here will be in front of the subject.
    color.rgb = normalBlend(color.rgb, colorForeground.rgb, colorForeground.a);
@@ -100,7 +101,8 @@ vec4 main(float2 fragCoord) {
    // Get the accumulated snow buffer. r contains its mask, g contains some random noise.
    vec2 accSnow = accumulatedSnow.eval(adjustedUv).rg;
    // Sharpen the mask of the accumulated snow, but not in excess.
    float accSnowMask = smoothstep(0.1, 0.9, /* mask= */ accSnow.r);
    float accSnowMask = smoothstep( (1.-intensity), 1.0, /* mask= */accSnow.r);
    if (accSnowMask > 0) {
        // Makes the edges of the snow layer accumulation rougher.
        accSnowMask = map(accSnowMask, 1. - cloudsNoise.b - 0.3 * dither, 1., 0., 1.);
        // Load snow texture and dither. Make it have gray-ish values.
@@ -111,6 +113,7 @@ vec4 main(float2 fragCoord) {
        accSnowMask = map(accSnowMask, 0., 1., 0., 1.- 0.6 * accSnowTexture - 0.35 * dither);

        color.rgb = normalBlendNotPremultiplied(color.rgb, snowColor.rgb, snowColor.a * accSnowMask);
    }

    // 5. Generate snow in front of the subject.
    for (float i = midSnowLayerIndex; i >= closestSnowLayerIndex; i--) {
+43 −16
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect.Compan
import com.google.android.wallpaper.weathereffects.graphics.WeatherEffectBase
import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
import com.google.android.wallpaper.weathereffects.graphics.utils.MathUtils
import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.getScale
import com.google.android.wallpaper.weathereffects.graphics.utils.TimeUtils
import java.util.concurrent.Executor

@@ -54,7 +55,11 @@ class SnowEffect(

    init {
        frameBuffer.setRenderEffect(
            RenderEffect.createBlurEffect(4f / bitmapScale, 4f / bitmapScale, Shader.TileMode.CLAMP)
            RenderEffect.createBlurEffect(
                BLUR_RADIUS / bitmapScale,
                BLUR_RADIUS / bitmapScale,
                Shader.TileMode.CLAMP,
            )
        )
        updateTextureUniforms()
        adjustCropping(surfaceSize)
@@ -88,23 +93,26 @@ class SnowEffect(
         * Increase effect speed as weather intensity decreases. This compensates for the floaty
         * appearance when there are fewer particles at the original speed.
         */
        if (this.intensity != intensity) {
            snowSpeed = MathUtils.map(intensity, 0f, 1f, 2.5f, 1.7f)
            this.intensity = intensity
        // Regenerate accumulated snow since the uniform changed.
        generateAccumulatedSnow()
        }
    }

    override fun setBitmaps(foreground: Bitmap?, background: Bitmap): Boolean {
        if (!super.setBitmaps(foreground, background)) {
            return false
        }

        frameBuffer.close()
        frameBuffer =
            FrameBuffer(background.width, background.height).apply {
                setRenderEffect(
        frameBuffer = FrameBuffer(background.width, background.height)
        val newScale = getScale(parallaxMatrix)
        if (bitmapScale != newScale) {
            bitmapScale = newScale
            frameBuffer.setRenderEffect(
                RenderEffect.createBlurEffect(
                        4f / bitmapScale,
                        4f / bitmapScale,
                    BLUR_RADIUS / bitmapScale,
                    BLUR_RADIUS / bitmapScale,
                    Shader.TileMode.CLAMP,
                )
            )
@@ -128,9 +136,19 @@ class SnowEffect(
        get() = snowConfig.colorGradingIntensity

    override fun setMatrix(matrix: Matrix) {
        val oldScale = bitmapScale
        super.setMatrix(matrix)
        if (bitmapScale != oldScale) {
            frameBuffer.setRenderEffect(
                RenderEffect.createBlurEffect(
                    BLUR_RADIUS / bitmapScale,
                    BLUR_RADIUS / bitmapScale,
                    Shader.TileMode.CLAMP,
                )
            )
            generateAccumulatedSnow()
        }
    }

    override fun updateTextureUniforms() {
        super.updateTextureUniforms()
@@ -155,13 +173,14 @@ class SnowEffect(
        snowConfig.accumulatedSnowShader.setFloatUniform("scale", bitmapScale)
        snowConfig.accumulatedSnowShader.setFloatUniform(
            "snowThickness",
            snowConfig.maxAccumulatedSnowThickness * intensity / bitmapScale,
            SNOW_THICKNESS / bitmapScale,
        )
        snowConfig.accumulatedSnowShader.setFloatUniform("screenWidth", surfaceSize.width)
        snowConfig.accumulatedSnowShader.setInputBuffer(
            "foreground",
            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
        )

        renderingCanvas.drawPaint(frameBufferPaint)
        frameBuffer.endDrawing()

@@ -180,4 +199,12 @@ class SnowEffect(
        val gridSize = GraphicsUtils.computeDefaultGridSize(newSurfaceSize, snowConfig.pixelDensity)
        snowConfig.shader.setFloatUniform("gridSize", 7 * gridSize, 2f * gridSize)
    }

    companion object {
        val BLUR_RADIUS = 4f
        // Use blur effect for both blurring the snow accumulation and generating a gradient edge
        // so that intensity can control snow thickness by cut the gradient edge in snow_effect
        // shader.
        val SNOW_THICKNESS = 6f
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -64,16 +64,16 @@ data class SnowEffectConfig(
        lut = GraphicsUtils.loadTexture(assets, LOOKUP_TABLE_TEXTURE_PATH),
        pixelDensity,
        COLOR_GRADING_INTENSITY,
        MAX_SNOW_THICKNESS
        MAX_SNOW_THICKNESS,
    )

    private companion object {
    companion object {
        private const val SHADER_PATH = "shaders/snow_effect.agsl"
        private const val ACCUMULATED_SNOW_SHADER_PATH = "shaders/snow_accumulation.agsl"
        private const val COLOR_GRADING_SHADER_PATH = "shaders/color_grading_lut.agsl"
        private const val NOISE_TEXTURE_PATH = "textures/clouds.png"
        private const val LOOKUP_TABLE_TEXTURE_PATH = "textures/snow_lut.png"
        private const val COLOR_GRADING_INTENSITY = 0.25f
        private const val MAX_SNOW_THICKNESS = 10f
        const val MAX_SNOW_THICKNESS = 10f
    }
}