From af52e371919329640cf57440e5ab4ff28d17e529 Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Tue, 10 Mar 2026 17:38:02 +0200 Subject: [PATCH 1/7] Fix race condition reading layer properties in a background thread. --- .../skiko/context/AngleContextHandler.kt | 7 ++- .../context/ContextBasedContextHandler.kt | 2 +- .../context/ContextFreeContextHandler.kt | 8 ++-- .../skiko/context/Direct3DContextHandler.kt | 7 ++- .../context/DirectSoftwareContextHandler.kt | 7 ++- .../skiko/context/JvmContextHandler.kt | 7 +++ .../skiko/context/MetalContextHandler.kt | 7 ++- .../skiko/context/OpenGLContextHandler.kt | 7 ++- .../skiko/context/SoftwareContextHandler.kt | 7 ++- .../skiko/redrawer/Direct3DRedrawer.kt | 15 ++++-- .../jetbrains/skiko/redrawer/MetalRedrawer.kt | 21 +++++---- .../org/jetbrains/skiko/SkiaLayerTest.kt | 2 +- .../kotlin/org/jetbrains/skiko/util/UiTest.kt | 2 +- .../jetbrains/skiko/context/ContextHandler.kt | 46 +++++++++++++++++-- .../context/MetalContextHandler.macos.kt | 15 ++++-- .../context/OpenGLContextHandler.macos.kt | 24 ++++++---- 16 files changed, 123 insertions(+), 61 deletions(-) diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt index e1d6aee18..af55ef463 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt @@ -23,12 +23,11 @@ internal class AngleContextHandler(layer: SkiaLayer) : ContextBasedContextHandle return false } - override fun initCanvas() { + override fun DrawScope.initCanvas() { val context = context ?: return - val scale = layer.contentScale - val w = (layer.width * scale).toInt().coerceAtLeast(0) - val h = (layer.height * scale).toInt().coerceAtLeast(0) + val w = scaledLayerWidth + val h = scaledLayerHeight if (isSizeChanged(w, h) || surface == null) { disposeCanvas() diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/ContextBasedContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/ContextBasedContextHandler.kt index 0453d4f74..b02e75bbc 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/ContextBasedContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/ContextBasedContextHandler.kt @@ -6,7 +6,7 @@ import org.jetbrains.skiko.SkiaLayer internal abstract class ContextBasedContextHandler(layer: SkiaLayer, val name: String) : JvmContextHandler(layer) { - abstract protected fun makeContext(): DirectContext + protected abstract fun makeContext(): DirectContext override fun initContext(): Boolean { try { diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/ContextFreeContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/ContextFreeContextHandler.kt index 1fe411406..ae4eb7d0d 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/ContextFreeContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/ContextFreeContextHandler.kt @@ -3,13 +3,13 @@ package org.jetbrains.skiko.context import org.jetbrains.skiko.SkiaLayer internal abstract class ContextFreeContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) { - private var isInited = false + private var isInitialized = false override fun initContext(): Boolean { - if (!isInited) { - isInited = true + if (!isInitialized) { + isInitialized = true onContextInitialized() } - return isInited + return isInitialized } } \ No newline at end of file diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt index fa8c5fa06..275b9406b 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt @@ -30,15 +30,14 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan return false } - override fun initCanvas() { + override fun DrawScope.initCanvas() { val context = context ?: return - val scale = layer.contentScale // Direct3D can't work with zero size. // Don't rewrite code to skipping, as we need the whole pipeline in zero case too // (drawing -> flushing -> swapping -> waiting for vsync) - val width = (layer.width * scale).toInt().coerceAtLeast(1) - val height = (layer.height * scale).toInt().coerceAtLeast(1) + val width = scaledLayerWidth.coerceAtLeast(1) + val height = scaledLayerHeight.coerceAtLeast(1) if (isSizeChanged(width, height) || isSurfacesNull()) { disposeCanvas() diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt index 6c4cfd88a..4484c0272 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt @@ -20,10 +20,9 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextFreeConte return false } - override fun initCanvas() { - val scale = layer.contentScale - val w = (layer.width * scale).toInt().coerceAtLeast(0) - val h = (layer.height * scale).toInt().coerceAtLeast(0) + override fun DrawScope.initCanvas() { + val w = scaledLayerWidth + val h = scaledLayerHeight if (isSizeChanged(w, h) || surface == null) { disposeCanvas() if (w > 0 && h > 0) { diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt index 624f75b75..0f691e4aa 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt @@ -4,6 +4,13 @@ import org.jetbrains.skiko.Logger import org.jetbrains.skiko.SkiaLayer internal abstract class JvmContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) { + + override fun createDrawScope() = DrawScope( + layerWidth = layer.width, + layerHeight = layer.height, + scale = layer.contentScale + ) + protected fun onContextInitialized() { if (System.getProperty("skiko.hardwareInfo.enabled") == "true") { Logger.info { "Renderer info:\n ${rendererInfo()}" } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt index e4d19d628..a3b1523b3 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt @@ -20,12 +20,11 @@ internal class MetalContextHandler( private val device: MetalDevice, private val adapter: MetalAdapter ) : ContextBasedContextHandler(layer, "Metal") { - override fun initCanvas() { + override fun DrawScope.initCanvas() { disposeCanvas() - val scale = layer.contentScale - val width = (layer.backedLayer.width * scale).toInt().coerceAtLeast(0) - val height = (layer.backedLayer.height * scale).toInt().coerceAtLeast(0) + val width = scaledLayerWidth + val height = scaledLayerHeight if (width > 0 && height > 0) { renderTarget = makeRenderTarget(width, height) diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt index c57f959e3..4c951863c 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt @@ -18,10 +18,9 @@ internal class OpenGLContextHandler(layer: SkiaLayer) : ContextBasedContextHandl return false } - override fun initCanvas() { - val scale = layer.contentScale - val w = (layer.width * scale).toInt().coerceAtLeast(0) - val h = (layer.height * scale).toInt().coerceAtLeast(0) + override fun DrawScope.initCanvas() { + val w = scaledLayerWidth + val h = scaledLayerHeight if (isSizeChanged(w, h) || surface == null) { disposeCanvas() diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt index f74c1c89d..2285c3c52 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt @@ -22,12 +22,11 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHand var imageData: ByteArray? = null var raster: WritableRaster? = null - override fun initCanvas() { + override fun DrawScope.initCanvas() { disposeCanvas() - val scale = layer.contentScale - val w = (layer.width * scale).toInt().coerceAtLeast(0) - val h = (layer.height * scale).toInt().coerceAtLeast(0) + val w = scaledLayerWidth + val h = scaledLayerHeight if (storage.width != w || storage.height != h) { storage.allocPixelsFlags(ImageInfo.makeS32(w, h, ColorAlphaType.PREMUL), false) diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/Direct3DRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/Direct3DRedrawer.kt index 5024012e3..ce89330d9 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/Direct3DRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/Direct3DRedrawer.kt @@ -7,6 +7,7 @@ import org.jetbrains.skia.SurfaceProps import org.jetbrains.skia.impl.InteropPointer import org.jetbrains.skia.impl.interopScope import org.jetbrains.skiko.* +import org.jetbrains.skiko.context.ContextHandler import org.jetbrains.skiko.context.Direct3DContextHandler internal class Direct3DRedrawer( @@ -73,24 +74,28 @@ internal class Direct3DRedrawer( update() inDrawScope { if (!isDisposed) { // Redrawer may be disposed in user code, during `update` - drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately) + contextHandler.inDrawScope { + drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately) + } } } } private suspend fun draw() { inDrawScope { - withContext(dispatcherToBlockOn) { - drawAndSwap(withVsync = properties.isVsyncEnabled) + contextHandler.inDrawScope { + withContext(dispatcherToBlockOn) { + drawAndSwap(withVsync = properties.isVsyncEnabled) + } } } } - private fun drawAndSwap(withVsync: Boolean) = synchronized(drawLock) { + private fun ContextHandler.DrawScope.drawAndSwap(withVsync: Boolean) = synchronized(drawLock) { if (isDisposed) { return } - contextHandler.draw() + contextHandlerDraw() swap(withVsync) } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt index 918007126..38fcb3057 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt @@ -3,6 +3,7 @@ package org.jetbrains.skiko.redrawer import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import org.jetbrains.skiko.* +import org.jetbrains.skiko.context.ContextHandler import org.jetbrains.skiko.context.MetalContextHandler import java.util.concurrent.atomic.AtomicBoolean import javax.swing.SwingUtilities.* @@ -104,7 +105,9 @@ internal class MetalRedrawer( update() inDrawScope { if (!isDisposed) { // Redrawer may be disposed in user code, during `update` - performDraw() + contextHandler.inDrawScope { + performDraw() + } // Trying to draw immediately in Metal will result in lost (undrawn) // frames if there are more than two between consecutive vsync events. if (SkikoProperties.macOSWaitForPreviousFrameVsyncOnRedrawImmediately) { @@ -118,11 +121,13 @@ internal class MetalRedrawer( private suspend fun draw() { inDrawScope { - // Move drawing to another thread to free the main thread - // It can be expensive to run it in the main thread and FPS can become unstable. - // This is visible by running [SkiaLayerPerformanceTest], standard deviation is increased significantly. - withContext(dispatcherToBlockOn) { - performDraw() + contextHandler.inDrawScope { + // Move drawing to another thread to free the main thread + // It can be expensive to run it in the main thread and FPS can become unstable. + // This is visible by running [SkiaLayerPerformanceTest], standard deviation is increased significantly. + withContext(dispatcherToBlockOn) { + performDraw() + } } } if (isDisposed) throw CancellationException() @@ -144,10 +149,10 @@ internal class MetalRedrawer( windowOcclusionStateChannel.trySend(isOccluded) } - private fun performDraw() = synchronized(drawLock) { + private fun ContextHandler.DrawScope.performDraw() = synchronized(drawLock) { if (!isDisposed) { autoreleasepool { - contextHandler.draw() + contextHandlerDraw() } } } diff --git a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt index 9500e28a2..004c86d92 100644 --- a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt +++ b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt @@ -578,7 +578,7 @@ class SkiaLayerTest { object : BaseTestRedrawer(layer) { private val contextHandler = object : JvmContextHandler(layer) { override fun initContext() = false - override fun initCanvas() = Unit + override fun DrawScope.initCanvas() = Unit } override fun renderImmediately() = layer.inDrawScope(contextHandler::draw) } diff --git a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt index 0cc78799d..d04d24233 100644 --- a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt +++ b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt @@ -15,7 +15,7 @@ internal fun uiTest( block: suspend UiTestScope.() -> Unit ) { assumeFalse(GraphicsEnvironment.isHeadless()) - assumeTrue(System.getProperty("skiko.test.ui.enabled", "false") == "true") +// assumeTrue(System.getProperty("skiko.test.ui.enabled", "false") == "true") val renderApiProperty = System.getProperty("skiko.test.ui.renderApi", "all") diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt index 79c87453d..abd9ad2a2 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt @@ -13,7 +13,7 @@ internal abstract class ContextHandler( protected var canvas: Canvas? = null protected abstract fun initContext(): Boolean - protected abstract fun initCanvas() + protected abstract fun DrawScope.initCanvas() protected open fun flush() { context?.flush() @@ -34,8 +34,30 @@ internal abstract class ContextHandler( "OS: ${hostOs.id} ${hostArch.id}\n" } - // throws RenderException if initialization of graphic context was not successful + /** + * This function will be called only in a thread where it is valid to access layer properties. + */ + protected abstract fun createDrawScope(): DrawScope + + /** + * Reads layer properties, creating a [DrawScope] in which [DrawScope.contextHandlerDraw] can later be called on a + * background thread. + * + * This function should be called only in a thread where it is valid to access layer properties. + */ + inline fun inDrawScope(block: DrawScope.() -> Unit) { + createDrawScope().block() + } + + /** + * This function should be called only in a thread where it is valid to access layer properties. + */ fun draw() { + createDrawScope().contextHandlerDraw() + } + + // throws RenderException if initialization of graphic context was not successful + private fun DrawScope.drawImpl() { if (!initContext()) { throw RenderException("Cannot init graphic context") } @@ -46,4 +68,22 @@ internal abstract class ContextHandler( } flush() } -} \ No newline at end of file + + inner class DrawScope( + val scaledLayerWidth: Int, + val scaledLayerHeight: Int + ) { + constructor(layerWidth: Int, layerHeight: Int, scale: Float): this( + scaledLayerWidth = (layerWidth * scale).toInt().coerceAtLeast(0), + scaledLayerHeight = (layerHeight * scale).toInt().coerceAtLeast(0) + ) + constructor(layerWidth: Double, layerHeight: Double, scale: Float): this( + scaledLayerWidth = (layerWidth * scale).toInt().coerceAtLeast(0), + scaledLayerHeight = (layerHeight * scale).toInt().coerceAtLeast(0) + ) + + fun contextHandlerDraw() { + drawImpl() + } + } +} diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt index e4c37e960..dd32bf77f 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt @@ -7,7 +7,7 @@ import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.redrawer.MacOsMetalRedrawer /** - * Metal ContextHandler implementation for MacOs. + * Metal ContextHandler implementation for macOS. */ internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) { private val metalRedrawer: MacOsMetalRedrawer @@ -25,12 +25,17 @@ internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer return true } - override fun initCanvas() { + override fun createDrawScope() = DrawScope( + layerWidth = layer.nsView.frame.useContents { size.width }, + layerHeight = layer.nsView.frame.useContents { size.height }, + scale = layer.contentScale + ) + + override fun DrawScope.initCanvas() { disposeCanvas() - val scale = layer.contentScale - val w = (layer.nsView.frame.useContents { size.width } * scale).toInt().coerceAtLeast(0) - val h = (layer.nsView.frame.useContents { size.height } * scale).toInt().coerceAtLeast(0) + val w = scaledLayerWidth + val h = scaledLayerHeight if (w > 0 && h > 0) { renderTarget = metalRedrawer.makeRenderTarget(w, h) diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt index bcf02f13f..4183c8d3e 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt @@ -10,7 +10,8 @@ import platform.OpenGL.glGetIntegerv import platform.OpenGLCommon.GLenum /** - * OpenGL context handler for MacOs (native). + * OpenGL context handler for macOS (native). + * * Not used anymore, unless corresponding [GraphicsApi] is hardcoded in [SkiaLayer]. * See [MacOsMetalContextHandler] instead. */ @@ -20,7 +21,7 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye if (context == null) { context = DirectContext.makeGL() } - } catch (e: Exception) { + } catch (_: Exception) { println("Failed to create Skia OpenGL context!") return false } @@ -29,15 +30,21 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye @ExperimentalUnsignedTypes private fun openglGetIntegerv(pname: GLenum): UInt { - var result: UInt = 0U + var result = 0U memScoped { val data = alloc() - glGetIntegerv(pname, data.ptr); - result = data.value.toUInt(); + glGetIntegerv(pname, data.ptr) + result = data.value.toUInt() } return result } + override fun createDrawScope() = DrawScope( + layerWidth = layer.nsView.frame.useContents { size.width }, + layerHeight = layer.nsView.frame.useContents { size.height }, + scale = layer.contentScale + ) + private var currentWidth = 0 private var currentHeight = 0 private fun isSizeChanged(width: Int, height: Int): Boolean { @@ -49,10 +56,9 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye return false } - override fun initCanvas() { - val scale = layer.contentScale - val w = (layer.nsView.frame.useContents { size.width } * scale).toInt().coerceAtLeast(0) - val h = (layer.nsView.frame.useContents { size.height } * scale).toInt().coerceAtLeast(0) + override fun DrawScope.initCanvas() { + val w = scaledLayerWidth + val h = scaledLayerHeight if (isSizeChanged(w, h)) { val fbId = openglGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING.toUInt()) renderTarget = BackendRenderTarget.makeGL( From ce8dc2d2d6f051261c63385c4393da9b7589b405 Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Wed, 11 Mar 2026 20:28:21 +0200 Subject: [PATCH 2/7] Revert checking skiko.test.ui.enabled --- skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt index d04d24233..0cc78799d 100644 --- a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt +++ b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/util/UiTest.kt @@ -15,7 +15,7 @@ internal fun uiTest( block: suspend UiTestScope.() -> Unit ) { assumeFalse(GraphicsEnvironment.isHeadless()) -// assumeTrue(System.getProperty("skiko.test.ui.enabled", "false") == "true") + assumeTrue(System.getProperty("skiko.test.ui.enabled", "false") == "true") val renderApiProperty = System.getProperty("skiko.test.ui.renderApi", "all") From 7974028d96146ae73c1628300bf911d40ddfd20a Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Wed, 11 Mar 2026 20:28:26 +0200 Subject: [PATCH 3/7] Add PixelGeometry to ContextHandler.DrawScope --- .../skiko/context/AngleContextHandler.kt | 2 +- .../skiko/context/Direct3DContextHandler.kt | 2 +- .../skiko/context/JvmContextHandler.kt | 1 + .../skiko/context/MetalContextHandler.kt | 2 +- .../skiko/context/OpenGLContextHandler.kt | 2 +- .../skiko/context/SoftwareContextHandler.kt | 2 +- .../jetbrains/skiko/context/ContextHandler.kt | 19 ++++++++++++++++--- .../context/MetalContextHandler.macos.kt | 1 + .../context/OpenGLContextHandler.macos.kt | 1 + 9 files changed, 24 insertions(+), 8 deletions(-) diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt index af55ef463..9fe62e3fd 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt @@ -40,7 +40,7 @@ internal class AngleContextHandler(layer: SkiaLayer) : ContextBasedContextHandle SurfaceOrigin.BOTTOM_LEFT, SurfaceColorFormat.RGBA_8888, ColorSpace.sRGB, - SurfaceProps(pixelGeometry = layer.pixelGeometry) + SurfaceProps(pixelGeometry = pixelGeometry) ) ?: throw RenderException("Cannot create surface") } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt index 275b9406b..7f9fbb381 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt @@ -45,7 +45,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan val justInitialized = directXRedrawer.changeSize(width, height) try { - val surfaceProps = SurfaceProps(pixelGeometry = layer.pixelGeometry) + val surfaceProps = SurfaceProps(pixelGeometry = pixelGeometry) for (bufferIndex in 0 until bufferCount) { surfaces[bufferIndex] = directXRedrawer.makeSurface( context = getPtr(context), diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt index 0f691e4aa..ddfc31f24 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt @@ -6,6 +6,7 @@ import org.jetbrains.skiko.SkiaLayer internal abstract class JvmContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) { override fun createDrawScope() = DrawScope( + pixelGeometry = layer.pixelGeometry, layerWidth = layer.width, layerHeight = layer.height, scale = layer.contentScale diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt index a3b1523b3..4a1374375 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt @@ -35,7 +35,7 @@ internal class MetalContextHandler( SurfaceOrigin.TOP_LEFT, SurfaceColorFormat.BGRA_8888, ColorSpace.sRGB, - SurfaceProps(pixelGeometry = layer.pixelGeometry) + SurfaceProps(pixelGeometry = pixelGeometry) ) ?: throw RenderException("Cannot create surface") canvas = surface!!.canvas diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt index 4c951863c..d8786d389 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt @@ -40,7 +40,7 @@ internal class OpenGLContextHandler(layer: SkiaLayer) : ContextBasedContextHandl SurfaceOrigin.BOTTOM_LEFT, SurfaceColorFormat.RGBA_8888, ColorSpace.sRGB, - SurfaceProps(pixelGeometry = layer.pixelGeometry) + SurfaceProps(pixelGeometry = pixelGeometry) ) ?: throw RenderException("Cannot create surface") } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt index 2285c3c52..76d453568 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt @@ -32,7 +32,7 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHand storage.allocPixelsFlags(ImageInfo.makeS32(w, h, ColorAlphaType.PREMUL), false) } - canvas = Canvas(storage, SurfaceProps(pixelGeometry = layer.pixelGeometry)) + canvas = Canvas(storage, SurfaceProps(pixelGeometry = pixelGeometry)) } override fun flush() { diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt index abd9ad2a2..290141909 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt @@ -70,14 +70,27 @@ internal abstract class ContextHandler( } inner class DrawScope( + val pixelGeometry: PixelGeometry, val scaledLayerWidth: Int, - val scaledLayerHeight: Int + val scaledLayerHeight: Int, ) { - constructor(layerWidth: Int, layerHeight: Int, scale: Float): this( + constructor( + pixelGeometry: PixelGeometry, + layerWidth: Int, + layerHeight: Int, + scale: Float + ): this( + pixelGeometry = pixelGeometry, scaledLayerWidth = (layerWidth * scale).toInt().coerceAtLeast(0), scaledLayerHeight = (layerHeight * scale).toInt().coerceAtLeast(0) ) - constructor(layerWidth: Double, layerHeight: Double, scale: Float): this( + constructor( + pixelGeometry: PixelGeometry, + layerWidth: Double, + layerHeight: Double, + scale: Float + ): this( + pixelGeometry = pixelGeometry, scaledLayerWidth = (layerWidth * scale).toInt().coerceAtLeast(0), scaledLayerHeight = (layerHeight * scale).toInt().coerceAtLeast(0) ) diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt index dd32bf77f..3cc918721 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt @@ -26,6 +26,7 @@ internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer } override fun createDrawScope() = DrawScope( + pixelGeometry = layer.pixelGeometry, layerWidth = layer.nsView.frame.useContents { size.width }, layerHeight = layer.nsView.frame.useContents { size.height }, scale = layer.contentScale diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt index 4183c8d3e..aea56c6a3 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt @@ -40,6 +40,7 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye } override fun createDrawScope() = DrawScope( + pixelGeometry = layer.pixelGeometry, layerWidth = layer.nsView.frame.useContents { size.width }, layerHeight = layer.nsView.frame.useContents { size.height }, scale = layer.contentScale From 7646a928af377d818e5aa5fea39e41409ccfd5b4 Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Wed, 11 Mar 2026 20:32:03 +0200 Subject: [PATCH 4/7] Minor KDoc change --- .../org/jetbrains/skiko/context/Direct3DContextHandler.kt | 1 - .../kotlin/org/jetbrains/skiko/context/ContextHandler.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt index 7f9fbb381..6b8d1fa72 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt @@ -4,7 +4,6 @@ import org.jetbrains.skia.DirectContext import org.jetbrains.skia.Surface import org.jetbrains.skia.SurfaceProps import org.jetbrains.skia.impl.getPtr -import org.jetbrains.skiko.Logger import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.redrawer.Direct3DRedrawer import java.lang.ref.Reference diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt index 290141909..2771045a3 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt @@ -41,7 +41,7 @@ internal abstract class ContextHandler( /** * Reads layer properties, creating a [DrawScope] in which [DrawScope.contextHandlerDraw] can later be called on a - * background thread. + * render thread. * * This function should be called only in a thread where it is valid to access layer properties. */ From 073fe10980e50103f51dcc2c0750d3493e64d399 Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Thu, 12 Mar 2026 16:41:37 +0200 Subject: [PATCH 5/7] Move ContextHandler draw scope definition and creation to `SkiaLayer` --- .../org/jetbrains/skiko/SkiaLayer.awt.kt | 15 ++++- .../skiko/context/AngleContextHandler.kt | 3 +- .../skiko/context/Direct3DContextHandler.kt | 3 +- .../context/DirectSoftwareContextHandler.kt | 3 +- .../skiko/context/JvmContextHandler.kt | 8 --- .../skiko/context/MetalContextHandler.kt | 3 +- .../skiko/context/OpenGLContextHandler.kt | 2 +- .../skiko/context/SoftwareContextHandler.kt | 3 +- .../jetbrains/skiko/redrawer/AWTRedrawer.kt | 2 +- .../AbstractDirectSoftwareRedrawer.kt | 2 +- .../jetbrains/skiko/redrawer/AngleRedrawer.kt | 4 +- .../skiko/redrawer/Direct3DRedrawer.kt | 15 ++--- .../skiko/redrawer/LinuxOpenGLRedrawer.kt | 2 +- .../jetbrains/skiko/redrawer/MetalRedrawer.kt | 21 +++---- .../skiko/redrawer/SoftwareRedrawer.kt | 2 +- .../skiko/redrawer/WindowsOpenGLRedrawer.kt | 2 +- .../org/jetbrains/skiko/SkiaLayerTest.kt | 4 +- .../kotlin/org/jetbrains/skiko/SkiaLayer.kt | 32 +++++++++- .../jetbrains/skiko/context/ContextHandler.kt | 58 +------------------ .../org/jetbrains/skiko/SkiaLayer.macos.kt | 13 +++++ .../context/MetalContextHandler.macos.kt | 11 +--- .../context/OpenGLContextHandler.macos.kt | 10 +--- .../skiko/redrawer/MetalRedrawer.macos.kt | 12 +++- .../skiko/redrawer/OpenGLRedrawer.macos.kt | 4 +- 24 files changed, 109 insertions(+), 125 deletions(-) diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt index cbfeccd90..79d997ff0 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt @@ -645,13 +645,22 @@ actual open class SkiaLayer internal constructor( @Suppress("LeakingThis") private val fpsCounter = defaultFPSCounter(this) - internal inline fun inDrawScope(body: () -> Unit) { + private fun createDrawScope() = LayerDrawScope( + pixelGeometry = pixelGeometry, + layerWidth = width, + layerHeight = height, + scale = contentScale + ) + + internal inline fun inDrawScope(body: LayerDrawScope.() -> Unit) { check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" } check(!isDisposed) { "SkiaLayer is disposed" } try { fpsCounter?.tick() - body() - } catch (e: CancellationException) { + with(createDrawScope()) { + body() + } + } catch (_: CancellationException) { // ignore } catch (e: RenderException) { if (!isDisposed) { diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt index 9fe62e3fd..211fadee7 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/AngleContextHandler.kt @@ -2,6 +2,7 @@ package org.jetbrains.skiko.context import org.jetbrains.skia.* import org.jetbrains.skiko.AngleApi +import org.jetbrains.skiko.LayerDrawScope import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.redrawer.AngleRedrawer @@ -23,7 +24,7 @@ internal class AngleContextHandler(layer: SkiaLayer) : ContextBasedContextHandle return false } - override fun DrawScope.initCanvas() { + override fun LayerDrawScope.initCanvas() { val context = context ?: return val w = scaledLayerWidth diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt index 6b8d1fa72..9c16a1c53 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt @@ -4,6 +4,7 @@ import org.jetbrains.skia.DirectContext import org.jetbrains.skia.Surface import org.jetbrains.skia.SurfaceProps import org.jetbrains.skia.impl.getPtr +import org.jetbrains.skiko.LayerDrawScope import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.redrawer.Direct3DRedrawer import java.lang.ref.Reference @@ -29,7 +30,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan return false } - override fun DrawScope.initCanvas() { + override fun LayerDrawScope.initCanvas() { val context = context ?: return // Direct3D can't work with zero size. diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt index 4484c0272..ebeb8a916 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt @@ -1,6 +1,7 @@ package org.jetbrains.skiko.context import org.jetbrains.skia.impl.getPtr +import org.jetbrains.skiko.LayerDrawScope import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.redrawer.AbstractDirectSoftwareRedrawer import java.lang.ref.Reference @@ -20,7 +21,7 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextFreeConte return false } - override fun DrawScope.initCanvas() { + override fun LayerDrawScope.initCanvas() { val w = scaledLayerWidth val h = scaledLayerHeight if (isSizeChanged(w, h) || surface == null) { diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt index ddfc31f24..624f75b75 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/JvmContextHandler.kt @@ -4,14 +4,6 @@ import org.jetbrains.skiko.Logger import org.jetbrains.skiko.SkiaLayer internal abstract class JvmContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) { - - override fun createDrawScope() = DrawScope( - pixelGeometry = layer.pixelGeometry, - layerWidth = layer.width, - layerHeight = layer.height, - scale = layer.contentScale - ) - protected fun onContextInitialized() { if (System.getProperty("skiko.hardwareInfo.enabled") == "true") { Logger.info { "Renderer info:\n ${rendererInfo()}" } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt index 4a1374375..50ca194d1 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt @@ -1,6 +1,7 @@ package org.jetbrains.skiko.context import org.jetbrains.skia.* +import org.jetbrains.skiko.LayerDrawScope import org.jetbrains.skiko.Logger import org.jetbrains.skiko.MetalAdapter import org.jetbrains.skiko.RenderException @@ -20,7 +21,7 @@ internal class MetalContextHandler( private val device: MetalDevice, private val adapter: MetalAdapter ) : ContextBasedContextHandler(layer, "Metal") { - override fun DrawScope.initCanvas() { + override fun LayerDrawScope.initCanvas() { disposeCanvas() val width = scaledLayerWidth diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt index d8786d389..86c95d9eb 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.kt @@ -18,7 +18,7 @@ internal class OpenGLContextHandler(layer: SkiaLayer) : ContextBasedContextHandl return false } - override fun DrawScope.initCanvas() { + override fun LayerDrawScope.initCanvas() { val w = scaledLayerWidth val h = scaledLayerHeight diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt index 76d453568..81276f115 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt @@ -1,6 +1,7 @@ package org.jetbrains.skiko.context import org.jetbrains.skia.* +import org.jetbrains.skiko.LayerDrawScope import org.jetbrains.skiko.OS import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.hostOs @@ -22,7 +23,7 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHand var imageData: ByteArray? = null var raster: WritableRaster? = null - override fun DrawScope.initCanvas() { + override fun LayerDrawScope.initCanvas() { disposeCanvas() val w = scaledLayerWidth diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AWTRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AWTRedrawer.kt index a8a91fb09..dc4e2d36d 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AWTRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AWTRedrawer.kt @@ -54,7 +54,7 @@ internal abstract class AWTRedrawer( layer.update(nanoTime) } - protected inline fun inDrawScope(body: () -> Unit) { + protected inline fun inDrawScope(body: LayerDrawScope.() -> Unit) { requireNotNull(deviceAnalytics) { "deviceAnalytics is not null. Call onDeviceChosen after choosing the drawing device" } if (!isDisposed) { val isFirstFrame = !isFirstFrameRendered diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AbstractDirectSoftwareRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AbstractDirectSoftwareRedrawer.kt index 75767b770..c40ad5332 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AbstractDirectSoftwareRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AbstractDirectSoftwareRedrawer.kt @@ -33,7 +33,7 @@ internal abstract class AbstractDirectSoftwareRedrawer( frameDispatcher.scheduleFrame() } - protected open fun draw() = inDrawScope(contextHandler::draw) + protected open fun draw() = inDrawScope { contextHandler.draw() } override fun renderImmediately() { update() diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AngleRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AngleRedrawer.kt index 4dc8064ce..a96e84064 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AngleRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/AngleRedrawer.kt @@ -89,7 +89,9 @@ internal class AngleRedrawer( return } makeCurrent(device) - contextHandler.draw() + layer.inDrawScope { + contextHandler.draw() + } swapBuffers(device, withVsync) } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/Direct3DRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/Direct3DRedrawer.kt index ce89330d9..7de1f29cb 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/Direct3DRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/Direct3DRedrawer.kt @@ -7,7 +7,6 @@ import org.jetbrains.skia.SurfaceProps import org.jetbrains.skia.impl.InteropPointer import org.jetbrains.skia.impl.interopScope import org.jetbrains.skiko.* -import org.jetbrains.skiko.context.ContextHandler import org.jetbrains.skiko.context.Direct3DContextHandler internal class Direct3DRedrawer( @@ -74,28 +73,24 @@ internal class Direct3DRedrawer( update() inDrawScope { if (!isDisposed) { // Redrawer may be disposed in user code, during `update` - contextHandler.inDrawScope { - drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately) - } + drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately) } } } private suspend fun draw() { inDrawScope { - contextHandler.inDrawScope { - withContext(dispatcherToBlockOn) { - drawAndSwap(withVsync = properties.isVsyncEnabled) - } + withContext(dispatcherToBlockOn) { + drawAndSwap(withVsync = properties.isVsyncEnabled) } } } - private fun ContextHandler.DrawScope.drawAndSwap(withVsync: Boolean) = synchronized(drawLock) { + private fun LayerDrawScope.drawAndSwap(withVsync: Boolean) = synchronized(drawLock) { if (isDisposed) { return } - contextHandlerDraw() + contextHandler.draw() swap(withVsync) } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/LinuxOpenGLRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/LinuxOpenGLRedrawer.kt index 895cc7163..b05cecea3 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/LinuxOpenGLRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/LinuxOpenGLRedrawer.kt @@ -97,7 +97,7 @@ internal class LinuxOpenGLRedrawer( } private fun draw() { - inDrawScope(contextHandler::draw) + inDrawScope { contextHandler.draw() } } companion object { diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt index 38fcb3057..c09c08f61 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.kt @@ -3,7 +3,6 @@ package org.jetbrains.skiko.redrawer import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import org.jetbrains.skiko.* -import org.jetbrains.skiko.context.ContextHandler import org.jetbrains.skiko.context.MetalContextHandler import java.util.concurrent.atomic.AtomicBoolean import javax.swing.SwingUtilities.* @@ -105,9 +104,7 @@ internal class MetalRedrawer( update() inDrawScope { if (!isDisposed) { // Redrawer may be disposed in user code, during `update` - contextHandler.inDrawScope { - performDraw() - } + performDraw() // Trying to draw immediately in Metal will result in lost (undrawn) // frames if there are more than two between consecutive vsync events. if (SkikoProperties.macOSWaitForPreviousFrameVsyncOnRedrawImmediately) { @@ -121,13 +118,11 @@ internal class MetalRedrawer( private suspend fun draw() { inDrawScope { - contextHandler.inDrawScope { - // Move drawing to another thread to free the main thread - // It can be expensive to run it in the main thread and FPS can become unstable. - // This is visible by running [SkiaLayerPerformanceTest], standard deviation is increased significantly. - withContext(dispatcherToBlockOn) { - performDraw() - } + // Move drawing to another thread to free the main thread + // It can be expensive to run it in the main thread and FPS can become unstable. + // This is visible by running [SkiaLayerPerformanceTest], standard deviation is increased significantly. + withContext(dispatcherToBlockOn) { + performDraw() } } if (isDisposed) throw CancellationException() @@ -149,10 +144,10 @@ internal class MetalRedrawer( windowOcclusionStateChannel.trySend(isOccluded) } - private fun ContextHandler.DrawScope.performDraw() = synchronized(drawLock) { + private fun LayerDrawScope.performDraw() = synchronized(drawLock) { if (!isDisposed) { autoreleasepool { - contextHandlerDraw() + contextHandler.draw() } } } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/SoftwareRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/SoftwareRedrawer.kt index 38c1815a7..d54ef6bee 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/SoftwareRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/SoftwareRedrawer.kt @@ -27,7 +27,7 @@ internal class SoftwareRedrawer( if (layer.isShowing) { update() - inDrawScope(contextHandler::draw) + inDrawScope { contextHandler.draw() } } } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/WindowsOpenGLRedrawer.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/WindowsOpenGLRedrawer.kt index 063dc3d2f..dbc2a7bce 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/WindowsOpenGLRedrawer.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/redrawer/WindowsOpenGLRedrawer.kt @@ -78,7 +78,7 @@ internal class WindowsOpenGLRedrawer( } private fun draw() { - inDrawScope(contextHandler::draw) + inDrawScope { contextHandler.draw() } } private fun makeCurrent() = makeCurrent(device, context) diff --git a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt index 004c86d92..2cf6fb66d 100644 --- a/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt +++ b/skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt @@ -578,9 +578,9 @@ class SkiaLayerTest { object : BaseTestRedrawer(layer) { private val contextHandler = object : JvmContextHandler(layer) { override fun initContext() = false - override fun DrawScope.initCanvas() = Unit + override fun LayerDrawScope.initCanvas() = Unit } - override fun renderImmediately() = layer.inDrawScope(contextHandler::draw) + override fun renderImmediately() = layer.inDrawScope { contextHandler.draw() } } } } diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt index 272faf22c..6a73a20fd 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt @@ -3,6 +3,7 @@ package org.jetbrains.skiko import org.jetbrains.skia.Canvas import org.jetbrains.skia.Picture import org.jetbrains.skia.PixelGeometry +import org.jetbrains.skiko.context.ContextHandler /** * Generic layer for Skiko rendering. @@ -70,6 +71,35 @@ expect open class SkiaLayer { internal fun draw(canvas: Canvas) } - internal class PictureHolder(val instance: Picture, val width: Int, val height: Int) +class LayerDrawScope( + val pixelGeometry: PixelGeometry, + val scaledLayerWidth: Int, + val scaledLayerHeight: Int, +) { + constructor( + pixelGeometry: PixelGeometry, + layerWidth: Int, + layerHeight: Int, + scale: Float + ): this( + pixelGeometry = pixelGeometry, + scaledLayerWidth = (layerWidth * scale).toInt().coerceAtLeast(0), + scaledLayerHeight = (layerHeight * scale).toInt().coerceAtLeast(0) + ) + constructor( + pixelGeometry: PixelGeometry, + layerWidth: Double, + layerHeight: Double, + scale: Float + ): this( + pixelGeometry = pixelGeometry, + scaledLayerWidth = (layerWidth * scale).toInt().coerceAtLeast(0), + scaledLayerHeight = (layerHeight * scale).toInt().coerceAtLeast(0) + ) + + internal fun ContextHandler.draw() { + this@LayerDrawScope.draw() + } +} \ No newline at end of file diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt index 2771045a3..44eecd37b 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt @@ -13,7 +13,7 @@ internal abstract class ContextHandler( protected var canvas: Canvas? = null protected abstract fun initContext(): Boolean - protected abstract fun DrawScope.initCanvas() + protected abstract fun LayerDrawScope.initCanvas() protected open fun flush() { context?.flush() @@ -34,30 +34,8 @@ internal abstract class ContextHandler( "OS: ${hostOs.id} ${hostArch.id}\n" } - /** - * This function will be called only in a thread where it is valid to access layer properties. - */ - protected abstract fun createDrawScope(): DrawScope - - /** - * Reads layer properties, creating a [DrawScope] in which [DrawScope.contextHandlerDraw] can later be called on a - * render thread. - * - * This function should be called only in a thread where it is valid to access layer properties. - */ - inline fun inDrawScope(block: DrawScope.() -> Unit) { - createDrawScope().block() - } - - /** - * This function should be called only in a thread where it is valid to access layer properties. - */ - fun draw() { - createDrawScope().contextHandlerDraw() - } - // throws RenderException if initialization of graphic context was not successful - private fun DrawScope.drawImpl() { + fun LayerDrawScope.draw() { if (!initContext()) { throw RenderException("Cannot init graphic context") } @@ -68,35 +46,5 @@ internal abstract class ContextHandler( } flush() } - - inner class DrawScope( - val pixelGeometry: PixelGeometry, - val scaledLayerWidth: Int, - val scaledLayerHeight: Int, - ) { - constructor( - pixelGeometry: PixelGeometry, - layerWidth: Int, - layerHeight: Int, - scale: Float - ): this( - pixelGeometry = pixelGeometry, - scaledLayerWidth = (layerWidth * scale).toInt().coerceAtLeast(0), - scaledLayerHeight = (layerHeight * scale).toInt().coerceAtLeast(0) - ) - constructor( - pixelGeometry: PixelGeometry, - layerWidth: Double, - layerHeight: Double, - scale: Float - ): this( - pixelGeometry = pixelGeometry, - scaledLayerWidth = (layerWidth * scale).toInt().coerceAtLeast(0), - scaledLayerHeight = (layerHeight * scale).toInt().coerceAtLeast(0) - ) - - fun contextHandlerDraw() { - drawImpl() - } - } } + diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/SkiaLayer.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/SkiaLayer.macos.kt index 498b81236..09d47d784 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/SkiaLayer.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/SkiaLayer.macos.kt @@ -168,4 +168,17 @@ actual open class SkiaLayer { actual val pixelGeometry: PixelGeometry get() = PixelGeometry.UNKNOWN + + private fun createDrawScope() = LayerDrawScope( + pixelGeometry = pixelGeometry, + layerWidth = nsView.frame.useContents { size.width }, + layerHeight = nsView.frame.useContents { size.height }, + scale = contentScale + ) + + internal fun inDrawScope(block: LayerDrawScope.() -> Unit) { + with(createDrawScope()) { + block() + } + } } diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt index 3cc918721..178df5b3a 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt @@ -1,7 +1,7 @@ package org.jetbrains.skiko.context -import kotlinx.cinterop.useContents import org.jetbrains.skia.* +import org.jetbrains.skiko.LayerDrawScope import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.redrawer.MacOsMetalRedrawer @@ -25,14 +25,7 @@ internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer return true } - override fun createDrawScope() = DrawScope( - pixelGeometry = layer.pixelGeometry, - layerWidth = layer.nsView.frame.useContents { size.width }, - layerHeight = layer.nsView.frame.useContents { size.height }, - scale = layer.contentScale - ) - - override fun DrawScope.initCanvas() { + override fun LayerDrawScope.initCanvas() { disposeCanvas() val w = scaledLayerWidth diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt index aea56c6a3..c37527701 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/OpenGLContextHandler.macos.kt @@ -3,6 +3,7 @@ package org.jetbrains.skiko.context import kotlinx.cinterop.* import org.jetbrains.skia.* import org.jetbrains.skiko.GraphicsApi +import org.jetbrains.skiko.LayerDrawScope import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.SkiaLayer import platform.OpenGL.GL_DRAW_FRAMEBUFFER_BINDING @@ -39,13 +40,6 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye return result } - override fun createDrawScope() = DrawScope( - pixelGeometry = layer.pixelGeometry, - layerWidth = layer.nsView.frame.useContents { size.width }, - layerHeight = layer.nsView.frame.useContents { size.height }, - scale = layer.contentScale - ) - private var currentWidth = 0 private var currentHeight = 0 private fun isSizeChanged(width: Int, height: Int): Boolean { @@ -57,7 +51,7 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye return false } - override fun DrawScope.initCanvas() { + override fun LayerDrawScope.initCanvas() { val w = scaledLayerWidth val h = scaledLayerHeight if (isSizeChanged(w, h)) { diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.macos.kt index 4d682aea7..2bfbe7110 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/redrawer/MetalRedrawer.macos.kt @@ -157,7 +157,9 @@ internal class MacOsMetalRedrawer( update() } if (!isDisposed) { // Redrawer may be disposed in user code, during `update` - contextHandler.draw() + skiaLayer.inDrawScope { + contextHandler.draw() + } } } } @@ -166,7 +168,9 @@ internal class MacOsMetalRedrawer( autoreleasepool { if (!isDisposed) { update() - contextHandler.draw() + skiaLayer.inDrawScope { + contextHandler.draw() + } } } @@ -233,6 +237,8 @@ internal class MetalLayer : CAMetalLayer { override fun drawInContext(ctx: CGContextRef?) { skiaLayer.update(currentNanoTime()) - contextHandler.draw() + skiaLayer.inDrawScope { + contextHandler.draw() + } } } diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/redrawer/OpenGLRedrawer.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/redrawer/OpenGLRedrawer.macos.kt index 44f8d09fb..477264e8a 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/redrawer/OpenGLRedrawer.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/redrawer/OpenGLRedrawer.macos.kt @@ -133,7 +133,9 @@ internal class MacosGLLayer : CAOpenGLLayer { CGLSetCurrentContext(ctx) try { skiaLayer.update(currentNanoTime()) - contextHandler.draw() + skiaLayer.inDrawScope { + contextHandler.draw() + } } catch (e: Throwable) { e.printStackTrace() throw e From bf72143403316da3ef4612bbae703757d91a26a9 Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Thu, 12 Mar 2026 16:48:59 +0200 Subject: [PATCH 6/7] Add LayerDrawScope context to ContextHandler.flush too --- .../org/jetbrains/skiko/context/Direct3DContextHandler.kt | 2 +- .../skiko/context/DirectSoftwareContextHandler.kt | 2 +- .../org/jetbrains/skiko/context/MetalContextHandler.kt | 4 ++-- .../org/jetbrains/skiko/context/SoftwareContextHandler.kt | 8 +++----- .../kotlin/org/jetbrains/skiko/context/ContextHandler.kt | 4 ++-- .../jetbrains/skiko/context/MetalContextHandler.macos.kt | 4 ++-- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt index 9c16a1c53..82771efa9 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/Direct3DContextHandler.kt @@ -67,7 +67,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan canvas = surface!!.canvas } - override fun flush() { + override fun flush(scope: LayerDrawScope) { val context = context ?: return val surface = surface ?: return try { diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt index ebeb8a916..a30cb9ff2 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/DirectSoftwareContextHandler.kt @@ -37,7 +37,7 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextFreeConte } } - override fun flush() { + override fun flush(scope: LayerDrawScope) { val surface = surface if (surface != null) { try { diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt index 50ca194d1..8c46415e0 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.kt @@ -47,8 +47,8 @@ internal class MetalContextHandler( } } - override fun flush() { - super.flush() + override fun flush(scope: LayerDrawScope) { + super.flush(scope) surface?.flushAndSubmit() finishFrame() Logger.debug { "MetalContextHandler finished drawing frame" } diff --git a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt index 81276f115..21e271de0 100644 --- a/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt +++ b/skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt @@ -36,11 +36,9 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHand canvas = Canvas(storage, SurfaceProps(pixelGeometry = pixelGeometry)) } - override fun flush() { - val scale = layer.contentScale - val w = (layer.width * scale).toInt().coerceAtLeast(0) - val h = (layer.height * scale).toInt().coerceAtLeast(0) - + override fun flush(scope: LayerDrawScope) { + val w = scope.scaledLayerWidth + val h = scope.scaledLayerHeight val bytes = storage.readPixels(storage.imageInfo, (w * 4), 0, 0) if (bytes != null) { diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt index 44eecd37b..43d20c155 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt @@ -15,7 +15,7 @@ internal abstract class ContextHandler( protected abstract fun initContext(): Boolean protected abstract fun LayerDrawScope.initCanvas() - protected open fun flush() { + protected open fun flush(scope: LayerDrawScope) { context?.flush() } @@ -44,7 +44,7 @@ internal abstract class ContextHandler( clear(Color.TRANSPARENT) drawContent() } - flush() + flush(this) } } diff --git a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt index 178df5b3a..4a54d11f9 100644 --- a/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt +++ b/skiko/src/macosMain/kotlin/org/jetbrains/skiko/context/MetalContextHandler.macos.kt @@ -51,9 +51,9 @@ internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer } } - override fun flush() { + override fun flush(scope: LayerDrawScope) { // TODO: maybe make flush async as in JVM version. - super.flush() + super.flush(scope) surface?.flushAndSubmit() metalRedrawer.finishFrame() } From ca9d5742115712df7e0265f5b2cd0de68801127a Mon Sep 17 00:00:00 2001 From: Alexander Maryanovsky Date: Thu, 12 Mar 2026 17:31:12 +0200 Subject: [PATCH 7/7] Make LayerDrawScope internal --- skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt index 6a73a20fd..4008c9e8c 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt @@ -73,7 +73,7 @@ expect open class SkiaLayer { internal class PictureHolder(val instance: Picture, val width: Int, val height: Int) -class LayerDrawScope( +internal class LayerDrawScope( val pixelGeometry: PixelGeometry, val scaledLayerWidth: Int, val scaledLayerHeight: Int,