Unverified Commit ed693b57 authored by Nikolay Igotti's avatar Nikolay Igotti Committed by GitHub

Separate AWT from JVM code. (#427)

parent d389d095
......@@ -29,7 +29,7 @@ jobs:
- shell: bash
run: |
cd $GITHUB_WORKSPACE/skiko
./gradlew --stacktrace --info -Pskiko.native.enabled=true jvmTest
./gradlew --stacktrace --info -Pskiko.native.enabled=true awtTest
./gradlew --stacktrace --info -Pskiko.native.enabled=true macosX64Test
./gradlew --stacktrace --info -Pskiko.native.enabled=true publishToMavenLocal
- uses: actions/upload-artifact@v2
......@@ -87,7 +87,7 @@ jobs:
sudo apt-get install ninja-build fontconfig libfontconfig1-dev libglu1-mesa-dev libxrandr-dev zip xvfb -y
sudo Xvfb :0 -screen 0 1280x720x24 &
export DISPLAY=:0
./gradlew --stacktrace --info -Pskiko.native.enabled=true linuxX64Test jvmTest
./gradlew --stacktrace --info -Pskiko.native.enabled=true linuxX64Test awtTest
./gradlew --stacktrace --info publishToMavenLocal
- uses: actions/upload-artifact@v2
with:
......@@ -145,7 +145,7 @@ jobs:
- shell: bash
run: |
cd ./skiko
./gradlew --stacktrace --info jvmTest
./gradlew --stacktrace --info awtTest
./gradlew --stacktrace --info publishToMavenLocal
- uses: actions/upload-artifact@v2
with:
......
......@@ -110,6 +110,6 @@ Add `-Dskiko.test.ui.enabled=true` to enable UI tests (integration tests, which
For example, if we want to include UI tests when we test JVM target, call this:
```
./gradlew jvmTest -Dskiko.test.ui.enabled=true
./gradlew awtTest -Dskiko.test.ui.enabled=true
```
Don't run any background tasks, click mouse, or press keys during the tests. Otherwise, they probably fail.
......@@ -110,10 +110,13 @@ kotlin {
resources.srcDirs(unzipTask.map { it.destinationDir })
}
val macosMain by creating {
val darwinMain by creating {
dependsOn(nativeMain)
}
val macosMain by creating {
dependsOn(darwinMain)
}
if (hostOs == "macos") {
val archTargetMain = when (host) {
......@@ -132,7 +135,7 @@ kotlin {
else -> throw GradleException("Host OS is not supported")
}
val iosMain by creating {
dependsOn(nativeMain)
dependsOn(darwinMain)
}
val iosX64Main by getting {
dependsOn(iosMain)
......
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="jvmTest" type="GradleRunConfiguration" factoryName="Gradle">
<configuration default="false" name="awtTest" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="jvmTest -Dskiko.test.ui.enabled=true -Dskiko.test.ui.renderApi=all" />
<option name="scriptParameters" value="awtTest -Dskiko.test.ui.enabled=true -Dskiko.test.ui.renderApi=all" />
<option name="taskDescriptions">
<list />
</option>
......
......@@ -214,12 +214,20 @@ val Project.supportAndroid: Boolean
get() = findProperty("skiko.android.enabled") == "true"
kotlin {
jvm {
jvm("awt") {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
}
if (supportAndroid) {
jvm("android") {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
}
}
if (supportWasm) {
js(IR) {
browser() {
......@@ -245,7 +253,7 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
implementation(kotlin("stdlib"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
}
}
......@@ -259,18 +267,37 @@ kotlin {
/*
val androidMain by getting {
dependencies {
compileOnly(files(androidJar()))
}
} */
val jvmMain by getting {
val jvmMain by creating {
dependsOn(commonMain)
dependencies {
implementation(kotlin("stdlib"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:$coroutinesVersion")
}
}
val awtMain by getting {
dependsOn(jvmMain)
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:$coroutinesVersion")
}
}
val jvmTest by getting {
if (supportAndroid) {
val androidMain by getting {
dependsOn(jvmMain)
dependencies {
compileOnly(files(androidJar()))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
}
}
}
val jvmTest by creating {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
implementation(kotlin("test-junit"))
......@@ -278,6 +305,16 @@ kotlin {
}
}
val awtTest by getting {
dependsOn(jvmTest)
}
if (supportAndroid) {
val androidTest by getting {
dependsOn(jvmTest)
}
}
if (supportWasm || supportNative) {
val nativeJsMain by creating {
dependsOn(commonMain)
......@@ -838,7 +875,7 @@ fun generateVersionTask(targetOs: OS, targetArch: Arch) = project.tasks.register
val skikoJvmJar by project.tasks.registering(Jar::class) {
archiveBaseName.set("skiko-jvm")
from(kotlin.jvm().compilations["main"].output.allOutputs)
from(kotlin.jvm("awt").compilations["main"].output.allOutputs)
}
fun maybeSignTask(targetOs: OS, targetArch: Arch, linkJvmBindings: Provider<LinkSkikoTask>) =
......@@ -1037,7 +1074,7 @@ publishing {
afterEvaluate {
artifact(entry.value.map { it.archiveFile.get() })
var jvmSourcesArtifact: Any? = null
kotlin.jvm().mavenPublication {
kotlin.jvm("awt").mavenPublication {
jvmSourcesArtifact = artifacts.find { it.classifier == "sources" }
}
if (jvmSourcesArtifact == null) {
......
package org.jetbrains.skiko
import org.jetbrains.skiko.redrawer.*
import javax.swing.UIManager
actual fun setSystemLookAndFeel() = UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
internal actual fun makeDefaultRenderFactory(): RenderFactory {
return object : RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
properties: SkiaLayerProperties
): Redrawer = when (hostOs) {
OS.MacOS -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, properties)
else -> MetalRedrawer(layer, properties)
}
OS.Windows -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, properties)
GraphicsApi.SOFTWARE_FAST -> WindowsSoftwareRedrawer(layer, properties)
GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, properties)
else -> Direct3DRedrawer(layer, properties)
}
OS.Linux -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, properties)
GraphicsApi.SOFTWARE_FAST -> LinuxSoftwareRedrawer(layer, properties)
else -> LinuxOpenGLRedrawer(layer, properties)
}
OS.Android -> TODO()
OS.JS, OS.Ios -> {
TODO("Commonize me")
}
}
}
}
package org.jetbrains.skiko
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.awt.Canvas
import java.awt.Component
import java.awt.Graphics
import java.awt.event.InputMethodEvent
import javax.accessibility.Accessible
import javax.accessibility.AccessibleContext
import kotlin.time.ExperimentalTime
internal open class HardwareLayer(
externalAccessibleFactory: ((Component) -> Accessible)? = null
......@@ -82,3 +87,50 @@ internal open class HardwareLayer(
return res ?: super.getAccessibleContext()
}
}
/**
* HardwareLayer should not dispose native resources while [scope] is active.
*
* So wait for scope cancellation in dispose method:
* ```
* runBlocking {
* frameJob.cancelAndJoin()
* }
* ```
*
* Can be accessed from multiple threads.
*/
@OptIn(ExperimentalTime::class)
@Suppress("UNUSED_PARAMETER")
internal fun FrameLimiter(
scope: CoroutineScope,
component: HardwareLayer,
onNewFrameLimit: (frameLimit: Double) -> Unit = {}
): FrameLimiter {
val state = object {
@Volatile
var frameLimit = MinMainstreamMonitorRefreshRate
}
val frames = Channel<Unit>(Channel.CONFLATED)
frames.trySend(Unit)
scope.launch {
while (true) {
frames.receive()
// TODO will lockLinuxDrawingSurface inside getDisplayRefreshRate can cause draw lock too?
// it takes 2ms on my machine on Linux (0.01ms on macOs, 0.1ms on Windows)
state.frameLimit = component.getDisplayRefreshRate()
onNewFrameLimit(state.frameLimit)
delay(1000)
}
}
return FrameLimiter(
scope,
frameMillis = {
frames.trySend(Unit)
(1000 / state.frameLimit).toLong()
}
)
}
\ No newline at end of file
......@@ -74,13 +74,13 @@ internal val platformOperations: PlatformOperations by lazy {
override fun isFullscreen(component: Component): Boolean {
val window = SwingUtilities.getRoot(component) as Window
val device = window.graphicsConfiguration.device
return device.getFullScreenWindow() == window
return device.fullScreenWindow == window
}
override fun setFullscreen(component: Component, value: Boolean) {
val window = SwingUtilities.getRoot(component) as Window
val device = window.graphicsConfiguration.device
device.setFullScreenWindow(if (value) window else null)
device.fullScreenWindow = if (value) window else null
}
override fun disableTitleBar(component: Component, headerHeight: Float) {
......@@ -99,13 +99,13 @@ internal val platformOperations: PlatformOperations by lazy {
override fun isFullscreen(component: Component): Boolean {
val window = SwingUtilities.getRoot(component) as Window
val device = window.graphicsConfiguration.device
return device.getFullScreenWindow() == window
return device.fullScreenWindow == window
}
override fun setFullscreen(component: Component, value: Boolean) {
val window = SwingUtilities.getRoot(component) as Window
val device = window.graphicsConfiguration.device
device.setFullScreenWindow(if (value) window else null)
device.fullScreenWindow = if (value) window else null
}
override fun disableTitleBar(component: Component, headerHeight: Float) {
......
package org.jetbrains.skiko
import org.jetbrains.skia.*
import org.jetbrains.skiko.context.ContextHandler
import org.jetbrains.skiko.redrawer.Redrawer
import java.awt.Color
import java.awt.Component
......
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainCoroutineDispatcher
import kotlinx.coroutines.swing.Swing
actual val MainUIDispatcher: MainCoroutineDispatcher
get() = Dispatchers.Swing
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.OpenGLContextHandler
......@@ -97,7 +96,7 @@ internal class LinuxOpenGLRedrawer(
.filterNot(LinuxOpenGLRedrawer::isDisposed)
.filter { it.layer.isShowing }
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
toRedrawCopy.addAll(toRedraw)
toRedraw.clear()
......
......@@ -6,7 +6,6 @@ import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.OpenGLContextHandler
import org.jetbrains.skiko.context.SoftwareContextHandler
internal class WindowsOpenGLRedrawer(
private val layer: SkiaLayer,
......
package org.jetbrains.skiko
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
......@@ -9,53 +8,6 @@ import kotlin.time.ExperimentalTime
private const val NanosecondsPerMillisecond = 1_000_000L
/**
* HardwareLayer should not dispose native resources while [scope] is active.
*
* So wait for scope cancellation in dispose method:
* ```
* runBlocking {
* frameJob.cancelAndJoin()
* }
* ```
*
* Can be accessed from multiple threads.
*/
@OptIn(ExperimentalTime::class)
@Suppress("UNUSED_PARAMETER")
internal fun FrameLimiter(
scope: CoroutineScope,
component: HardwareLayer,
onNewFrameLimit: (frameLimit: Double) -> Unit = {}
): FrameLimiter {
val state = object {
@Volatile
var frameLimit = MinMainstreamMonitorRefreshRate
}
val frames = Channel<Unit>(Channel.CONFLATED)
frames.trySend(Unit)
scope.launch {
while (true) {
frames.receive()
// TODO will lockLinuxDrawingSurface inside getDisplayRefreshRate can cause draw lock too?
// it takes 2ms on my machine on Linux (0.01ms on macOs, 0.1ms on Windows)
state.frameLimit = component.getDisplayRefreshRate()
onNewFrameLimit(state.frameLimit)
delay(1000)
}
}
return FrameLimiter(
scope,
frameMillis = {
frames.trySend(Unit)
(1000 / state.frameLimit).toLong()
}
)
}
/**
* Limit the duration of the frames (to avoid high CPU usage) to [frameMillis].
* The actual delay depends on the precision of the system timer
......
......@@ -19,7 +19,7 @@ internal fun isVideoCardSupported(renderApi: GraphicsApi): Boolean {
if (it == null) { return false }
}
adaptersList.forEach {
if (adapter.startsWith(it)) {
if (adapter?.startsWith(it) == true) {
return false
}
}
......
......@@ -23,7 +23,7 @@ class OpenGLApi private constructor() {
external fun glEnable(flag: Int)
external fun glBindTexture(target: Int, texture: Int)
external fun glGetIntegerv(pname: Int): Int
external fun glGetString(value: Int): String
external fun glGetString(value: Int): String?
companion object {
init {
......
......@@ -10,32 +10,4 @@ internal interface RenderFactory {
}
}
private fun makeDefaultRenderFactory(): RenderFactory {
return object : RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
properties: SkiaLayerProperties
): Redrawer = when (hostOs) {
OS.MacOS -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, properties)
else -> MetalRedrawer(layer, properties)
}
OS.Windows -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, properties)
GraphicsApi.SOFTWARE_FAST -> WindowsSoftwareRedrawer(layer, properties)
GraphicsApi.OPENGL -> WindowsOpenGLRedrawer(layer, properties)
else -> Direct3DRedrawer(layer, properties)
}
OS.Linux -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT -> SoftwareRedrawer(layer, properties)
GraphicsApi.SOFTWARE_FAST -> LinuxSoftwareRedrawer(layer, properties)
else -> LinuxOpenGLRedrawer(layer, properties)
}
OS.Android -> TODO()
OS.JS, OS.Ios -> {
TODO("Commonize me")
}
}
}
}
internal expect fun makeDefaultRenderFactory(): RenderFactory
package org.jetbrains.skiko
import javax.swing.UIManager
expect fun setSystemLookAndFeel()
internal object Setup {
fun init(
......@@ -22,7 +22,7 @@ internal object Setup {
try {
if (globalLAF) {
// Setup menu look and feel.
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
setSystemLookAndFeel()
}
if (useScreenMenuBar) {
System.setProperty("apple.laf.useScreenMenuBar", "true")
......
......@@ -2,7 +2,6 @@ package org.jetbrains.skiko.redrawer
import org.jetbrains.skia.Surface
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.FrameLimiter
import org.jetbrains.skiko.RenderException
......@@ -19,7 +18,7 @@ internal abstract class AbstractDirectSoftwareRedrawer(
private val frameJob = Job()
private val frameLimiter = FrameLimiter(CoroutineScope(Dispatchers.IO + frameJob), layer.backedLayer)
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
if (properties.isVsyncEnabled && properties.isVsyncFramelimitFallbackEnabled) {
frameLimiter.awaitNextFrame()
}
......
......@@ -20,7 +20,7 @@ internal class AngleRedrawer(
private var isDisposed = false
private var device: Long = 0
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
if (layer.isShowing) {
update(System.nanoTime())
draw()
......
......@@ -25,7 +25,7 @@ internal class Direct3DRedrawer(
}
}
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
if (layer.isShowing) {
update(System.nanoTime())
draw()
......
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.FrameLimiter
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.context.MetalContextHandler
import org.jetbrains.skiko.context.SoftwareContextHandler
expect val MainUIDispatcher: MainCoroutineDispatcher
internal class SoftwareRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
......@@ -19,7 +19,7 @@ internal class SoftwareRedrawer(
private val frameJob = Job()
private val frameLimiter = FrameLimiter(CoroutineScope(Dispatchers.IO + frameJob), layer.backedLayer)
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
if (properties.isVsyncEnabled && properties.isVsyncFramelimitFallbackEnabled) {
frameLimiter.awaitNextFrame()
}
......
......@@ -5,8 +5,8 @@ import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.RenderException
internal class WindowsSoftwareRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
layer: SkiaLayer,
properties: SkiaLayerProperties
) : AbstractDirectSoftwareRedrawer(layer, properties) {
init {
......
......@@ -65,13 +65,13 @@ internal class MacOsMetalRedrawer(
override fun syncSize() {
syncContentScale()
val osView = skiaLayer.nsView!!
val (w, h) = osView.frame.useContents {
val osFrame = skiaLayer.nsView.frame
val (w, h) = osFrame.useContents {
size.width to size.height
}
CATransaction.begin()
CATransaction.setDisableActions(true)
metalLayer.frame = osView.frame
metalLayer.frame = osFrame
metalLayer.init(skiaLayer, contextHandler, device)
metalLayer.drawableSize = CGSizeMake(w * metalLayer.contentsScale, h * metalLayer.contentsScale)
CATransaction.commit()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment