Unverified Commit afcd9e54 authored by Alexander Maryanovsky's avatar Alexander Maryanovsky Committed by GitHub

Fix drawing issues and add API to request render-and-draw explicitly (#1104)

parent 2044a13b
......@@ -15,8 +15,8 @@ internal class AndroidOpenGLRedrawer(
private val properties: SkiaLayerProperties
) : Redrawer {
override fun dispose() = TODO()
override fun needRedraw(canUpdateImmediately: Boolean) = TODO()
override fun redrawImmediately(updateNeeded: Boolean) = TODO()
override fun needRender(canUpdateImmediately: Boolean) = TODO()
override fun renderImmediately() = TODO()
override fun update(nanoTime: Long) = TODO()
override val renderInfo: String get() = "Android renderer"
......
......@@ -47,7 +47,7 @@ actual open class SkiaLayer {
view.setFocusableInTouchMode(true)
needRedraw()
needRender()
}
actual fun detach() {
......@@ -57,12 +57,14 @@ actual open class SkiaLayer {
}
}
actual fun needRedraw(throttledToVsync: Boolean) {
actual fun needRender(throttledToVsync: Boolean) {
glView?.apply {
scheduleFrame()
}
}
actual fun needRedraw() = needRender()
actual val pixelGeometry: PixelGeometry
get() = PixelGeometry.UNKNOWN
......
......@@ -113,8 +113,8 @@ actual open class SkiaLayer internal constructor(
// 2. HardwareLayer.paint is also not always called.
// For example, on macOs when we resize window or change DPI
//
// 3. to avoid double paint in one single frame, use needRedraw instead of redrawImmediately
redrawer?.needRedraw(throttledToVsync = false)
// 3. to avoid double paint in one single frame, use needRender instead of renderImmediately
redrawer?.needRender(throttledToVsync = false)
}
@Suppress("OVERRIDE_DEPRECATION")
......@@ -124,7 +124,7 @@ actual open class SkiaLayer internal constructor(
super.reshape(x, y, width, height)
redrawer?.syncBounds()
redrawer?.needRedraw(throttledToVsync = false)
redrawer?.needRender(throttledToVsync = false)
}
override fun getInputMethodRequests(): InputMethodRequests? {
......@@ -384,7 +384,7 @@ actual open class SkiaLayer internal constructor(
@Suppress("DEPRECATION")
super.reshape(x, y, w, h)
// Calling redrawImmediately as early as possible improves the situation with
// Calling renderImmediately as early as possible improves the situation with
// the visual glitch when the drawn content is scaled during window resize.
// Note, however, that this actually causes the reverse glitch (content appears
// scaled in the other direction from the window size), but this seems to
......@@ -394,7 +394,7 @@ actual open class SkiaLayer internal constructor(
// so apply this fix only for the Direct3D case.
if (renderApi == GraphicsApi.DIRECT3D && isShowing) {
redrawer?.syncBounds()
redrawer?.redrawImmediately(updateNeeded = true)
redrawer?.renderImmediately()
}
// Setting the bounds of children should be done only in the layout pass,
......@@ -416,24 +416,29 @@ actual open class SkiaLayer internal constructor(
override fun paint(g: Graphics) {
Logger.debug { "paint called on SkiaLayer $this" }
val updateNeeded = checkContentScale()
redrawer?.redrawImmediately(updateNeeded = updateNeeded)
checkContentScale()
redrawer?.needRender(throttledToVsync = false)
}
// Workaround for JBR-5274 and JBR-5305
fun checkContentScale(): Boolean {
fun checkContentScale() {
val currentGraphicsContextScaleTransform = graphicsConfiguration.defaultTransform
return (currentGraphicsContextScaleTransform != latestReceivedGraphicsContextScaleTransform).also {
if (it) {
firePropertyChange(
"graphicsContextScaleTransform",
latestReceivedGraphicsContextScaleTransform,
currentGraphicsContextScaleTransform
)
}
if (currentGraphicsContextScaleTransform != latestReceivedGraphicsContextScaleTransform) {
firePropertyChange(
"graphicsContextScaleTransform",
latestReceivedGraphicsContextScaleTransform,
currentGraphicsContextScaleTransform
)
}
}
/**
* Updates the layer and redraws synchronously.
*/
fun updateAndDrawImmediately() {
redrawer?.renderImmediately()
}
// We need to delegate all event listeners to the Canvas (so and focus/input)
// Canvas is heavyweight AWT component, JPanel is lightweight Swing component
// Event handling doesn't properly work when we mix heavyweight and lightweight components.
......@@ -546,12 +551,14 @@ actual open class SkiaLayer internal constructor(
/**
* Redraw on the next animation Frame (on vsync signal if vsync is enabled).
*/
actual fun needRedraw(throttledToVsync: Boolean) {
actual fun needRender(throttledToVsync: Boolean) {
check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
check(!isDisposed) { "SkiaLayer is disposed" }
redrawer?.needRedraw(throttledToVsync)
redrawer?.needRender(throttledToVsync)
}
actual fun needRedraw() = needRender()
internal fun update(nanoTime: Long) {
check(isEventDispatchThread()) { "Method should be called from AWT event dispatch thread" }
check(!isDisposed) { "SkiaLayer is disposed" }
......@@ -608,7 +615,7 @@ actual open class SkiaLayer internal constructor(
if (!isDisposed) {
Logger.warn(e) { "Exception in draw scope" }
redrawerManager.findNextWorkingRenderApi()
redrawer?.redrawImmediately(updateNeeded = true)
redrawer?.renderImmediately()
}
}
}
......
......@@ -29,16 +29,14 @@ internal abstract class AbstractDirectSoftwareRedrawer(
protected var device = 0L
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
frameDispatcher.scheduleFrame()
}
protected open fun draw() = inDrawScope(contextHandler::draw)
override fun redrawImmediately(updateNeeded: Boolean) {
if (updateNeeded) {
update()
}
override fun renderImmediately() {
update()
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
draw()
}
......
......@@ -59,16 +59,14 @@ internal class AngleRedrawer(
super.dispose()
}
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
checkDisposed()
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately(updateNeeded: Boolean) {
override fun renderImmediately() {
checkDisposed()
if (updateNeeded) {
update()
}
update()
inDrawScope {
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately)
......
......@@ -63,16 +63,14 @@ internal class Direct3DRedrawer(
super.dispose()
}
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
checkDisposed()
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately(updateNeeded: Boolean) {
override fun renderImmediately() {
checkDisposed()
if (updateNeeded) {
update()
}
update()
inDrawScope {
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
drawAndSwap(withVsync = SkikoProperties.windowsWaitForVsyncOnRedrawImmediately)
......
......@@ -72,17 +72,15 @@ internal class LinuxOpenGLRedrawer(
super.dispose()
}
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
checkDisposed()
toRedraw.add(this)
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately(updateNeeded: Boolean) = layer.backedLayer.lockLinuxDrawingSurface {
override fun renderImmediately() = layer.backedLayer.lockLinuxDrawingSurface {
checkDisposed()
if (updateNeeded) {
update()
}
update()
inDrawScope {
it.makeCurrent(context)
contextHandler.draw()
......
......@@ -31,8 +31,8 @@ internal class LinuxSoftwareRedrawer(
super.draw()
}
override fun redrawImmediately(updateNeeded: Boolean) = layer.backedLayer.lockLinuxDrawingSurface {
super.redrawImmediately(updateNeeded)
override fun renderImmediately() = layer.backedLayer.lockLinuxDrawingSurface {
super.renderImmediately()
}
override fun resize(width: Int, height: Int) = layer.backedLayer.lockLinuxDrawingSurface {
......
......@@ -94,26 +94,23 @@ internal class MetalRedrawer(
super.dispose()
}
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
checkDisposed()
frameDispatcher.scheduleFrame(needUpdate = true, throttledToVsync = throttledToVsync)
}
override fun redrawImmediately(updateNeeded: Boolean) {
override fun renderImmediately() {
checkDisposed()
if (updateNeeded) {
update()
}
// Trying to draw immediately in Metal will result in lost (undrawn)
// frames if there are more than two between consecutive vsync events.
if (layer.isShowing) {
frameDispatcher.scheduleFrame(needUpdate = false, throttledToVsync = false)
} else {
// But if the layer isn't showing yet, we want to draw immediately,
// so that if it shows before the next vsync, there is no background flash
inDrawScope {
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
performDraw()
update()
inDrawScope {
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
performDraw()
// Trying to draw immediately in Metal will result in lost (undrawn)
// frames if there are more than two between consecutive vsync events.
if (SkikoProperties.macOSWaitForPreviousFrameVsyncOnRedrawImmediately) {
runBlocking {
vSyncer?.waitForVSync()
}
}
}
}
......
......@@ -42,15 +42,13 @@ internal class SoftwareRedrawer(
super.dispose()
}
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately(updateNeeded: Boolean) {
override fun renderImmediately() {
checkDisposed()
if (updateNeeded) {
update()
}
update()
inDrawScope {
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
contextHandler.draw()
......
......@@ -55,17 +55,15 @@ internal class WindowsOpenGLRedrawer(
super.dispose()
}
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
check(!isDisposed) { "WindowsOpenGLRedrawer is disposed" }
toRedraw.add(this)
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately(updateNeeded: Boolean) {
override fun renderImmediately() {
check(!isDisposed) { "WindowsOpenGLRedrawer is disposed" }
if (updateNeeded) {
update()
}
update()
inDrawScope {
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
makeCurrent()
......
......@@ -59,7 +59,7 @@ class SkiaLayerPerformanceTest {
if (canCollect && frameTimes.size < frameCount) {
frameTimes.add(System.nanoTime()) // we check the real time, not the time provided by the argument
}
window.layer.needRedraw()
window.layer.needRender()
}
}
window.isUndecorated = true
......
......@@ -106,11 +106,11 @@ class SkiaLayerTest {
override fun keyTyped(e: KeyEvent?) {
launch {
val redrawer = window.layer.redrawer as MetalRedrawer
redrawer.redrawImmediately(updateNeeded = true)
redrawer.renderImmediately()
counter1 += 1
redrawer.redrawImmediately(updateNeeded = true)
redrawer.renderImmediately()
counter2 += 1
redrawer.redrawImmediately(updateNeeded = true)
redrawer.renderImmediately()
}
}
})
......@@ -179,7 +179,7 @@ class SkiaLayerTest {
screenshots.assert(window.bounds, "frame1")
app.rectWidth = 100
window.layer.needRedraw()
window.layer.needRender()
delay(1000)
screenshots.assert(window.bounds, "frame2")
......@@ -238,7 +238,7 @@ class SkiaLayerTest {
screenshots.assert(window.bounds, "frame1")
app.rectWidth = 100
window.layer.needRedraw()
window.layer.needRender()
delay(1000)
screenshots.assert(window.bounds, "frame2")
......@@ -271,12 +271,12 @@ class SkiaLayerTest {
window.defaultCloseOperation = WindowConstants.DISPOSE_ON_CLOSE
window.isUndecorated = true
window.isVisible = true
layer.needRedraw()
layer.needRender()
delay(1000)
assertEquals(0, renderedWidth)
renderedWidth = -1
layer.needRedraw()
layer.needRender()
delay(1000)
assertEquals(0, renderedWidth)
......@@ -439,7 +439,7 @@ class SkiaLayerTest {
}
@Test
fun `should call onRender after init, after resize, and only once after needRedraw`() = uiTest {
fun `should call onRender after init, after resize, and only once after needRender`() = uiTest {
var renderCount = 0
val window = UiTestWindow()
......@@ -464,7 +464,7 @@ class SkiaLayerTest {
assertTrue(renderCount > 0)
renderCount = 0
window.layer.needRedraw()
window.layer.needRender()
delay(1000)
assertEquals(1, renderCount)
} finally {
......@@ -561,7 +561,7 @@ class SkiaLayerTest {
delay(100)
val window = openWindow()
window.isVisible = true
window.layer.needRedraw()
window.layer.needRender()
yield()
window.paint(window.graphics)
assertRenderApiFor(window.layer)
......@@ -570,9 +570,12 @@ class SkiaLayerTest {
}
private abstract class BaseTestRedrawer(val layer: SkiaLayer): Redrawer {
private val frameDispatcher = FrameDispatcher(MainUIDispatcher) {
renderImmediately()
}
override fun dispose() = Unit
override fun needRedraw(throttledToVsync: Boolean) = Unit
override fun redrawImmediately(updateNeeded: Boolean) = Unit
override fun needRender(throttledToVsync: Boolean) = frameDispatcher.scheduleFrame()
override fun renderImmediately() = Unit
override fun update(nanoTime: Long) = layer.update(nanoTime)
override val renderInfo: String
......@@ -587,7 +590,7 @@ class SkiaLayerTest {
override fun initContext() = false
override fun initCanvas() = Unit
}
override fun redrawImmediately(updateNeeded: Boolean) = layer.inDrawScope(contextHandler::draw)
override fun renderImmediately() = layer.inDrawScope(contextHandler::draw)
}
}
}
......@@ -601,7 +604,7 @@ class SkiaLayerTest {
fun `fallback to software renderer, fail on draw`() = uiTest {
testFallbackToSoftware { layer, _, _, _ ->
object : BaseTestRedrawer(layer) {
override fun redrawImmediately(updateNeeded: Boolean) = layer.inDrawScope {
override fun renderImmediately() = layer.inDrawScope {
throw RenderException()
}
}
......@@ -625,7 +628,7 @@ class SkiaLayerTest {
screenshots.assert(window.bounds, "frame1", "testFallbackToSoftware")
app.rectWidth = 100
window.layer.needRedraw()
window.layer.needRender()
delay(1000)
screenshots.assert(window.bounds, "frame2", "testFallbackToSoftware")
......@@ -657,7 +660,7 @@ class SkiaLayerTest {
val window = UiTestWindow(
renderFactory = OverrideNonSoftwareRenderFactory { layer, _, _, _ ->
object : BaseTestRedrawer(layer) {
override fun redrawImmediately(updateNeeded: Boolean) = layer.inDrawScope {
override fun renderImmediately() = layer.inDrawScope {
throw RenderException()
}
}
......@@ -703,7 +706,7 @@ class SkiaLayerTest {
drawCount++
if (drawCount < targetDrawCount) {
window.layer.needRedraw()
window.layer.needRender()
} else {
onDrawCompleted.complete(Unit)
}
......@@ -823,7 +826,7 @@ class SkiaLayerTest {
repeat(10) {
window.isVisible = true
delay(16)
window.layer.needRedraw()
window.layer.needRender()
delay(500)
window.isVisible = false
......@@ -1145,7 +1148,7 @@ class SkiaLayerTest {
}
@Test
fun `temporary change is not visible`() = uiTest {
fun `temporary change is not visible with needRender(throttledToVsync = false)`() = uiTest {
assumeTrue(hostOs.isMacOS)
// The separation between update and draw is only implemented in MetalRedrawer at the moment
// Don't use assumeTrue, as uiTest iterates over multiple renderers,
......@@ -1181,18 +1184,19 @@ class SkiaLayerTest {
val robot = Robot()
var tempColorVisibleCount = 0
val testCount = 50
try {
repeat(50) {
repeat(testCount) {
// Wait for just after the next vsync, so we have plenty of time until the one after it
val vSyncer = MetalVSyncer(window.layer.windowHandle)
vSyncer.waitForVSync()
// Set the color to temp, then immediately back to normal
renderDelegate.color = tempColor
renderDelegate.layer.needRedraw(throttledToVsync = false)
renderDelegate.layer.needRender(throttledToVsync = false)
renderChannel.receive() // Wait until render is actually called
renderDelegate.color = color
renderDelegate.layer.needRedraw(throttledToVsync = false)
renderDelegate.layer.needRender(throttledToVsync = false)
// Check whether the temp color was visible
val startTime = System.currentTimeMillis()
......@@ -1208,15 +1212,15 @@ class SkiaLayerTest {
// color is reverted, we allow a small percentage of the tries to fail. This way the flakiness of the test
// is reduced.
// Note that in practice, however, this test had never failed on an M1 Ultra machine with a 60Hz monitor.
assertTrue(tempColorVisibleCount < 5)
assertTrue(tempColorVisibleCount < 5, "Temp color was visible $tempColorVisibleCount/$testCount times")
} finally {
window.dispose()
}
}
@Test
fun `needRedraw throttled and regular calls render and draw once`() = uiTest {
// Check that calling both needRedraw(true) and needRedraw(false) causes only one render and one draw call
fun `needRender throttled and regular calls render and draw once`() = uiTest {
// Check that calling both needRender(true) and needRender(false) causes only one render and one draw call
var renderCalls = 0
val renderChannel = Channel<Unit>(Channel.CONFLATED)
......@@ -1259,27 +1263,72 @@ class SkiaLayerTest {
renderCalls = 0
drawCalls = 0
withContext(MainUIDispatcher) {
window.layer.needRedraw(true)
window.layer.needRedraw(false)
window.layer.needRender(true)
window.layer.needRender(false)
}
delay(100)
assertEquals("Render was called more than once on needRedraw(true), needRedraw(false)", 1, renderCalls)
assertEquals("Draw was called more than once on needRedraw(true), needRedraw(false)", 1, drawCalls)
assertEquals("Render was called more than once on needRender(true), needRender(false)", 1, renderCalls)
assertEquals("Draw was called more than once on needRender(true), needRender(false)", 1, drawCalls)
renderCalls = 0
drawCalls = 0
withContext(MainUIDispatcher) {
window.layer.needRedraw(false)
window.layer.needRedraw(true)
window.layer.needRender(false)
window.layer.needRender(true)
}
delay(100)
assertEquals("Render was called more than once on needRedraw(false), needRedraw(true)", 1, renderCalls)
assertEquals("Draw was called more than once on needRedraw(true), needRedraw(true)", 1, drawCalls)
assertEquals("Render was called more than once on needRender(false), needRender(true)", 1, renderCalls)
assertEquals("Draw was called more than once on needRender(true), needRender(true)", 1, drawCalls)
} finally {
window.dispose()
}
}
@Test
fun `updateAndDrawImmediately updates and draws synchronously`() = uiTest {
// Check that calling both needRender(true) and needRender(false) causes only one render and one draw call
var renderCalls = 0
val renderChannel = Channel<Unit>(Channel.CONFLATED)
var drawCalls = 0
val deviceAnalytics = object : SkiaLayerAnalytics.DeviceAnalytics {
override fun beforeFrameRender() {
drawCalls++
}
}
val analytics = object : SkiaLayerAnalytics {
@ExperimentalSkikoApi
override fun device(
skikoVersion: String,
os: OS,
api: GraphicsApi,
deviceName: String?
): SkiaLayerAnalytics.DeviceAnalytics {
return deviceAnalytics
}
}
val window = UiTestWindow(analytics = analytics) {
size = Dimension(600, 600)
location = Point(400, 400)
layer.renderDelegate = object: SkikoRenderDelegate {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
renderCalls++
renderChannel.trySend(Unit)
}
}
contentPane.add(layer, BorderLayout.CENTER)
}
window.pack()
val initRenderCalls = renderCalls
val initDrawCalls = drawCalls
window.layer.updateAndDrawImmediately()
// Can't check renderCalls == initRenderCalls+1 because if drawing fails, render will be called again with
// the fallback renderer.
assertTrue(renderCalls > initRenderCalls)
assertTrue(drawCalls > initDrawCalls)
}
private class RectRenderer(
private val getContentScale: () -> Float,
var rectWidth: Int,
......@@ -1341,7 +1390,7 @@ class SkiaLayerTest {
color = Color.RED.rgb
})
layer.needRedraw()
layer.needRender()
}
}
......@@ -1354,7 +1403,7 @@ class SkiaLayerTest {
var continuousRedraw = continuousRedraw
set(value) {
if (value)
layer.needRedraw(throttledToVsync = true)
layer.needRender(throttledToVsync = true)
field = value
}
......@@ -1370,7 +1419,7 @@ class SkiaLayerTest {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), paint)
if (continuousRedraw) {
layer.needRedraw()
layer.needRender()
}
}
}
......
......@@ -61,7 +61,13 @@ expect open class SkiaLayer {
* @param throttledToVsync Whether to throttle calling [renderDelegate]'s [SkikoRenderDelegate.onRender] to at most
* once between vsync signals (if vsync is enabled).
*/
fun needRedraw(throttledToVsync: Boolean = true)
fun needRender(throttledToVsync: Boolean = true)
@Deprecated(
"Use needRender(throttledToVsync) instead",
replaceWith = ReplaceWith("needRender(throttledToVsync)")
)
fun needRedraw() // TODO: Remove this sometime after 2026-07
/**
* Drawing function.
......
......@@ -15,6 +15,6 @@ open class SkiaLayerRenderDelegate(
canvas.scale(contentScale, contentScale)
renderDelegate.onRender(canvas, (width / contentScale).toInt(), (height / contentScale).toInt(), nanoTime)
// Request next frame immediately.
layer.needRedraw()
layer.needRender()
}
}
......@@ -6,8 +6,8 @@ private val initialTime = TimeSource.Monotonic.markNow()
internal interface Redrawer {
fun dispose()
fun needRedraw(throttledToVsync: Boolean)
fun redrawImmediately(updateNeeded: Boolean)
fun needRender(throttledToVsync: Boolean)
fun renderImmediately()
fun syncBounds() = Unit
fun update(nanoTime: Long = initialTime.elapsedNow().inWholeNanoseconds)
fun setVisible(isVisible: Boolean) = Unit
......
......@@ -42,6 +42,10 @@ object SkikoProperties {
}
}
val macOSWaitForPreviousFrameVsyncOnRedrawImmediately: Boolean get() {
return getProperty("skiko.rendering.macos.waitForPreviousFrameVsyncOnRedrawImmediately")?.toBoolean() ?: true
}
val windowsWaitForVsyncOnRedrawImmediately: Boolean get() {
return getProperty("skiko.rendering.windows.waitForFrameVsyncOnRedrawImmediately")?.toBoolean() ?: false
}
......
......@@ -17,9 +17,10 @@ actual open class SkiaLayer {
set(value) {}
actual val component: Any?
get() = TODO("Not yet implemented")
actual fun needRedraw(throttledToVsync: Boolean) {
actual fun needRender(throttledToVsync: Boolean) {
TODO("unimplemented")
}
actual fun needRedraw() = needRender()
actual fun attachTo(container: Any) {
TODO("unimplemented")
}
......
......@@ -85,13 +85,13 @@ actual open class SkiaLayer {
@ObjCAction
fun frameDidChange(notification: NSNotification) {
redrawer?.syncBounds()
redrawer?.redrawImmediately(updateNeeded = true)
redrawer?.renderImmediately()
}
@ObjCAction
fun windowDidChangeBackingProperties(notification: NSNotification) {
redrawer?.syncBounds()
redrawer?.redrawImmediately(updateNeeded = true)
redrawer?.renderImmediately()
}
fun addObserver() {
......@@ -127,7 +127,7 @@ actual open class SkiaLayer {
nsViewObserver.addObserver()
redrawer = createNativeRedrawer(this, renderApi).apply {
syncBounds()
needRedraw()
needRender()
}
}
......@@ -140,10 +140,12 @@ actual open class SkiaLayer {
/**
* Schedules a frame to an appropriate moment.
*/
actual fun needRedraw(throttledToVsync: Boolean) {
redrawer?.needRedraw(throttledToVsync)
actual fun needRender(throttledToVsync: Boolean) {
redrawer?.needRender(throttledToVsync)
}
actual fun needRedraw() = needRender()
/**
* Updates the [picture] according to current [nanoTime]
*/
......
......@@ -137,7 +137,7 @@ internal class MacOsMetalRedrawer(
/**
* Schedules a frame [draw] to an appropriate moment.
*/
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
checkDisposed()
frameDispatcher.scheduleFrame()
}
......@@ -150,10 +150,10 @@ internal class MacOsMetalRedrawer(
/**
* Invokes [draw] right away.
*/
override fun redrawImmediately(updateNeeded: Boolean) {
override fun renderImmediately() {
checkDisposed()
autoreleasepool {
if (!isDisposed && updateNeeded) {
if (!isDisposed) {
update()
}
if (!isDisposed) { // Redrawer may be disposed in user code, during `update`
......
......@@ -38,7 +38,7 @@ internal class MacOsOpenGLRedrawer(
}
private val frameDispatcher = FrameDispatcher(SkikoDispatchers.Main) {
redrawImmediately(updateNeeded = true)
renderImmediately()
}
override fun dispose() {
......@@ -70,11 +70,11 @@ internal class MacOsOpenGLRedrawer(
skiaLayer.update(nanoTime)
}
override fun needRedraw(throttledToVsync: Boolean) {
override fun needRender(throttledToVsync: Boolean) {
frameDispatcher.scheduleFrame()
}
override fun redrawImmediately(updateNeeded: Boolean) {
override fun renderImmediately() {
glLayer.setNeedsDisplay()
skiaLayer.nsView.setNeedsDisplay(true)
}
......
......@@ -23,10 +23,12 @@ actual open class SkiaLayer {
get() = false
set(_) { throw UnsupportedOperationException() }
actual fun needRedraw(throttledToVsync: Boolean) {
actual fun needRender(throttledToVsync: Boolean) {
needRedrawCallback.invoke()
}
actual fun needRedraw() = needRender()
actual val component: Any?
get() = this.view
......
......@@ -65,7 +65,7 @@ class SkikoUIView : UIView {
}
)
skiaLayer.needRedrawCallback = _redrawer::needRedraw
skiaLayer.needRedrawCallback = _redrawer::needRender
skiaLayer.view = this
}
......
......@@ -190,7 +190,7 @@ internal class MetalRedrawer(
context.close()
}
internal fun needRedraw() {
internal fun needRender() {
displayLinkConditions.needsRedrawOnNextVsync = true
}
......
......@@ -48,10 +48,12 @@ actual open class SkiaLayer {
/**
* Schedules a drawFrame to the appropriate moment.
*/
actual fun needRedraw(throttledToVsync: Boolean) {
actual fun needRender(throttledToVsync: Boolean) {
state?.needRedraw()
}
actual fun needRedraw() = needRender()
/**
* An implementation of [SkikoRenderDelegate] with content rendering and
* event processing logic.
......
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