From 2d27204f534047958b60c79fb603020a266a6b02 Mon Sep 17 00:00:00 2001 From: Sunny Chung Date: Sat, 16 Nov 2024 08:09:20 +0800 Subject: [PATCH] add rendering API option to switch between hardware acceleration or software rendering --- CHANGELOG.md | 1 + .../multiplatform/hellohttp/AppContext.kt | 2 + .../multiplatform/hellohttp/Main.kt | 15 ++++ .../hellohttp/model/RenderingApi.kt | 18 ++++ .../hellohttp/model/UserPreference.kt | 1 + .../multiplatform/hellohttp/ux/AppText.kt | 7 +- .../hellohttp/ux/SettingDialogView.kt | 82 ++++++++++++++----- .../test/bigtext/BigTextImplBenchmarkTest.kt | 2 + 8 files changed, 106 insertions(+), 22 deletions(-) create mode 100644 src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/RenderingApi.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index f6a6b749..caa120ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - Certificates in P7B (PKCS#7) format can now be imported - Private keys in PEM or PKCS#1 formats can now be imported, and does not limit to RSA keys anymore. - PKCS#12 (known as p12) and PFX files can now be imported as client certificates +- [Experimental] Options to change rendering APIs to work around display issues on some Windows devices. Hardware acceleration can be disabled via this setting. ### Changed - The main monospace font has been changed to Pitagon Sans Mono and unified among all platforms diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/AppContext.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/AppContext.kt index 9ba2e21f..061976ae 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/AppContext.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/AppContext.kt @@ -66,6 +66,8 @@ class AppContext { lateinit var dataDir: File + lateinit var renderingApi: String + companion object { var instance = AppContext() diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/Main.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/Main.kt index d57cd4c3..bed8d5ca 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/Main.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/Main.kt @@ -25,7 +25,9 @@ import com.jayway.jsonpath.spi.mapper.MappingProvider import com.sunnychung.application.multiplatform.hellohttp.document.OperationalDI import com.sunnychung.application.multiplatform.hellohttp.document.UserPreferenceDI import com.sunnychung.application.multiplatform.hellohttp.error.MultipleProcessError +import com.sunnychung.application.multiplatform.hellohttp.model.UserPreference import com.sunnychung.application.multiplatform.hellohttp.model.Version +import com.sunnychung.application.multiplatform.hellohttp.model.getApplicableRenderingApiList import com.sunnychung.application.multiplatform.hellohttp.platform.LinuxOS import com.sunnychung.application.multiplatform.hellohttp.platform.MacOS import com.sunnychung.application.multiplatform.hellohttp.platform.WindowsOS @@ -51,6 +53,8 @@ import kotlin.system.exitProcess fun main() { System.setProperty("apple.awt.application.appearance", "system") +// System.setProperty("skiko.renderApi", "OPENGL") // IllegalArgumentException: "MacOS does not support OPENGL rendering API." +// System.setProperty("skiko.renderApi", "SOFTWARE") val appDir = AppDirsFactory.getInstance().getUserDataDir("Hello HTTP", null, null) println("appDir = $appDir") AppContext.dataDir = File(appDir) @@ -79,6 +83,7 @@ fun main() { var dataVersion: Version? = null var appVersion: Version? = null + var userPreference: UserPreference? = null coroutineScope { withContext(Dispatchers.IO) { @@ -88,6 +93,8 @@ fun main() { dataVersion = AppContext.OperationalRepository.read(OperationalDI())!!.data.appVersion.let { Version(it) } appVersion = AppContext.MetadataManager.version.let { Version(it) } + + userPreference = AppContext.UserPreferenceRepository.read(UserPreferenceDI())!!.preference } launch { @@ -96,6 +103,12 @@ fun main() { } } + val applicableRenderingApis = getApplicableRenderingApiList(currentOS()).toSet() + userPreference!!.preferredRenderingApi_Experimental?.takeIf { it in applicableRenderingApis }?.value?.let { + System.setProperty("skiko.renderApi", it) + println("Set skiko.renderApi = $it") + } + val prepareCounter = AtomicInteger(0) application { @@ -150,6 +163,8 @@ fun main() { icon = painterResource("image/appicon.svg"), state = rememberWindowState(width = 1024.dp, height = 560.dp) ) { + AppContext.instance.renderingApi = this.window.renderApi.name + with(LocalDensity.current) { window.minimumSize = if (isMacOs()) { Dimension(800, 450) diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/RenderingApi.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/RenderingApi.kt new file mode 100644 index 00000000..b53bd6df --- /dev/null +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/RenderingApi.kt @@ -0,0 +1,18 @@ +package com.sunnychung.application.multiplatform.hellohttp.model + +import com.sunnychung.application.multiplatform.hellohttp.platform.LinuxOS +import com.sunnychung.application.multiplatform.hellohttp.platform.MacOS +import com.sunnychung.application.multiplatform.hellohttp.platform.OS +import com.sunnychung.application.multiplatform.hellohttp.platform.WindowsOS + +enum class RenderingApi(val value: String?) { + Default(null), OpenGL("OPENGL"), Software("SOFTWARE") +} + +fun getApplicableRenderingApiList(os: OS): List { + return when (os) { + MacOS -> RenderingApi.values().filter { it != RenderingApi.OpenGL } + LinuxOS -> RenderingApi.values().filter { it != RenderingApi.OpenGL } + WindowsOS -> RenderingApi.values().toList() + } +} diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/UserPreference.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/UserPreference.kt index 2630a6cf..57bae9d8 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/UserPreference.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/model/UserPreference.kt @@ -10,6 +10,7 @@ val DEFAULT_BACKUP_RETENTION_DAYS = 15 data class UserPreference( var colourTheme: ColourTheme, var backupRetentionDays: Int? = null, + var preferredRenderingApi_Experimental: RenderingApi? = null, ) enum class ColourTheme { diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/AppText.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/AppText.kt index 77c077b1..30ef945d 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/AppText.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/AppText.kt @@ -22,13 +22,14 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.TextUnit +import com.sunnychung.application.multiplatform.hellohttp.util.annotatedString import com.sunnychung.application.multiplatform.hellohttp.util.log import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalColor import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalFont @Composable fun AppText( - text: String, + text: CharSequence, modifier: Modifier = Modifier, isDisableWordWrap: Boolean = false, color: Color = LocalColor.current.text, @@ -57,13 +58,13 @@ fun AppText( var isHover by remember { mutableStateOf(false) } val textToUse = if (isDisableWordWrap) { - text.replace(' ', '\u00A0') // disable breaking by words + text.replace(" ".toRegex(), "\u00A0") // disable breaking by words } else { text } Text( - text = textToUse, + text = textToUse.annotatedString(), modifier = modifier .run { @OptIn(ExperimentalComposeUiApi::class) diff --git a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SettingDialogView.kt b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SettingDialogView.kt index a6d95d07..b0a92f0e 100644 --- a/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SettingDialogView.kt +++ b/src/jvmMain/kotlin/com/sunnychung/application/multiplatform/hellohttp/ux/SettingDialogView.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -25,6 +26,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import com.darkrockstudios.libraries.mpfilepicker.DirectoryPicker import com.sunnychung.application.multiplatform.hellohttp.AppContext @@ -40,6 +44,9 @@ import com.sunnychung.application.multiplatform.hellohttp.importer.PostmanV2Json import com.sunnychung.application.multiplatform.hellohttp.importer.PostmanV2ZipImporter import com.sunnychung.application.multiplatform.hellohttp.model.ColourTheme import com.sunnychung.application.multiplatform.hellohttp.model.DEFAULT_BACKUP_RETENTION_DAYS +import com.sunnychung.application.multiplatform.hellohttp.model.RenderingApi +import com.sunnychung.application.multiplatform.hellohttp.model.getApplicableRenderingApiList +import com.sunnychung.application.multiplatform.hellohttp.platform.currentOS import com.sunnychung.application.multiplatform.hellohttp.util.log import com.sunnychung.application.multiplatform.hellohttp.ux.local.LocalColor import com.sunnychung.application.multiplatform.hellohttp.ux.viewmodel.rememberFileDialogState @@ -80,7 +87,7 @@ private enum class SettingTab { private val COLUMN_HEADER_WIDTH = 140.dp @Composable -private fun Section(title: String, content: @Composable ColumnScope.() -> Unit) { +private fun Section(title: CharSequence, content: @Composable ColumnScope.() -> Unit) { val colors = LocalColor.current Column(modifier = Modifier.padding(bottom = 12.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -336,26 +343,63 @@ private fun DataTab(modifier: Modifier = Modifier, closeDialog: () -> Unit) { fun AppearanceTab() { val currentColourTheme by AppContext.UserPreferenceViewModel.colourTheme.collectAsState() + val userPreferenceRepository = AppContext.UserPreferenceRepository + userPreferenceRepository.subscribeUpdates().collectAsState(null).value + val userPreference = runBlocking { // TODO don't use runBlocking + userPreferenceRepository.read(UserPreferenceDI())!!.preference + } + Column { - Row(verticalAlignment = Alignment.CenterVertically) { - AppText(text = "Colour Theme", modifier = Modifier.width(COLUMN_HEADER_WIDTH)) - DropDownView( - selectedItem = DropDownValue(currentColourTheme.name), - items = ColourTheme.values().map { DropDownValue(it.name) }, - onClickItem = { - val newColourTheme = ColourTheme.valueOf(it.displayText) - AppContext.UserPreferenceViewModel.setColorTheme(newColourTheme) - - runBlocking { - val userPreferenceRepository = AppContext.UserPreferenceRepository - val userPreference = userPreferenceRepository.read(UserPreferenceDI())!!.preference - userPreference.colourTheme = newColourTheme - userPreferenceRepository.notifyUpdated(UserPreferenceDI()) - } + Section("Theme") { + Row(verticalAlignment = Alignment.CenterVertically) { + AppText(text = "Colour Theme", modifier = Modifier.width(COLUMN_HEADER_WIDTH)) + DropDownView( + selectedItem = DropDownValue(currentColourTheme.name), + items = ColourTheme.values().map { DropDownValue(it.name) }, + onClickItem = { + val newColourTheme = ColourTheme.valueOf(it.displayText) + AppContext.UserPreferenceViewModel.setColorTheme(newColourTheme) - true - }, - ) + runBlocking { + val userPreferenceRepository = AppContext.UserPreferenceRepository + val userPreference = userPreferenceRepository.read(UserPreferenceDI())!!.preference + userPreference.colourTheme = newColourTheme + userPreferenceRepository.notifyUpdated(UserPreferenceDI()) + } + + true + }, + ) + } + } + + Spacer(modifier = Modifier.height(12.dp)) + + Section(buildAnnotatedString { + append("Experimental ") + withStyle(SpanStyle(color = LocalColor.current.warning)) { + append("(Warning: Changing may cause something VERY BAD!)") + } + }) { + Column { + Row(verticalAlignment = Alignment.CenterVertically) { + AppText(text = "Preferred Rendering (Requires restarting the app to change) (Current: ${AppContext.instance.renderingApi})", modifier = Modifier.width(COLUMN_HEADER_WIDTH)) + DropDownView( + selectedItem = run { + val item = userPreference.preferredRenderingApi_Experimental ?: RenderingApi.Default + DropDownKeyValue(item, item.name) + }, + items = getApplicableRenderingApiList(currentOS()).map { + DropDownKeyValue(it, it.name) + }, + onClickItem = { + userPreference.preferredRenderingApi_Experimental = it.key.takeIf { it != RenderingApi.Default } + userPreferenceRepository.notifyUpdated(UserPreferenceDI()) + true + }, + ) + } + } } } } diff --git a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/bigtext/BigTextImplBenchmarkTest.kt b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/bigtext/BigTextImplBenchmarkTest.kt index 02b6d8b1..40519ca3 100644 --- a/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/bigtext/BigTextImplBenchmarkTest.kt +++ b/src/jvmTest/kotlin/com/sunnychung/application/multiplatform/hellohttp/test/bigtext/BigTextImplBenchmarkTest.kt @@ -8,6 +8,7 @@ import com.sunnychung.application.multiplatform.hellohttp.util.JvmLogger import com.sunnychung.application.multiplatform.hellohttp.ux.bigtext.BigTextImpl import com.sunnychung.lib.multiplatform.kdatetime.KDuration import com.sunnychung.lib.multiplatform.kdatetime.KInstant +import org.junit.jupiter.api.Disabled import kotlin.random.Random import kotlin.test.Test @@ -16,6 +17,7 @@ private val log = Logger(object : MutableLoggerConfig { override var minSeverity: Severity = Severity.Info }, tag = "BigTextImplBenchmarkTest") +@Disabled class BigTextImplBenchmarkTest { private fun chunkSizes() = listOf(64, 1024, 64 * 1024, 256 * 1024, 1024 * 1024, 2 * 1024 * 1024, 4 * 1024 * 1024)