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(
externalAccessibleFactory: ((Component) -> Accessible)? = null,
isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled,
isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled,
frameBuffering: FrameBuffering = SkikoProperties.frameBuffering,
renderApi: GraphicsApi = SkikoProperties.renderApi,
analytics: SkiaLayerAnalytics = SkiaLayerAnalytics.Empty,
pixelGeometry: PixelGeometry = PixelGeometry.UNKNOWN,
......@@ -64,6 +65,7 @@ actual open class SkiaLayer internal constructor(
SkiaLayerProperties(
isVsyncEnabled,
isVsyncFramelimitFallbackEnabled,
frameBuffering,
renderApi
),
RenderFactory.Default,
......
......@@ -72,7 +72,7 @@ internal class Direct3DRedrawer(
check(!isDisposed) { "Direct3DRedrawer is disposed" }
inDrawScope {
update(System.nanoTime())
drawAndSwap(withVsync = false)
drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately)
}
}
......
......@@ -84,12 +84,17 @@ internal class LinuxOpenGLRedrawer(
inDrawScope {
it.makeCurrent(context)
contextHandler.draw()
val turnOfVsync = properties.isVsyncEnabled && !SkikoProperties.linuxWaitForVsyncOnRedrawImmediately
if (turnOfVsync) {
it.setSwapInterval(0)
}
it.swapBuffers()
OpenGLApi.instance.glFinish()
if (turnOfVsync) {
it.setSwapInterval(swapInterval)
}
}
}
private fun draw() {
inDrawScope(contextHandler::draw)
......
......@@ -67,8 +67,9 @@ internal class MetalRedrawer(
init {
onDeviceChosen(adapter.name)
val numberOfBuffers = properties.frameBuffering.numberOfBuffers() ?: 0 // zero means default for system
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
contextHandler = MetalContextHandler(layer, initDevice, adapter)
......@@ -113,7 +114,7 @@ internal class MetalRedrawer(
inDrawScope {
update(System.nanoTime())
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
performDraw()
performDraw(waitVsync = SkikoProperties.macOSWaitForPreviousFrameVsyncOnRedrawImmediately)
}
}
}
......@@ -154,13 +155,14 @@ internal class MetalRedrawer(
windowOcclusionStateChannel.trySend(isOccluded)
}
private fun performDraw() = synchronized(drawLock) {
private fun performDraw(waitVsync: Boolean = true) = synchronized(drawLock) {
if (!isDisposed) {
if (waitVsync) {
// Wait for vsync because:
// - macOS drops the second/next drawables if they are sent in the same vsync
// - it makes frames consistent and limits FPS
displayLinkThrottler.waitVSync()
}
autoreleasepool {
contextHandler.draw()
}
......@@ -185,7 +187,7 @@ internal class MetalRedrawer(
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 resizeLayers(device: Long, x: Int, y: Int, width: Int, height: Int)
private external fun setLayerVisible(device: Long, isVisible: Boolean)
......
......@@ -69,6 +69,9 @@ internal class WindowsOpenGLRedrawer(
contextHandler.draw()
swapBuffers()
OpenGLApi.instance.glFinish()
if (SkikoProperties.windowsWaitForVsyncOnRedrawImmediately) {
dwmFlush()
}
}
}
......
......@@ -118,7 +118,7 @@ extern "C"
{
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 {
id<MTLDevice> adapter = (__bridge id<MTLDevice>) (void *) adapterPtr;
......@@ -133,6 +133,10 @@ JNIEXPORT jlong JNICALL Java_org_jetbrains_skiko_redrawer_MetalRedrawer_createMe
[container setNeedsDisplayOnBoundsChange: YES];
AWTMetalLayer *layer = [AWTMetalLayer new];
if (frameBuffering == 2 || frameBuffering == 3) {
layer.maximumDrawableCount = frameBuffering;
}
[container addSublayer: layer];
layer.javaRef = env->NewGlobalRef(redrawer);
......
......@@ -29,3 +29,17 @@ enum class GpuPriority(val value: String) {
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
class SkiaLayerProperties(
val isVsyncEnabled: Boolean = SkikoProperties.vsyncEnabled,
val isVsyncFramelimitFallbackEnabled: Boolean = SkikoProperties.vsyncFramelimitFallbackEnabled,
val frameBuffering: FrameBuffering = SkikoProperties.frameBuffering,
val renderApi: GraphicsApi = SkikoProperties.renderApi,
val adapterPriority: GpuPriority = SkikoProperties.gpuPriority,
) {
......
......@@ -34,6 +34,26 @@ object SkikoProperties {
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),
* 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