compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

Desktop software renderer bad performance

Open RafaelAthosPrime opened this issue 1 year ago • 15 comments

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:

  • Desktop

Versions

  • Kotlin version: 1.9.22
  • Compose Multiplatform version: 1.5.12
  • OS version(s) (required for Desktop and iOS issues): Windows 10
  • OS architecture (x86 or arm64): x64
  • JDK (for desktop issues): corretto-17.0.9
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)
}

RafaelAthosPrime avatar Jan 29 '24 22:01 RafaelAthosPrime

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

MohamedRejeb avatar Feb 08 '24 16:02 MohamedRejeb

Any news? Same problem with the version 1.6.0

RafaelAthosPrime avatar Mar 01 '24 03:03 RafaelAthosPrime

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

igordmn avatar Mar 12 '24 01:03 igordmn

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.

igordmn avatar Mar 12 '24 01:03 igordmn

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 avatar Mar 13 '24 14:03 RafaelAthosPrime

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 avatar Mar 15 '24 03:03 RafaelAthosPrime

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 avatar Mar 15 '24 04:03 RafaelAthosPrime

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

image

RafaelAthosPrime avatar Mar 17 '24 05:03 RafaelAthosPrime

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.

igordmn avatar Mar 17 '24 23:03 igordmn

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.

Him188 avatar May 10 '24 23:05 Him188

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.

MatkovIvan avatar May 13 '24 10:05 MatkovIvan

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)

igordmn avatar May 13 '24 10:05 igordmn

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.

  • Template project created by IntelliJ, with a simple lazy column
  • JDK corretto-17.0.8 aarch64
  • MacBook Pro, M2 Max chip
  • macOS 14.4.1 (23E224)
  • renderApi: METAL

    println("renderApi: " + this.window.renderApi)

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

Him188 avatar May 13 '24 19:05 Him188

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

MohamedRejeb avatar May 14 '24 05:05 MohamedRejeb

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

Him188 avatar May 14 '24 17:05 Him188