Unverified Commit 89888a47 authored by Alexander Maryanovsky's avatar Alexander Maryanovsky Committed by GitHub

Fix race condition reading layer properties in a background thread (#1181)

parent 901d632a
...@@ -645,13 +645,22 @@ actual open class SkiaLayer internal constructor( ...@@ -645,13 +645,22 @@ actual open class SkiaLayer internal constructor(
@Suppress("LeakingThis") @Suppress("LeakingThis")
private val fpsCounter = defaultFPSCounter(this) 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(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
check(!isDisposed) { "SkiaLayer is disposed" } check(!isDisposed) { "SkiaLayer is disposed" }
try { try {
fpsCounter?.tick() fpsCounter?.tick()
with(createDrawScope()) {
body() body()
} catch (e: CancellationException) { }
} catch (_: CancellationException) {
// ignore // ignore
} catch (e: RenderException) { } catch (e: RenderException) {
if (!isDisposed) { if (!isDisposed) {
......
...@@ -2,6 +2,7 @@ package org.jetbrains.skiko.context ...@@ -2,6 +2,7 @@ package org.jetbrains.skiko.context
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.AngleApi import org.jetbrains.skiko.AngleApi
import org.jetbrains.skiko.LayerDrawScope
import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.AngleRedrawer import org.jetbrains.skiko.redrawer.AngleRedrawer
...@@ -23,12 +24,11 @@ internal class AngleContextHandler(layer: SkiaLayer) : ContextBasedContextHandle ...@@ -23,12 +24,11 @@ internal class AngleContextHandler(layer: SkiaLayer) : ContextBasedContextHandle
return false return false
} }
override fun initCanvas() { override fun LayerDrawScope.initCanvas() {
val context = context ?: return val context = context ?: return
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0) val w = scaledLayerWidth
val h = (layer.height * scale).toInt().coerceAtLeast(0) val h = scaledLayerHeight
if (isSizeChanged(w, h) || surface == null) { if (isSizeChanged(w, h) || surface == null) {
disposeCanvas() disposeCanvas()
...@@ -41,7 +41,7 @@ internal class AngleContextHandler(layer: SkiaLayer) : ContextBasedContextHandle ...@@ -41,7 +41,7 @@ internal class AngleContextHandler(layer: SkiaLayer) : ContextBasedContextHandle
SurfaceOrigin.BOTTOM_LEFT, SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888, SurfaceColorFormat.RGBA_8888,
ColorSpace.sRGB, ColorSpace.sRGB,
SurfaceProps(pixelGeometry = layer.pixelGeometry) SurfaceProps(pixelGeometry = pixelGeometry)
) ?: throw RenderException("Cannot create surface") ) ?: throw RenderException("Cannot create surface")
} }
......
...@@ -6,7 +6,7 @@ import org.jetbrains.skiko.SkiaLayer ...@@ -6,7 +6,7 @@ import org.jetbrains.skiko.SkiaLayer
internal abstract class ContextBasedContextHandler(layer: SkiaLayer, val name: String) : JvmContextHandler(layer) { 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 { override fun initContext(): Boolean {
try { try {
......
...@@ -3,13 +3,13 @@ package org.jetbrains.skiko.context ...@@ -3,13 +3,13 @@ package org.jetbrains.skiko.context
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
internal abstract class ContextFreeContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) { internal abstract class ContextFreeContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
private var isInited = false private var isInitialized = false
override fun initContext(): Boolean { override fun initContext(): Boolean {
if (!isInited) { if (!isInitialized) {
isInited = true isInitialized = true
onContextInitialized() onContextInitialized()
} }
return isInited return isInitialized
} }
} }
\ No newline at end of file
...@@ -4,7 +4,7 @@ import org.jetbrains.skia.DirectContext ...@@ -4,7 +4,7 @@ import org.jetbrains.skia.DirectContext
import org.jetbrains.skia.Surface import org.jetbrains.skia.Surface
import org.jetbrains.skia.SurfaceProps import org.jetbrains.skia.SurfaceProps
import org.jetbrains.skia.impl.getPtr import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skiko.Logger import org.jetbrains.skiko.LayerDrawScope
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.Direct3DRedrawer import org.jetbrains.skiko.redrawer.Direct3DRedrawer
import java.lang.ref.Reference import java.lang.ref.Reference
...@@ -30,15 +30,14 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan ...@@ -30,15 +30,14 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan
return false return false
} }
override fun initCanvas() { override fun LayerDrawScope.initCanvas() {
val context = context ?: return val context = context ?: return
val scale = layer.contentScale
// Direct3D can't work with zero size. // Direct3D can't work with zero size.
// Don't rewrite code to skipping, as we need the whole pipeline in zero case too // Don't rewrite code to skipping, as we need the whole pipeline in zero case too
// (drawing -> flushing -> swapping -> waiting for vsync) // (drawing -> flushing -> swapping -> waiting for vsync)
val width = (layer.width * scale).toInt().coerceAtLeast(1) val width = scaledLayerWidth.coerceAtLeast(1)
val height = (layer.height * scale).toInt().coerceAtLeast(1) val height = scaledLayerHeight.coerceAtLeast(1)
if (isSizeChanged(width, height) || isSurfacesNull()) { if (isSizeChanged(width, height) || isSurfacesNull()) {
disposeCanvas() disposeCanvas()
...@@ -46,7 +45,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan ...@@ -46,7 +45,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan
val justInitialized = directXRedrawer.changeSize(width, height) val justInitialized = directXRedrawer.changeSize(width, height)
try { try {
val surfaceProps = SurfaceProps(pixelGeometry = layer.pixelGeometry) val surfaceProps = SurfaceProps(pixelGeometry = pixelGeometry)
for (bufferIndex in 0 until bufferCount) { for (bufferIndex in 0 until bufferCount) {
surfaces[bufferIndex] = directXRedrawer.makeSurface( surfaces[bufferIndex] = directXRedrawer.makeSurface(
context = getPtr(context), context = getPtr(context),
...@@ -68,7 +67,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan ...@@ -68,7 +67,7 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextBasedContextHan
canvas = surface!!.canvas canvas = surface!!.canvas
} }
override fun flush() { override fun flush(scope: LayerDrawScope) {
val context = context ?: return val context = context ?: return
val surface = surface ?: return val surface = surface ?: return
try { try {
......
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import org.jetbrains.skia.impl.getPtr import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skiko.LayerDrawScope
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.AbstractDirectSoftwareRedrawer import org.jetbrains.skiko.redrawer.AbstractDirectSoftwareRedrawer
import java.lang.ref.Reference import java.lang.ref.Reference
...@@ -20,10 +21,9 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextFreeConte ...@@ -20,10 +21,9 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextFreeConte
return false return false
} }
override fun initCanvas() { override fun LayerDrawScope.initCanvas() {
val scale = layer.contentScale val w = scaledLayerWidth
val w = (layer.width * scale).toInt().coerceAtLeast(0) val h = scaledLayerHeight
val h = (layer.height * scale).toInt().coerceAtLeast(0)
if (isSizeChanged(w, h) || surface == null) { if (isSizeChanged(w, h) || surface == null) {
disposeCanvas() disposeCanvas()
if (w > 0 && h > 0) { if (w > 0 && h > 0) {
...@@ -37,7 +37,7 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextFreeConte ...@@ -37,7 +37,7 @@ internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextFreeConte
} }
} }
override fun flush() { override fun flush(scope: LayerDrawScope) {
val surface = surface val surface = surface
if (surface != null) { if (surface != null) {
try { try {
......
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.LayerDrawScope
import org.jetbrains.skiko.Logger import org.jetbrains.skiko.Logger
import org.jetbrains.skiko.MetalAdapter import org.jetbrains.skiko.MetalAdapter
import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.RenderException
...@@ -20,12 +21,11 @@ internal class MetalContextHandler( ...@@ -20,12 +21,11 @@ internal class MetalContextHandler(
private val device: MetalDevice, private val device: MetalDevice,
private val adapter: MetalAdapter private val adapter: MetalAdapter
) : ContextBasedContextHandler(layer, "Metal") { ) : ContextBasedContextHandler(layer, "Metal") {
override fun initCanvas() { override fun LayerDrawScope.initCanvas() {
disposeCanvas() disposeCanvas()
val scale = layer.contentScale val width = scaledLayerWidth
val width = (layer.backedLayer.width * scale).toInt().coerceAtLeast(0) val height = scaledLayerHeight
val height = (layer.backedLayer.height * scale).toInt().coerceAtLeast(0)
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
renderTarget = makeRenderTarget(width, height) renderTarget = makeRenderTarget(width, height)
...@@ -36,7 +36,7 @@ internal class MetalContextHandler( ...@@ -36,7 +36,7 @@ internal class MetalContextHandler(
SurfaceOrigin.TOP_LEFT, SurfaceOrigin.TOP_LEFT,
SurfaceColorFormat.BGRA_8888, SurfaceColorFormat.BGRA_8888,
ColorSpace.sRGB, ColorSpace.sRGB,
SurfaceProps(pixelGeometry = layer.pixelGeometry) SurfaceProps(pixelGeometry = pixelGeometry)
) ?: throw RenderException("Cannot create surface") ) ?: throw RenderException("Cannot create surface")
canvas = surface!!.canvas canvas = surface!!.canvas
...@@ -47,8 +47,8 @@ internal class MetalContextHandler( ...@@ -47,8 +47,8 @@ internal class MetalContextHandler(
} }
} }
override fun flush() { override fun flush(scope: LayerDrawScope) {
super.flush() super.flush(scope)
surface?.flushAndSubmit() surface?.flushAndSubmit()
finishFrame() finishFrame()
Logger.debug { "MetalContextHandler finished drawing frame" } Logger.debug { "MetalContextHandler finished drawing frame" }
......
...@@ -18,10 +18,9 @@ internal class OpenGLContextHandler(layer: SkiaLayer) : ContextBasedContextHandl ...@@ -18,10 +18,9 @@ internal class OpenGLContextHandler(layer: SkiaLayer) : ContextBasedContextHandl
return false return false
} }
override fun initCanvas() { override fun LayerDrawScope.initCanvas() {
val scale = layer.contentScale val w = scaledLayerWidth
val w = (layer.width * scale).toInt().coerceAtLeast(0) val h = scaledLayerHeight
val h = (layer.height * scale).toInt().coerceAtLeast(0)
if (isSizeChanged(w, h) || surface == null) { if (isSizeChanged(w, h) || surface == null) {
disposeCanvas() disposeCanvas()
...@@ -41,7 +40,7 @@ internal class OpenGLContextHandler(layer: SkiaLayer) : ContextBasedContextHandl ...@@ -41,7 +40,7 @@ internal class OpenGLContextHandler(layer: SkiaLayer) : ContextBasedContextHandl
SurfaceOrigin.BOTTOM_LEFT, SurfaceOrigin.BOTTOM_LEFT,
SurfaceColorFormat.RGBA_8888, SurfaceColorFormat.RGBA_8888,
ColorSpace.sRGB, ColorSpace.sRGB,
SurfaceProps(pixelGeometry = layer.pixelGeometry) SurfaceProps(pixelGeometry = pixelGeometry)
) ?: throw RenderException("Cannot create surface") ) ?: throw RenderException("Cannot create surface")
} }
......
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.LayerDrawScope
import org.jetbrains.skiko.OS import org.jetbrains.skiko.OS
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.hostOs import org.jetbrains.skiko.hostOs
...@@ -22,25 +23,22 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHand ...@@ -22,25 +23,22 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHand
var imageData: ByteArray? = null var imageData: ByteArray? = null
var raster: WritableRaster? = null var raster: WritableRaster? = null
override fun initCanvas() { override fun LayerDrawScope.initCanvas() {
disposeCanvas() disposeCanvas()
val scale = layer.contentScale val w = scaledLayerWidth
val w = (layer.width * scale).toInt().coerceAtLeast(0) val h = scaledLayerHeight
val h = (layer.height * scale).toInt().coerceAtLeast(0)
if (storage.width != w || storage.height != h) { if (storage.width != w || storage.height != h) {
storage.allocPixelsFlags(ImageInfo.makeS32(w, h, ColorAlphaType.PREMUL), false) 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() { override fun flush(scope: LayerDrawScope) {
val scale = layer.contentScale val w = scope.scaledLayerWidth
val w = (layer.width * scale).toInt().coerceAtLeast(0) val h = scope.scaledLayerHeight
val h = (layer.height * scale).toInt().coerceAtLeast(0)
val bytes = storage.readPixels(storage.imageInfo, (w * 4), 0, 0) val bytes = storage.readPixels(storage.imageInfo, (w * 4), 0, 0)
if (bytes != null) { if (bytes != null) {
......
...@@ -54,7 +54,7 @@ internal abstract class AWTRedrawer( ...@@ -54,7 +54,7 @@ internal abstract class AWTRedrawer(
layer.update(nanoTime) 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" } requireNotNull(deviceAnalytics) { "deviceAnalytics is not null. Call onDeviceChosen after choosing the drawing device" }
if (!isDisposed) { if (!isDisposed) {
val isFirstFrame = !isFirstFrameRendered val isFirstFrame = !isFirstFrameRendered
......
...@@ -33,7 +33,7 @@ internal abstract class AbstractDirectSoftwareRedrawer( ...@@ -33,7 +33,7 @@ internal abstract class AbstractDirectSoftwareRedrawer(
frameDispatcher.scheduleFrame() frameDispatcher.scheduleFrame()
} }
protected open fun draw() = inDrawScope(contextHandler::draw) protected open fun draw() = inDrawScope { contextHandler.draw() }
override fun renderImmediately() { override fun renderImmediately() {
update() update()
......
...@@ -89,7 +89,9 @@ internal class AngleRedrawer( ...@@ -89,7 +89,9 @@ internal class AngleRedrawer(
return return
} }
makeCurrent(device) makeCurrent(device)
layer.inDrawScope {
contextHandler.draw() contextHandler.draw()
}
swapBuffers(device, withVsync) swapBuffers(device, withVsync)
} }
......
...@@ -86,7 +86,7 @@ internal class Direct3DRedrawer( ...@@ -86,7 +86,7 @@ internal class Direct3DRedrawer(
} }
} }
private fun drawAndSwap(withVsync: Boolean) = synchronized(drawLock) { private fun LayerDrawScope.drawAndSwap(withVsync: Boolean) = synchronized(drawLock) {
if (isDisposed) { if (isDisposed) {
return return
} }
......
...@@ -97,7 +97,7 @@ internal class LinuxOpenGLRedrawer( ...@@ -97,7 +97,7 @@ internal class LinuxOpenGLRedrawer(
} }
private fun draw() { private fun draw() {
inDrawScope(contextHandler::draw) inDrawScope { contextHandler.draw() }
} }
companion object { companion object {
......
...@@ -144,7 +144,7 @@ internal class MetalRedrawer( ...@@ -144,7 +144,7 @@ internal class MetalRedrawer(
windowOcclusionStateChannel.trySend(isOccluded) windowOcclusionStateChannel.trySend(isOccluded)
} }
private fun performDraw() = synchronized(drawLock) { private fun LayerDrawScope.performDraw() = synchronized(drawLock) {
if (!isDisposed) { if (!isDisposed) {
autoreleasepool { autoreleasepool {
contextHandler.draw() contextHandler.draw()
......
...@@ -27,7 +27,7 @@ internal class SoftwareRedrawer( ...@@ -27,7 +27,7 @@ internal class SoftwareRedrawer(
if (layer.isShowing) { if (layer.isShowing) {
update() update()
inDrawScope(contextHandler::draw) inDrawScope { contextHandler.draw() }
} }
} }
......
...@@ -78,7 +78,7 @@ internal class WindowsOpenGLRedrawer( ...@@ -78,7 +78,7 @@ internal class WindowsOpenGLRedrawer(
} }
private fun draw() { private fun draw() {
inDrawScope(contextHandler::draw) inDrawScope { contextHandler.draw() }
} }
private fun makeCurrent() = makeCurrent(device, context) private fun makeCurrent() = makeCurrent(device, context)
......
...@@ -578,9 +578,9 @@ class SkiaLayerTest { ...@@ -578,9 +578,9 @@ class SkiaLayerTest {
object : BaseTestRedrawer(layer) { object : BaseTestRedrawer(layer) {
private val contextHandler = object : JvmContextHandler(layer) { private val contextHandler = object : JvmContextHandler(layer) {
override fun initContext() = false override fun initContext() = false
override fun initCanvas() = Unit override fun LayerDrawScope.initCanvas() = Unit
} }
override fun renderImmediately() = layer.inDrawScope(contextHandler::draw) override fun renderImmediately() = layer.inDrawScope { contextHandler.draw() }
} }
} }
} }
......
...@@ -3,6 +3,7 @@ package org.jetbrains.skiko ...@@ -3,6 +3,7 @@ package org.jetbrains.skiko
import org.jetbrains.skia.Canvas import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Picture import org.jetbrains.skia.Picture
import org.jetbrains.skia.PixelGeometry import org.jetbrains.skia.PixelGeometry
import org.jetbrains.skiko.context.ContextHandler
/** /**
* Generic layer for Skiko rendering. * Generic layer for Skiko rendering.
...@@ -70,6 +71,35 @@ expect open class SkiaLayer { ...@@ -70,6 +71,35 @@ expect open class SkiaLayer {
internal fun draw(canvas: Canvas) internal fun draw(canvas: Canvas)
} }
internal class PictureHolder(val instance: Picture, val width: Int, val height: Int) internal class PictureHolder(val instance: Picture, val width: Int, val height: Int)
internal 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
...@@ -13,9 +13,9 @@ internal abstract class ContextHandler( ...@@ -13,9 +13,9 @@ internal abstract class ContextHandler(
protected var canvas: Canvas? = null protected var canvas: Canvas? = null
protected abstract fun initContext(): Boolean protected abstract fun initContext(): Boolean
protected abstract fun initCanvas() protected abstract fun LayerDrawScope.initCanvas()
protected open fun flush() { protected open fun flush(scope: LayerDrawScope) {
context?.flush() context?.flush()
} }
...@@ -35,7 +35,7 @@ internal abstract class ContextHandler( ...@@ -35,7 +35,7 @@ internal abstract class ContextHandler(
} }
// throws RenderException if initialization of graphic context was not successful // throws RenderException if initialization of graphic context was not successful
fun draw() { fun LayerDrawScope.draw() {
if (!initContext()) { if (!initContext()) {
throw RenderException("Cannot init graphic context") throw RenderException("Cannot init graphic context")
} }
...@@ -44,6 +44,7 @@ internal abstract class ContextHandler( ...@@ -44,6 +44,7 @@ internal abstract class ContextHandler(
clear(Color.TRANSPARENT) clear(Color.TRANSPARENT)
drawContent() drawContent()
} }
flush() flush(this)
} }
} }
...@@ -168,4 +168,17 @@ actual open class SkiaLayer { ...@@ -168,4 +168,17 @@ actual open class SkiaLayer {
actual val pixelGeometry: PixelGeometry actual val pixelGeometry: PixelGeometry
get() = PixelGeometry.UNKNOWN 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()
}
}
} }
package org.jetbrains.skiko.context package org.jetbrains.skiko.context
import kotlinx.cinterop.useContents
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.LayerDrawScope
import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.MacOsMetalRedrawer 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) { internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) {
private val metalRedrawer: MacOsMetalRedrawer private val metalRedrawer: MacOsMetalRedrawer
...@@ -25,12 +25,11 @@ internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer ...@@ -25,12 +25,11 @@ internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer
return true return true
} }
override fun initCanvas() { override fun LayerDrawScope.initCanvas() {
disposeCanvas() disposeCanvas()
val scale = layer.contentScale val w = scaledLayerWidth
val w = (layer.nsView.frame.useContents { size.width } * scale).toInt().coerceAtLeast(0) val h = scaledLayerHeight
val h = (layer.nsView.frame.useContents { size.height } * scale).toInt().coerceAtLeast(0)
if (w > 0 && h > 0) { if (w > 0 && h > 0) {
renderTarget = metalRedrawer.makeRenderTarget(w, h) renderTarget = metalRedrawer.makeRenderTarget(w, h)
...@@ -52,9 +51,9 @@ internal class MacOsMetalContextHandler(layer: SkiaLayer) : ContextHandler(layer ...@@ -52,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. // TODO: maybe make flush async as in JVM version.
super.flush() super.flush(scope)
surface?.flushAndSubmit() surface?.flushAndSubmit()
metalRedrawer.finishFrame() metalRedrawer.finishFrame()
} }
......
...@@ -3,6 +3,7 @@ package org.jetbrains.skiko.context ...@@ -3,6 +3,7 @@ package org.jetbrains.skiko.context
import kotlinx.cinterop.* import kotlinx.cinterop.*
import org.jetbrains.skia.* import org.jetbrains.skia.*
import org.jetbrains.skiko.GraphicsApi import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.LayerDrawScope
import org.jetbrains.skiko.RenderException import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayer import org.jetbrains.skiko.SkiaLayer
import platform.OpenGL.GL_DRAW_FRAMEBUFFER_BINDING import platform.OpenGL.GL_DRAW_FRAMEBUFFER_BINDING
...@@ -10,7 +11,8 @@ import platform.OpenGL.glGetIntegerv ...@@ -10,7 +11,8 @@ import platform.OpenGL.glGetIntegerv
import platform.OpenGLCommon.GLenum 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]. * Not used anymore, unless corresponding [GraphicsApi] is hardcoded in [SkiaLayer].
* See [MacOsMetalContextHandler] instead. * See [MacOsMetalContextHandler] instead.
*/ */
...@@ -20,7 +22,7 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye ...@@ -20,7 +22,7 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye
if (context == null) { if (context == null) {
context = DirectContext.makeGL() context = DirectContext.makeGL()
} }
} catch (e: Exception) { } catch (_: Exception) {
println("Failed to create Skia OpenGL context!") println("Failed to create Skia OpenGL context!")
return false return false
} }
...@@ -29,11 +31,11 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye ...@@ -29,11 +31,11 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye
@ExperimentalUnsignedTypes @ExperimentalUnsignedTypes
private fun openglGetIntegerv(pname: GLenum): UInt { private fun openglGetIntegerv(pname: GLenum): UInt {
var result: UInt = 0U var result = 0U
memScoped { memScoped {
val data = alloc<IntVar>() val data = alloc<IntVar>()
glGetIntegerv(pname, data.ptr); glGetIntegerv(pname, data.ptr)
result = data.value.toUInt(); result = data.value.toUInt()
} }
return result return result
} }
...@@ -49,10 +51,9 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye ...@@ -49,10 +51,9 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye
return false return false
} }
override fun initCanvas() { override fun LayerDrawScope.initCanvas() {
val scale = layer.contentScale val w = scaledLayerWidth
val w = (layer.nsView.frame.useContents { size.width } * scale).toInt().coerceAtLeast(0) val h = scaledLayerHeight
val h = (layer.nsView.frame.useContents { size.height } * scale).toInt().coerceAtLeast(0)
if (isSizeChanged(w, h)) { if (isSizeChanged(w, h)) {
val fbId = openglGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING.toUInt()) val fbId = openglGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING.toUInt())
renderTarget = BackendRenderTarget.makeGL( renderTarget = BackendRenderTarget.makeGL(
......
...@@ -157,18 +157,22 @@ internal class MacOsMetalRedrawer( ...@@ -157,18 +157,22 @@ internal class MacOsMetalRedrawer(
update() update()
} }
if (!isDisposed) { // Redrawer may be disposed in user code, during `update` if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
skiaLayer.inDrawScope {
contextHandler.draw() contextHandler.draw()
} }
} }
} }
}
private suspend fun draw() { private suspend fun draw() {
autoreleasepool { autoreleasepool {
if (!isDisposed) { if (!isDisposed) {
update() update()
skiaLayer.inDrawScope {
contextHandler.draw() contextHandler.draw()
} }
} }
}
// When window is not visible - it doesn't make sense to redraw fast to avoid battery drain. // When window is not visible - it doesn't make sense to redraw fast to avoid battery drain.
if (isWindowOccluded) { if (isWindowOccluded) {
...@@ -233,6 +237,8 @@ internal class MetalLayer : CAMetalLayer { ...@@ -233,6 +237,8 @@ internal class MetalLayer : CAMetalLayer {
override fun drawInContext(ctx: CGContextRef?) { override fun drawInContext(ctx: CGContextRef?) {
skiaLayer.update(currentNanoTime()) skiaLayer.update(currentNanoTime())
skiaLayer.inDrawScope {
contextHandler.draw() contextHandler.draw()
} }
}
} }
...@@ -133,7 +133,9 @@ internal class MacosGLLayer : CAOpenGLLayer { ...@@ -133,7 +133,9 @@ internal class MacosGLLayer : CAOpenGLLayer {
CGLSetCurrentContext(ctx) CGLSetCurrentContext(ctx)
try { try {
skiaLayer.update(currentNanoTime()) skiaLayer.update(currentNanoTime())
skiaLayer.inDrawScope {
contextHandler.draw() contextHandler.draw()
}
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
throw e throw e
......
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