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
15.62k stars 1.14k forks source link

Desktop software renderer bad performance #4199

Open RafaelAthosPrime opened 6 months ago

RafaelAthosPrime commented 6 months ago

Describe the problem I have been having performance problems with some clients that use my desktop software on Windows using renderApi SOFTWARE_FAST, my clients do not have an integrated video card and are unable to use OPENGL, the FPS is around 10~20. The FPS drops even more on higher resolutions, for example 1920x1080. I'm very worried because I spend 2 years working everyday in my desktop project since the compose multiplatform was in 1.0 alpha image

Affected platforms Select one of the platforms below:

Versions

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.ApplicationScope
import androidx.compose.ui.window.MenuBar
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import javax.swing.UIManager

@Composable
@Preview
fun App() {

    var text by remember { mutableStateOf("Hello, World!") }
    var fpsCounter by remember { mutableIntStateOf(0) }

    FPSCounter {
        fpsCounter = it
    }

    MaterialTheme {
        Column {
            Text("$fpsCounter FPS")
            for(r in 1..7) {
                Row {
                    for(c in 1..5) {
                        Button(onClick = {
                            text = "Hello, Desktop!"
                        }, modifier = Modifier.padding(8.dp)) {
                            Text(text)
                        }
                    }
                }
            }
        }
    }
}

fun main() {
    System.setProperty("skiko.renderApi", "SOFTWARE_FAST")

    application {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())

        val applicationState = remember { MyApplicationState() }

        for (window in applicationState.windows) {
            key(window) {
                MyWindow(window)
            }
        }
    }
}

@Composable
fun FPSCounter(onUpdateFPS: (Int) -> Unit) {
    LaunchedEffect(Unit) {
        val fpsCounter = org.jetbrains.skiko.FPSCounter(logOnTick = true)
        while(true) {
            withFrameNanos {
                onUpdateFPS(fpsCounter.average)
                fpsCounter.tick()
            }
        }
    }
}

@Composable
private fun ApplicationScope.MyWindow(
    state: MyWindowState
) = Window(onCloseRequest = state::close, title = state.title) {
    MenuBar {
        Menu("File") {
            Item("New window", onClick = state.openNewWindow)
            Item("Exit", onClick = state.exit)
        }
    }
    App()
}

private class MyApplicationState {
    val windows = mutableStateListOf<MyWindowState>()

    init {
        windows += MyWindowState("Initial window")
    }

    fun openNewWindow() {
        windows += MyWindowState("Window ${windows.size}")
    }

    fun exit() {
        windows.clear()
    }

    private fun MyWindowState(
        title: String
    ) = MyWindowState(
        title,
        openNewWindow = ::openNewWindow,
        exit = ::exit,
        windows::remove
    )
}

private class MyWindowState(
    val title: String,
    val openNewWindow: () -> Unit,
    val exit: () -> Unit,
    private val close: (MyWindowState) -> Unit
) {
    fun close() = close(this)
}
MohamedRejeb commented 5 months ago

Check this one as well: https://github.com/JetBrains/compose-multiplatform/issues/3543

RafaelAthosPrime commented 5 months ago

Any news? Same problem with the version 1.6.0

igordmn commented 4 months ago

Implementing this in Compose/Skiko can help, but it is not a trivial task, so it isn't planned on the nearest versions.

igordmn commented 4 months ago

I suggest to not use any animations/indications on low level machines, it might slightly help until we speed up the software renderer in Compose.

RafaelAthosPrime commented 4 months ago

I suggest to not use any animations/indications on low level machines, it might slightly help until we speed up the software renderer in Compose.

I think maybe is also related with the ripple effect and the shadow (elevation) of the Button component and the Modifier.clickable

I made a custom button with a simple Box and the performance boost from 25FPS to 63FPS with the software renderer

val interactionSource = remember { MutableInteractionSource() }
val isPressed by interactionSource.collectIsPressedAsState()
val color = if (isPressed) MaterialTheme.colors.primary else MaterialTheme.colors.secondary

Box(Modifier.padding(8.dp).clickable(interactionSource = interactionSource, indication = null) {
   text = "Hello, Desktop!"
}.background(color, shape = RoundedCornerShape(8.dp))) {
   Text(text, color = MaterialTheme.colors.onPrimary, modifier = Modifier.padding(16.dp, 8.dp), fontSize = 12.sp, fontWeight = FontWeight.Medium)
}

image

RafaelAthosPrime commented 4 months ago

I made some more tests with the software renderer, in a page of my application the FPS was 35 FPS and removing the clickables ripple didn't help, what really made the difference is after I add elevation = 0.dp of all the Card() component removing the shadow the FPS became around 58~60FPS

RafaelAthosPrime commented 4 months ago

I made some more tests with the software renderer, in a page of my application the FPS was 35 FPS and removing the clickables ripple didn't help, what really made the difference is after I add elevation = 0.dp of all the Card() component removing the shadow the FPS became around 58~60FPS

Button with shadow 20 FPS

Button(onClick = {
    text = "Hello, Desktop!"
}, modifier = Modifier.padding(8.dp)) {
    Text(text)
}

image

Button without shadow 63 FPS

Button(onClick = {
    text = "Hello, Desktop!"
}, modifier = Modifier.padding(8.dp), elevation = ButtonDefaults.elevation(0.dp,0.dp,0.dp,0.dp,0.dp)) {
    Text(text)
}

image

RafaelAthosPrime commented 4 months ago

Based on my first example the FPS drops on every new window opened, is there any way to improve that?

image

igordmn commented 4 months ago

Based on my first example the FPS drops on every new window opened, is there any way to improve that?

It may be because of the FPS counter itself. Try to hide it in background windows:

if (LocalWindowInfo.current.isWindowFocused) {
  FPSCounter()
}

When you add an FPS counter, it requests redrawing of the whole window each frame.

Him188 commented 2 months ago

My app is suffering from the same issue. It's a video player and one of the most important features is to display some moving text across the screen, hence I can not disable animations (and 60 FPS is very important here). The app is completely not usable now :(

Is there a workaround like somehow use GPU acceleration? The app currently uses 150% CPU with 0% GPU.

MatkovIvan commented 2 months ago

Is there a workaround like somehow use GPU acceleration?

Compose uses GPU acceleration by default. Software renderer is only used as a fallback (also might be set via parameters). If it's a question about video player, please ask the question in the issue tracker of the library that you use.

igordmn commented 2 months ago

Compose uses GPU acceleration by default. Software renderer is only used as a fallback

Though, it uses Software in VM's, on some old unsupported GPU's and in a case if something is broken in the system (system dll's/so's or drivers)

Him188 commented 2 months ago

Compose uses GPU acceleration by default

@MatkovIvan However, even in a minimal project, Compose is not using GPU on my machine. CPU 44% with GPU 0% when scrolling the list.

https://github.com/JetBrains/compose-multiplatform/assets/12100985/a359f2c8-6624-40f7-93d1-f0ce6f8869cf

MohamedRejeb commented 2 months ago

I'm having 0% GPU when running with IntelliJ as well, but with a packaged release distributable, the GPU acceleration is working fine @Him188

Him188 commented 2 months ago

I'm having 0% GPU when running with IntelliJ as well, but with a packaged release distributable, the GPU acceleration is working fine @Him188

Thanks @MohamedRejeb ! runReleaseDistributable worked for me, seeing up to 20% GPU usage