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

Commit 60bdbc69 authored by Alec Mouri's avatar Alec Mouri
Browse files

Add workload that toggles blur behind

Also:
* Add plumbing for specifying a background blur on a scene node
* Add a concept of a properties functor, which is intended to allow for
  animating scene properties without drawing any content
* Add helper for drawing a solid color

Bug: 405591499
Flag: EXEMPT test only
Test: run apk
Change-Id: Ie4e28985afa361e084675bb68f81ec3fed37257b
parent e9b21948
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@
            </intent-filter>
        </activity>

        <activity android:name=".activities.BlurOnOffActivity" />
        <activity android:name=".activities.TrivialActivity" />

    </application>
+3 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.widget.BaseExpandableListAdapter
import android.widget.ExpandableListView
import android.widget.TextView
import androidx.activity.ComponentActivity
import com.android.test.transactionflinger.activities.BlurOnOffActivity
import com.android.test.transactionflinger.activities.TrivialActivity
import kotlin.reflect.KClass

@@ -40,7 +41,8 @@ data class DemoGroup(val groupName: String, val demos: List<Demo>)
private val AllDemos = listOf(
    DemoGroup(
        "Workloads", listOf(
            Demo("TrivialActivity", TrivialActivity::class)
            Demo("TrivialActivity", TrivialActivity::class),
            Demo("BlurOnOffActivity", BlurOnOffActivity::class)
        )
    )
)
+75 −29
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.test.transactionflinger

import android.graphics.ColorSpace
import android.graphics.HardwareBufferRenderer
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RenderNode
import android.hardware.HardwareBuffer
import android.view.Choreographer
@@ -28,31 +30,34 @@ import java.util.concurrent.CompletableFuture
 * A scene of SurfaceControls!
 */
class Scene {

    private val children = mutableListOf<Scene>()
    private var drawFunctor: (Scene.(Choreographer.FrameData) -> RenderNode?)? = null
    private var drawFunctor: (Scene.(Choreographer.FrameData, Int, Int) -> RenderNode?)? = null
    private var propertiesFunctor: (Scene.(Choreographer.FrameData) -> Unit)? = null

    /**
     * Time that we first started drawing the first frame of the scene
     * Typically this is for rolling your own animations
     */
    /** Radius of a blur applied to content behind this scene */
    var backgroundBlurRadius = 0

    /** Location of the top-left position in the x-direction of this scene, on a range of [0, 1] */
    var x = 0.0

    /** Location of the top-left position in the y-direction of this scene, on a range of [0, 1] */
    var y = 0.0

    /** Width of the scene normalized on [0, 1] */
    var width = 1.0

    /** Height of the scene normalized on [0, 1] */
    var height = 1.0
    var startTime = 0L
        private set

    /**
     * SurfaceControl that will contain the content for this scene on the display
     */
    val surfaceControl: SurfaceControl =
        SurfaceControl.Builder().setName("scene").setHidden(true).build()

    /**
     * Width in pixels
     */
    var width = 0

    /**
     * Height in pixels
     */
    var height = 0
    fun properties(functor: Scene.(Choreographer.FrameData) -> Unit) {
        propertiesFunctor = functor
    }

