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

Commit 47cb56a5 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Cache snow accumulation" into main

parents 9705c430 793d9772
Loading
Loading
Loading
Loading
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.
 */

// `foregroundOutline`: Original size bitmap with blurred foreground outline
uniform shader foregroundOutline;
uniform shader noise;
uniform mat3 transformMatrixBitmapScaleOnly;

#include "shaders/simplex2d.agsl"
#include "shaders/utils.agsl"

/**
 * This shader generates ready-to-use snow accumulation with noise and a fluffy effect
 * added to the foreground outline. It also proportionally scales the input bitmap
 * to fit the screen size for memory efficiency.
 *
 * For snow_effects, only transform `fragCoord` based on parallax translation to access this result.
 * - R channel: Controls accumulation thickness based on intensity.
 * - G and B channels: Cache intermediate values to avoid redundant per-frame calculations.
 */
vec4 main(float2 fragCoord) {
    vec4 color = vec4(0, 0, 0, 1.0);
    // Apply transform matrix to fragCoord to scale down output
    float2 adjustedUv = transformPoint(transformMatrixBitmapScaleOnly, fragCoord);
    // Load noise texture to give "fluffiness" to the snow. Displace the sampling of the noise.
    vec3 cloudsNoise = noise.eval(adjustedUv * 7000 + vec2(fragCoord.y, -fragCoord.x)).rgb;
    // Add dither to give texture to the snow and ruffle the edges.
    float dither = abs(triangleNoise(fragCoord * 0.01));
    // Get the accumulated snow buffer. r contains its mask, g contains some random noise.
    vec2 accSnow = foregroundOutline.eval(adjustedUv).rg;
    //  R channel as intensity threshold
    color.r = accSnow.r;
    // Sharpen the mask of the accumulated snow, but not in excess.
    // Makes the edges of the snow layer accumulation rougher.
    color.g =  1. - cloudsNoise.b - 0.3 * dither;
    // Load snow texture and dither. Make it have gray-ish values.
    float accSnowTexture = smoothstep(0.2, 0.7, /* noise= */ accSnow.g) * 0.7;
    accSnowTexture = map(accSnowTexture, dither - 1, 1, 0, 1);
    // Adjust snow texture coverage/shape.
    accSnowTexture = map(accSnowTexture, 0.67, 0.8, 0, 1);
    color.b = 1.- 0.6 * accSnowTexture - 0.35 * dither;
    return color;
}
+6 −22
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
uniform shader foreground;
uniform shader background;
uniform shader accumulatedSnow;
uniform shader noise;
uniform float2 gridSize;
uniform float time;
uniform float screenAspectRatio;
@@ -53,7 +52,8 @@ 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 weatherUv = transformPoint(transformMatrixWeather, fragCoord);
    float2 uv = weatherUv / screenSize;
    float2 uvAdjusted = vec2(uv.x, uv.y / screenAspectRatio);

    vec4 colorForeground = foreground.eval(adjustedUv);
