Skip to content

Commit

Permalink
Add iOS target to multiplatform benchmarks (#5176)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjBooms authored Dec 16, 2024
1 parent 93b2f1d commit ac1d6e7
Show file tree
Hide file tree
Showing 43 changed files with 801 additions and 175 deletions.
13 changes: 9 additions & 4 deletions benchmarks/multiplatform/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Compose Multiplatform benchmarks

## Run Desktop
- `./gradlew :run`
- `./gradlew :benchmarks:run`

## Run native on iOS
Open the project in Fleet or Android Studio with KMM plugin installed and
choose `iosApp` run configuration. Make sure that you build the app in `Release` configuration.
Alternatively you may open `iosApp/iosApp` project in XCode and run the app from there.

## Run native on MacOS
- `./gradlew runReleaseExecutableMacosArm64` (Works on Arm64 processors)
- `./gradlew runReleaseExecutableMacosX64` (Works on Intel processors)
- `./gradlew :benchmarks:runReleaseExecutableMacosArm64` (Works on Arm64 processors)
- `./gradlew :benchmarks:runReleaseExecutableMacosX64` (Works on Intel processors)

## Run in web browser:

Please run your browser with manual GC enabled before running the benchmark, like for Google Chrome:

`open -a Google\ Chrome --args --js-flags="--expose-gc"`

- `./gradlew wasmJsBrowserProductionRun` (you can see the results printed on the page itself)
- `./gradlew :benchmarks:wasmJsBrowserProductionRun` (you can see the results printed on the page itself)
98 changes: 98 additions & 0 deletions benchmarks/multiplatform/benchmarks/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack

plugins {
kotlin("multiplatform")
id("org.jetbrains.kotlin.plugin.compose")
id("org.jetbrains.compose")
}

version = "1.0-SNAPSHOT"

repositories {
mavenLocal()
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}

kotlin {
jvm("desktop")

listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "benchmarks"
isStatic = true
}
}

listOf(
macosX64(),
macosArm64()
).forEach { macosTarget ->
macosTarget.binaries {
executable {
entryPoint = "main"
}
}
}

@OptIn(ExperimentalWasmDsl::class)
wasmJs {
binaries.executable()
browser ()
}

sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.ui)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.runtime)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
}
}

val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.1")
}
}
}
}

compose.desktop {
application {
mainClass = "Main_desktopKt"
}
}

val runArguments: String? by project

