Unverified Commit ec704f7b authored by Igor Demin's avatar Igor Demin Committed by GitHub

Merge pull request #88 from JetBrains/fix_blocking_vsync

macOs, Windows. draw in a background thread
parents b0bcda7b b207ba16
......@@ -174,7 +174,7 @@ open class SkiaLayer(
@Suppress("LeakingThis")
private val fpsCounter = defaultFPSCounter(this)
open fun update(nanoTime: Long) {
internal fun update(nanoTime: Long) {
check(!isDisposed)
check(isEventDispatchThread())
......@@ -208,14 +208,21 @@ open class SkiaLayer(
}
}
open fun draw() {
internal fun prepareDrawContext(): Boolean {
check(!isDisposed)
contextHandler?.apply {
if (!initContext()) {
fallbackToNextApi()
return
return false
}
initCanvas()
}
return true
}
internal fun draw() {
check(!isDisposed)
contextHandler?.apply {
clearCanvas()
synchronized(pictureLock) {
val picture = picture
......@@ -248,6 +255,6 @@ open class SkiaLayer(
redrawer?.dispose()
contextHandler = createContextHandler(this, renderApi)
redrawer = platformOperations.createRedrawer(this, renderApi, properties)
repaint()
redrawer!!.redrawImmediately()
}
}
......@@ -55,7 +55,7 @@ internal object SkikoProperties {
when (hostOs) {
OS.Linux -> renderApiList = mutableListOf(GraphicsApi.OPENGL, GraphicsApi.SOFTWARE)
OS.MacOS -> renderApiList = mutableListOf(GraphicsApi.METAL, GraphicsApi.OPENGL, GraphicsApi.SOFTWARE)
OS.MacOS -> renderApiList = mutableListOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE)
OS.Windows -> renderApiList = mutableListOf(GraphicsApi.DIRECT3D, GraphicsApi.OPENGL, GraphicsApi.SOFTWARE)
}
renderApiList.remove(head)
......
......@@ -2,6 +2,7 @@ package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import org.jetbrains.skija.BackendRenderTarget
import org.jetbrains.skija.DirectContext
import org.jetbrains.skiko.FrameDispatcher
......@@ -14,13 +15,14 @@ internal class Direct3DRedrawer(
) : Redrawer {
private var isDisposed = false
private var disposeLock = Any()
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
update(System.nanoTime())
draw()
}
override fun dispose() {
override fun dispose() = synchronized(disposeLock) {
frameDispatcher.cancel()
isDisposed = true
}
......@@ -32,17 +34,27 @@ internal class Direct3DRedrawer(
override fun redrawImmediately() {
check(!isDisposed)
update(System.nanoTime())
draw()
// TODO now we wait until previous layer.draw is finished. it ends only on the next vsync.
// because of that we lose one frame on resize and can theoretically see very small white bars on the sides of the window
// to avoid this we should be able to draw in two modes: with vsync and without.
frameDispatcher.scheduleFrame()
}
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private fun draw() {
private suspend fun draw() {
if (layer.prepareDrawContext()) {
withContext(Dispatchers.IO) {
synchronized(disposeLock) {
if (!isDisposed) {
layer.draw()
}
}
}
}
}
fun makeContext(device: Long) = DirectContext(
makeDirectXContext(device)
......
......@@ -49,8 +49,10 @@ internal class LinuxOpenGLRedrawer(
}
private fun draw() {
if (layer.prepareDrawContext()) {
layer.draw()
}
}
companion object {
private val toRedraw = mutableSetOf<LinuxOpenGLRedrawer>()
......
package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.SkiaLayer
......@@ -11,7 +10,6 @@ import org.jetbrains.skiko.Task
import org.jetbrains.skiko.useDrawingSurfacePlatformInfo
import javax.swing.SwingUtilities.convertPoint
import javax.swing.SwingUtilities.getRootPane
import kotlin.system.measureNanoTime
// Current implementation is fragile (it works in all tested cases, but we can't test everything)
//
......@@ -31,9 +29,11 @@ internal class MacOsOpenGLRedrawer(
private val drawLayer = object : AWTGLLayer(containerLayerPtr, setNeedsDisplayOnBoundsChange = true) {
override fun draw() = synchronized(drawLock) {
if (!isDisposed) {
if (layer.prepareDrawContext()) {
layer.draw()
}
}
}
suspend fun display() = display(::setNeedsDisplay)
}
......
......@@ -2,6 +2,7 @@ package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import org.jetbrains.skija.BackendRenderTarget
import org.jetbrains.skija.DirectContext
import org.jetbrains.skiko.FrameDispatcher
......@@ -16,6 +17,7 @@ internal class MetalRedrawer(
private val properties: SkiaLayerProperties
) : Redrawer {
private var isDisposed = false
private var disposeLock = Any()
private val device = layer.backedLayer.useDrawingSurfacePlatformInfo(::createMetalDevice)
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
......@@ -23,7 +25,7 @@ internal class MetalRedrawer(
draw()
}
override fun dispose() {
override fun dispose() = synchronized(disposeLock) {
frameDispatcher.cancel()
disposeDevice(device)
isDisposed = true
......@@ -36,17 +38,38 @@ internal class MetalRedrawer(
override fun redrawImmediately() {
check(!isDisposed)
update(System.nanoTime())
draw()
// TODO now we wait until previous layer.draw is finished. it ends only on the next vsync.
// because of that we lose one frame on resize and can theoretically see very small white bars on the sides of the window
// to avoid this we should be able to draw in two modes: with vsync and without.
frameDispatcher.scheduleFrame()
}
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private fun draw() {
private suspend fun draw() {
if (layer.prepareDrawContext()) {
// 2,3 GHz 8-Core Intel Core i9
//
// Test1. 8 windows, multiple clocks, 800x600
//
// Executors.newSingleThreadExecutor().asCoroutineDispatcher(): 20 FPS, 130% CPU
// Dispatchers.IO: 58 FPS, 460% CPU
//
// Test2. 60 windows, single clock, 800x600
//
// Executors.newSingleThreadExecutor().asCoroutineDispatcher(): 50 FPS, 150% CPU
// Dispatchers.IO: 50 FPS, 200% CPU
withContext(Dispatchers.IO) {
synchronized(disposeLock) {
if (!isDisposed) {
layer.draw()
}
}
}
}
}
override fun syncSize() {
val rootPane = getRootPane(layer)
......
......@@ -11,8 +11,10 @@ internal class SoftwareRedrawer(
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
layer.update(System.nanoTime())
if (layer.prepareDrawContext()) {
layer.draw()
}
}
override fun dispose() {
frameDispatcher.cancel()
......@@ -24,6 +26,8 @@ internal class SoftwareRedrawer(
override fun redrawImmediately() {
layer.update(System.nanoTime())
if (layer.prepareDrawContext()) {
layer.draw()
}
}
}
\ No newline at end of file
......@@ -53,8 +53,10 @@ internal class WindowsOpenGLRedrawer(
}
private fun draw() {
if (layer.prepareDrawContext()) {
layer.draw()
}
}
private fun makeCurrent() = makeCurrent(device, context)
private fun swapBuffers() = swapBuffers(device)
......
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