    /**
     * Adds a child scene
@@ -64,34 +69,58 @@ class Scene {
        return scene
    }

    /**
     * Specifies a function that will instruct this scene node to draw content
     */
    fun content(draw: Scene.(Choreographer.FrameData) -> RenderNode?) {
    fun content(draw: Scene.(Choreographer.FrameData, Int, Int) -> RenderNode?) {
        drawFunctor = draw
    }

    /**
     * Draw the scene and its children, and accumulate updates into the provided transaction
     */
    fun onDraw(
    fun drawAndSubmit(data: Choreographer.FrameData, width: Int, height: Int) {
        val transaction = SurfaceControl.Transaction()
        synchronized(transaction) {
            transaction.setVisibility(surfaceControl, true)
        }
        onDraw(data, transaction, width, height).get()
        synchronized(transaction) {
            transaction.apply()
        }
    }


    private fun onDraw(
        data: Choreographer.FrameData,
        transaction: SurfaceControl.Transaction
        transaction: SurfaceControl.Transaction,
        parentWidth: Int,
        parentHeight: Int
    ): CompletableFuture<Void> {
        if (startTime == 0L) {
            startTime = data.preferredFrameTimeline.deadlineNanos
            synchronized(transaction) {
                transaction.setPosition(
                    surfaceControl,
                    (parentWidth * x).toFloat(),
                    (parentHeight * y).toFloat()
                )
                for (child in children) {
                    transaction.reparent(child.surfaceControl, surfaceControl)
                }
            }
        }

        propertiesFunctor?.let {
            it.invoke(this@Scene, data)
            synchronized(transaction) {
                transaction.setBackgroundBlurRadius(surfaceControl, backgroundBlurRadius)
            }
        }

        val physicalWidth = (parentWidth * width).toInt()
        val physicalHeight = (parentHeight * height).toInt()
        val futuresList: MutableList<CompletableFuture<Void>> = mutableListOf()
        drawFunctor?.invoke(this@Scene, data)?.let { node ->

        drawFunctor?.invoke(this@Scene, data, physicalWidth, physicalHeight)?.let { node ->
            val drawFuture = CompletableFuture<Void>()
            futuresList.add(drawFuture)
            val buffer = HardwareBuffer.create(
                width, height, HardwareBuffer.RGBA_8888, 1,
                physicalWidth, physicalHeight, HardwareBuffer.RGBA_8888, 1,
                HardwareBuffer.USAGE_COMPOSER_OVERLAY or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
                        or HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
            )
@@ -103,6 +132,8 @@ class Scene {
                .draw(
                    Runnable::run
                ) {
                    // We could instead wrap the rendering result into a payload that we then
                    // dispatch to the main thread, but a lock is easier to write :)
                    synchronized(transaction) {
                        transaction.setBuffer(surfaceControl, buffer, it.fence).setVisibility(
                            surfaceControl, true
@@ -111,12 +142,27 @@ class Scene {
                    drawFuture.complete(null)
                }
        }


        futuresList.addAll(children.asSequence()
            .map { it.onDraw(data, transaction) }
            .map { it.onDraw(data, transaction, physicalWidth, physicalHeight) }
            .toList())

        return CompletableFuture<Void>.allOf(*futuresList.toTypedArray())
    }

    fun drawColor(color: Int, data: Choreographer.FrameData, width: Int, height: Int): RenderNode? {
        if (startTime < data.preferredFrameTimeline.deadlineNanos) {
            return null
        }
        val renderNode = RenderNode("cogsapp")
        renderNode.setPosition(Rect(0, 0, width, height))
        val paint = Paint()
        paint.color = color
        renderNode.beginRecording(width, height).drawPaint(paint)
        renderNode.endRecording()
        return renderNode
    }
}

/**
+87 −0
Original line number Diff line number Diff line
/*
 * Copyright 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.
 */

package com.android.test.transactionflinger.activities

import android.graphics.Color
import com.android.test.transactionflinger.Scene
import com.android.test.transactionflinger.scene
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.Duration.Companion.seconds

/**
 * Scene that draws a 2x2 checkboard background, toggling a blur every second
 */
class BlurOnOffActivity : SceneActivity() {

    override fun obtainScene(): Scene {
        return scene {
            scene {
                content { data, width, height ->
                    drawColor(Color.BLACK, data, width, height)
                }
                x = 0.0
                y = 0.0
                width = 0.5
                height = 0.5
            }
            scene {
                content { data, width, height ->
                    drawColor(Color.WHITE, data, width, height)
                }
                x = 0.0
                y = 0.0
                width = 0.5
                height = 0.5
            }
            scene {
                content { data, width, height ->
                    drawColor(Color.WHITE, data, width, height)
                }
                x = 0.5
                y = 0.0
                width = 0.5
                height = 0.5
            }
            scene {
                content { data, width, height ->
                    drawColor(Color.BLACK, data, width, height)
                }
                x = 0.5
                y = 0.5
                width = 0.5
                height = 0.5
            }

            scene {
                content { data, width, height ->
                    // SurfaceControl blurs don't work unless we draw a transparent buffer.
                    // https://www.youtube.com/watch?v=76p_ncbffCE
                    drawColor(Color.TRANSPARENT, data, width, height)
                }
                properties { data ->
                    val animationTime =
                        ((data.preferredFrameTimeline.deadlineNanos - startTime) % 2.seconds.inWholeNanoseconds).nanoseconds
                    if (animationTime < 1.seconds) {
                        backgroundBlurRadius = 0
                    } else {
                        backgroundBlurRadius = 10
                    }
                }
            }
        }
    }
}
 No newline at end of file
+13 −8
Original line number Diff line number Diff line
@@ -38,6 +38,8 @@ abstract class SceneActivity : ComponentActivity(), SurfaceHolder.Callback, Vsyn
    private lateinit var surfaceView: SurfaceView
    private lateinit var choreographer: Choreographer
    private lateinit var scene: Scene
    private var width = 0
    private var height = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
@@ -65,22 +67,25 @@ abstract class SceneActivity : ComponentActivity(), SurfaceHolder.Callback, Vsyn
    override fun surfaceCreated(holder: SurfaceHolder) {}

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        scene.width = width
        scene.height = height
        this@SceneActivity.width = width
        this@SceneActivity.height = height
        SurfaceControl.Transaction()
            .reparent(scene.surfaceControl, surfaceView.surfaceControl).apply()
        choreographer.postVsyncCallback(this)
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {}
    override fun surfaceDestroyed(holder: SurfaceHolder) {
        width = 0
        height = 0
        SurfaceControl.Transaction()
            .reparent(scene.surfaceControl, null)
    }

    override fun onVsync(data: Choreographer.FrameData) {
        val transaction = SurfaceControl.Transaction()
        scene.onDraw(data, transaction).get()
        synchronized(transaction) {
            transaction.setFrameTimelineVsync(data.preferredFrameTimeline.vsyncId)
            transaction.apply()
        if (width == 0 || height == 0) {
            return
        }
        scene.drawAndSubmit(data, width, height)
        choreographer.postVsyncCallback(this@SceneActivity)
    }

Loading