Unverified Commit 285c4f20 authored by Roman Sedaikin's avatar Roman Sedaikin Committed by GitHub

Merge branch 'master' into directx

parents 4851cff2 f2b5c392
package org.jetbrains.skiko
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
......@@ -204,6 +205,44 @@ class SkiaWindowTest {
delay(5000)
}
@Test(timeout = 20000)
fun `render continuously empty content without vsync`() = swingTest {
assumeTrue(hostOs != OS.MacOS) // TODO remove when we will support drawing without vsync on macOs
val targetDrawCount = 500
var drawCount = 0
val onDrawCompleted = CompletableDeferred<Unit>()
val window = SkiaWindow(
properties = SkiaLayerProperties(
isVsyncEnabled = false
)
)
try {
window.setLocation(200, 200)
window.setSize(400, 200)
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.layer.renderer = object : SkiaRenderer {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
drawCount++
if (drawCount < targetDrawCount) {
window.layer.needRedraw()
} else {
onDrawCompleted.complete(Unit)
}
}
}
window.isUndecorated = true
window.isVisible = true
onDrawCompleted.await()
} finally {
window.close()
}
}
@Test
fun `render text (Windows)`() {
testRenderText(OS.Windows)
......
......@@ -370,7 +370,8 @@ tasks.withType(LinkSharedLibrary::class.java).configureEach {
"-lGL",
"-lfontconfig",
// Hack to fix problem with linker not always finding certain declarations.
skiaDir.get().absolutePath + "/out/${buildType.id}-${targetArch.id}/libsksg.a"
skiaDir.get().absolutePath + "/out/${buildType.id}-${targetArch.id}/libsksg.a",
skiaDir.get().absolutePath + "/out/${buildType.id}-${targetArch.id}/libskia.a"
)
)
}
......
......@@ -14,7 +14,7 @@ internal interface PlatformOperations {
fun isFullscreen(component: Component): Boolean
fun setFullscreen(component: Component, value: Boolean)
fun getDpiScale(component: Component): Float
fun createRedrawer(layer: HardwareLayer, renderApi: GraphicsApi): Redrawer
fun createRedrawer(layer: HardwareLayer, renderApi: GraphicsApi, properties: SkiaLayerProperties): Redrawer
}
internal val platformOperations: PlatformOperations by lazy {
......@@ -32,9 +32,9 @@ internal val platformOperations: PlatformOperations by lazy {
return component.graphicsConfiguration.defaultTransform.scaleX.toFloat()
}
override fun createRedrawer(layer: HardwareLayer, renderApi: GraphicsApi) = when (renderApi) {
override fun createRedrawer(layer: HardwareLayer, renderApi: GraphicsApi, properties: SkiaLayerProperties) = when(renderApi) {
GraphicsApi.SOFTWARE -> SoftwareRedrawer(layer)
else -> MacOsOpenGLRedrawer(layer)
else -> MacOsOpenGLRedrawer(layer, properties)
}
}
OS.Windows -> {
......@@ -55,10 +55,10 @@ internal val platformOperations: PlatformOperations by lazy {
return component.graphicsConfiguration.defaultTransform.scaleX.toFloat()
}
override fun createRedrawer(layer: HardwareLayer, renderApi: GraphicsApi) = when (renderApi) {
override fun createRedrawer(layer: HardwareLayer, renderApi: GraphicsApi, properties: SkiaLayerProperties) = when(renderApi) {
GraphicsApi.SOFTWARE -> SoftwareRedrawer(layer)
GraphicsApi.DIRECT3D -> Direct3DRedrawer(layer)
else -> WindowsOpenGLRedrawer(layer)
else -> WindowsOpenGLRedrawer(layer, properties)
}
}
}
......@@ -92,9 +92,9 @@ internal val platformOperations: PlatformOperations by lazy {
// return component.useDrawingSurfacePlatformInfo(::linuxGetDpiScaleNative)
}
override fun createRedrawer(layer: HardwareLayer, renderApi: GraphicsApi) = when (renderApi) {
override fun createRedrawer(layer: HardwareLayer, renderApi: GraphicsApi, properties: SkiaLayerProperties) = when(renderApi) {
GraphicsApi.SOFTWARE -> SoftwareRedrawer(layer)
else -> LinuxOpenGLRedrawer(layer)
else -> LinuxOpenGLRedrawer(layer, properties)
}
}
}
......
......@@ -21,7 +21,9 @@ interface SkiaRenderer {
private class PictureHolder(val instance: Picture, val width: Int, val height: Int)
open class SkiaLayer : HardwareLayer() {
open class SkiaLayer(
private val properties: SkiaLayerProperties = SkiaLayerProperties()
) : HardwareLayer() {
var renderer: SkiaRenderer? = null
val clipComponents = mutableListOf<ClipRectangle>()
......@@ -41,7 +43,7 @@ open class SkiaLayer : HardwareLayer() {
super.init()
val initialRenderApi = fallbackRenderApiQueue.removeAt(0)
contextHandler = createContextHandler(this, initialRenderApi)
redrawer = platformOperations.createRedrawer(this, initialRenderApi)
redrawer = platformOperations.createRedrawer(this, initialRenderApi, properties)
redrawer?.syncSize()
redrawer?.redrawImmediately()
}
......
package org.jetbrains.skiko
class SkiaLayerProperties(
val isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled
)
\ No newline at end of file
......@@ -2,8 +2,10 @@ package org.jetbrains.skiko
import javax.swing.JFrame
open class SkiaWindow : JFrame() {
val layer = SkiaLayer()
open class SkiaWindow(
properties: SkiaLayerProperties = SkiaLayerProperties()
) : JFrame() {
val layer = SkiaLayer(properties)
init {
contentPane.add(layer)
......
......@@ -7,11 +7,12 @@ import org.jetbrains.skiko.DrawingSurface
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skiko.OpenGLApi
import org.jetbrains.skiko.SkikoProperties
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.getDrawingSurface
internal class LinuxOpenGLRedrawer(
private val layer: HardwareLayer
private val layer: HardwareLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val context = layer.lockDrawingSurface {
it.createContext()
......@@ -70,6 +71,8 @@ internal class LinuxOpenGLRedrawer(
}
}
val isVsyncEnabled = toRedrawAlive.all { it.properties.isVsyncEnabled }
val drawingSurfaces = toRedrawAlive.map { lockDrawingSurface(it.layer) }.toList()
try {
toRedrawAlive.forEachIndexed { index, redrawer ->
......@@ -79,7 +82,7 @@ internal class LinuxOpenGLRedrawer(
toRedrawAlive.forEachIndexed { index, _ ->
// it is ok to set swap interval every frame, there is no performance overhead
drawingSurfaces[index].setSwapInterval(if (SkikoProperties.vsyncEnabled) 1 else 0)
drawingSurfaces[index].setSwapInterval(if (isVsyncEnabled) 1 else 0)
drawingSurfaces[index].swapBuffers()
}
......
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.HardwareLayer
import org.jetbrains.skiko.OpenGLApi
import org.jetbrains.skiko.SkikoProperties
import org.jetbrains.skiko.useDrawingSurfacePlatformInfo
import org.jetbrains.skiko.SkiaLayerProperties
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)
//
// We should investigate can we implement our own CAOpenGLLayer, without its restrictions.
// (see, for example https://github.com/gnustep/libs-gui/blob/master/Source/NSOpenGLView.m)
//
// P.S. MacOsOpenGLRedrawer will not be used by default in the future, because we will support Metal.
internal class MacOsOpenGLRedrawer(
private val layer: HardwareLayer
private val layer: HardwareLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val containerLayerPtr = layer.useDrawingSurfacePlatformInfo(::initContainer)
private val drawLock = Any()
......@@ -24,6 +34,8 @@ internal class MacOsOpenGLRedrawer(
layer.draw()
}
}
suspend fun display() = display(::setNeedsDisplay)
}
// use a separate layer for vsync, because with single layer we cannot asynchronously update layer
......@@ -52,7 +64,7 @@ internal class MacOsOpenGLRedrawer(
return canDraw
}
override fun setNeedsDisplay() {
private fun requestAsyncDisplay() {
// Use asynchronous mode instead of just setNeedsDisplay,
// so Core Animation will wait for the next frame in vsync signal
//
......@@ -63,13 +75,13 @@ internal class MacOsOpenGLRedrawer(
// https://chromium.googlesource.com/chromium/chromium/+/0489078bf98350b00876070cf2fdce230905f47e/content/browser/renderer_host/compositing_iosurface_layer_mac.mm#57
if (!isAsynchronous) {
isAsynchronous = true
super.setNeedsDisplay()
setNeedsDisplay()
}
}
suspend fun sync() {
canDraw = true
display()
display(::requestAsyncDisplay)
canDraw = false
}
}
......@@ -78,13 +90,11 @@ internal class MacOsOpenGLRedrawer(
synchronized(drawLock) {
layer.update(System.nanoTime())
}
if (SkikoProperties.vsyncEnabled) {
if (properties.isVsyncEnabled) {
drawLayer.setNeedsDisplay()
vsyncLayer.sync()
} else {
// If vsync is disabled we should await the drawing to end.
// Otherwise we will call 'update' multiple times.
drawLayer.display()
error("Drawing without vsync isn't supported on macOs with OpenGL")
}
}
......@@ -113,7 +123,16 @@ internal class MacOsOpenGLRedrawer(
override fun redrawImmediately() {
layer.update(System.nanoTime())
drawLayer.setNeedsDisplay()
// macOs will call 'draw' itself because of 'setNeedsDisplayOnBoundsChange=true'.
// But we schedule new frame after vsync anyway.
// Because 'redrawImmediately' can be called after 'draw',
// and we need at least one 'draw' after 'redrawImmediately'.
//
// We don't use setNeedsDisplay, because frequent calls of it are unreliable.
// 'setNeedsDisplayOnBoundsChange=true' with combination of 'scheduleFrame' is enough
// to not see the white bars on resize.
frameDispatcher.scheduleFrame()
}
}
......@@ -134,15 +153,32 @@ private abstract class AWTGLLayer(private val containerPtr: Long, setNeedsDispla
get() = isAsynchronous(ptr)
set(value) = setAsynchronous(ptr, value)
open fun setNeedsDisplay() = setNeedsDisplayOnMainThread(ptr)
/**
* Schedule next [draw] as soon as possible (not waiting for vsync)
*
* WARNING!!!
*
* CAOpenGLLayer will not call [draw] if we call [setNeedsDisplay] too often.
*
* Experimentally we found out that after 15 draw's between two vsync's (900 FPS on 60 Hz display) will cause
* setNeedsDisplay to not schedule the next draw at all.
*
* Only after the next vsync, [setNeedsDisplay] will be working again.
*/
fun setNeedsDisplay() {
setNeedsDisplayOnMainThread(ptr)
}
suspend fun display() = display.runAndAwait {
setNeedsDisplay()
protected suspend fun display(
startDisplay: () -> Unit
) = display.runAndAwait {
startDisplay()
}
// Called in AppKit Thread
protected open fun canDraw() = true
// Called in AppKit Thread
@Suppress("unused") // called from native code
private fun performDraw() {
try {
......
......@@ -7,11 +7,12 @@ import kotlinx.coroutines.withContext
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.HardwareLayer
import org.jetbrains.skiko.OpenGLApi
import org.jetbrains.skiko.SkikoProperties
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.useDrawingSurfacePlatformInfo
internal class WindowsOpenGLRedrawer(
private val layer: HardwareLayer
private val layer: HardwareLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val device = layer.useDrawingSurfacePlatformInfo(::getDevice)
private val context = createContext(device)
......@@ -92,7 +93,8 @@ internal class WindowsOpenGLRedrawer(
OpenGLApi.instance.glFinish()
}
if (SkikoProperties.vsyncEnabled) {
val isVsyncEnabled = toRedrawAlive.all { it.properties.isVsyncEnabled }
if (isVsyncEnabled) {
withContext(Dispatchers.IO) {
dwmFlush() // wait for vsync
}
......
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