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

Commit 10e8d60c authored by Sherry Zhou's avatar Sherry Zhou Committed by Android (Google) Code Review
Browse files

Merge "Add tolerance to FPSThrottler to avoid skipped frame when surface frame...

Merge "Add tolerance to FPSThrottler to avoid skipped frame when surface frame rate matches FPS limit" into main
parents c1a22014 4ae0c3fb
Loading
Loading
Loading
Loading
+19 −25
Original line number Diff line number Diff line
@@ -22,41 +22,38 @@ package com.google.android.torus.core.power
 */
class FpsThrottler {
    companion object {
        private const val NANO_TO_MILLIS = 1 / 1E6
        const val NANO_TO_MILLIS = 1 / 1E6

        const val FPS_120 = 120f
        const val FPS_60 = 60f
        const val FPS_30 = 30f
        const val FPS_18 = 18f

        @Deprecated(message = "Use FPS_60 instead.")
        const val HIGH_FPS = 60f
        @Deprecated(message = "Use FPS_30 instead.")
        const val MED_FPS = 30f
        @Deprecated(message = "Use FPS_18 instead.")
        const val LOW_FPS = 18f
        @Deprecated(message = "Use FPS_60 instead.") const val HIGH_FPS = 60f
        @Deprecated(message = "Use FPS_30 instead.") const val MED_FPS = 30f
        @Deprecated(message = "Use FPS_18 instead.") const val LOW_FPS = 18f

        /** Small tolerance (ms) for float precision in frame timing. */
        const val TOLERANCE_MILLIS = 1L
    }

    private var fps: Float = FPS_60

    @Volatile
    private var frameTimeMillis: Double = 1000.0 / fps.toDouble()
    @Volatile private var frameTimeMillis: Double = 1000.0 / fps.toDouble()
    private var lastFrameTimeNanos: Long = -1

    @Volatile
    private var continuousRenderingMode: Boolean = true
    @Volatile private var continuousRenderingMode: Boolean = true

    @Volatile
    private var requestRendering: Boolean = false
    @Volatile private var requestRendering: Boolean = false

    private fun updateFrameTime() {
        frameTimeMillis = 1000.0 / fps.toDouble()
    }

    /**
     * If [fps] is non-zero, update the requested FPS and calculate the frame time
     * for the requested FPS. Otherwise disable continuous rendering (on demand rendering)
     * without changing the frame rate.
     * If [fps] is non-zero, update the requested FPS and calculate the frame time for the requested
     * FPS. Otherwise disable continuous rendering (on demand rendering) without changing the frame
     * rate.
     *
     * @param fps The requested FPS value.
     */
@@ -86,13 +83,11 @@ class FpsThrottler {
    }

