Unverified Commit a2eed5a7 authored by Nikolay Igotti's avatar Nikolay Igotti Committed by GitHub

macos dispatcher (#295)

parent bbd1c31e
...@@ -6,9 +6,11 @@ import kotlin.math.PI ...@@ -6,9 +6,11 @@ import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
class BouncingBalls: SkikoView { class BouncingBalls(private val withFps: Boolean = false): SkikoView {
private data class Circle(var x: Float, var y: Float, var r: Float) private data class Circle(var x: Float, var y: Float, var r: Float)
private val fpsCounter = FPSCounter(2.0, true)
companion object { companion object {
private fun moveCircle(c: Circle, s: Float, angle: Float, width: Int, height: Int, r: Float) { private fun moveCircle(c: Circle, s: Float, angle: Float, width: Int, height: Int, r: Float) {
c.x = (c.x + s * sin(angle)).coerceAtLeast(r).coerceAtMost(width.toFloat() - r) c.x = (c.x + s * sin(angle)).coerceAtLeast(r).coerceAtMost(width.toFloat() - r)
...@@ -85,6 +87,8 @@ class BouncingBalls: SkikoView { ...@@ -85,6 +87,8 @@ class BouncingBalls: SkikoView {
)).toMutableList() )).toMutableList()
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
if (withFps) fpsCounter.tick()
canvas.clear(-1) canvas.clear(-1)
val dtime = (nanoTime - prevTimestamp) val dtime = (nanoTime - prevTimestamp)
prevTimestamp = nanoTime prevTimestamp = nanoTime
......
...@@ -7,7 +7,7 @@ import org.jetbrains.skiko.* ...@@ -7,7 +7,7 @@ import org.jetbrains.skiko.*
import kotlinx.cinterop.* import kotlinx.cinterop.*
import platform.Foundation.NSMakeRect import platform.Foundation.NSMakeRect
fun makeApp() = BouncingBalls() fun makeApp() = BouncingBalls(true)
fun main() { fun main() {
NSApplication.sharedApplication() NSApplication.sharedApplication()
......
...@@ -383,14 +383,17 @@ kotlin { ...@@ -383,14 +383,17 @@ kotlin {
} }
} }
if (hostOs == OS.MacOS) { if (hostOs == OS.MacOS) {
val macosMain by creating { val darwinMain by creating {
dependsOn(nativeMain) dependsOn(nativeMain)
} }
val macosMain by creating {
dependsOn(darwinMain)
}
val macosTest by creating { val macosTest by creating {
dependsOn(nativeTest) dependsOn(nativeTest)
} }
val iosMain by creating { val iosMain by creating {
dependsOn(nativeMain) dependsOn(darwinMain)
} }
val iosTest by creating { val iosTest by creating {
dependsOn(nativeTest) dependsOn(nativeTest)
......
package org.jetbrains.skiko package org.jetbrains.skiko
internal expect inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R internal expect inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R
internal expect fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties internal expect fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties
\ No newline at end of file
internal expect fun currentNanoTime(): Long
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import java.awt.Component
import kotlin.math.roundToInt import kotlin.math.roundToInt
internal class FPSCounter( class FPSCounter(
private val periodSeconds: Double, private val periodSeconds: Double,
private val showLongFrames: Boolean, private val showLongFrames: Boolean,
private val getLongFrameMillis: () -> Double private val getLongFrameMillis: () -> Double = {
1.5 * 1000 / 60
}
) { ) {
private val times = mutableListOf<Long>() private val times = mutableListOf<Long>()
private var lastLogTime = System.nanoTime() private var lastLogTime = currentNanoTime()
private var lastTime = System.nanoTime() private var lastTime = currentNanoTime()
fun tick() { fun tick() {
val time = System.nanoTime() val time = currentNanoTime()
val timestamp = time.nanosToMillis().toLong() val timestamp = time.nanosToMillis().toLong()
val frameTime = time - lastTime val frameTime = time - lastTime
lastTime = time lastTime = time
...@@ -21,7 +22,7 @@ internal class FPSCounter( ...@@ -21,7 +22,7 @@ internal class FPSCounter(
times.add(frameTime) times.add(frameTime)
if (showLongFrames && frameTime > getLongFrameMillis().millisToNanos()) { if (showLongFrames && frameTime > getLongFrameMillis().millisToNanos()) {
println("[%d] Long frame %.2f ms".format(timestamp, frameTime.nanosToMillis())) println("$timestamp Long frame ${frameTime.nanosToMillis()} ms")
} }
if ((time - lastLogTime) > periodSeconds.secondsToNanos()) { if ((time - lastLogTime) > periodSeconds.secondsToNanos()) {
...@@ -39,21 +40,4 @@ internal class FPSCounter( ...@@ -39,21 +40,4 @@ internal class FPSCounter(
private fun Long.nanosToMillis(): Double = this / nanosPerMillis private fun Long.nanosToMillis(): Double = this / nanosPerMillis
private fun Double.millisToNanos(): Long = (this * nanosPerMillis).toLong() private fun Double.millisToNanos(): Long = (this * nanosPerMillis).toLong()
private fun Double.secondsToNanos(): Long = (this * nanosPerSecond).toLong() private fun Double.secondsToNanos(): Long = (this * nanosPerSecond).toLong()
}
internal fun defaultFPSCounter(
component: Component
): FPSCounter? = with(SkikoProperties) {
if (!SkikoProperties.fpsEnabled) return@with null
// it is slow on Linux (100ms), so we cache it. Also refreshRate available only after window is visible
val refreshRate by lazy { component.graphicsConfiguration.device.displayMode.refreshRate }
FPSCounter(
periodSeconds = fpsPeriodSeconds,
showLongFrames = fpsLongFramesShow,
getLongFrameMillis = {
fpsLongFramesMillis ?: 1.5 * 1000 / refreshRate
}
)
} }
\ No newline at end of file
...@@ -23,7 +23,7 @@ actual open class SkiaLayer( ...@@ -23,7 +23,7 @@ actual open class SkiaLayer(
set(value) { throw UnsupportedOperationException() } set(value) { throw UnsupportedOperationException() }
actual val contentScale: Float actual val contentScale: Float
get() = view!!.contentScaleFactor?.toFloat() get() = view!!.contentScaleFactor.toFloat()
actual var fullscreen: Boolean actual var fullscreen: Boolean
get() = true get() = true
...@@ -50,9 +50,11 @@ actual open class SkiaLayer( ...@@ -50,9 +50,11 @@ actual open class SkiaLayer(
internal var view: UIView? = null internal var view: UIView? = null
// We need to keep reference to controller as Objective-C will only keep weak reference here. // We need to keep reference to controller as Objective-C will only keep weak reference here.
lateinit private var controller: NSObject lateinit private var controller: NSObject
actual fun attachTo(container: Any) { actual fun attachTo(container: Any) {
attachTo(container as UIView) attachTo(container as UIView)
} }
fun attachTo(view: UIView) { fun attachTo(view: UIView) {
this.view = view this.view = view
contextHandler = MetalContextHandler(this) contextHandler = MetalContextHandler(this)
...@@ -79,8 +81,9 @@ actual open class SkiaLayer( ...@@ -79,8 +81,9 @@ actual open class SkiaLayer(
// We have ':' in selector to take care of function argument. // We have ':' in selector to take care of function argument.
view.addGestureRecognizer(UITapGestureRecognizer(controller, NSSelectorFromString("onTap:"))) view.addGestureRecognizer(UITapGestureRecognizer(controller, NSSelectorFromString("onTap:")))
// TODO: maybe add observer for view.viewDidDisappear() to detach us? // TODO: maybe add observer for view.viewDidDisappear() to detach us?
redrawer = MetalRedrawer(this, properties) redrawer = MetalRedrawer(this, properties).apply {
redrawer?.redrawImmediately() needRedraw()
}
} }
private var isDisposed = false private var isDisposed = false
......
...@@ -36,7 +36,7 @@ internal class MetalRedrawer( ...@@ -36,7 +36,7 @@ internal class MetalRedrawer(
private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) { private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) {
if (layer.isShowing()) { if (layer.isShowing()) {
update(getTimeNanos()) layer.update(getTimeNanos())
draw() draw()
} }
} }
...@@ -49,8 +49,11 @@ internal class MetalRedrawer( ...@@ -49,8 +49,11 @@ internal class MetalRedrawer(
} }
override fun dispose() { override fun dispose() {
frameDispatcher.cancel() if (!isDisposed) {
isDisposed = true frameDispatcher.cancel()
metalLayer.dispose()
isDisposed = true
}
} }
override fun syncSize() { override fun syncSize() {
...@@ -71,14 +74,10 @@ internal class MetalRedrawer( ...@@ -71,14 +74,10 @@ internal class MetalRedrawer(
override fun redrawImmediately() { override fun redrawImmediately() {
check(!isDisposed) { "MetalRedrawer is disposed" } check(!isDisposed) { "MetalRedrawer is disposed" }
update(getTimeNanos()) layer.update(getTimeNanos())
draw() draw()
} }
private fun update(nanoTime: Long) {
layer.update(nanoTime)
}
private fun draw() { private fun draw() {
// TODO: maybe make flush async as in JVM version. // TODO: maybe make flush async as in JVM version.
autoreleasepool { autoreleasepool {
...@@ -127,23 +126,14 @@ class MetalLayer : CAMetalLayer { ...@@ -127,23 +126,14 @@ class MetalLayer : CAMetalLayer {
} }
} }
fun draw() {
skiaLayer.update(getTimeNanos())
skiaLayer.draw()
}
fun dispose() { fun dispose() {
this.removeFromSuperlayer() this.removeFromSuperlayer()
// TODO: anything else to dispose the layer? // TODO: anything else to dispose the layer?
} }
@Suppress("unused")
private fun performDraw() {
draw()
}
override fun drawInContext(ctx: CGContextRef?) { override fun drawInContext(ctx: CGContextRef?) {
draw() skiaLayer.update(getTimeNanos())
skiaLayer.draw()
super.drawInContext(ctx) super.drawInContext(ctx)
} }
} }
...@@ -5,4 +5,6 @@ internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties { ...@@ -5,4 +5,6 @@ internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties {
} }
internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R = internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R =
block() block()
\ No newline at end of file
actual fun currentNanoTime(): Long = (kotlinx.browser.window.performance.now() * 1_000_000).toLong()
\ No newline at end of file
...@@ -5,4 +5,6 @@ internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties { ...@@ -5,4 +5,6 @@ internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties {
} }
internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R = internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R =
synchronized(lock, block) synchronized(lock, block)
\ No newline at end of file
actual fun currentNanoTime(): Long = System.nanoTime()
\ No newline at end of file
...@@ -526,6 +526,20 @@ fun orderEmojiAndSymbolsPopup() { ...@@ -526,6 +526,20 @@ fun orderEmojiAndSymbolsPopup() {
platformOperations.orderEmojiAndSymbolsPopup() platformOperations.orderEmojiAndSymbolsPopup()
} }
internal fun defaultFPSCounter(
component: Component
): FPSCounter? = with(SkikoProperties) {
if (!SkikoProperties.fpsEnabled) return@with null
// it is slow on Linux (100ms), so we cache it. Also refreshRate available only after window is visible
val refreshRate by lazy { component.graphicsConfiguration.device.displayMode.refreshRate }
FPSCounter(
periodSeconds = fpsPeriodSeconds,
showLongFrames = fpsLongFramesShow,
getLongFrameMillis = { fpsLongFramesMillis ?: 1.5 * 1000 / refreshRate }
)
}
// InputEvent is abstract, so we wrap to match modality. // InputEvent is abstract, so we wrap to match modality.
actual class SkikoPlatformInputEvent(val wrapped: InputEvent) actual class SkikoPlatformInputEvent(val wrapped: InputEvent)
actual typealias SkikoPlatformKeyboardEvent = KeyEvent actual typealias SkikoPlatformKeyboardEvent = KeyEvent
......
...@@ -74,6 +74,9 @@ actual open class SkiaLayer( ...@@ -74,6 +74,9 @@ actual open class SkiaLayer(
} }
nsView = object : NSView(NSMakeRect(0.0, 0.0, width, height)) { nsView = object : NSView(NSMakeRect(0.0, 0.0, width, height)) {
private var trackingArea : NSTrackingArea? = null private var trackingArea : NSTrackingArea? = null
override fun wantsUpdateLayer(): Boolean {
return true
}
override fun acceptsFirstResponder(): Boolean { override fun acceptsFirstResponder(): Boolean {
return true return true
} }
...@@ -121,8 +124,9 @@ actual open class SkiaLayer( ...@@ -121,8 +124,9 @@ actual open class SkiaLayer(
center.addObserver(nsView, NSSelectorFromString("onWindowClose:"), center.addObserver(nsView, NSSelectorFromString("onWindowClose:"),
NSWindowWillCloseNotification!!, window) NSWindowWillCloseNotification!!, window)
window.contentView!!.addSubview(nsView) window.contentView!!.addSubview(nsView)
redrawer = createNativeRedrawer(this, GraphicsApi.OPENGL, properties) redrawer = createNativeRedrawer(this, GraphicsApi.OPENGL, properties).apply {
redrawer?.redrawImmediately() needRedraw()
}
} }
actual fun detach() { actual fun detach() {
......
...@@ -2,8 +2,10 @@ package org.jetbrains.skiko.redrawer ...@@ -2,8 +2,10 @@ package org.jetbrains.skiko.redrawer
import kotlinx.cinterop.CPointer import kotlinx.cinterop.CPointer
import kotlinx.cinterop.useContents import kotlinx.cinterop.useContents
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.SkikoDispatchers
import platform.CoreFoundation.CFTimeInterval import platform.CoreFoundation.CFTimeInterval
import platform.CoreGraphics.CGRectMake import platform.CoreGraphics.CGRectMake
import platform.CoreVideo.CVTimeStamp import platform.CoreVideo.CVTimeStamp
...@@ -15,19 +17,23 @@ import platform.QuartzCore.* ...@@ -15,19 +17,23 @@ import platform.QuartzCore.*
import kotlin.system.getTimeNanos import kotlin.system.getTimeNanos
internal class MacOsOpenGLRedrawer( internal class MacOsOpenGLRedrawer(
private val layer: SkiaLayer, private val skiaLayer: SkiaLayer,
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : Redrawer { ) : Redrawer {
private val drawLayer = MacosGLLayer(layer, setNeedsDisplayOnBoundsChange = true) private val glLayer = MacosGLLayer(skiaLayer)
private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) {
redrawImmediately()
}
override fun dispose() { override fun dispose() {
drawLayer.dispose() glLayer.dispose()
} }
override fun syncSize() { override fun syncSize() {
// TODO: What do we really do here? // TODO: What do we really do here?
layer.nsView.frame.useContents { skiaLayer.nsView.frame.useContents {
drawLayer.setFrame( glLayer.setFrame(
origin.x.toInt(), origin.x.toInt(),
origin.y.toInt(), origin.y.toInt(),
size.width.toInt().coerceAtLeast(0), size.width.toInt().coerceAtLeast(0),
...@@ -37,28 +43,24 @@ internal class MacOsOpenGLRedrawer( ...@@ -37,28 +43,24 @@ internal class MacOsOpenGLRedrawer(
} }
override fun needRedraw() { override fun needRedraw() {
frameDispatcher.scheduleFrame()
} }
override fun redrawImmediately() { override fun redrawImmediately() {
layer.update(getTimeNanos()) glLayer.setNeedsDisplay()
skiaLayer.nsView.setNeedsDisplay(true)
} }
} }
class MacosGLLayer(val layer: SkiaLayer, setNeedsDisplayOnBoundsChange: Boolean) : CAOpenGLLayer() { internal class MacosGLLayer(val layer: SkiaLayer) : CAOpenGLLayer() {
init { init {
this.setNeedsDisplayOnBoundsChange(setNeedsDisplayOnBoundsChange) this.setNeedsDisplayOnBoundsChange(true)
this.removeAllAnimations() this.removeAllAnimations()
this.setAutoresizingMask(kCALayerWidthSizable or kCALayerHeightSizable ) this.setAutoresizingMask(kCALayerWidthSizable or kCALayerHeightSizable )
this.setAsynchronous(true)
layer.nsView.layer = this layer.nsView.layer = this
layer.nsView.wantsLayer = true layer.nsView.wantsLayer = true
} }
fun draw() {
layer.update(getTimeNanos())
layer.draw()
}
fun setFrame(x: Int, y: Int, width: Int, height: Int) { fun setFrame(x: Int, y: Int, width: Int, height: Int) {
val newY = layer.nsView.frame.useContents { size.height } - y - height val newY = layer.nsView.frame.useContents { size.height } - y - height
...@@ -73,16 +75,6 @@ class MacosGLLayer(val layer: SkiaLayer, setNeedsDisplayOnBoundsChange: Boolean) ...@@ -73,16 +75,6 @@ class MacosGLLayer(val layer: SkiaLayer, setNeedsDisplayOnBoundsChange: Boolean)
// TODO: anything else to dispose the layer? // TODO: anything else to dispose the layer?
} }
@Suppress("unused")
private fun performDraw() {
try {
draw()
} catch (e: Throwable) {
e.printStackTrace()
}
//display.finish()
}
override fun canDrawInCGLContext( override fun canDrawInCGLContext(
ctx: CGLContextObj?, ctx: CGLContextObj?,
pixelFormat: CGLPixelFormatObj?, pixelFormat: CGLPixelFormatObj?,
...@@ -99,9 +91,14 @@ class MacosGLLayer(val layer: SkiaLayer, setNeedsDisplayOnBoundsChange: Boolean) ...@@ -99,9 +91,14 @@ class MacosGLLayer(val layer: SkiaLayer, setNeedsDisplayOnBoundsChange: Boolean)
displayTime: CPointer<CVTimeStamp>? displayTime: CPointer<CVTimeStamp>?
) { ) {
CGLSetCurrentContext(ctx); CGLSetCurrentContext(ctx);
performDraw() try {
layer.update(getTimeNanos())
layer.draw()
} catch (e: Throwable) {
e.printStackTrace()
throw e
}
//context.flush() // TODO: I thought the below should call context.flush().
super.drawInCGLContext(ctx, pixelFormat,forLayerTime, displayTime) super.drawInCGLContext(ctx, pixelFormat,forLayerTime, displayTime)
} }
} }
......
...@@ -5,4 +5,6 @@ internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties { ...@@ -5,4 +5,6 @@ internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties {
} }
internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R = internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R =
block() block()
\ No newline at end of file
actual fun currentNanoTime(): Long = kotlin.system.getTimeNanos()
\ No newline at end of file
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