Unverified Commit 4462fe46 authored by Alexander Maryanovsky's avatar Alexander Maryanovsky Committed by GitHub

Call the listeners of PropertyKind.Renderer on render API fallback (#1033)

parent ea19091b
......@@ -8,7 +8,6 @@ import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.StringSelection
import java.awt.datatransfer.UnsupportedFlavorException
import java.io.IOException
import java.net.MalformedURLException
import java.net.URI
import java.net.URL
import javax.swing.UIManager
......@@ -16,13 +15,8 @@ import javax.swing.UIManager
actual fun setSystemLookAndFeel() = UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
internal actual fun makeDefaultRenderFactory(): RenderFactory =
object : RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
): Redrawer = when (hostOs) {
RenderFactory { layer, renderApi, analytics, properties ->
when (hostOs) {
OS.MacOS -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, analytics, properties)
else -> MetalRedrawer(layer, analytics, properties)
......
......@@ -284,22 +284,22 @@ actual open class SkiaLayer internal constructor(
@Volatile
private var isDisposed = false
private val redrawerManager = RedrawerManager<Redrawer>(properties.renderApi) { renderApi, oldRedrawer ->
private val redrawerManager = RedrawerManager<Redrawer>(
defaultRenderApi = properties.renderApi,
redrawerFactory = { renderApi, oldRedrawer ->
oldRedrawer?.dispose()
val newRedrawer = renderFactory.createRedrawer(this, renderApi, analytics, properties)
newRedrawer.syncBounds()
newRedrawer
renderFactory.createRedrawer(this, renderApi, analytics, properties).also {
it.syncBounds()
}
internal val redrawer: Redrawer?
get() = redrawerManager.redrawer
actual var renderApi: GraphicsApi
get() = redrawerManager.renderApi
set(value) {
redrawerManager.forceRenderApi(value)
},
onRenderApiChanged = {
notifyChange(PropertyKind.Renderer)
}
)
internal val redrawer: Redrawer? by redrawerManager::redrawer
actual var renderApi: GraphicsApi by redrawerManager::renderApi
val renderInfo: String
get() = if (redrawer == null)
......@@ -320,15 +320,15 @@ actual open class SkiaLayer internal constructor(
isInited = true
}
private val stateHandlers =
private val stateChangeListeners =
mutableMapOf<PropertyKind, MutableList<(SkiaLayer) -> Unit>>()
fun onStateChanged(kind: PropertyKind, handler: (SkiaLayer) -> Unit) {
stateHandlers.getOrPut(kind, { mutableListOf() }) += handler
stateChangeListeners.getOrPut(kind, ::mutableListOf) += handler
}
private fun notifyChange(kind: PropertyKind) {
stateHandlers.get(kind)?.let { handlers ->
stateChangeListeners[kind]?.let { handlers ->
handlers.forEach { it(this) }
}
}
......@@ -583,7 +583,7 @@ actual open class SkiaLayer internal constructor(
}
}
actual internal fun draw(canvas: Canvas) {
internal actual fun draw(canvas: Canvas) {
check(!isDisposed) { "SkiaLayer is disposed" }
lockPicture {
canvas.drawPicture(it.instance)
......
......@@ -7,17 +7,19 @@ import org.jetbrains.skiko.SkikoProperties
internal class RedrawerManager<R>(
defaultRenderApi: GraphicsApi,
private val redrawerFactory: (renderApi: GraphicsApi, oldRedrawer: R?) -> R
private val redrawerFactory: (renderApi: GraphicsApi, oldRedrawer: R?) -> R,
private val onRenderApiChanged: ((GraphicsApi) -> Unit)? = null
) {
private var _redrawer: R? = null
private val fallbackRenderApiQueue = SkikoProperties.fallbackRenderApiQueue(defaultRenderApi).toMutableList()
private var _renderApi = fallbackRenderApiQueue[0]
val redrawer: R?
get() = _redrawer
var redrawer: R? = null
private set
val renderApi: GraphicsApi
get() = _renderApi
var renderApi: GraphicsApi = fallbackRenderApiQueue[0]
set(value) {
field = value
onRenderApiChanged?.invoke(value)
}
fun findNextWorkingRenderApi(recreation: Boolean = false) {
if (recreation) {
......@@ -27,10 +29,10 @@ internal class RedrawerManager<R>(
do {
thrown = false
try {
_renderApi = fallbackRenderApiQueue.removeAt(0)
_redrawer = redrawerFactory(_renderApi, redrawer)
renderApi = fallbackRenderApiQueue.removeAt(0)
redrawer = redrawerFactory(renderApi, redrawer)
} catch (e: RenderException) {
_redrawer = null
redrawer = null
Logger.warn(e) { "Fallback to next API" }
thrown = true
}
......@@ -41,11 +43,7 @@ internal class RedrawerManager<R>(
}
}
fun forceRenderApi(renderApi: GraphicsApi) {
_renderApi = renderApi
}
fun dispose() {
_redrawer = null
redrawer = null
}
}
\ No newline at end of file
......@@ -4,11 +4,11 @@ import org.jetbrains.skia.Canvas
import org.jetbrains.skiko.*
import org.jetbrains.skiko.redrawer.RedrawerManager
import java.awt.Component
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.GraphicsConfiguration
import javax.accessibility.Accessible
import javax.accessibility.AccessibleContext
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.SwingUtilities.isEventDispatchThread
......@@ -65,10 +65,13 @@ open class SkiaSwingLayer(
get() = this@SkiaSwingLayer.properties.adapterPriority
}
private val redrawerManager = RedrawerManager<SwingRedrawer>(properties.renderApi) { renderApi, oldRedrawer ->
private val redrawerManager = RedrawerManager<SwingRedrawer>(
properties.renderApi,
redrawerFactory = { renderApi, oldRedrawer ->
oldRedrawer?.dispose()
createSwingRedrawer(swingLayerProperties, renderDelegateWithClipping, renderApi, analytics)
}
)
private val redrawer: SwingRedrawer?
get() = redrawerManager.redrawer
......@@ -109,7 +112,7 @@ open class SkiaSwingLayer(
}
}
override fun paint(g: java.awt.Graphics) {
override fun paint(g: Graphics) {
try {
redrawer?.redraw(g as Graphics2D)
} catch (e: RenderException) {
......
......@@ -541,63 +541,41 @@ class SkiaLayerTest {
}
}
private abstract class BaseTestRedrawer: Redrawer {
override fun dispose() = Unit
override fun needRedraw() = Unit
override fun redrawImmediately() = Unit
override val renderInfo: String
get() = ""
}
@Test(timeout = 60000)
fun `fallback to software renderer, fail on init context`() = uiTest {
testFallbackToSoftware(
object : RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = object : Redrawer {
testFallbackToSoftware { layer, _, _, _ ->
object : BaseTestRedrawer() {
private val contextHandler = object : JvmContextHandler(layer) {
override fun initContext() = false
override fun initCanvas() = Unit
}
override fun dispose() = Unit
override fun needRedraw() = Unit
override fun redrawImmediately() = layer.inDrawScope(contextHandler::draw)
override val renderInfo = ""
}
}
)
}
@Test(timeout = 60000)
fun `fallback to software renderer, fail on create redrawer`() = uiTest {
testFallbackToSoftware(
object : RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = throw RenderException()
}
)
testFallbackToSoftware { _, _, _, _ -> throw RenderException() }
}
@Test(timeout = 60000)
fun `fallback to software renderer, fail on draw`() = uiTest {
testFallbackToSoftware(
object : RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = object : Redrawer {
override fun dispose() = Unit
override fun needRedraw() = Unit
testFallbackToSoftware { layer, _, _, _ ->
object : BaseTestRedrawer() {
override fun redrawImmediately() = layer.inDrawScope {
throw RenderException()
}
override val renderInfo = ""
}
}
)
}
private suspend fun UiTestScope.testFallbackToSoftware(nonSoftwareRenderFactory: RenderFactory) {
......@@ -644,6 +622,35 @@ class SkiaLayerTest {
}
}
@Test(timeout = 60000)
fun `renderApi change callback is invoked on fallback`() = uiTest {
val window = UiTestWindow(
renderFactory = OverrideNonSoftwareRenderFactory { layer, _, _, _ ->
object : BaseTestRedrawer() {
override fun redrawImmediately() = layer.inDrawScope {
throw RenderException()
}
}
}
)
try {
var rendererChangedCallbackInvoked = false
window.layer.onStateChanged(SkiaLayer.PropertyKind.Renderer) {
rendererChangedCallbackInvoked = true
}
window.setLocation(200, 200)
window.setSize(400, 200)
window.isVisible = true
delay(1000)
assertEquals(GraphicsApi.SOFTWARE_COMPAT, window.layer.renderApi)
assertTrue(rendererChangedCallbackInvoked)
} finally {
window.close()
}
}
@Test(timeout = 20000)
fun `render continuously empty content without vsync`() = uiTest {
val targetDrawCount = 500
......@@ -981,6 +988,7 @@ class SkiaLayerTest {
}
contentPane.add(layer)
}
try {
window.size = Dimension(400, 400)
window.isVisible = true
......@@ -997,6 +1005,9 @@ class SkiaLayerTest {
assert(layoutCount <= 3) {
"Layout count: $layoutCount"
}
} finally {
window.dispose()
}
}
private class RectRenderer(
......
......@@ -2,7 +2,7 @@ package org.jetbrains.skiko
import org.jetbrains.skiko.redrawer.*
internal interface RenderFactory {
internal fun interface RenderFactory {
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