    /**
     * Calculates whether we can render the next frame. In continuous mode return true only
     * if enough time has passed since the last render to maintain requested FPS.
     * In on demand mode, return true only if [requestRendering] was called to render
     * the next frame.
     * Calculates whether we can render the next frame. In continuous mode return true only if
     * enough time has passed since the last render to maintain requested FPS. In on demand mode,
     * return true only if [requestRendering] was called to render the next frame.
     *
     * @param frameTimeNanos The time in nanoseconds when the current frame started.
     *
     * @return true if we can render the next frame.
     */
    fun canRender(frameTimeNanos: Long): Boolean {
@@ -102,7 +97,7 @@ class FpsThrottler {
                true
            } else {
                val deltaMillis = (frameTimeNanos - lastFrameTimeNanos) * NANO_TO_MILLIS
                return (deltaMillis >= frameTimeMillis) && (fps > 0f)
                return (deltaMillis >= frameTimeMillis - TOLERANCE_MILLIS) && (fps > 0f)
            }
        } else {
            // on demand rendering
@@ -120,7 +115,6 @@ class FpsThrottler {
     * @param frameTimeNanos The time in nanoseconds when the current frame started.
     * @param onRenderPermitted The client delegate to dispatch if rendering is permitted at this
     *   time.
     *
     * @return true if a frame is permitted and then actually rendered.
     */
    fun tryRender(frameTimeNanos: Long, onRenderPermitted: () -> Boolean): Boolean {
+44 −48
Original line number Diff line number Diff line
@@ -31,9 +31,9 @@ import com.google.android.torus.core.wallpaper.LiveWallpaper
import java.io.PrintWriter

/**
 * Class that implements [TorusEngine] using Canvas and can be used in a [LiveWallpaper]. This
 * class also inherits from [LiveWallpaper.LiveWallpaperConnector] which allows to do some calls
 * related to Live Wallpapers, like the method [isPreview] or [notifyWallpaperColorsChanged].
 * Class that implements [TorusEngine] using Canvas and can be used in a [LiveWallpaper]. This class
 * also inherits from [LiveWallpaper.LiveWallpaperConnector] which allows to do some calls related
 * to Live Wallpapers, like the method [isPreview] or [notifyWallpaperColorsChanged].
 *
 * By default it won't start [startUpdateLoop]. To run animations and update logic per frame, call
 * [startUpdateLoop] and [stopUpdateLoop] when it's no longer needed.
@@ -46,22 +46,22 @@ abstract class CanvasWallpaperEngine(

    /**
     * Defines if the surface should be hardware accelerated or not. If you are using
     * [RuntimeShader], this value should be set to true. When setting it to true, some
     * functions might not be supported. Please refer to the documentation:
     * [RuntimeShader], this value should be set to true. When setting it to true, some functions
     * might not be supported. Please refer to the documentation:
     * https://developer.android.com/guide/topics/graphics/hardware-accel#unsupported
     */
    private val hardwareAccelerated: Boolean = false,
) : LiveWallpaper.LiveWallpaperConnector(), TorusEngine {

    private val choreographer = Choreographer.getInstance()
    private val timeController = TimeController().also {
        it.resetDeltaTime(SystemClock.uptimeMillis())
    }
    private val timeController =
        TimeController().also { it.resetDeltaTime(SystemClock.uptimeMillis()) }
    private val frameScheduler = FrameCallback()
    private val fpsThrottler = FpsThrottler()

    protected var screenSize = Size(0, 0)
        private set

    private var resizeCalled: Boolean = false

    private var isWallpaperEngineVisible = false
@@ -110,8 +110,8 @@ abstract class CanvasWallpaperEngine(
     * update logic and render in this loop.
     *
     * @param deltaMillis The time in millis since the last time [onUpdate] was called.
     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered,
     * in the [System.nanoTime] timebase.
     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, in the
     *   [System.nanoTime] timebase.
     */
    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
    open fun onUpdate(deltaMillis: Long, frameTimeNanos: Long) {
@@ -130,9 +130,10 @@ abstract class CanvasWallpaperEngine(
    }

    final override fun create(isFirstActiveInstance: Boolean) {
        screenSize = Size(
        screenSize =
            Size(
                getCurrentSurfaceHolder().surfaceFrame.width(),
            getCurrentSurfaceHolder().surfaceFrame.height()
                getCurrentSurfaceHolder().surfaceFrame.height(),
            )

        onCreate(isFirstActiveInstance)
@@ -141,8 +142,10 @@ abstract class CanvasWallpaperEngine(

        if (shouldInvokeResume) {
            Log.e(
                TAG, "Force invoke resume. onVisibilityChanged must have been called" +
                        "before onCreate.")
                TAG,
                "Force invoke resume. onVisibilityChanged must have been called" +
                    "before onCreate.",
            )
            resume()
            shouldInvokeResume = false
        }
@@ -151,8 +154,10 @@ abstract class CanvasWallpaperEngine(
    final override fun pause() {
        if (!isCreated) {
            Log.e(
                TAG, "Engine is not yet created but pause is called. Set a flag to invoke" +
                        " resume on next create.")
                TAG,
                "Engine is not yet created but pause is called. Set a flag to invoke" +
                    " resume on next create.",
            )
            shouldInvokeResume = true
            return
        }
@@ -166,8 +171,10 @@ abstract class CanvasWallpaperEngine(
    final override fun resume() {
        if (!isCreated) {
            Log.e(
                TAG, "Engine is not yet created but resume is called. Set a flag to " +
                        "invoke resume on next create.")
                TAG,
                "Engine is not yet created but resume is called. Set a flag to " +
                    "invoke resume on next create.",
            )
            shouldInvokeResume = true
            return
        }
@@ -200,7 +207,6 @@ abstract class CanvasWallpaperEngine(
     * @param frameTimeNanos The time in nanoseconds when the frame started being rendered, in the
     *   [System.nanoTime] timebase.
     * @param onRender The callback triggered when the canvas is ready for render.
     *
     * @return Whether it is rendered.
     */
    fun renderWithFpsLimit(frameTimeNanos: Long, onRender: (canvas: Canvas) -> Unit): Boolean {
@@ -215,16 +221,13 @@ abstract class CanvasWallpaperEngine(
            return renderWithFpsLimit(frameTimeNanos, onRender)
        }

        return fpsThrottler.tryRender(frameTimeNanos) {
            renderToCanvas(onRender)
        }
        return fpsThrottler.tryRender(frameTimeNanos) { renderToCanvas(onRender) }
    }

    /**
     * Renders to canvas.
     *
     * @param onRender The callback triggered when the canvas is ready for render.
     *
     * @return Whether it is rendered.
     */
    fun render(onRender: (canvas: Canvas) -> Unit): Boolean {
@@ -250,9 +253,7 @@ abstract class CanvasWallpaperEngine(
        fpsThrottler.updateFps(fps)
    }

    /**
     * Starts the update loop.
     */
    /** Starts the update loop. */
    protected fun startUpdateLoop() {
        if (!frameScheduler.running) {
            frameScheduler.running = true
@@ -260,9 +261,7 @@ abstract class CanvasWallpaperEngine(
        }
    }

    /**
     * Stops the update loop.
     */
    /** Stops the update loop. */
    protected fun stopUpdateLoop() {
        if (frameScheduler.running) {
            frameScheduler.running = false
@@ -276,14 +275,14 @@ abstract class CanvasWallpaperEngine(
        var canvas: Canvas? = null

        try {
            canvas = if (hardwareAccelerated) {
            canvas =
                if (hardwareAccelerated) {
                    surfaceHolder.lockHardwareCanvas()
                } else {
                    surfaceHolder.lockCanvas()
                } ?: return false

            onRender(canvas)

        } catch (e: java.lang.Exception) {
            Log.e("canvas_exception", "canvas exception", e)
        } finally {
@@ -294,12 +293,9 @@ abstract class CanvasWallpaperEngine(
        return true
    }

    private fun getCurrentSurfaceHolder(): SurfaceHolder =
        getEngineSurfaceHolder() ?: defaultHolder
    private fun getCurrentSurfaceHolder(): SurfaceHolder = getEngineSurfaceHolder() ?: defaultHolder

    /**
     * Implementation of [Choreographer.FrameCallback] which triggers [onUpdate].
     */
    /** Implementation of [Choreographer.FrameCallback] which triggers [onUpdate]. */
    inner class FrameCallback : Choreographer.FrameCallback {
        internal var running: Boolean = false