Ivan Matkov 244b99fa8c AWT: replace the per-backend Redrawer hierarchy with one generic frame loop
With each backend collapsed into a single render context, the AWT Redrawer
subclasses had nothing left to abstract: every one ran the same
schedule-coalesce-draw-present loop and differed only in which GPU objects
it touched. That duplicated loop is where the frame-scheduling bugs lived.

Extract one generic on-screen frame loop, OnScreenRedrawer, and make it the
Redrawer the SkiaLayer path drives: it wraps a per-API backend and calls its
hooks. AWTRedrawer stops being an abstract base class each backend extended
and becomes the interface those backends implement -- native device and
swap-chain lifecycle, the Skia DirectContext and the frame's surface,
present, and the backend's own pacing.

The RenderFactory/Redrawer seam is unchanged: RenderFactory still produces a
Redrawer and SkiaLayer still selects and paces it through RedrawerManager,
exactly as before. AWT's makeDefaultRenderFactory now builds
OnScreenRedrawer(backend) instead of a monolithic backend -- the only change
to the factory. SkiaLayer itself is untouched.

Not every frame is skiko's to schedule. During a platform-driven
presentation session -- macOS live resize, where AppKit presents
synchronously on its own thread inside a CATransaction -- scheduling frames
through the loop's dispatcher races the OS and glitches. So the interface
carries interceptFrameScheduling(): while a context reports such a session,
the loop stops scheduling and routes each redraw request to
onFrameSchedulingIntercepted() instead, letting that context present through
its own path.

The AWT backends keep their `*Redrawer` class names. Several are JNI symbol
names mangled into the native sources
(Java_org_jetbrains_skiko_redrawer_MetalRedrawer_*, among others), so
renaming the Kotlin class alone would silently unbind them. A later change
renames the family and its JNI symbols together.
2026-07-20 14:19:12 +02:00
2022-12-20 11:45:54 +01:00
2022-11-24 16:28:30 +01:00
2020-07-27 13:27:14 +03:00
2025-01-30 13:52:29 +01:00

official project version

Kotlin Multiplatform library for Skia and window management

Skiko (short for Skia for Kotlin) is the graphical library exposing significant part of Skia library APIs to Kotlin, along with the gluing code for rendering context.

Supported platforms:

  • Kotlin/JVM on Linux(x86_64 and arm64)
  • Kotlin/JVM on Windows(x86_64)
  • Kotlin/JVM on macOS(x86_64 and arm64)
  • Kotlin/JVM on Android(x86_64 and arm64), starting with API version 24
  • Kotlin/JS + WebAssembly in browsers
  • Kotlin/Native on iOS(arm64 and x64)
  • Kotlin/Native on macOS (arm64 and x64)

API documentation

See autogenerated API docs at https://jetbrains.github.io/skiko/

Using as dependency

To use in build scripts one has to compute appropriate target platform and version, i.e. something like this

    repositories {
        mavenCentral()
        maven("https://redirector.kotlinlang.org/maven/compose-dev")
    }

    val osName = System.getProperty("os.name")
    val targetOs = when {
        osName == "Mac OS X" -> "macos"
        osName.startsWith("Win") -> "windows"
        osName.startsWith("Linux") -> "linux"
        else -> error("Unsupported OS: $osName")
    }

    val osArch = System.getProperty("os.arch")
    val targetArch = when (osArch) {
        "x86_64", "amd64" -> "x64"
        "aarch64" -> "arm64"
        else -> error("Unsupported arch: $osArch")
    }

    val version = "0.8.9" // or any more recent version
    val target = "${targetOs}-${targetArch}"
    dependencies {
        implementation("org.jetbrains.skiko:skiko-awt-runtime-$target:$version")
    }

Simple example for Kotlin/JVM

fun main() {
    val skiaLayer = SkiaLayer()
    skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
        val paint = Paint().apply {
            color = Color.RED
        }
        override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
            canvas.clear(Color.CYAN)
            val ts = nanoTime / 5_000_000
            canvas.drawCircle( (ts % width).toFloat(), (ts % height).toFloat(), 20f, paint )
        }
    })
    SwingUtilities.invokeLater {
        val window = JFrame("Skiko example").apply {
            defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
            preferredSize = Dimension(800, 600)
        }
        skiaLayer.attachTo(window.contentPane)
        skiaLayer.needRedraw()
        window.pack()
        window.isVisible = true
    }
}

Simple example for iOS

fun main() {
    val args = emptyArray<String>()
    memScoped {
        val argc = args.size + 1
        val argv = (arrayOf("skikoApp") + args).map { it.cstr.ptr }.toCValues()
        autoreleasepool {
            UIApplicationMain(argc, argv, null, NSStringFromClass(SkikoAppDelegate))
        }
    }
}

class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
    companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta

    @ObjCObjectBase.OverrideInit
    constructor() : super()

    private var _window: UIWindow? = null
    override fun window() = _window
    override fun setWindow(window: UIWindow?) {
        _window = window
    }

    override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
        window = UIWindow(frame = UIScreen.mainScreen.bounds)
        window!!.rootViewController = SkikoViewController(
            SkikoUIView(
                SkiaLayer().apply {
                    renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
                      val paint = Paint().apply { color = Color.RED }
                      override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
                        canvas.clear(Color.CYAN)
                        val ts = nanoTime / 5_000_000
                        canvas.drawCircle( (ts % width).toFloat(), (ts % height).toFloat(), 20f, paint )
                      }
                    })
                }
            )
        )
        window!!.makeKeyAndVisible()
        return true
    }
}

See this sample for complete example.

S
Description
No description provided
Readme Apache-2.0
42 MiB
Languages
Kotlin 50%
C++ 48.1%
Objective-C++ 1.2%
Dockerfile 0.3%
JavaScript 0.2%