mirror of
https://github.com/bitsycore/skiko.git
synced 2026-10-05 14:37:26 +00:00
ivan.matkov/awt-render-context-collapse
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.
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.
Languages
Kotlin
50%
C++
48.1%
Objective-C++
1.2%
Dockerfile
0.3%
JavaScript
0.2%