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 ...@@ -8,7 +8,6 @@ import java.awt.datatransfer.DataFlavor
import java.awt.datatransfer.StringSelection import java.awt.datatransfer.StringSelection
import java.awt.datatransfer.UnsupportedFlavorException import java.awt.datatransfer.UnsupportedFlavorException
import java.io.IOException import java.io.IOException
import java.net.MalformedURLException
import java.net.URI import java.net.URI
import java.net.URL import java.net.URL
import javax.swing.UIManager import javax.swing.UIManager
...@@ -16,13 +15,8 @@ import javax.swing.UIManager ...@@ -16,13 +15,8 @@ import javax.swing.UIManager
actual fun setSystemLookAndFeel() = UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) actual fun setSystemLookAndFeel() = UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
internal actual fun makeDefaultRenderFactory(): RenderFactory = internal actual fun makeDefaultRenderFactory(): RenderFactory =
object : RenderFactory { RenderFactory { layer, renderApi, analytics, properties ->
override fun createRedrawer( when (hostOs) {
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
): Redrawer = when (hostOs) {
OS.MacOS -> when (renderApi) { OS.MacOS -> when (renderApi) {
GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, analytics, properties) GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.SOFTWARE_FAST -> SoftwareRedrawer(layer, analytics, properties)
else -> MetalRedrawer(layer, analytics, properties) else -> MetalRedrawer(layer, analytics, properties)
......
...@@ -284,22 +284,22 @@ actual open class SkiaLayer internal constructor( ...@@ -284,22 +284,22 @@ actual open class SkiaLayer internal constructor(
@Volatile @Volatile
private var isDisposed = false private var isDisposed = false
private val redrawerManager = RedrawerManager<Redrawer>(properties.renderApi) { renderApi, oldRedrawer -> private val redrawerManager = RedrawerManager<Redrawer>(
oldRedrawer?.dispose() defaultRenderApi = properties.renderApi,
val newRedrawer = renderFactory.createRedrawer(this, renderApi, analytics, properties) redrawerFactory = { renderApi, oldRedrawer ->
newRedrawer.syncBounds() oldRedrawer?.dispose()
newRedrawer renderFactory.createRedrawer(this, renderApi, analytics, properties).also {
} it.syncBounds()
}
internal val redrawer: Redrawer? },
get() = redrawerManager.redrawer onRenderApiChanged = {
actual var renderApi: GraphicsApi
get() = redrawerManager.renderApi
set(value) {
redrawerManager.forceRenderApi(value)
notifyChange(PropertyKind.Renderer) notifyChange(PropertyKind.Renderer)
} }
)
internal val redrawer: Redrawer? by redrawerManager::redrawer
actual var renderApi: GraphicsApi by redrawerManager::renderApi
val renderInfo: String val renderInfo: String
get() = if (redrawer == null) get() = if (redrawer == null)
...@@ -320,15 +320,15 @@ actual open class SkiaLayer internal constructor( ...@@ -320,15 +320,15 @@ actual open class SkiaLayer internal constructor(
isInited = true isInited = true
} }
private val stateHandlers = private val stateChangeListeners =
mutableMapOf<PropertyKind, MutableList<(SkiaLayer) -> Unit>>() mutableMapOf<PropertyKind, MutableList<(SkiaLayer) -> Unit>>()
fun onStateChanged(kind: PropertyKind, handler: (SkiaLayer) -> Unit) { fun onStateChanged(kind: PropertyKind, handler: (SkiaLayer) -> Unit) {
stateHandlers.getOrPut(kind, { mutableListOf() }) += handler stateChangeListeners.getOrPut(kind, ::mutableListOf) += handler
} }
private fun notifyChange(kind: PropertyKind) { private fun notifyChange(kind: PropertyKind) {
stateHandlers.get(kind)?.let { handlers -> stateChangeListeners[kind]?.let { handlers ->
handlers.forEach { it(this) } handlers.forEach { it(this) }
} }
} }
...@@ -583,7 +583,7 @@ actual open class SkiaLayer internal constructor( ...@@ -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" } check(!isDisposed) { "SkiaLayer is disposed" }
lockPicture { lockPicture {
canvas.drawPicture(it.instance) canvas.drawPicture(it.instance)
......
...@@ -7,17 +7,19 @@ import org.jetbrains.skiko.SkikoProperties ...@@ -7,17 +7,19 @@ import org.jetbrains.skiko.SkikoProperties
internal class RedrawerManager<R>( internal class RedrawerManager<R>(
defaultRenderApi: GraphicsApi, 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 val fallbackRenderApiQueue = SkikoProperties.fallbackRenderApiQueue(defaultRenderApi).toMutableList()
private var _renderApi = fallbackRenderApiQueue[0]
val redrawer: R? var redrawer: R? = null
get() = _redrawer private set
val renderApi: GraphicsApi var renderApi: GraphicsApi = fallbackRenderApiQueue[0]
get() = _renderApi set(value) {
field = value
onRenderApiChanged?.invoke(value)
}
fun findNextWorkingRenderApi(recreation: Boolean = false) { fun findNextWorkingRenderApi(recreation: Boolean = false) {
if (recreation) { if (recreation) {
...@@ -27,10 +29,10 @@ internal class RedrawerManager<R>( ...@@ -27,10 +29,10 @@ internal class RedrawerManager<R>(
do { do {
thrown = false thrown = false
try { try {
_renderApi = fallbackRenderApiQueue.removeAt(0) renderApi = fallbackRenderApiQueue.removeAt(0)
_redrawer = redrawerFactory(_renderApi, redrawer) redrawer = redrawerFactory(renderApi, redrawer)
} catch (e: RenderException) { } catch (e: RenderException) {
_redrawer = null redrawer = null
Logger.warn(e) { "Fallback to next API" } Logger.warn(e) { "Fallback to next API" }
thrown = true thrown = true
} }
...@@ -41,11 +43,7 @@ internal class RedrawerManager<R>( ...@@ -41,11 +43,7 @@ internal class RedrawerManager<R>(
} }
} }
fun forceRenderApi(renderApi: GraphicsApi) {
_renderApi = renderApi
}
fun dispose() { fun dispose() {
_redrawer = null redrawer = null
} }
} }
\ No newline at end of file
...@@ -4,11 +4,11 @@ import org.jetbrains.skia.Canvas ...@@ -4,11 +4,11 @@ import org.jetbrains.skia.Canvas
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
import org.jetbrains.skiko.redrawer.RedrawerManager import org.jetbrains.skiko.redrawer.RedrawerManager
import java.awt.Component import java.awt.Component
import java.awt.Graphics
import java.awt.Graphics2D import java.awt.Graphics2D
import java.awt.GraphicsConfiguration import java.awt.GraphicsConfiguration
import javax.accessibility.Accessible import javax.accessibility.Accessible
import javax.accessibility.AccessibleContext import javax.accessibility.AccessibleContext
import javax.swing.JComponent
import javax.swing.JPanel import javax.swing.JPanel
import javax.swing.SwingUtilities.isEventDispatchThread import javax.swing.SwingUtilities.isEventDispatchThread
...@@ -65,10 +65,13 @@ open class SkiaSwingLayer( ...@@ -65,10 +65,13 @@ open class SkiaSwingLayer(
get() = this@SkiaSwingLayer.properties.adapterPriority get() = this@SkiaSwingLayer.properties.adapterPriority
} }
private val redrawerManager = RedrawerManager<SwingRedrawer>(properties.renderApi) { renderApi, oldRedrawer -> private val redrawerManager = RedrawerManager<SwingRedrawer>(
oldRedrawer?.dispose() properties.renderApi,
createSwingRedrawer(swingLayerProperties, renderDelegateWithClipping, renderApi, analytics) redrawerFactory = { renderApi, oldRedrawer ->
} oldRedrawer?.dispose()
createSwingRedrawer(swingLayerProperties, renderDelegateWithClipping, renderApi, analytics)
}
)
private val redrawer: SwingRedrawer? private val redrawer: SwingRedrawer?
get() = redrawerManager.redrawer get() = redrawerManager.redrawer
...@@ -109,7 +112,7 @@ open class SkiaSwingLayer( ...@@ -109,7 +112,7 @@ open class SkiaSwingLayer(
} }
} }
override fun paint(g: java.awt.Graphics) { override fun paint(g: Graphics) {
try { try {
redrawer?.redraw(g as Graphics2D) redrawer?.redraw(g as Graphics2D)
} catch (e: RenderException) { } catch (e: RenderException) {
......
...@@ -541,63 +541,41 @@ class SkiaLayerTest { ...@@ -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) @Test(timeout = 60000)
fun `fallback to software renderer, fail on init context`() = uiTest { fun `fallback to software renderer, fail on init context`() = uiTest {
testFallbackToSoftware( testFallbackToSoftware { layer, _, _, _ ->
object : RenderFactory { object : BaseTestRedrawer() {
override fun createRedrawer( private val contextHandler = object : JvmContextHandler(layer) {
layer: SkiaLayer, override fun initContext() = false
renderApi: GraphicsApi, override fun initCanvas() = Unit
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = object : Redrawer {
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 = ""
} }
override fun redrawImmediately() = layer.inDrawScope(contextHandler::draw)
} }
) }
} }
@Test(timeout = 60000) @Test(timeout = 60000)
fun `fallback to software renderer, fail on create redrawer`() = uiTest { fun `fallback to software renderer, fail on create redrawer`() = uiTest {
testFallbackToSoftware( testFallbackToSoftware { _, _, _, _ -> throw RenderException() }
object : RenderFactory {
override fun createRedrawer(
layer: SkiaLayer,
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = throw RenderException()
}
)
} }
@Test(timeout = 60000) @Test(timeout = 60000)
fun `fallback to software renderer, fail on draw`() = uiTest { fun `fallback to software renderer, fail on draw`() = uiTest {
testFallbackToSoftware( testFallbackToSoftware { layer, _, _, _ ->
object : RenderFactory { object : BaseTestRedrawer() {
override fun createRedrawer( override fun redrawImmediately() = layer.inDrawScope {
layer: SkiaLayer, throw RenderException()
renderApi: GraphicsApi,
analytics: SkiaLayerAnalytics,
properties: SkiaLayerProperties
) = object : Redrawer {
override fun dispose() = Unit
override fun needRedraw() = Unit
override fun redrawImmediately() = layer.inDrawScope {
throw RenderException()
}
override val renderInfo = ""
} }
} }
) }
} }
private suspend fun UiTestScope.testFallbackToSoftware(nonSoftwareRenderFactory: RenderFactory) { private suspend fun UiTestScope.testFallbackToSoftware(nonSoftwareRenderFactory: RenderFactory) {
...@@ -644,6 +622,35 @@ class SkiaLayerTest { ...@@ -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) @Test(timeout = 20000)
fun `render continuously empty content without vsync`() = uiTest { fun `render continuously empty content without vsync`() = uiTest {
val targetDrawCount = 500 val targetDrawCount = 500
...@@ -981,21 +988,25 @@ class SkiaLayerTest { ...@@ -981,21 +988,25 @@ class SkiaLayerTest {
} }
contentPane.add(layer) contentPane.add(layer)
} }
window.size = Dimension(400, 400) try {
window.isVisible = true window.size = Dimension(400, 400)
window.isVisible = true
repeat(20) { repeat(20) {
window.location = window.location.let { window.location = window.location.let {
java.awt.Point(it.x + 10, it.y + 10) java.awt.Point(it.x + 10, it.y + 10)
}
delay(50)
} }
delay(50)
}
// Ideally, layoutCount would be just 1, but Swing appears to call layout one extra time, so it ends up being 2. // Ideally, layoutCount would be just 1, but Swing appears to call layout one extra time, so it ends up being 2.
// Compare to 3 just to avoid a false-failure if there's another layout for whatever reason. // Compare to 3 just to avoid a false-failure if there's another layout for whatever reason.
// What we're interested to validate is that there's no layout occurring on every window move. // What we're interested to validate is that there's no layout occurring on every window move.
assert(layoutCount <= 3) { assert(layoutCount <= 3) {
"Layout count: $layoutCount" "Layout count: $layoutCount"
}
} finally {
window.dispose()
} }
} }
......
...@@ -2,7 +2,7 @@ package org.jetbrains.skiko ...@@ -2,7 +2,7 @@ package org.jetbrains.skiko
import org.jetbrains.skiko.redrawer.* import org.jetbrains.skiko.redrawer.*
internal interface RenderFactory { internal fun interface RenderFactory {
fun createRedrawer( fun createRedrawer(
layer: SkiaLayer, layer: SkiaLayer,
renderApi: GraphicsApi, 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