Unverified Commit 0f45ab4e authored by Igor Demin's avatar Igor Demin Committed by GitHub

Create ContextHandler inside Redrawer (#300)

parent e7c30e9e
......@@ -35,7 +35,7 @@ fun main() {
}
skiaLayer.renderer = GenericSkikoApp(skiaLayer, app)
skiaLayer.setCanvas(canvas)
skiaLayer.draw()
skiaLayer.needRedraw()
}
}
}
package org.jetbrains.skiko
internal class RenderException(
message: String? = null,
cause: Throwable? = null
) : RuntimeException(message, cause)
package org.jetbrains.skiko
import org.jetbrains.skiko.context.*
import org.jetbrains.skiko.redrawer.*
internal interface RenderFactory {
fun createContextHandler(layer: SkiaLayer, renderApi: GraphicsApi): ContextHandler
fun createRedrawer(layer: SkiaLayer, renderApi: GraphicsApi, properties: SkiaLayerProperties): Redrawer
companion object {
......
......@@ -5,41 +5,33 @@ import org.jetbrains.skia.Canvas
import org.jetbrains.skia.DirectContext
import org.jetbrains.skia.Picture
import org.jetbrains.skia.Surface
import org.jetbrains.skiko.GraphicsApi
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.hostArch
import org.jetbrains.skiko.hostOs
import org.jetbrains.skiko.OS
internal abstract class ContextHandler(val layer: SkiaLayer) {
open val clearColor = if (layer.transparency || hostOs == OS.MacOS) 0 else -1
var context: DirectContext? = null
var renderTarget: BackendRenderTarget? = null
var surface: Surface? = null
var canvas: Canvas? = null
abstract fun initContext(): Boolean
abstract fun initCanvas()
fun clearCanvas() {
val color = if (layer.fullscreen && hostOs != OS.MacOS) -1 else clearColor
canvas?.clear(color)
}
open fun drawOnCanvas(picture: Picture) {
canvas?.drawPicture(picture)
}
open fun flush() {
import org.jetbrains.skiko.*
import org.jetbrains.skiko.RenderException
internal abstract class ContextHandler(
protected val layer: SkiaLayer,
private val drawContent: Canvas.() -> Unit
) {
// TODO can we simplify clearColor logic? is there a reason why SoftwareContextHandler has opposite logic?
protected open val clearColor = if (layer.transparency || hostOs == OS.MacOS) 0 else -1
protected var context: DirectContext? = null
protected var renderTarget: BackendRenderTarget? = null
protected var surface: Surface? = null
protected var canvas: Canvas? = null
protected abstract fun initContext(): Boolean
protected abstract fun initCanvas()
protected open fun flush() {
context?.flush()
}
open fun dispose() {
disposeCanvas()
destroyContext()
context?.close()
}
open fun disposeCanvas() {
protected open fun disposeCanvas() {
surface?.close()
renderTarget?.close()
}
......@@ -49,7 +41,16 @@ internal abstract class ContextHandler(val layer: SkiaLayer) {
"OS: ${hostOs.id} ${hostArch.id}\n"
}
protected open fun destroyContext() {
context?.close()
// throws RenderException if initialization of graphic context was not successful
fun draw() {
if (!initContext()) {
throw RenderException("Cannot init graphic context")
}
initCanvas()
canvas?.apply {
clear(if (layer.fullscreen && hostOs != OS.MacOS) -1 else clearColor)
drawContent()
}
flush()
}
}
......@@ -5,4 +5,5 @@ internal interface Redrawer {
fun needRedraw()
fun redrawImmediately()
fun syncSize() = Unit
val renderInfo: String
}
\ No newline at end of file
......@@ -2,6 +2,7 @@ package org.jetbrains.skiko
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.useContents
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.PictureRecorder
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.context.MetalContextHandler
......@@ -121,18 +122,10 @@ actual open class SkiaLayer(
this.picture = PictureHolder(picture, pictureWidth.toInt(), pictureHeight.toInt())
}
fun draw() {
contextHandler?.apply {
if (!initContext()) {
error("initContext() failure")
}
initCanvas()
clearCanvas()
val picture = picture
if (picture != null) {
drawOnCanvas(picture.instance)
}
flush()
internal fun draw(canvas: Canvas) {
check(!isDisposed) { "SkiaLayer is disposed" }
picture?.also {
canvas.drawPicture(it.instance)
}
}
}
......
......@@ -8,7 +8,7 @@ import org.jetbrains.skia.SurfaceOrigin
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.MetalRedrawer
internal class MetalContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
internal class MetalContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) {
val metalRedrawer: MetalRedrawer
get() = layer.redrawer!!
......
......@@ -8,6 +8,8 @@ import kotlinx.cinterop.useContents
import org.jetbrains.skia.BackendRenderTarget
import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.ContextHandler
import org.jetbrains.skiko.context.MetalContextHandler
import platform.CoreGraphics.CGColorCreate
import platform.CoreGraphics.CGColorSpaceCreateDeviceRGB
import platform.CoreGraphics.CGContextRef
......@@ -24,6 +26,9 @@ internal class MetalRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = MetalContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
internal val device = MTLCreateSystemDefaultDevice()!!
private val queue = device.newCommandQueue()!!
......@@ -31,7 +36,7 @@ internal class MetalRedrawer(
private val metalLayer = MetalLayer()
init {
metalLayer.init(this.layer, device)
metalLayer.init(this.layer, contextHandler, device)
}
private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) {
......@@ -51,6 +56,7 @@ internal class MetalRedrawer(
override fun dispose() {
if (!isDisposed) {
frameDispatcher.cancel()
contextHandler.dispose()
metalLayer.dispose()
isDisposed = true
}
......@@ -63,7 +69,7 @@ internal class MetalRedrawer(
size.width to size.height
}
metalLayer.frame = osView.frame
metalLayer.init(layer, device)
metalLayer.init(layer, contextHandler, device)
metalLayer.drawableSize = CGSizeMake(w * metalLayer.contentsScale, h * metalLayer.contentsScale)
}
......@@ -82,7 +88,7 @@ internal class MetalRedrawer(
// TODO: maybe make flush async as in JVM version.
autoreleasepool {
if (!isDisposed) {
layer.draw()
contextHandler.draw()
}
}
}
......@@ -100,15 +106,22 @@ internal class MetalRedrawer(
}
}
class MetalLayer : CAMetalLayer {
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(skiaLayer: SkiaLayer, theDevice: MTLDeviceProtocol) {
fun init(
skiaLayer: SkiaLayer,
contextHandler: ContextHandler,
theDevice: MTLDeviceProtocol
) {
this.skiaLayer = skiaLayer
this.contextHandler = contextHandler
this.setNeedsDisplayOnBoundsChange(true)
this.removeAllAnimations()
// TODO: looks like a bug in K/N interop.
......@@ -133,7 +146,7 @@ class MetalLayer : CAMetalLayer {
override fun drawInContext(ctx: CGContextRef?) {
skiaLayer.update(getTimeNanos())
skiaLayer.draw()
contextHandler.draw()
super.drawInContext(ctx)
}
}
package org.jetbrains.skiko
import org.jetbrains.skiko.context.*
import org.jetbrains.skiko.redrawer.*
internal actual fun makeDefaultRenderFactory(): RenderFactory {
return object : RenderFactory {
override fun createContextHandler(layer: SkiaLayer, renderApi: GraphicsApi): ContextHandler {
TODO()
}
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
......
......@@ -25,7 +25,7 @@ actual open class SkiaLayer(properties: SkiaLayerProperties = makeDefaultSkiaLay
}
actual fun needRedraw() {
draw()
state?.draw()
}
actual var skikoView: SkikoView? = null
......@@ -99,10 +99,6 @@ actual open class SkiaLayer(properties: SkiaLayerProperties = makeDefaultSkiaLay
))
})
}
fun draw() {
state?.draw()
}
}
actual typealias SkikoPlatformInputEvent = InputEvent
......
package org.jetbrains.skiko
import java.io.File
import java.lang.RuntimeException
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.jvm.JvmStatic
internal class RenderException(
message: String? = null,
cause: Exception? = null
) : RuntimeException(message, cause)
internal class RenderExceptionsHandler {
companion object {
private var output: File? = null
......
package org.jetbrains.skiko
import org.jetbrains.skiko.context.*
import org.jetbrains.skiko.redrawer.*
internal actual fun makeDefaultRenderFactory(): RenderFactory {
return object : RenderFactory {
override fun createContextHandler(layer: SkiaLayer, renderApi: GraphicsApi): ContextHandler {
return when (renderApi) {
GraphicsApi.DIRECT_SOFTWARE -> return when (hostOs) {
OS.Windows, OS.Linux -> DirectSoftwareContextHandler(layer)
else -> SoftwareContextHandler(layer)
}
GraphicsApi.SOFTWARE -> SoftwareContextHandler(layer)
GraphicsApi.OPENGL -> OpenGLContextHandler(layer)
GraphicsApi.DIRECT3D -> Direct3DContextHandler(layer)
GraphicsApi.METAL -> MetalContextHandler(layer)
else -> TODO("Unsupported yet.")
}
}
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
......
package org.jetbrains.skiko
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Bitmap
import org.jetbrains.skia.ColorAlphaType
import org.jetbrains.skia.ColorInfo
import org.jetbrains.skia.ColorType
import org.jetbrains.skia.ColorSpace
import org.jetbrains.skia.ClipMode
import org.jetbrains.skia.ImageInfo
import org.jetbrains.skia.PictureRecorder
import org.jetbrains.skia.Rect
import org.jetbrains.skia.*
import org.jetbrains.skiko.context.ContextHandler
import org.jetbrains.skiko.redrawer.Redrawer
import java.awt.Color
......@@ -209,7 +200,6 @@ actual open class SkiaLayer internal constructor(
@Volatile
private var isDisposed = false
internal var redrawer: Redrawer? = null
internal var contextHandler: ContextHandler? = null
private val fallbackRenderApiQueue = SkikoProperties.fallbackRenderApiQueue.toMutableList()
private var renderApi_ = fallbackRenderApiQueue[0]
actual var renderApi: GraphicsApi
......@@ -219,10 +209,10 @@ actual open class SkiaLayer internal constructor(
notifyChange(PropertyKind.Renderer)
}
val renderInfo: String
get() = if (contextHandler?.context == null)
"ContextHandler hasn't been initialized yet."
get() = if (redrawer == null)
"SkiaLayer isn't initialized yet"
else
contextHandler!!.rendererInfo()
redrawer!!.renderInfo
@Volatile
private var picture: PictureHolder? = null
......@@ -235,9 +225,7 @@ actual open class SkiaLayer internal constructor(
thrown = false
try {
renderApi = fallbackRenderApiQueue.removeAt(0)
contextHandler?.dispose()
redrawer?.dispose()
contextHandler = renderFactory.createContextHandler(this, renderApi)
redrawer = renderFactory.createRedrawer(this, renderApi, properties)
redrawer?.syncSize()
} catch (e: RenderException) {
......@@ -251,7 +239,7 @@ actual open class SkiaLayer internal constructor(
}
}
protected open fun init(recreation: Boolean = false) {
private fun init(recreation: Boolean = false) {
isDisposed = false
backedLayer.init()
pictureRecorder = PictureRecorder()
......@@ -281,8 +269,6 @@ actual open class SkiaLayer internal constructor(
// we should dispose redrawer first (to cancel `draw` in rendering thread)
redrawer?.dispose()
redrawer = null
contextHandler?.dispose()
contextHandler = null
picture?.instance?.close()
picture = null
pictureRecorder?.close()
......@@ -413,6 +399,7 @@ actual open class SkiaLayer internal constructor(
check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
check(!isDisposed) { "SkiaLayer is disposed" }
FrameWatcher.nextFrame()
fpsCounter?.tick()
val pictureWidth = (width * contentScale).toInt().coerceAtLeast(0)
......@@ -459,36 +446,28 @@ actual open class SkiaLayer internal constructor(
}
}
// can be called from non-swing thread
// throws exception if initialization of graphic context was not successful
internal fun draw() {
contextHandler?.apply {
if (!initContext()) {
throw RenderException("Cannot init graphic context")
internal fun draw(canvas: Canvas) {
check(!isDisposed) { "SkiaLayer is disposed" }
lockPicture {
canvas.drawPicture(it.instance)
}
initCanvas()
}
check(!isDisposed) { "SkiaLayer is disposed" }
contextHandler?.apply {
clearCanvas()
synchronized(pictureLock) {
private fun <T : Any> lockPicture(action: (PictureHolder) -> T): T? {
return synchronized(pictureLock) {
val picture = picture
if (picture != null) {
drawOnCanvas(picture.instance)
}
action(picture)
} else {
null
}
flush()
}
FrameWatcher.nextFrame()
}
// Captures current layer as bitmap.
fun screenshot(): Bitmap? {
return contextHandler?.let {
synchronized(pictureLock) {
val picture = picture
if (picture != null) {
check(!isDisposed) { "SkiaLayer is disposed" }
return lockPicture { picture ->
val store = Bitmap()
val ci = ColorInfo(
ColorType.BGRA_8888, ColorAlphaType.OPAQUE, ColorSpace.sRGB)
......@@ -498,10 +477,6 @@ actual open class SkiaLayer internal constructor(
canvas.drawPicture(picture.instance)
store.setImmutable()
store
} else {
null
}
}
}
}
......
......@@ -8,7 +8,7 @@ import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.AngleRedrawer
internal class AngleContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
internal class AngleContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
val angleRedrawer: AngleRedrawer
get() = layer.redrawer!! as AngleRedrawer
......
......@@ -7,7 +7,7 @@ import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.Direct3DRedrawer
import java.lang.ref.Reference
internal class Direct3DContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
internal class Direct3DContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
private val bufferCount = 2
private var surfaces: Array<Surface?> = arrayOfNulls(bufferCount)
......@@ -85,10 +85,6 @@ internal class Direct3DContextHandler(layer: SkiaLayer) : ContextHandler(layer)
}
}
override fun destroyContext() {
context?.close()
}
override fun disposeCanvas() {
for (bufferIndex in 0 until bufferCount) {
surfaces[bufferIndex]?.close()
......
......@@ -5,9 +5,8 @@ import org.jetbrains.skia.impl.Native
import org.jetbrains.skia.impl.getPtr
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.AbstractDirectSoftwareRedrawer
import java.lang.ref.Reference
internal class DirectSoftwareContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
internal class DirectSoftwareContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
var isInited = false
val softwareRedrawer: AbstractDirectSoftwareRedrawer
......
package org.jetbrains.skiko.context
import org.jetbrains.skiko.SkiaLayer
internal abstract class JvmContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw)
\ No newline at end of file
......@@ -7,7 +7,7 @@ import org.jetbrains.skia.SurfaceOrigin
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.redrawer.MetalRedrawer
internal class MetalContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
internal class MetalContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
val metalRedrawer: MetalRedrawer
get() = layer.redrawer!! as MetalRedrawer
......
......@@ -10,7 +10,7 @@ import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.makeGLContext
import org.jetbrains.skiko.makeGLRenderTarget
internal class OpenGLContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
internal class OpenGLContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
override fun initContext(): Boolean {
try {
if (context == null) {
......@@ -73,13 +73,4 @@ internal class OpenGLContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
"Model: ${gl.glGetString(gl.GL_RENDERER)}\n" +
"Total VRAM: ${gl.glGetIntegerv(gl.GL_TOTAL_MEMORY) / 1024} MB\n"
}
fun disposeInOpenGLContext() {
super.dispose()
}
// dispose should be called only in the OpenGL context.
// TODO remove in https://github.com/JetBrains/skiko/pull/300
override open fun dispose() {
}
}
......@@ -18,7 +18,7 @@ import java.awt.image.DataBufferByte
import java.awt.image.Raster
import java.awt.image.WritableRaster
internal class SoftwareContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
internal class SoftwareContextHandler(layer: SkiaLayer) : JvmContextHandler(layer) {
override val clearColor = if (layer.transparency && hostOs == OS.MacOS) 0 else -1
val colorModel = ComponentColorModel(
......@@ -59,9 +59,7 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : ContextHandler(layer)
canvas = Canvas(storage)
}
override fun drawOnCanvas(picture: Picture) {
super.drawOnCanvas(picture)
override fun flush() {
val scale = layer.contentScale
val w = (layer.width * scale).toInt().coerceAtLeast(0)
val h = (layer.height * scale).toInt().coerceAtLeast(0)
......@@ -87,8 +85,4 @@ internal class SoftwareContextHandler(layer: SkiaLayer) : ContextHandler(layer)
graphics?.drawImage(image!!, 0, 0, layer.width, layer.height, null)
}
}
override fun flush() {
// Raster does not need to flush canvas
}
}
......@@ -8,11 +8,15 @@ import org.jetbrains.skiko.FrameLimiter
import org.jetbrains.skiko.RenderException
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.context.DirectSoftwareContextHandler
internal abstract class AbstractDirectSoftwareRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = DirectSoftwareContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private val frameJob = Job()
private val frameLimiter = FrameLimiter(CoroutineScope(Dispatchers.IO + frameJob), layer.backedLayer)
private val frameDispatcher = FrameDispatcher(Dispatchers.Swing) {
......@@ -22,7 +26,7 @@ internal abstract class AbstractDirectSoftwareRedrawer(
if (layer.isShowing) {
layer.update(System.nanoTime())
layer.inDrawScope(layer::draw)
layer.inDrawScope(contextHandler::draw)
}
}
......@@ -34,7 +38,7 @@ internal abstract class AbstractDirectSoftwareRedrawer(
override fun redrawImmediately() {
layer.update(System.nanoTime())
layer.inDrawScope(layer::draw)
layer.inDrawScope(contextHandler::draw)
}
open fun resize(width: Int, height: Int) = resize(device, width, height)
......@@ -47,8 +51,9 @@ internal abstract class AbstractDirectSoftwareRedrawer(
}
open fun finishFrame() = finishFrame(device)
override fun dispose() {
disposeDevice(device)
frameDispatcher.cancel()
contextHandler.dispose()
disposeDevice(device)
runBlocking {
frameJob.cancelAndJoin()
}
......
......@@ -7,11 +7,15 @@ import org.jetbrains.skia.DirectContext
import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.context.AngleContextHandler
import org.jetbrains.skiko.context.DirectSoftwareContextHandler
internal class AngleRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = AngleContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
private var device: Long = 0
......@@ -25,6 +29,7 @@ internal class AngleRedrawer(
override fun dispose() {
frameDispatcher.cancel()
contextHandler.dispose()
disposeDevice(device)
isDisposed = true
}
......@@ -45,7 +50,7 @@ internal class AngleRedrawer(
}
private fun draw() {
layer.draw()
contextHandler.draw()
}
fun createDevice(): Long {
......
......@@ -6,11 +6,15 @@ import kotlinx.coroutines.withContext
import org.jetbrains.skia.DirectContext
import org.jetbrains.skia.Surface
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.AngleContextHandler
import org.jetbrains.skiko.context.Direct3DContextHandler
internal class Direct3DRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = Direct3DContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
private var drawLock = Any()
......@@ -29,8 +33,9 @@ internal class Direct3DRedrawer(
}
override fun dispose() = synchronized(drawLock) {
disposeDevice(device)
frameDispatcher.cancel()
contextHandler.dispose()
disposeDevice(device)
isDisposed = true
}
......@@ -61,7 +66,7 @@ internal class Direct3DRedrawer(
private fun drawAndSwap(withVsync: Boolean) = synchronized(drawLock) {
if (!isDisposed) {
layer.draw()
contextHandler.draw()
swap(device, withVsync)
}
}
......
......@@ -3,12 +3,16 @@ package org.jetbrains.skiko.redrawer
import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.Direct3DContextHandler
import org.jetbrains.skiko.context.OpenGLContextHandler
internal class LinuxOpenGLRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = OpenGLContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private var isDisposed = false
private var context = 0L
private val swapInterval = if (properties.isVsyncEnabled) 1 else 0
......@@ -52,8 +56,7 @@ internal class LinuxOpenGLRedrawer(
// makeCurrent is mandatory to destroy context, otherwise, OpenGL will destroy wrong context (from another window).
// see the official example: https://www.khronos.org/opengl/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX)
it.makeCurrent(context)
// TODO remove in https://github.com/JetBrains/skiko/pull/300
(layer.contextHandler as OpenGLContextHandler).disposeInOpenGLContext()
contextHandler.dispose()
it.destroyContext(context)
}
runBlocking {
......@@ -84,7 +87,7 @@ internal class LinuxOpenGLRedrawer(
}
private fun draw() {
layer.inDrawScope(layer::draw)
layer.inDrawScope(contextHandler::draw)
}
companion object {
......
......@@ -8,12 +8,17 @@ import kotlinx.coroutines.withContext
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(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = MetalContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
companion object {
init {
Library.load()
......@@ -39,6 +44,7 @@ internal class MetalRedrawer(
override fun dispose() = synchronized(drawLock) {
frameDispatcher.cancel()
contextHandler.dispose()
disposeDevice(device)
isDisposed = true
}
......@@ -90,7 +96,7 @@ internal class MetalRedrawer(
if (!isDisposed) {
val handle = startRendering()
try {
layer.draw()
contextHandler.draw()
} finally {
endRendering(handle)
}
......
......@@ -6,11 +6,16 @@ import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.FrameLimiter
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.context.MetalContextHandler
import org.jetbrains.skiko.context.SoftwareContextHandler
internal class SoftwareRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = SoftwareContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private val frameJob = Job()
private val frameLimiter = FrameLimiter(CoroutineScope(Dispatchers.IO + frameJob), layer.backedLayer)
......@@ -21,12 +26,13 @@ internal class SoftwareRedrawer(
if (layer.isShowing) {
layer.update(System.nanoTime())
layer.inDrawScope(layer::draw)
layer.inDrawScope(contextHandler::draw)
}
}
override fun dispose() {
frameDispatcher.cancel()
contextHandler.dispose()
runBlocking {
frameJob.cancelAndJoin()
}
......@@ -38,6 +44,6 @@ internal class SoftwareRedrawer(
override fun redrawImmediately() {
layer.update(System.nanoTime())
layer.inDrawScope(layer::draw)
layer.inDrawScope(contextHandler::draw)
}
}
\ No newline at end of file
......@@ -6,11 +6,15 @@ import kotlinx.coroutines.swing.Swing
import kotlinx.coroutines.withContext
import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.OpenGLContextHandler
import org.jetbrains.skiko.context.SoftwareContextHandler
internal class WindowsOpenGLRedrawer(
private val layer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = OpenGLContextHandler(layer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private val device = layer.backedLayer.useDrawingSurfacePlatformInfo(::getDevice)
private val context = createContext(device, layer.contentHandle, layer.transparency).also {
if (it == 0L) {
......@@ -35,8 +39,7 @@ internal class WindowsOpenGLRedrawer(
override fun dispose() {
check(!isDisposed) { "WindowsOpenGLRedrawer is disposed" }
makeCurrent()
// TODO remove in https://github.com/JetBrains/skiko/pull/300
(layer.contextHandler as OpenGLContextHandler).disposeInOpenGLContext()
contextHandler.dispose()
deleteContext(context)
isDisposed = true
}
......@@ -61,7 +64,7 @@ internal class WindowsOpenGLRedrawer(
}
private fun draw() {
layer.inDrawScope(layer::draw)
layer.inDrawScope(contextHandler::draw)
}
private fun makeCurrent() = makeCurrent(device, context)
......
......@@ -11,11 +11,10 @@ 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.context.ContextHandler
import org.jetbrains.skiko.context.JvmContextHandler
import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.util.ScreenshotTestRule
import org.jetbrains.skiko.util.swingTest
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assume.assumeTrue
import org.junit.Rule
......@@ -174,7 +173,7 @@ class SkiaWindowTest {
@Test
fun `render three windows`() = swingTest {
fun window(color: Color) = SkiaWindow().apply {
setLocation(200,200)
setLocation(200, 200)
setSize(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = RectRenderer(layer, 200, 100, color)
......@@ -244,7 +243,7 @@ class SkiaWindowTest {
@Test(timeout = 60000)
fun `stress test - open multiple windows`() = swingTest {
fun window(isAnimated: Boolean) = SkiaWindow().apply {
setLocation(200,200)
setLocation(200, 200)
setSize(40, 20)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = if (isAnimated) {
......@@ -288,7 +287,7 @@ class SkiaWindowTest {
fun openWindow() = SkiaWindow(
properties = SkiaLayerProperties(isVsyncEnabled = false, isVsyncFramelimitFallbackEnabled = true)
).apply {
setLocation(200,200)
setLocation(200, 200)
setSize(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
layer.skikoView = AnimatedBoxRenderer(layer, pixelsPerSecond = 20.0, size = 20.0)
......@@ -311,7 +310,7 @@ class SkiaWindowTest {
fun openWindow() = SkiaWindow(
properties = SkiaLayerProperties(isVsyncEnabled = false, isVsyncFramelimitFallbackEnabled = true)
).apply {
setLocation(200,200)
setLocation(200, 200)
setSize(400, 200)
preferredSize = Dimension(400, 200)
defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
......@@ -336,22 +335,20 @@ class SkiaWindowTest {
fun `fallback to software renderer, fail on init context`() = swingTest {
testFallbackToSoftware(
object : RenderFactory {
override fun createContextHandler(
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi
) = object : ContextHandler(layer) {
renderApi: GraphicsApi,
properties: SkiaLayerProperties
) = object : Redrawer {
private val contextHandler = object : JvmContextHandler(layer) {
override fun initContext() = false
override fun initCanvas() = Unit
}
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
properties: SkiaLayerProperties
): Redrawer = object : Redrawer {
override fun dispose() = Unit
override fun needRedraw() = Unit
override fun redrawImmediately() = layer.inDrawScope { layer.draw() }
override fun redrawImmediately() = layer.inDrawScope(contextHandler::draw)
override val renderInfo = ""
}
}
)
......@@ -361,14 +358,6 @@ class SkiaWindowTest {
fun `fallback to software renderer, fail on create redrawer`() = swingTest {
testFallbackToSoftware(
object : RenderFactory {
override fun createContextHandler(
layer: SkiaLayer,
renderApi: GraphicsApi
) = object : ContextHandler(layer) {
override fun initContext() = true
override fun initCanvas() = Unit
}
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
......@@ -382,14 +371,6 @@ class SkiaWindowTest {
fun `fallback to software renderer, fail on draw`() = swingTest {
testFallbackToSoftware(
object : RenderFactory {
override fun createContextHandler(
layer: SkiaLayer,
renderApi: GraphicsApi
) = object : ContextHandler(layer) {
override fun initContext() = true
override fun initCanvas() = Unit
}
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
......@@ -400,6 +381,7 @@ class SkiaWindowTest {
override fun redrawImmediately() = layer.inDrawScope {
throw RenderException()
}
override val renderInfo = ""
}
}
)
......@@ -439,14 +421,6 @@ class SkiaWindowTest {
private class OverrideNonSoftwareRenderFactory(
private val nonSoftwareRenderFactory: RenderFactory
) : RenderFactory {
override fun createContextHandler(layer: SkiaLayer, renderApi: GraphicsApi): ContextHandler {
return if (renderApi == GraphicsApi.SOFTWARE) {
RenderFactory.Default.createContextHandler(layer, renderApi)
} else {
nonSoftwareRenderFactory.createContextHandler(layer, renderApi)
}
}
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
......
......@@ -37,8 +37,6 @@ actual open class SkiaLayer(
actual var skikoView: SkikoView? = null
private var contextHandler = MacOSOpenGLContextHandler(this)
private var redrawer: Redrawer? = null
private var picture: PictureHolder? = null
......@@ -131,13 +129,11 @@ actual open class SkiaLayer(
}
nsView.frame = CGRectMake(0.0, 0.0, w, h)
redrawer?.syncSize()
initedCanvas = false
redrawer?.redrawImmediately()
}
override fun windowDidChangeBackingProperties(notification: NSNotification) {
redrawer?.syncSize()
initedCanvas = false
redrawer?.redrawImmediately()
}
}
......@@ -151,14 +147,13 @@ actual open class SkiaLayer(
actual fun detach() {
redrawer?.dispose()
redrawer = null
initedCanvas = false
}
actual fun needRedraw() {
redrawer?.needRedraw()
}
fun update(nanoTime: Long) {
internal fun update(nanoTime: Long) {
val width = nsView.frame.useContents { size.width }
val height = nsView.frame.useContents { size.height }
......@@ -173,24 +168,9 @@ actual open class SkiaLayer(
this.picture = PictureHolder(picture, pictureWidth.toInt(), pictureHeight.toInt())
}
private var initedCanvas = false
fun draw() {
contextHandler.apply {
if (!initedCanvas) {
if (!initContext()) {
error("initContext() failure")
return
}
initCanvas()
initedCanvas = true
}
clearCanvas()
val picture = picture
if (picture != null) {
drawOnCanvas(picture.instance)
}
flush()
internal fun draw(canvas: Canvas) {
picture?.also {
canvas.drawPicture(it.instance)
}
}
}
......
......@@ -7,7 +7,7 @@ import platform.OpenGL.GL_DRAW_FRAMEBUFFER_BINDING
import platform.OpenGL.glGetIntegerv
import platform.OpenGLCommon.GLenum
internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(layer) {
internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(layer, layer::draw) {
override fun initContext(): Boolean {
try {
if (context == null) {
......@@ -31,7 +31,10 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye
return result
}
var initedCanvas = 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)
......@@ -55,5 +58,8 @@ internal class MacOSOpenGLContextHandler(layer: SkiaLayer) : ContextHandler(laye
canvas = surface?.canvas
?: error("Could not obtain Canvas from Surface")
initedCanvas = true
}
}
}
......@@ -6,6 +6,8 @@ import org.jetbrains.skiko.FrameDispatcher
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerProperties
import org.jetbrains.skiko.SkikoDispatchers
import org.jetbrains.skiko.context.ContextHandler
import org.jetbrains.skiko.context.MacOSOpenGLContextHandler
import platform.CoreFoundation.CFTimeInterval
import platform.CoreGraphics.CGRectMake
import platform.CoreVideo.CVTimeStamp
......@@ -20,10 +22,13 @@ internal class MacOsOpenGLRedrawer(
private val skiaLayer: SkiaLayer,
private val properties: SkiaLayerProperties
) : Redrawer {
private val contextHandler = MacOSOpenGLContextHandler(skiaLayer)
override val renderInfo: String get() = contextHandler.rendererInfo()
private val glLayer = MacosGLLayer()
init {
glLayer.init(skiaLayer)
glLayer.init(skiaLayer, contextHandler)
}
private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) {
......@@ -31,6 +36,7 @@ internal class MacOsOpenGLRedrawer(
}
override fun dispose() {
contextHandler.dispose()
glLayer.dispose()
}
......@@ -44,6 +50,7 @@ internal class MacOsOpenGLRedrawer(
size.height.toInt().coerceAtLeast(0)
)
}
contextHandler.initedCanvas = false
}
private fun syncContentScale() {
......@@ -66,13 +73,15 @@ internal class MacOsOpenGLRedrawer(
internal class MacosGLLayer : CAOpenGLLayer {
private lateinit var layer: SkiaLayer
private lateinit var contextHandler: ContextHandler
@OverrideInit
constructor(): super()
@OverrideInit
constructor(layer: Any): super(layer)
fun init(layer: SkiaLayer) {
fun init(layer: SkiaLayer, contextHandler: ContextHandler) {
this.layer = layer
this.contextHandler = contextHandler
this.setNeedsDisplayOnBoundsChange(true)
this.removeAllAnimations()
this.setAutoresizingMask(kCALayerWidthSizable or kCALayerHeightSizable )
......@@ -112,7 +121,7 @@ internal class MacosGLLayer : CAOpenGLLayer {
CGLSetCurrentContext(ctx);
try {
layer.update(getTimeNanos())
layer.draw()
contextHandler.draw()
} catch (e: Throwable) {
e.printStackTrace()
throw e
......
......@@ -13,10 +13,6 @@ internal expect fun createNativeRedrawer(
internal actual fun makeDefaultRenderFactory(): RenderFactory {
return object : RenderFactory {
override fun createContextHandler(layer: SkiaLayer, renderApi: GraphicsApi): ContextHandler {
return createNativeContextHandler(layer, renderApi)
}
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
......
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