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( ...@@ -174,7 +174,7 @@ open class SkiaLayer(
@Suppress("LeakingThis") @Suppress("LeakingThis")
private val fpsCounter = defaultFPSCounter(this) private val fpsCounter = defaultFPSCounter(this)
open fun update(nanoTime: Long) { internal fun update(nanoTime: Long) {
check(!isDisposed) check(!isDisposed)
check(isEventDispatchThread()) check(isEventDispatchThread())
...@@ -208,14 +208,21 @@ open class SkiaLayer( ...@@ -208,14 +208,21 @@ open class SkiaLayer(
} }
} }
open fun draw() { internal fun prepareDrawContext(): Boolean {
check(!isDisposed) check(!isDisposed)
contextHandler?.apply { contextHandler?.apply {
if (!initContext()) { if (!initContext()) {
fallbackToNextApi() fallbackToNextApi()
return return false
} }
initCanvas() initCanvas()
}
return true
}
internal fun draw() {
check(!isDisposed)
contextHandler?.apply {
clearCanvas() clearCanvas()
synchronized(pictureLock) { synchronized(pictureLock) {
val picture = picture val picture = picture
...@@ -248,6 +255,6 @@ open class SkiaLayer( ...@@ -248,6 +255,6 @@ open class SkiaLayer(
redrawer?.dispose() redrawer?.dispose()
contextHandler = createContextHandler(this, renderApi) contextHandler = createContextHandler(this, renderApi)
redrawer = platformOperations.createRedrawer(this, renderApi, properties) redrawer = platformOperations.createRedrawer(this, renderApi, properties)
repaint() redrawer!!.redrawImmediately()
} }
} }
...@@ -55,7 +55,7 @@ internal object SkikoProperties { ...@@ -55,7 +55,7 @@ internal object SkikoProperties {
when (hostOs) { when (hostOs) {
OS.Linux -> renderApiList = mutableListOf(GraphicsApi.OPENGL, GraphicsApi.SOFTWARE) 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) OS.Windows -> renderApiList = mutableListOf(GraphicsApi.DIRECT3D, GraphicsApi.OPENGL, GraphicsApi.SOFTWARE)
} }
renderApiList.remove(head) renderApiList.remove(head)
......
...@@ -2,6 +2,7 @@ package org.jetbrains.skiko.redrawer ...@@ -2,6 +2,7 @@ package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import org.jetbrains.skija.BackendRenderTarget import org.jetbrains.skija.BackendRenderTarget
import org.jetbrains.skija.DirectContext import org.jetbrains.skija.DirectContext
import org.jetbrains.skiko.FrameDispatcher import org.jetbrains.skiko.FrameDispatcher
...@@ -14,13 +15,14 @@ internal class Direct3DRedrawer( ...@@ -14,13 +15,14 @@ internal class Direct3DRedrawer(
) : Redrawer { ) : Redrawer {
private var isDisposed = false private var isDisposed = false
private var disposeLock = Any()
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) { private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
update(System.nanoTime()) update(System.nanoTime())
draw() draw()
} }
override fun dispose() { override fun dispose() = synchronized(disposeLock) {
frameDispatcher.cancel() frameDispatcher.cancel()
isDisposed = true isDisposed = true
} }
...@@ -32,17 +34,27 @@ internal class Direct3DRedrawer( ...@@ -32,17 +34,27 @@ internal class Direct3DRedrawer(
override fun redrawImmediately() { override fun redrawImmediately() {
check(!isDisposed) check(!isDisposed)
update(System.nanoTime()) // TODO now we wait until previous layer.draw is finished. it ends only on the next vsync.
draw() // 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) { private fun update(nanoTime: Long) {
layer.update(nanoTime) layer.update(nanoTime)
} }
private fun draw() { private suspend fun draw() {
if (layer.prepareDrawContext()) {
withContext(Dispatchers.IO) {
synchronized(disposeLock) {
if (!isDisposed) {
layer.draw() layer.draw()
} }
}
}
}
}
fun makeContext(device: Long) = DirectContext( fun makeContext(device: Long) = DirectContext(
makeDirectXContext(device) makeDirectXContext(device)
......
...@@ -49,8 +49,10 @@ internal class LinuxOpenGLRedrawer( ...@@ -49,8 +49,10 @@ internal class LinuxOpenGLRedrawer(
} }
private fun draw() { private fun draw() {
if (layer.prepareDrawContext()) {
layer.draw() layer.draw()
} }
}
companion object { companion object {
private val toRedraw = mutableSetOf<LinuxOpenGLRedrawer>() private val toRedraw = mutableSetOf<LinuxOpenGLRedrawer>()
......
package org.jetbrains.skiko.redrawer package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.swing.Swing import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.FrameDispatcher import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
...@@ -11,7 +10,6 @@ import org.jetbrains.skiko.Task ...@@ -11,7 +10,6 @@ import org.jetbrains.skiko.Task
import org.jetbrains.skiko.useDrawingSurfacePlatformInfo import org.jetbrains.skiko.useDrawingSurfacePlatformInfo
import javax.swing.SwingUtilities.convertPoint import javax.swing.SwingUtilities.convertPoint
import javax.swing.SwingUtilities.getRootPane 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) // Current implementation is fragile (it works in all tested cases, but we can't test everything)
// //
...@@ -31,9 +29,11 @@ internal class MacOsOpenGLRedrawer( ...@@ -31,9 +29,11 @@ internal class MacOsOpenGLRedrawer(
private val drawLayer = object : AWTGLLayer(containerLayerPtr, setNeedsDisplayOnBoundsChange = true) { private val drawLayer = object : AWTGLLayer(containerLayerPtr, setNeedsDisplayOnBoundsChange = true) {
override fun draw() = synchronized(drawLock) { override fun draw() = synchronized(drawLock) {
if (!isDisposed) { if (!isDisposed) {
if (layer.prepareDrawContext()) {
layer.draw() layer.draw()
} }
} }
}
suspend fun display() = display(::setNeedsDisplay) suspend fun display() = display(::setNeedsDisplay)
} }
......
...@@ -2,6 +2,7 @@ package org.jetbrains.skiko.redrawer ...@@ -2,6 +2,7 @@ package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.swing.Swing import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import org.jetbrains.skija.BackendRenderTarget import org.jetbrains.skija.BackendRenderTarget
import org.jetbrains.skija.DirectContext import org.jetbrains.skija.DirectContext
import org.jetbrains.skiko.FrameDispatcher import org.jetbrains.skiko.FrameDispatcher
...@@ -16,6 +17,7 @@ internal class MetalRedrawer( ...@@ -16,6 +17,7 @@ internal class MetalRedrawer(
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : Redrawer { ) : Redrawer {
private var isDisposed = false private var isDisposed = false
private var disposeLock = Any()
private val device = layer.backedLayer.useDrawingSurfacePlatformInfo(::createMetalDevice) private val device = layer.backedLayer.useDrawingSurfacePlatformInfo(::createMetalDevice)
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) { private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
...@@ -23,7 +25,7 @@ internal class MetalRedrawer( ...@@ -23,7 +25,7 @@ internal class MetalRedrawer(
draw() draw()
} }
override fun dispose() { override fun dispose() = synchronized(disposeLock) {
frameDispatcher.cancel() frameDispatcher.cancel()
disposeDevice(device) disposeDevice(device)
isDisposed = true isDisposed = true
...@@ -36,17 +38,38 @@ internal class MetalRedrawer( ...@@ -36,17 +38,38 @@ internal class MetalRedrawer(
override fun redrawImmediately() { override fun redrawImmediately() {
check(!isDisposed) check(!isDisposed)
update(System.nanoTime()) // TODO now we wait until previous layer.draw is finished. it ends only on the next vsync.
draw() // 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) { private fun update(nanoTime: Long) {
layer.update(nanoTime) 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() layer.draw()
} }
}
}
}
}
override fun syncSize() { override fun syncSize() {
val rootPane = getRootPane(layer) val rootPane = getRootPane(layer)
......
...@@ -11,8 +11,10 @@ internal class SoftwareRedrawer( ...@@ -11,8 +11,10 @@ internal class SoftwareRedrawer(
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) { private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
layer.update(System.nanoTime()) layer.update(System.nanoTime())
if (layer.prepareDrawContext()) {
layer.draw() layer.draw()
} }
}
override fun dispose() { override fun dispose() {
frameDispatcher.cancel() frameDispatcher.cancel()
...@@ -24,6 +26,8 @@ internal class SoftwareRedrawer( ...@@ -24,6 +26,8 @@ internal class SoftwareRedrawer(
override fun redrawImmediately() { override fun redrawImmediately() {
layer.update(System.nanoTime()) layer.update(System.nanoTime())
if (layer.prepareDrawContext()) {
layer.draw() layer.draw()
} }
}
} }
\ No newline at end of file
...@@ -53,8 +53,10 @@ internal class WindowsOpenGLRedrawer( ...@@ -53,8 +53,10 @@ internal class WindowsOpenGLRedrawer(
} }
private fun draw() { private fun draw() {
if (layer.prepareDrawContext()) {
layer.draw() layer.draw()
} }
}
private fun makeCurrent() = makeCurrent(device, context) private fun makeCurrent() = makeCurrent(device, context)
private fun swapBuffers() = swapBuffers(device) 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