Unverified Commit 2d41c8df authored by Pavel's avatar Pavel Committed by GitHub

Add a flag to control number of buffers used by Metal. Add a flag to control...

Add a flag to control number of buffers used by Metal. Add a flag to control Vsync on window resize (#968)

#### Number of buffers
MacOS
[doc](https://developer.apple.com/documentation/quartzcore/cametallayer/2938720-maximumdrawablecount?language=objc).
As far as I know AWT uses double buffering by default. So we would like
to experiment with this setting in Fleet. Also I expect that double
buffering should reduce user interaction latency.

#### Control VSync on window resize

I've noticed that on Linux and Windows we don't wait for VSync when
repainting synchronously, but only on macOS we do.
I suspect that locking EDT when we are waiting for VSync might lead to
problems with app responsivenes. So it would be nice to experiment with
this setting.
parent c8147b1e
...@@ -56,6 +56,7 @@ actual open class SkiaLayer internal constructor( ...@@ -56,6 +56,7 @@ actual open class SkiaLayer internal constructor(
externalAccessibleFactory: ((Component) -> Accessible)? = null, externalAccessibleFactory: ((Component) -> Accessible)? = null,
isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled, isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled,
isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled, isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled,
frameBuffering: FrameBuffering = SkikoProperties.frameBuffering,
renderApi: GraphicsApi = SkikoProperties.renderApi, renderApi: GraphicsApi = SkikoProperties.renderApi,
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty, analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
pixelGeometry: PixelGeometry = PixelGeometry.UNKNOWN, pixelGeometry: PixelGeometry = PixelGeometry.UNKNOWN,
...@@ -64,6 +65,7 @@ actual open class SkiaLayer internal constructor( ...@@ -64,6 +65,7 @@ actual open class SkiaLayer internal constructor(
SkiaLayerProperties( SkiaLayerProperties(
isVsyncEnabled, isVsyncEnabled,
isVsyncFramelimitFallbackEnabled, isVsyncFramelimitFallbackEnabled,
frameBuffering,
renderApi renderApi
), ),
RenderFactory.Default, RenderFactory.Default,
......
...@@ -72,7 +72,7 @@ internal class Direct3DRedrawer( ...@@ -72,7 +72,7 @@ internal class Direct3DRedrawer(
check(!isDisposed) { "Direct3DRedrawer is disposed" } check(!isDisposed) { "Direct3DRedrawer is disposed" }
inDrawScope { inDrawScope {
update(System.nanoTime()) update(System.nanoTime())
drawAndSwap(withVsync = false) drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately)
} }
} }
......
...@@ -84,10 +84,15 @@ internal class LinuxOpenGLRedrawer( ...@@ -84,10 +84,15 @@ internal class LinuxOpenGLRedrawer(
inDrawScope { inDrawScope {
it.makeCurrent(context) it.makeCurrent(context)
contextHandler.draw() contextHandler.draw()
it.setSwapInterval(0) val turnOfVsync = properties.isVsyncEnabled && !SkikoProperties.linuxWaitForVsyncOnRedrawImmediately
if (turnOfVsync) {
it.setSwapInterval(0)
}
it.swapBuffers() it.swapBuffers()
OpenGLApi.instance.glFinish() OpenGLApi.instance.glFinish()
it.setSwapInterval(swapInterval) if (turnOfVsync) {
it.setSwapInterval(swapInterval)
}
} }
} }
......
...@@ -67,8 +67,9 @@ internal class MetalRedrawer( ...@@ -67,8 +67,9 @@ internal class MetalRedrawer(
init { init {
onDeviceChosen(adapter.name) onDeviceChosen(adapter.name)
val numberOfBuffers = properties.frameBuffering.numberOfBuffers() ?: 0 // zero means default for system
val initDevice = layer.backedLayer.useDrawingSurfacePlatformInfo { val initDevice = layer.backedLayer.useDrawingSurfacePlatformInfo {
MetalDevice(createMetalDevice(layer.windowHandle, layer.transparency, adapter.ptr, it)) MetalDevice(createMetalDevice(layer.windowHandle, layer.transparency, numberOfBuffers, adapter.ptr, it))
} }
_device = initDevice _device = initDevice
contextHandler = MetalContextHandler(layer, initDevice, adapter) contextHandler = MetalContextHandler(layer, initDevice, adapter)
...@@ -113,7 +114,7 @@ internal class MetalRedrawer( ...@@ -113,7 +114,7 @@ internal class MetalRedrawer(
inDrawScope { inDrawScope {
update(System.nanoTime()) update(System.nanoTime())
if (!isDisposed) { // Redrawer may be disposed in user code, during `update` if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
performDraw() performDraw(waitVsync = SkikoProperties.macOSWaitForPreviousFrameVsyncOnRedrawImmediately)
} }
} }
} }
...@@ -154,13 +155,14 @@ internal class MetalRedrawer( ...@@ -154,13 +155,14 @@ internal class MetalRedrawer(
windowOcclusionStateChannel.trySend(isOccluded) windowOcclusionStateChannel.trySend(isOccluded)
} }
private fun performDraw() = synchronized(drawLock) { private fun performDraw(waitVsync: Boolean = true) = synchronized(drawLock) {
if (!isDisposed) { if (!isDisposed) {
// Wait for vsync because: if (waitVsync) {
// - macOS drops the second/next drawables if they are sent in the same vsync // Wait for vsync because:
// - it makes frames consistent and limits FPS // - macOS drops the second/next drawables if they are sent in the same vsync
displayLinkThrottler.waitVSync() // - it makes frames consistent and limits FPS
displayLinkThrottler.waitVSync()
}
autoreleasepool { autoreleasepool {
contextHandler.draw() contextHandler.draw()
} }
...@@ -185,7 +187,7 @@ internal class MetalRedrawer( ...@@ -185,7 +187,7 @@ internal class MetalRedrawer(
setLayerVisible(device.ptr, isVisible) setLayerVisible(device.ptr, isVisible)
} }
private external fun createMetalDevice(window: Long, transparency: Boolean, adapter: Long, platformInfo: Long): Long private external fun createMetalDevice(window: Long, transparency: Boolean, frameBuffering: Int, adapter: Long, platformInfo: Long): Long
private external fun disposeDevice(device: Long) private external fun disposeDevice(device: Long)
private external fun resizeLayers(device: Long, x: Int, y: Int, width: Int, height: Int) private external fun resizeLayers(device: Long, x: Int, y: Int, width: Int, height: Int)
private external fun setLayerVisible(device: Long, isVisible: Boolean) private external fun setLayerVisible(device: Long, isVisible: Boolean)
......
...@@ -69,6 +69,9 @@ internal class WindowsOpenGLRedrawer( ...@@ -69,6 +69,9 @@ internal class WindowsOpenGLRedrawer(
contextHandler.draw() contextHandler.draw()
swapBuffers() swapBuffers()
OpenGLApi.instance.glFinish() OpenGLApi.instance.glFinish()
if (SkikoProperties.windowsWaitForVsyncOnRedrawImmediately) {
dwmFlush()
}
} }
} }
......
...@@ -118,7 +118,7 @@ extern "C" ...@@ -118,7 +118,7 @@ extern "C"
{ {
JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_createMetalDevice( JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_createMetalDevice(
JNIEnv *env, jobject redrawer, jlong windowPtr, jboolean transparency, jlong adapterPtr, jlong platformInfoPtr) JNIEnv *env, jobject redrawer, jlong windowPtr, jboolean transparency, jint frameBuffering, jlong adapterPtr, jlong platformInfoPtr)
{ {
@autoreleasepool { @autoreleasepool {
id<MTLDevice> adapter = (__bridge id<MTLDevice>) (void *) adapterPtr; id<MTLDevice> adapter = (__bridge id<MTLDevice>) (void *) adapterPtr;
...@@ -133,6 +133,10 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_createMe ...@@ -133,6 +133,10 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_createMe
[container setNeedsDisplayOnBoundsChange: YES]; [container setNeedsDisplayOnBoundsChange: YES];
AWTMetalLayer *layer = [AWTMetalLayer new]; AWTMetalLayer *layer = [AWTMetalLayer new];
if (frameBuffering == 2 || frameBuffering == 3) {
layer.maximumDrawableCount = frameBuffering;
}
[container addSublayer: layer]; [container addSublayer: layer];
layer.javaRef = env->NewGlobalRef(redrawer); layer.javaRef = env->NewGlobalRef(redrawer);
......
...@@ -29,3 +29,17 @@ enum class GpuPriority(val value: String) { ...@@ -29,3 +29,17 @@ enum class GpuPriority(val value: String) {
fun parseOrNull(value: String): GpuPriority? = GpuPriority.values().find { it.value == value } fun parseOrNull(value: String): GpuPriority? = GpuPriority.values().find { it.value == value }
} }
} }
enum class FrameBuffering {
DEFAULT,
DOUBLE,
TRIPLE
}
fun FrameBuffering.numberOfBuffers(): Int? {
return when (this) {
FrameBuffering.DEFAULT -> null
FrameBuffering.DOUBLE -> 2
FrameBuffering.TRIPLE -> 3
}
}
\ No newline at end of file
...@@ -18,6 +18,7 @@ package org.jetbrains.skiko ...@@ -18,6 +18,7 @@ package org.jetbrains.skiko
class SkiaLayerProperties( class SkiaLayerProperties(
val isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled, val isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled,
val isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled, val isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled,
val frameBuffering: FrameBuffering = SkikoProperties.frameBuffering,
val renderApi: GraphicsApi = SkikoProperties.renderApi, val renderApi: GraphicsApi = SkikoProperties.renderApi,
val adapterPriority: GpuPriority = SkikoProperties.gpuPriority, val adapterPriority: GpuPriority = SkikoProperties.gpuPriority,
) { ) {
......
...@@ -34,6 +34,26 @@ object SkikoProperties { ...@@ -34,6 +34,26 @@ object SkikoProperties {
val vsyncEnabled: Boolean get() = getProperty("skiko.vsync.enabled")?.toBoolean() ?: true val vsyncEnabled: Boolean get() = getProperty("skiko.vsync.enabled")?.toBoolean() ?: true
val frameBuffering: FrameBuffering get() {
return when (getProperty("skiko.buffering")) {
"DOUBLE" -> FrameBuffering.DOUBLE
"TRIPLE" -> FrameBuffering.TRIPLE
else -> FrameBuffering.DEFAULT
}
}
val macOSWaitForPreviousFrameVsyncOnRedrawImmediately: Boolean get() {
return getProperty("skiko.rendering.macos.waitForPreviousFrameVsyncOnRedrawImmediately")?.toBoolean() ?: true
}
val windowsWaitForVsyncOnRedrawImmediately: Boolean get() {
return getProperty("skiko.rendering.windows.waitForFrameVsyncOnRedrawImmediately")?.toBoolean() ?: false
}
val linuxWaitForVsyncOnRedrawImmediately: Boolean get() {
return getProperty("skiko.rendering.linux.waitForFrameVsyncOnRedrawImmediately")?.toBoolean() ?: false
}
/** /**
* If vsync is enabled, but platform can't support it (Software renderer, Linux with uninstalled drivers), * If vsync is enabled, but platform can't support it (Software renderer, Linux with uninstalled drivers),
* we enable frame limit by the display refresh rate. * we enable frame limit by the display refresh rate.
......
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