JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.27k stars 1.18k forks source link

Animation use very high CPU usage on macOS #1054

Closed YeungKC closed 1 month ago

YeungKC commented 3 years ago

example 0:


import androidx.compose.animation.core.*
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.ui.window.singleWindowApplication

fun main() {
    singleWindowApplication {
        val transition = rememberInfiniteTransition()
        val frameIndex by transition.animateValue(
            initialValue = 0,
            targetValue = 63,
            Int.VectorConverter,
            animationSpec = infiniteRepeatable(
                animation = keyframes {
                    durationMillis = 0
                    for (index in 0 until 64) {
                        index at durationMillis
                        durationMillis += 100
                    }
                }
            )
        )

        Text("frameIndex: $frameIndex")
    }
}

image

example 1:


import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.window.singleWindowApplication

fun main() {
    singleWindowApplication {
        var value by remember { mutableStateOf(0) }
        val transition = updateTransition(value)
        val transitionValue by transition.animateInt( {
            TweenSpec(durationMillis = 10000)
        }, targetValueByState = {
            it
        })

        Column {
            Text("transitionValue: $transitionValue")
            Button(onClick = {
                value = 64
            }) {
                Text("to 64")
            }
            Button(onClick = {
                value = 0
            }) {
                Text("to 0")
            }
        }
    }
}

image

igordmn commented 3 years ago

Found the reason, investigating how to fix it.

If we don't use application/singleWindowApplication, just Swing's ComposeWindow:

import androidx.compose.animation.core.*
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.ui.awt.ComposeWindow
import androidx.compose.ui.window.singleWindowApplication
import java.awt.Dimension
import javax.swing.SwingUtilities

fun main() = SwingUtilities.invokeLater {
    ComposeWindow().apply {
        size = Dimension(500, 500)
        setContent {
            val transition = rememberInfiniteTransition()
            val frameIndex by transition.animateValue(
                initialValue = 0,
                targetValue = 63,
                Int.VectorConverter,
                animationSpec = infiniteRepeatable(
                    animation = keyframes {
                        durationMillis = 0
                        for (index in 0 until 64) {
                            index at durationMillis
                            durationMillis += 100
                        }
                    }
                )
            )

            Text("frameIndex: $frameIndex")
        }
        isVisible = true
    }
}

then CPU usage is 1% instead of 10% (on Windows)

YeungKC commented 3 years ago

Found the reason, investigating how to fix it.

If we don't use application/singleWindowApplication, just Swing's ComposeWindow:

import androidx.compose.animation.core.*
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.ui.awt.ComposeWindow
import androidx.compose.ui.window.singleWindowApplication
import java.awt.Dimension
import javax.swing.SwingUtilities

fun main() = SwingUtilities.invokeLater {
    ComposeWindow().apply {
        size = Dimension(500, 500)
        setContent {
            val transition = rememberInfiniteTransition()
            val frameIndex by transition.animateValue(
                initialValue = 0,
                targetValue = 63,
                Int.VectorConverter,
                animationSpec = infiniteRepeatable(
                    animation = keyframes {
                        durationMillis = 0
                        for (index in 0 until 64) {
                            index at durationMillis
                            durationMillis += 100
                        }
                    }
                )
            )

            Text("frameIndex: $frameIndex")
        }
        isVisible = true
    }
}

then CPU usage is 1% instead of 10% (on Windows)

This also work for macOS, then CPU usage is around 9%.

igordmn commented 3 years ago

The bug with singleWindowApplication is fixed in v1.0.0-alpha4-build396.

I leave this issue open, as there are more ways to improve CPU/GPU usage.

For example, when we call animateValue or withFrameNanos, we constantly redraw the whole window content with 1 frame per 16ms, even if the animation should be updated as 1 frame per 100ms (or per 500ms as TextField cursor).

To fix that, we should add a new feature in skiko, which will allow to keep the buffer intact if there is no changes.

bmalkow commented 3 years ago

I'm not sure if is is related to animation, but CPU usage is about 100% when TextField has focus and cursor is blinking in it.

igordmn commented 3 years ago

I'm not sure if is is related to animation, but CPU usage is about 100% when TextField has focus and cursor is blinking in it.

Blinking of the cursor is an animation.

Is CPU usage low in 1.0.0-alpha4-build396?

bmalkow commented 3 years ago

Is CPU usage low in 1.0.0-alpha4-build396?

Yes. Now CPU usage is about 10%

igordmn commented 3 years ago

10% is still high for just blinking of the cursor, and I believe we can reduce it, when we optimize as described here. It probably can't be fixed in the user code.

ice1000 commented 3 years ago

Wow! When will this improvement be adapted to Toolbox App?

akurasov commented 3 years ago

It is better to ask Toolbox team. I think their next public release will have this issue fixed.

igordmn commented 3 years ago

Wow! When will this improvement be adapted to Toolbox App?

Toolbox doesn't use application/singleWindowApplication, it uses ComposeWindow directly. So the issue with the high CPU usage in Toolbox probably is another issue. Don't know if it will be fixed in the next build.

In Compose/Skiko there were two more fixes regarding CPU usage:

ice1000 commented 3 years ago

Thanks!!

okushnikov commented 4 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.