@@ -93,27 +93,11 @@ vec4 main(float2 fragCoord) {
    color.rgb = normalBlend(color.rgb, colorForeground.rgb, colorForeground.a);

    // 4. Add accumulated snow layer.
    // Load noise texture to give "fluffy-ness" to the snow. Displace the sampling of the noise.
    vec3 cloudsNoise = noise.eval(uvAdjusted * 7000 + vec2(fragCoord.y, -fragCoord.x)).rgb;
    // Add dither to give texture to the snow and ruffle the edges.
    float dither = abs(triangleNoise(fragCoord * 0.01));

    // 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.
    vec3 accSnow = accumulatedSnow.eval(weatherUv).rgb;
    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.
        float accSnowTexture = smoothstep(0.2, 0.7, /* noise= */ accSnow.g) * 0.7;
        accSnowTexture = map(accSnowTexture, dither - 1, 1, 0, 1);
        // Adjust snow texture coverage/shape.
        accSnowTexture = map(accSnowTexture, 0.67, 0.8, 0, 1);
        accSnowMask = map(accSnowMask, 0., 1., 0., 1.- 0.6 * accSnowTexture - 0.35 * dither);

    accSnowMask = map(accSnowMask, accSnow.g, 1., 0., 1.);
    accSnowMask = map(accSnowMask, 0., 1., 0., accSnow.b);
    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--) {
+14 −9
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ import android.graphics.RuntimeShader
import android.graphics.Shader
import android.util.SizeF
import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.calculateTranslationDifference
import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.calculateTransformDifference
import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.centerCropMatrix
import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.getScaleFromMatrixValues
import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.invertAndTransposeMatrix
@@ -37,7 +37,7 @@ abstract class WeatherEffectBase(
    /** The initial size of the surface where the effect will be shown. */
    private var surfaceSize: SizeF,
) : WeatherEffect {
    private var centerCropMatrix: Matrix =
    protected var centerCropMatrix: Matrix =
        centerCropMatrix(
            surfaceSize,
            SizeF(background.width.toFloat(), background.height.toFloat()),
@@ -54,6 +54,7 @@ abstract class WeatherEffectBase(
        FloatArray(9).apply { parallaxMatrix.getValues(this) }
    // Currently, we use same transform for both foreground and background
    protected open val transformMatrixBitmap: FloatArray = FloatArray(9)
    protected open val transformMatrixCenterCrop: FloatArray = FloatArray(9)
    // Apply to weather components not rely on image textures
    // Should be identity matrix in editor, and only change when parallax applied in homescreen
    private val transformMatrixWeather: FloatArray = FloatArray(9)
@@ -66,6 +67,10 @@ abstract class WeatherEffectBase(
    abstract val colorGradingIntensity: Float

    override fun setMatrix(matrix: Matrix) {
        if (matrix == this.parallaxMatrix) {
            return
        }

        this.parallaxMatrix.setAndUpdateFloatArray(matrix, parallaxMatrixValues)
        bitmapScale = getScaleFromMatrixValues(parallaxMatrixValues)
        adjustCropping(surfaceSize)
@@ -74,15 +79,10 @@ abstract class WeatherEffectBase(
    /** This function will be called every time parallax changes, don't do heavy things here */
    open fun adjustCropping(newSurfaceSize: SizeF) {
        invertAndTransposeMatrix(parallaxMatrix, transformMatrixBitmap)
        calculateTranslationDifference(
            centerCropMatrixValues,
            parallaxMatrixValues,
            transformMatrixWeather,
        )
        invertAndTransposeMatrix(centerCropMatrix, transformMatrixCenterCrop)
        calculateTransformDifference(centerCropMatrix, parallaxMatrix, transformMatrixWeather)
        shader.setFloatUniform("transformMatrixBitmap", transformMatrixBitmap)
        shader.setFloatUniform("transformMatrixWeather", transformMatrixWeather)
        shader.setFloatUniform("screenSize", newSurfaceSize.width, newSurfaceSize.height)
        shader.setFloatUniform("screenAspectRatio", GraphicsUtils.getAspectRatio(newSurfaceSize))
    }

    open fun updateGridSize(newSurfaceSize: SizeF) {}
@@ -94,6 +94,8 @@ abstract class WeatherEffectBase(
                surfaceSize,
                SizeF(background.width.toFloat(), background.height.toFloat()),
            )
        shader.setFloatUniform("screenSize", newSurfaceSize.width, newSurfaceSize.height)
        shader.setFloatUniform("screenAspectRatio", GraphicsUtils.getAspectRatio(newSurfaceSize))
        adjustCropping(newSurfaceSize)
        updateGridSize(newSurfaceSize)
    }
@@ -156,6 +158,9 @@ abstract class WeatherEffectBase(
            "background",
            BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
        )

        shader.setFloatUniform("screenSize", surfaceSize.width, surfaceSize.height)
        shader.setFloatUniform("screenAspectRatio", GraphicsUtils.getAspectRatio(surfaceSize))
    }

    private fun Matrix.setAndUpdateFloatArray(src: Matrix, targetFloatArray: FloatArray) {
+86 −20
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.graphics.RenderEffect
import android.graphics.RuntimeShader
import android.graphics.Shader
import android.util.SizeF
import androidx.core.graphics.createBitmap
import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer
import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect.Companion.DEFAULT_INTENSITY
import com.google.android.wallpaper.weathereffects.graphics.WeatherEffectBase
@@ -51,11 +52,23 @@ class SnowEffect(
    private var snowSpeed: Float = 0.8f
    private val snowPaint = Paint().also { it.shader = snowConfig.colorGradingShader }

    private var frameBuffer = FrameBuffer(background.width, background.height)
    private val frameBufferPaint = Paint().also { it.shader = snowConfig.accumulatedSnowShader }
    // Use outlineFrameBuffer and outlineFrameBufferPaint to get foreground outline
    // its process requires blur effects
    private var outlineFrameBuffer = FrameBuffer(background.width, background.height)
    private val outlineFrameBufferPaint =
        Paint().also { it.shader = snowConfig.accumulatedSnowOutlineShader }
    // accumulationFrameBuffer and accumulationFrameBufferPaint will get the result from
    // outlineFrameBuffer and add noise to snow fluffiness
    private var accumulationFrameBuffer =
        FrameBuffer(
            (background.width * bitmapScale).toInt(),
            (background.height * bitmapScale).toInt(),
        )
    private val accumulationFrameBufferPaint =
        Paint().also { it.shader = snowConfig.accumulatedSnowResultShader }

    init {
        frameBuffer.setRenderEffect(
        outlineFrameBuffer.setRenderEffect(
            RenderEffect.createBlurEffect(
                BLUR_RADIUS / bitmapScale,
                BLUR_RADIUS / bitmapScale,
@@ -85,7 +98,8 @@ class SnowEffect(

    override fun release() {
        super.release()
        frameBuffer.close()
        outlineFrameBuffer.close()
        accumulationFrameBuffer.close()
    }

    override fun setIntensity(intensity: Float) {
@@ -105,11 +119,17 @@ class SnowEffect(
            return false
        }

        frameBuffer.close()
        frameBuffer = FrameBuffer(background.width, background.height)
        outlineFrameBuffer.close()
        accumulationFrameBuffer.close()
        outlineFrameBuffer = FrameBuffer(background.width, background.height)
        val newScale = getScale(parallaxMatrix)
        bitmapScale = newScale
        frameBuffer.setRenderEffect(
        accumulationFrameBuffer =
            FrameBuffer(
                (background.width * bitmapScale).toInt(),
                (background.height * bitmapScale).toInt(),
            )
        outlineFrameBuffer.setRenderEffect(
            RenderEffect.createBlurEffect(
                BLUR_RADIUS / bitmapScale,
                BLUR_RADIUS / bitmapScale,
@@ -139,20 +159,34 @@ class SnowEffect(
        super.setMatrix(matrix)
        // Blur radius should change with scale because it decides the fluffiness of snow
        if (abs(bitmapScale - oldScale) > FLOAT_TOLERANCE) {
            frameBuffer.setRenderEffect(
            outlineFrameBuffer.close()
            accumulationFrameBuffer.close()
            outlineFrameBuffer = FrameBuffer((background.width), (background.height))

            outlineFrameBuffer.setRenderEffect(
                RenderEffect.createBlurEffect(
                    BLUR_RADIUS / bitmapScale,
                    BLUR_RADIUS / bitmapScale,
                    Shader.TileMode.CLAMP,
                )
            )
            accumulationFrameBuffer =
                FrameBuffer(
                    (background.width * bitmapScale).toInt(),
                    (background.height * bitmapScale).toInt(),
                )
            snowConfig.shader.setInputShader(
                "accumulatedSnow",
                BitmapShader(blankBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
            )

            generateAccumulatedSnow()
        }
    }

    override fun updateTextureUniforms() {
        super.updateTextureUniforms()
        snowConfig.shader.setInputBuffer(
        snowConfig.accumulatedSnowResultShader.setInputBuffer(
            "noise",
            BitmapShader(snowConfig.noiseTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT),
        )
@@ -168,28 +202,55 @@ class SnowEffect(
        }
    }

    // Generate accumulated snow requires two passes, first is to generate blurred foreground
    // outline, second is to add snow fluffiness to it.
    // It should only be called when bitmaps or screensize change, and should not be called
    // per frame.
    private fun generateAccumulatedSnow() {
        val renderingCanvas = frameBuffer.beginDrawing()
        snowConfig.accumulatedSnowShader.setFloatUniform("scale", bitmapScale)
        snowConfig.accumulatedSnowShader.setFloatUniform(
        // Generate foreground outline
        val renderingCanvas = outlineFrameBuffer.beginDrawing()
        snowConfig.accumulatedSnowOutlineShader.setFloatUniform("scale", bitmapScale)
        snowConfig.accumulatedSnowOutlineShader.setFloatUniform(
            "snowThickness",
            SNOW_THICKNESS / bitmapScale,
        )
        snowConfig.accumulatedSnowShader.setFloatUniform("screenWidth", surfaceSize.width)
        snowConfig.accumulatedSnowShader.setInputBuffer(
        snowConfig.accumulatedSnowOutlineShader.setFloatUniform("screenWidth", surfaceSize.width)
        snowConfig.accumulatedSnowOutlineShader.setInputBuffer(
            "foreground",
            BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
        )

        renderingCanvas.drawPaint(frameBufferPaint)
        frameBuffer.endDrawing()
        renderingCanvas.drawPaint(outlineFrameBufferPaint)
        outlineFrameBuffer.endDrawing()

        outlineFrameBuffer.tryObtainingImage(
            ::generateAccumulatedSnowWithBlurredOutline,
            mainExecutor,
        )
    }

    /** @param outlineImage is generated by outlineShader */
    private fun generateAccumulatedSnowWithBlurredOutline(outlineImage: Bitmap) {
        val renderingCanvas = accumulationFrameBuffer.beginDrawing()
        snowConfig.accumulatedSnowResultShader.setInputBuffer(
            "foregroundOutline",
            BitmapShader(outlineImage, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
        )
        // Actually, we should not generate it with bitmap
        snowConfig.accumulatedSnowResultShader.setFloatUniform(
            "transformMatrixBitmapScaleOnly",
            transformMatrixCenterCrop,
        )
        renderingCanvas.drawPaint(accumulationFrameBufferPaint)
        accumulationFrameBuffer.endDrawing()

        frameBuffer.tryObtainingImage(
        accumulationFrameBuffer.tryObtainingImage(
            { image ->
                snowConfig.shader.setInputBuffer(
                    "accumulatedSnow",
                    BitmapShader(image, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
                    BitmapShader(image, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP),
                )
                outlineFrameBuffer.close()
            },
            mainExecutor,
        )
@@ -201,10 +262,15 @@ class SnowEffect(
    }

    companion object {
        val BLUR_RADIUS = 4f
        const 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
        const val SNOW_THICKNESS = 6f
        // During wallpaper resizing, the updated accumulation texture might not be immediately
        // available.
        // To prevent displaying outdated accumulation, we use a tiny blank bitmap to temporarily
        // clear the rendering area before the new texture is ready.
        private val blankBitmap = createBitmap(1, 1)
    }
}
Loading