Unverified Commit eb4bee60 authored by Roman Sedaikin's avatar Roman Sedaikin Committed by GitHub

Added Metal renderer for macos-native. (#307)

* Added Metal renderer for macos-native.
parent 90f73ca5
......@@ -5,6 +5,8 @@ import org.jetbrains.skia.paragraph.FontCollection
import org.jetbrains.skia.paragraph.ParagraphBuilder
import org.jetbrains.skia.paragraph.ParagraphStyle
import org.jetbrains.skia.paragraph.TextStyle
import org.jetbrains.skiko.currentSystemTheme
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoView
import org.jetbrains.skiko.SkikoPointerEvent
import org.jetbrains.skiko.SkikoGestureEvent
......@@ -16,7 +18,7 @@ import kotlin.math.sin
import kotlin.math.PI
import kotlin.math.pow
class Clocks: SkikoView {
class Clocks(private val layer: SkiaLayer): SkikoView {
private var frame = 0
private var xpos = 0.0
private var ypos = 0.0
......@@ -84,6 +86,13 @@ class Clocks: SkikoView {
}
val style = ParagraphStyle()
val renderInfo = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("Graphics API: ${layer.renderApi} ✿゚ ${currentSystemTheme}")
.popStyle()
.build()
renderInfo.layout(Float.POSITIVE_INFINITY)
renderInfo.paint(canvas, 5f, 5f)
val frames = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xff9BC730L.toInt()).setFontSize(20f))
.addText("Frames: ${frame++}\nAngle: $rotate")
......
......@@ -3,12 +3,13 @@ package org.jetbrains.skiko.sample
import kotlinx.cinterop.*
import org.jetbrains.skiko.GenericSkikoView
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkikoView
import org.jetbrains.skiko.SkikoViewController
import platform.UIKit.*
import platform.Foundation.*
fun makeApp(): SkikoView = Clocks()
fun makeApp(skiaLayer: SkiaLayer) = Clocks(skiaLayer)
fun main() {
val args = emptyArray<String>()
......@@ -37,7 +38,7 @@ class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
window = UIWindow(frame = UIScreen.mainScreen.bounds)
window!!.rootViewController = SkikoViewController().apply {
setAppFactory { layer ->
GenericSkikoView(layer, makeApp()).also {
GenericSkikoView(layer, makeApp(layer)).also {
layer.skikoView = it
}
}
......
......@@ -7,7 +7,7 @@ import javax.swing.*
fun main() {
val skiaLayer = SkiaLayer()
skiaLayer.addView( GenericSkikoView(skiaLayer, Clocks()))
skiaLayer.addView( GenericSkikoView(skiaLayer, Clocks(skiaLayer)))
SwingUtilities.invokeLater {
val window = JFrame("Skiko example").apply {
defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
......
......@@ -8,7 +8,7 @@ import kotlinx.cinterop.*
import platform.Foundation.NSMakeRect
import platform.darwin.NSObject
fun makeApp() = Clocks()
fun makeApp(skiaLayer: SkiaLayer) = Clocks(skiaLayer)
fun main() {
val app = NSApplication.sharedApplication()
......@@ -27,7 +27,7 @@ fun main() {
backing = NSBackingStoreBuffered,
defer = true)
val skiaLayer = SkiaLayer()
skiaLayer.skikoView = GenericSkikoView(skiaLayer, makeApp())
skiaLayer.skikoView = GenericSkikoView(skiaLayer, makeApp(skiaLayer))
skiaLayer.attachTo(window)
window.orderFrontRegardless()
app.run()
......
package org.jetbrains.skiko
val currentSystemTheme: SystemTheme
// TODO: getting actual OS system theme
get() = SystemTheme.LIGHT
\ No newline at end of file
......@@ -9,7 +9,6 @@ import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.MetalContextHandler
import org.jetbrains.skiko.context.OpenGLContextHandler
import javax.swing.SwingUtilities.*
internal class MetalRedrawer(
......
package org.jetbrains.skiko
import org.jetbrains.skiko.context.ContextHandler
import org.jetbrains.skiko.redrawer.MacOsOpenGLRedrawer
import org.jetbrains.skiko.redrawer.MacOsMetalRedrawer
import org.jetbrains.skiko.redrawer.Redrawer
internal fun createNativeRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi
): Redrawer = when (renderApi) {
GraphicsApi.OPENGL -> MacOsOpenGLRedrawer(layer)
GraphicsApi.METAL -> MacOsMetalRedrawer(layer)
else -> throw IllegalArgumentException("Unsupported API $renderApi")
}
\ No newline at end of file
......@@ -2,7 +2,6 @@ package org.jetbrains.skiko
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents
import org.jetbrains.skiko.context.*
import org.jetbrains.skia.*
import org.jetbrains.skiko.redrawer.MacOsOpenGLRedrawer
import org.jetbrains.skiko.redrawer.Redrawer
......@@ -15,7 +14,11 @@ import platform.Foundation.addObserver
import platform.darwin.NSObject
import platform.CoreGraphics.CGRectMake
actual open class SkiaLayer {
actual open class SkiaLayer() {
fun isShowing(): Boolean {
return true
}
actual var renderApi: GraphicsApi = GraphicsApi.OPENGL
actual val contentScale: Float
get() = if (this::nsView.isInitialized) nsView.window!!.backingScaleFactor.toFloat() else 1.0f
......@@ -36,7 +39,7 @@ actual open class SkiaLayer {
actual var skikoView: SkikoView? = null
private var redrawer: Redrawer? = null
internal var redrawer: Redrawer? = null
private var picture: PictureHolder? = null
private val pictureRecorder = PictureRecorder()
......@@ -137,7 +140,7 @@ actual open class SkiaLayer {
}
}
window.contentView!!.addSubview(nsView)
redrawer = MacOsOpenGLRedrawer(this).apply {
redrawer = createNativeRedrawer(this, renderApi).apply {
syncSize()
needRedraw()
}
......
package org.jetbrains.skiko
import platform.Foundation.NSUserDefaults
val currentSystemTheme: SystemTheme
get() = when (NSUserDefaults.standardUserDefaults.stringForKey("AppleInterfaceStyle")) {
"Dark" -> SystemTheme.DARK
else -> SystemTheme.LIGHT
}
\ No newline at end of file
package org.jetbrains.skiko.context
import kotlinx.cinterop.useContents
import org.jetbrains.skia.ColorSpace
import org.jetbrains.skia.Surface
import org.jetbrains.skia.SurfaceColorFormat
import org.jetbrains.skia.SurfaceOrigin
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.MacOsMetalRedrawer
internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) {
val metalRedrawer: MacOsMetalRedrawer
get() = layer.redrawer!! as MacOsMetalRedrawer
override fun initContext(): Boolean {
try {
if (context == null) {
context = metalRedrawer.makeContext()
}
} catch (e: Exception) {
println("${e.message}\nFailed to create Skia Metal context!")
return false
}
return true
}
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)
renderTarget = metalRedrawer.makeRenderTarget(w, h)
surface = Surface.makeFromBackendRenderTarget(
context!!,
renderTarget!!,
SurfaceOrigin.TOP_LEFT,
SurfaceColorFormat.BGRA_8888,
ColorSpace.sRGB
)
canvas = surface!!.canvas
}
override fun flush() {
// TODO: maybe make flush async as in JVM version.
super.flush()
surface!!.flushAndSubmit()
metalRedrawer.finishFrame()
}
override fun rendererInfo(): String {
return "Native Metal: device ${metalRedrawer.device.name}"
}
}
......@@ -31,35 +31,41 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye
return result
}
var initedCanvas = false
private var currentWidth = 0
private var currentHeight = 0
private fun isSizeChanged(width: Int, height: Int): Boolean {
if (width != currentWidth || height != currentHeight) {
currentWidth = width
currentHeight = height
return true
}
return false
}
override fun initCanvas() {
if (!initedCanvas) {
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 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)
if (isSizeChanged(w, h)) {
val fbId = openglGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING.toUInt())
renderTarget = BackendRenderTarget.makeGL(
w,
h,
0,
8,
fbId.toInt(),
FramebufferFormat.GR_GL_RGBA8
)
w,
h,
0,
8,
fbId.toInt(),
FramebufferFormat.GR_GL_RGBA8
)
surface = Surface.makeFromBackendRenderTarget(
context!!,
renderTarget!!,
SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888,
ColorSpace.sRGB
)
context!!,
renderTarget!!,
SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888,
ColorSpace.sRGB
)
canvas = surface?.canvas
?: error("Could not obtain Canvas from Surface")
initedCanvas = true
}
}
}
package org.jetbrains.skiko.redrawer
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.autoreleasepool
import kotlinx.cinterop.objcPtr
import kotlinx.cinterop.usePinned
import kotlinx.cinterop.useContents
import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.SkikoDispatchers
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.context.ContextHandler
import org.jetbrains.skiko.context.MacOsMetalContextHandler
import platform.CoreGraphics.CGColorCreate
import platform.CoreGraphics.CGColorSpaceCreateDeviceRGB
import platform.CoreGraphics.CGContextRef
import platform.Metal.MTLCreateSystemDefaultDevice
import platform.Metal.MTLDeviceProtocol
import platform.Metal.MTLPixelFormatBGRA8Unorm
import platform.QuartzCore.CAMetalDrawableProtocol
import platform.QuartzCore.CAMetalLayer
import platform.QuartzCore.CATransaction
import platform.QuartzCore.kCAGravityTopLeft
import platform.QuartzCore.kCALayerHeightSizable
import platform.QuartzCore.kCALayerWidthSizable
import kotlin.system.getTimeNanos
import platform.CoreGraphics.CGSizeMake
internal class MacOsMetalRedrawer(
private val skiaLayer: SkiaLayer
) : Redrawer {
private val contextHandler = MacOsMetalContextHandler(skiaLayer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
internal val device = MTLCreateSystemDefaultDevice()!!
private val queue = device.newCommandQueue()!!
private var currentDrawable: CAMetalDrawableProtocol? = null
private val metalLayer = MetalLayer()
init {
metalLayer.init(skiaLayer, contextHandler, device)
}
private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) {
if (skiaLayer.isShowing()) {
draw()
}
}
fun makeContext() = DirectContext.makeMetal(device.objcPtr(), queue.objcPtr())
fun makeRenderTarget(width: Int, height: Int): BackendRenderTarget {
currentDrawable = metalLayer.nextDrawable()!!
return BackendRenderTarget.makeMetal(width, height, currentDrawable!!.texture.objcPtr())
}
override fun dispose() {
if (!isDisposed) {
metalLayer.dispose()
isDisposed = true
}
}
override fun syncSize() {
syncContentScale()
val osView = skiaLayer.nsView!!
val (w, h) = osView.frame.useContents {
size.width to size.height
}
CATransaction.begin()
CATransaction.setDisableActions(true)
metalLayer.frame = osView.frame
metalLayer.init(skiaLayer, contextHandler, device)
metalLayer.drawableSize = CGSizeMake(w * metalLayer.contentsScale, h * metalLayer.contentsScale)
CATransaction.commit()
CATransaction.flush()
}
private fun syncContentScale() {
CATransaction.begin()
CATransaction.setDisableActions(true)
metalLayer.contentsScale = skiaLayer.nsView.window!!.backingScaleFactor
CATransaction.commit()
CATransaction.flush()
}
override fun needRedraw() {
check(!isDisposed) { "MetalRedrawer is disposed" }
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately() {
check(!isDisposed) { "MetalRedrawer is disposed" }
draw()
}
private fun draw() {
// TODO: maybe make flush async as in JVM version.
autoreleasepool {
if (!isDisposed) {
skiaLayer.update(getTimeNanos())
contextHandler.draw()
}
}
}
fun finishFrame() {
autoreleasepool {
currentDrawable?.let {
val commandBuffer = queue.commandBuffer()!!
commandBuffer.label = "Present"
commandBuffer.presentDrawable(it)
commandBuffer.commit()
currentDrawable = null
}
}
}
}
internal class MetalLayer : CAMetalLayer {
private lateinit var skiaLayer: SkiaLayer
private lateinit var contextHandler: ContextHandler
@OverrideInit
constructor(): super()
@OverrideInit
constructor(layer: Any): super(layer)
fun init(
layer: SkiaLayer,
contextHandler: ContextHandler,
theDevice: MTLDeviceProtocol
) {
this.skiaLayer = layer
this.contextHandler = contextHandler
this.setNeedsDisplayOnBoundsChange(true)
this.removeAllAnimations()
this.setAutoresizingMask(kCALayerWidthSizable or kCALayerHeightSizable )
this.device = theDevice as objcnames.protocols.MTLDeviceProtocol?
this.pixelFormat = MTLPixelFormatBGRA8Unorm
this.opaque = false
doubleArrayOf(0.0, 0.0, 0.0, 0.0).usePinned {
this.backgroundColor =
CGColorCreate(CGColorSpaceCreateDeviceRGB(), it.addressOf(0))
}
this.displaySyncEnabled = false
skiaLayer.nsView.layer = this
skiaLayer.nsView.wantsLayer = true
this.contentsGravity = kCAGravityTopLeft;
}
fun dispose() {
this.removeFromSuperlayer()
}
override fun drawInContext(ctx: CGContextRef?) {
skiaLayer.update(getTimeNanos())
contextHandler.draw()
super.drawInContext(ctx)
}
}
......@@ -48,7 +48,6 @@ internal class MacOsOpenGLRedrawer(
size.height.toInt().coerceAtLeast(0)
)
}
contextHandler.initedCanvas = false
}
private fun syncContentScale() {
......@@ -70,21 +69,22 @@ internal class MacOsOpenGLRedrawer(
}
internal class MacosGLLayer : CAOpenGLLayer {
private lateinit var layer: SkiaLayer
private lateinit var skiaLayer: SkiaLayer
private lateinit var contextHandler: ContextHandler
@OverrideInit
constructor(): super()
@OverrideInit
constructor(layer: Any): super(layer)
fun init(layer: SkiaLayer, contextHandler: ContextHandler) {
this.layer = layer
skiaLayer = layer
this.contextHandler = contextHandler
this.setNeedsDisplayOnBoundsChange(true)
this.removeAllAnimations()
this.setAutoresizingMask(kCALayerWidthSizable or kCALayerHeightSizable )
layer.nsView.layer = this
layer.nsView.wantsLayer = true
skiaLayer.nsView.layer = this
skiaLayer.nsView.wantsLayer = true
this.contentsGravity = kCAGravityTopLeft;
}
......@@ -118,7 +118,7 @@ internal class MacosGLLayer : CAOpenGLLayer {
) {
CGLSetCurrentContext(ctx);
try {
layer.update(getTimeNanos())
skiaLayer.update(getTimeNanos())
contextHandler.draw()
} catch (e: Throwable) {
e.printStackTrace()
......
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