// Handle runArguments property
gradle.taskGraph.whenReady {
tasks.named<JavaExec>("run") {
args(runArguments?.split(" ") ?: listOf<String>())
}
tasks.forEach { t ->
if ((t is Exec) && t.name.startsWith("runReleaseExecutableMacos")) {
t.args(runArguments?.split(" ") ?: listOf<String>())
}
}
tasks.named<KotlinWebpack>("wasmJsBrowserProductionRun") {
val args = runArguments?.split(" ")
?.mapIndexed { index, arg -> "arg$index=$arg" }
?.joinToString("&") ?: ""

devServerProperty = devServerProperty.get().copy(
open = "http://localhost:8080?$args"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.objcPtr
import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.ColorSpace
import org.jetbrains.skia.DirectContext
import org.jetbrains.skia.PixelGeometry
import org.jetbrains.skia.Surface
import org.jetbrains.skia.SurfaceColorFormat
import org.jetbrains.skia.SurfaceOrigin
import org.jetbrains.skia.SurfaceProps
import platform.Metal.MTLCreateSystemDefaultDevice
import platform.Metal.MTLPixelFormatBGRA8Unorm
import platform.Metal.MTLTextureDescriptor
import platform.Metal.MTLTextureType2D
import platform.Metal.MTLTextureUsageRenderTarget
import platform.Metal.MTLTextureUsageShaderRead
import platform.Metal.MTLTextureUsageShaderWrite
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

@OptIn(ExperimentalForeignApi::class)
fun graphicsContext() = object : GraphicsContext {
private val device = MTLCreateSystemDefaultDevice() ?: throw IllegalStateException("Can't create MTLDevice")
private val commandQueue = device.newCommandQueue() ?: throw IllegalStateException("Can't create MTLCommandQueue")
private val directContext = DirectContext.makeMetal(device.objcPtr(), commandQueue.objcPtr())
private var cachedSurface: Surface? = null

override fun surface(width: Int, height: Int): Surface {
val oldSurface = cachedSurface

if (oldSurface != null && oldSurface.width == width && oldSurface.height == height) {
return oldSurface
}

val descriptor = MTLTextureDescriptor()
descriptor.width = width.toULong()
descriptor.height = height.toULong()
descriptor.usage = MTLTextureUsageShaderRead or MTLTextureUsageShaderWrite or MTLTextureUsageRenderTarget
descriptor.textureType = MTLTextureType2D
descriptor.pixelFormat = MTLPixelFormatBGRA8Unorm
descriptor.mipmapLevelCount = 1UL

val texture = device.newTextureWithDescriptor(descriptor) ?: throw IllegalStateException("Can't create MTLTexture")

val renderTarget = BackendRenderTarget.makeMetal(width, height, texture.objcPtr())

return Surface.makeFromBackendRenderTarget(
directContext,
renderTarget,
SurfaceOrigin.TOP_LEFT,
SurfaceColorFormat.BGRA_8888,
ColorSpace.sRGB,
SurfaceProps(pixelGeometry = PixelGeometry.UNKNOWN)
).also {
cachedSurface = it
} ?: throw IllegalStateException("Can't create Surface")
}

override suspend fun awaitGPUCompletion() {
val commandBuffer = commandQueue.commandBuffer() ?: return

suspendCoroutine { continuation ->
commandBuffer.addCompletedHandler {
continuation.resume(Unit)
}

commandBuffer.commit()
}
}
}




Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import compose_benchmarks.generated.resources.Res
import compose_benchmarks.generated.resources.compose_multiplatform
import compose_benchmarks.benchmarks.generated.resources.Res
import compose_benchmarks.benchmarks.generated.resources.compose_multiplatform
import kotlinx.coroutines.delay
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource
Expand Down
11 changes: 11 additions & 0 deletions benchmarks/multiplatform/benchmarks/src/iosMain/kotlin/main.ios.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/

import kotlinx.coroutines.runBlocking

fun main(args : List<String>) {
Args.parseArgs(args.toTypedArray())
runBlocking { runBenchmarks(graphicsContext = graphicsContext()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
import kotlinx.coroutines.runBlocking

fun main(args : Array<String>) {
Args.parseArgs(args)
runBlocking { runBenchmarks(graphicsContext = graphicsContext()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<title>Benchmarks Compose + K/Wasm</title>
<script src="compose-benchmarks.js"></script>
<script src="benchmarks.js"></script>
<script type="application/javascript">
const log = console.log.bind(console)
console.log = (...args) => {
Expand Down
110 changes: 12 additions & 98 deletions benchmarks/multiplatform/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,101 +1,15 @@
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack

plugins {
kotlin("multiplatform")
id("org.jetbrains.kotlin.plugin.compose")
id("org.jetbrains.compose")
}

version = "1.0-SNAPSHOT"

repositories {
mavenLocal()
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}

kotlin {
jvm("desktop")
macosX64 {
binaries {
executable {
entryPoint = "main"
}
}
}
macosArm64 {
binaries {
executable {
entryPoint = "main"
}
}
}

wasmJs {
binaries.executable()
browser ()
}

sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.ui)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.runtime)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
}
}

val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
runtimeOnly("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.1")
}
}

val nativeMain by creating {
dependsOn(commonMain)
}
val macosMain by creating {
dependsOn(nativeMain)
}
val macosX64Main by getting {
dependsOn(macosMain)
}
val macosArm64Main by getting {
dependsOn(macosMain)
}
}
}

compose.desktop {
application {
mainClass = "Main_desktopKt"
}
}

val runArguments: String? by project

// Handle runArguments property
gradle.taskGraph.whenReady {
tasks.named<JavaExec>("run") {
args(runArguments?.split(" ") ?: listOf<String>())
}
tasks.forEach { t ->
if ((t is Exec) && t.name.startsWith("runReleaseExecutableMacos")) {
t.args(runArguments?.split(" ") ?: listOf<String>())
}
}
tasks.named<KotlinWebpack>("wasmJsBrowserProductionRun") {
val args = runArguments?.split(" ")
?.mapIndexed { index, arg -> "arg$index=$arg" }
?.joinToString("&") ?: ""

devServerProperty = devServerProperty.get().copy(
open = "http://localhost:8080?$args"
)
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
kotlin("multiplatform") apply false
kotlin("plugin.compose") apply false
id("org.jetbrains.compose") apply false
}

allprojects {
repositories {
google()
mavenCentral()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
2 changes: 0 additions & 2 deletions benchmarks/multiplatform/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@ compose.desktop.verbose=true
android.useAndroidX=true
runArguments=benchmarks= modes=
kotlin.js.webpack.major.version=4
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.experimental.uikit.enabled=true
org.jetbrains.compose.resources.multimodule.disable=true
3 changes: 3 additions & 0 deletions benchmarks/multiplatform/iosApp/Configuration/Config.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TEAM_ID=
BUNDLE_ID=org.jetbrains.benchmarks
APP_NAME=ComposeBenchmarks
Loading

0 comments on commit ac1d6e7

Please sign in to comment.