Unverified Commit 1f91b396 authored by Alexander Maryanovsky's avatar Alexander Maryanovsky Committed by GitHub

Move canvas preparation from ContextHandler.draw to SkiaLayer.awt.update() (#1178)

parent 95f85e7a
...@@ -4,6 +4,7 @@ import android.content.* ...@@ -4,6 +4,7 @@ import android.content.*
import android.content.res.Configuration import android.content.res.Configuration
import android.view.View import android.view.View
import org.jetbrains.skiko.redrawer.Redrawer import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.redrawer.defaultIsTransparentBackgroundSupported
actual fun setSystemLookAndFeel(): Unit = TODO() actual fun setSystemLookAndFeel(): Unit = TODO()
...@@ -15,6 +16,7 @@ internal class AndroidOpenGLRedrawer( ...@@ -15,6 +16,7 @@ internal class AndroidOpenGLRedrawer(
override fun needRender(canUpdateImmediately: Boolean) = TODO() override fun needRender(canUpdateImmediately: Boolean) = TODO()
override fun renderImmediately() = TODO() override fun renderImmediately() = TODO()
override fun update(nanoTime: Long) = TODO() override fun update(nanoTime: Long) = TODO()
override fun isTransparentBackgroundSupported() = defaultIsTransparentBackgroundSupported(layer)
override val renderInfo: String get() = "Android renderer" override val renderInfo: String get() = "Android renderer"
} }
......
...@@ -20,21 +20,6 @@ actual open class SkiaLayer { ...@@ -20,21 +20,6 @@ actual open class SkiaLayer {
if (value) throw IllegalArgumentException("changing fullscreen is unsupported") if (value) throw IllegalArgumentException("changing fullscreen is unsupported")
} }
actual var transparency: Boolean
get() = false
set(value) {
if (value) throw IllegalArgumentException("transparency unsupported")
}
/**
* The background color of the layer.
*/
actual internal var backgroundColor: Int = Color.WHITE
set(value) {
field = value
needRender()
}
actual var renderDelegate: SkikoRenderDelegate? = null actual var renderDelegate: SkikoRenderDelegate? = null
actual fun attachTo(container: Any) { actual fun attachTo(container: Any) {
...@@ -81,8 +66,5 @@ actual open class SkiaLayer { ...@@ -81,8 +66,5 @@ actual open class SkiaLayer {
actual val component: Any? actual val component: Any?
get() = this.container get() = this.container
actual internal val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
internal actual fun draw(canvas: Canvas): Unit = TODO() internal actual fun draw(canvas: Canvas): Unit = TODO()
} }
\ No newline at end of file
...@@ -194,18 +194,16 @@ actual open class SkiaLayer internal constructor( ...@@ -194,18 +194,16 @@ actual open class SkiaLayer internal constructor(
} }
private var _transparency: Boolean = false private var _transparency: Boolean = false
actual var transparency: Boolean
/**
* Whether transparency is enabled.
*/
var transparency: Boolean
get() = _transparency get() = _transparency
set(value) { set(value) {
configureBackground(value, _background) configureBackground(value, _background)
} }
internal actual var backgroundColor: Int
get() = background.rgb // Will return an ancestor's non-null background after setBackground(null).
set(value) {
configureBackground(_transparency, Color(value, true))
}
// This is needed because after setBackground(null), getBackground() will not return null, but an ancestor's // This is needed because after setBackground(null), getBackground() will not return null, but an ancestor's
// non-null background. But we need to preserve the null value when modifying `transparency`. // non-null background. But we need to preserve the null value when modifying `transparency`.
private var _background: Color? = null private var _background: Color? = null
...@@ -333,11 +331,11 @@ actual open class SkiaLayer internal constructor( ...@@ -333,11 +331,11 @@ actual open class SkiaLayer internal constructor(
jComponent.add(this) jComponent.add(this)
} }
/**
* A list of rectangles to cut out from the rendered content; No content will be drawn inside them.
*/
val clipComponents = mutableListOf<ClipRectangle>() val clipComponents = mutableListOf<ClipRectangle>()
internal actual val cutoutRectangles: List<ClipRectangle>
get() = clipComponents
@Volatile @Volatile
private var isDisposed = false private var isDisposed = false
...@@ -603,13 +601,27 @@ actual open class SkiaLayer internal constructor( ...@@ -603,13 +601,27 @@ actual open class SkiaLayer internal constructor(
// If this approach will be changed, create an issue in https://youtrack.jetbrains.com/issues/CMP for changing it in // If this approach will be changed, create an issue in https://youtrack.jetbrains.com/issues/CMP for changing it in
// https://github.com/JetBrains/compose-multiplatform/blob/e4e2d329709cded91a09cc612d4defbce37aad96/benchmarks/multiplatform/benchmarks/src/commonMain/kotlin/MeasureComposable.kt#L151 as well // https://github.com/JetBrains/compose-multiplatform/blob/e4e2d329709cded91a09cc612d4defbce37aad96/benchmarks/multiplatform/benchmarks/src/commonMain/kotlin/MeasureComposable.kt#L151 as well
val contentScale = this.contentScale
val pictureWidth = (backedLayer.width * contentScale).coerceAtLeast(0f) val pictureWidth = (backedLayer.width * contentScale).coerceAtLeast(0f)
val pictureHeight = (backedLayer.height * contentScale).coerceAtLeast(0f) val pictureHeight = (backedLayer.height * contentScale).coerceAtLeast(0f)
val intWidth = pictureWidth.toInt() val intWidth = pictureWidth.toInt()
val intHeight = pictureHeight.toInt() val intHeight = pictureHeight.toInt()
val pictureRecorder = pictureRecorder!! val pictureRecorder = pictureRecorder!!
val canvas = pictureRecorder.beginRecording(0f, 0f, pictureWidth, pictureHeight) val canvas = pictureRecorder.beginRecording(0f, 0f, pictureWidth, pictureHeight).apply {
for (component in clipComponents) {
cutoutFromClip(component, contentScale)
}
val layerBg = background.rgb // Will return an ancestor's non-null background after setBackground(null).
clear(
if (transparency && (redrawer?.isTransparentBackgroundSupported() == true)) {
layerBg
} else {
layerBg or 0xFF000000.toInt()
}
)
}
try { try {
isRendering = true isRendering = true
......
...@@ -10,11 +10,6 @@ import java.awt.color.ColorSpace ...@@ -10,11 +10,6 @@ import java.awt.color.ColorSpace
import java.awt.image.* import java.awt.image.*
internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHandler(layer) { internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHandler(layer) {
override fun isTransparentBackgroundSupported(): Boolean {
// TODO: why Software rendering has another transparency logic from the beginning
return hostOs == OS.MacOS
}
val colorModel = ComponentColorModel( val colorModel = ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB), ColorSpace.getInstance(ColorSpace.CS_sRGB),
true, true,
......
...@@ -74,4 +74,6 @@ internal abstract class AWTRedrawer( ...@@ -74,4 +74,6 @@ internal abstract class AWTRedrawer(
protected fun checkDisposed() { protected fun checkDisposed() {
check(!isDisposed) { "${this.javaClass.simpleName} is disposed" } check(!isDisposed) { "${this.javaClass.simpleName} is disposed" }
} }
override fun isTransparentBackgroundSupported() = defaultIsTransparentBackgroundSupported(layer)
} }
\ No newline at end of file
...@@ -55,4 +55,9 @@ internal class SoftwareRedrawer( ...@@ -55,4 +55,9 @@ internal class SoftwareRedrawer(
} }
} }
} }
override fun isTransparentBackgroundSupported(): Boolean {
// TODO: why Software rendering has another transparency logic from the beginning
return hostOs == OS.MacOS
}
} }
\ No newline at end of file
package org.jetbrains.skiko.swing package org.jetbrains.skiko.swing
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.cutoutFromClip
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.Graphics
...@@ -46,8 +45,8 @@ open class SkiaSwingLayer( ...@@ -46,8 +45,8 @@ open class SkiaSwingLayer(
val scale = graphicsConfiguration.defaultTransform.scaleX.toFloat() val scale = graphicsConfiguration.defaultTransform.scaleX.toFloat()
// clipping // clipping
for (index in clipComponents.indices) { for (index in clipComponents.indices) {
val item = clipComponents[index] val component = clipComponents[index]
canvas.cutoutFromClip(item, scale) canvas.cutoutFromClip(component, scale)
} }
renderDelegate.onRender(canvas, width, height, nanoTime) renderDelegate.onRender(canvas, width, height, nanoTime)
} }
......
...@@ -15,6 +15,7 @@ import org.jetbrains.skiko.context.JvmContextHandler ...@@ -15,6 +15,7 @@ import org.jetbrains.skiko.context.JvmContextHandler
import org.jetbrains.skiko.redrawer.MetalRedrawer import org.jetbrains.skiko.redrawer.MetalRedrawer
import org.jetbrains.skiko.redrawer.MetalVSyncer import org.jetbrains.skiko.redrawer.MetalVSyncer
import org.jetbrains.skiko.redrawer.Redrawer import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.redrawer.defaultIsTransparentBackgroundSupported
import org.jetbrains.skiko.swing.SkiaSwingLayer import org.jetbrains.skiko.swing.SkiaSwingLayer
import org.jetbrains.skiko.util.ScreenshotTestRule import org.jetbrains.skiko.util.ScreenshotTestRule
import org.jetbrains.skiko.util.UiTestScope import org.jetbrains.skiko.util.UiTestScope
...@@ -565,6 +566,7 @@ class SkiaLayerTest { ...@@ -565,6 +566,7 @@ class SkiaLayerTest {
override fun needRender(throttledToVsync: Boolean) = frameDispatcher.scheduleFrame() override fun needRender(throttledToVsync: Boolean) = frameDispatcher.scheduleFrame()
override fun renderImmediately() = Unit override fun renderImmediately() = Unit
override fun update(nanoTime: Long) = layer.update(nanoTime) override fun update(nanoTime: Long) = layer.update(nanoTime)
override fun isTransparentBackgroundSupported() = defaultIsTransparentBackgroundSupported(layer)
override val renderInfo: String override val renderInfo: String
get() = "" get() = ""
......
...@@ -15,7 +15,7 @@ internal fun uiTest( ...@@ -15,7 +15,7 @@ internal fun uiTest(
block: suspend UiTestScope.() -> Unit block: suspend UiTestScope.() -> Unit
) { ) {
assumeFalse(GraphicsEnvironment.isHeadless()) assumeFalse(GraphicsEnvironment.isHeadless())
assumeTrue(System.getProperty("skiko.test.ui.enabled", "false") == "true") // assumeTrue(System.getProperty("skiko.test.ui.enabled", "false") == "true")
val renderApiProperty = System.getProperty("skiko.test.ui.renderApi", "all") val renderApiProperty = System.getProperty("skiko.test.ui.renderApi", "all")
......
...@@ -1849,3 +1849,12 @@ private external fun _nRestore(ptr: NativePointer) ...@@ -1849,3 +1849,12 @@ private external fun _nRestore(ptr: NativePointer)
@ExternalSymbolName("org_jetbrains_skia_Canvas__1nRestoreToCount") @ExternalSymbolName("org_jetbrains_skia_Canvas__1nRestoreToCount")
private external fun _nRestoreToCount(ptr: NativePointer, saveCount: Int) private external fun _nRestoreToCount(ptr: NativePointer, saveCount: Int)
internal inline fun Canvas.runRestoringState(block: Canvas.() -> Unit) {
val restoreCount = save()
try {
block()
} finally {
restoreToCount(restoreCount)
}
}
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.ClipMode
/** /**
* Rectangle used for clipping. * Rectangle used for clipping.
*/ */
...@@ -18,4 +21,18 @@ internal fun ClipRectangle(x: Float, y: Float, width: Float, height: Float) = ob ...@@ -18,4 +21,18 @@ internal fun ClipRectangle(x: Float, y: Float, width: Float, height: Float) = ob
override val y: Float = y override val y: Float = y
override val width: Float = width override val width: Float = width
override val height: Float = height override val height: Float = height
} }
\ No newline at end of file
@Suppress("NOTHING_TO_INLINE")
internal inline fun Canvas.cutoutFromClip(rect: ClipRectangle, scale: Float) {
val x = rect.x
val y = rect.y
clipRect(
left = x * scale,
top = y * scale,
right = (x + rect.width) * scale,
bottom = (y + rect.height) * scale,
mode = ClipMode.DIFFERENCE,
antiAlias = true
)
}
...@@ -28,26 +28,11 @@ expect open class SkiaLayer { ...@@ -28,26 +28,11 @@ expect open class SkiaLayer {
*/ */
var fullscreen: Boolean var fullscreen: Boolean
/**
* If transparency is enabled.
*/
var transparency: Boolean
/**
* The color, in ARGB format, with which the layer is cleared before rendering.
*/
internal var backgroundColor: Int
/** /**
* Underlying platform component. * Underlying platform component.
*/ */
val component: Any? val component: Any?
/**
* A list of rectangles to cut out from the rendered content; No content will be drawn inside them.
*/
internal val cutoutRectangles: List<ClipRectangle>
/** /**
* Current view used for rendering. * Current view used for rendering.
*/ */
......
...@@ -42,54 +42,8 @@ internal abstract class ContextHandler( ...@@ -42,54 +42,8 @@ internal abstract class ContextHandler(
initCanvas() initCanvas()
canvas?.runRestoringState { canvas?.runRestoringState {
clear(Color.TRANSPARENT) clear(Color.TRANSPARENT)
val scale = layer.contentScale
for (clip in layer.cutoutRectangles) {
cutoutFromClip(clip, scale)
}
val layerBg = layer.backgroundColor
clear(
if (layer.transparency && isTransparentBackgroundSupported()) {
layerBg
} else {
layerBg or 0xFF000000.toInt()
}
)
drawContent() drawContent()
} }
flush() flush()
} }
protected open fun isTransparentBackgroundSupported(): Boolean {
if (hostOs == OS.MacOS) {
// macOS transparency is always supported
return true
}
// for non-macOS in fullscreen transparency is not supported
return !layer.fullscreen
}
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun Canvas.cutoutFromClip(rectangle: ClipRectangle, scale: Float) {
clipRect(
left = rectangle.x * scale,
top = rectangle.y * scale,
right = (rectangle.x + rectangle.width) * scale,
bottom = (rectangle.y + rectangle.height) * scale,
mode = ClipMode.DIFFERENCE,
antiAlias = true
)
}
private inline fun Canvas.runRestoringState(block: Canvas.() -> Unit) {
val restoreCount = save()
try {
block()
} finally {
restoreToCount(restoreCount)
}
} }
\ No newline at end of file
package org.jetbrains.skiko.redrawer package org.jetbrains.skiko.redrawer
import org.jetbrains.skiko.OS
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.hostOs
import kotlin.time.TimeSource import kotlin.time.TimeSource
private val initialTime = TimeSource.Monotonic.markNow() private val initialTime = TimeSource.Monotonic.markNow()
...@@ -12,4 +15,15 @@ internal interface Redrawer { ...@@ -12,4 +15,15 @@ internal interface Redrawer {
fun update(nanoTime: Long = initialTime.elapsedNow().inWholeNanoseconds) fun update(nanoTime: Long = initialTime.elapsedNow().inWholeNanoseconds)
fun setVisible(isVisible: Boolean) = Unit fun setVisible(isVisible: Boolean) = Unit
val renderInfo: String val renderInfo: String
fun isTransparentBackgroundSupported(): Boolean
}
internal fun defaultIsTransparentBackgroundSupported(layer: SkiaLayer): Boolean {
if (hostOs == OS.MacOS) {
// macOS transparency is always supported
return true
}
// for non-macOS in fullscreen transparency is not supported
return !layer.fullscreen
} }
\ No newline at end of file
...@@ -12,16 +12,8 @@ actual open class SkiaLayer { ...@@ -12,16 +12,8 @@ actual open class SkiaLayer {
actual var fullscreen: Boolean actual var fullscreen: Boolean
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
set(value) {} set(value) {}
actual var transparency: Boolean
get() = TODO("Not yet implemented")
set(value) {}
internal actual var backgroundColor: Int
get() = TODO("Not yet implemented")
set(value) {}
actual val component: Any? actual val component: Any?
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
actual fun needRender(throttledToVsync: Boolean) { actual fun needRender(throttledToVsync: Boolean) {
TODO("unimplemented") TODO("unimplemented")
} }
......
...@@ -49,24 +49,6 @@ actual open class SkiaLayer { ...@@ -49,24 +49,6 @@ actual open class SkiaLayer {
if (value) throw IllegalArgumentException("fullscreen unsupported") if (value) throw IllegalArgumentException("fullscreen unsupported")
} }
/**
* Transparency is not supported on macOS native.
*/
actual var transparency: Boolean
get() = false
set(value) {
if (value) throw IllegalArgumentException("transparency unsupported")
}
/**
* The background color of the layer.
*/
internal actual var backgroundColor: Int = Color.WHITE
set(value) {
field = value
needRender()
}
/** /**
* Underlying [NSView] * Underlying [NSView]
*/ */
...@@ -76,9 +58,6 @@ actual open class SkiaLayer { ...@@ -76,9 +58,6 @@ actual open class SkiaLayer {
actual val component: Any? actual val component: Any?
get() = this.nsView get() = this.nsView
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
/** /**
* Implements rendering logic and events processing. * Implements rendering logic and events processing.
*/ */
...@@ -172,7 +151,9 @@ actual open class SkiaLayer { ...@@ -172,7 +151,9 @@ actual open class SkiaLayer {
val pictureWidth = (width * contentScale).coerceAtLeast(0.0) val pictureWidth = (width * contentScale).coerceAtLeast(0.0)
val pictureHeight = (height * contentScale).coerceAtLeast(0.0) val pictureHeight = (height * contentScale).coerceAtLeast(0.0)
val canvas = pictureRecorder.beginRecording(0f, 0f, pictureWidth.toFloat(), pictureHeight.toFloat()) val canvas = pictureRecorder.beginRecording(0f, 0f, pictureWidth.toFloat(), pictureHeight.toFloat()).apply {
clear(Color.WHITE)
}
renderDelegate?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime) renderDelegate?.onRender(canvas, pictureWidth.toInt(), pictureHeight.toInt(), nanoTime)
val picture = pictureRecorder.finishRecordingAsPicture() val picture = pictureRecorder.finishRecordingAsPicture()
......
...@@ -191,6 +191,8 @@ internal class MacOsMetalRedrawer( ...@@ -191,6 +191,8 @@ internal class MacOsMetalRedrawer(
} }
} }
} }
override fun isTransparentBackgroundSupported() = defaultIsTransparentBackgroundSupported(skiaLayer)
} }
internal class MetalLayer : CAMetalLayer { internal class MetalLayer : CAMetalLayer {
......
...@@ -78,6 +78,8 @@ internal class MacOsOpenGLRedrawer( ...@@ -78,6 +78,8 @@ internal class MacOsOpenGLRedrawer(
glLayer.setNeedsDisplay() glLayer.setNeedsDisplay()
skiaLayer.nsView.setNeedsDisplay(true) skiaLayer.nsView.setNeedsDisplay(true)
} }
override fun isTransparentBackgroundSupported() = defaultIsTransparentBackgroundSupported(skiaLayer)
} }
internal class MacosGLLayer : CAOpenGLLayer { internal class MacosGLLayer : CAOpenGLLayer {
......
...@@ -20,19 +20,6 @@ actual open class SkiaLayer { ...@@ -20,19 +20,6 @@ actual open class SkiaLayer {
get() = true get() = true
set(_) { throw UnsupportedOperationException() } set(_) { throw UnsupportedOperationException() }
actual var transparency: Boolean
get() = false
set(_) { throw UnsupportedOperationException() }
/**
* The background color of the layer, as transparency is not supported.
*/
internal actual var backgroundColor: Int = Color.WHITE
set(value) {
field = value
needRender()
}
actual fun needRender(throttledToVsync: Boolean) { actual fun needRender(throttledToVsync: Boolean) {
needRedrawCallback.invoke() needRedrawCallback.invoke()
} }
...@@ -46,9 +33,6 @@ actual open class SkiaLayer { ...@@ -46,9 +33,6 @@ actual open class SkiaLayer {
actual val component: Any? actual val component: Any?
get() = this.view get() = this.view
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
val width: Float val width: Float
get() = view!!.frame.useContents { get() = view!!.frame.useContents {
return@useContents size.width.toFloat() return@useContents size.width.toFloat()
...@@ -80,7 +64,9 @@ actual open class SkiaLayer { ...@@ -80,7 +64,9 @@ actual open class SkiaLayer {
} }
internal fun draw(surface: Surface) { internal fun draw(surface: Surface) {
renderDelegate?.onRender(surface.canvas, surface.width, surface.height, currentNanoTime()) val canvas = surface.canvas
canvas.clear(Color.WHITE)
renderDelegate?.onRender(canvas, surface.width, surface.height, currentNanoTime())
} }
actual val pixelGeometry: PixelGeometry actual val pixelGeometry: PixelGeometry
......
...@@ -37,24 +37,6 @@ actual open class SkiaLayer { ...@@ -37,24 +37,6 @@ actual open class SkiaLayer {
if (value) throw Exception("Fullscreen is not supported!") if (value) throw Exception("Fullscreen is not supported!")
} }
/**
* Transparency is not supported
*/
actual var transparency: Boolean
get() = false
set(value) {
if (value) throw Exception("Transparency is not supported!")
}
/**
* The background color of the layer, as transparency is not supported.
*/
internal actual var backgroundColor: Int = Color.WHITE
set(value) {
field = value
needRender()
}
/** /**
* Schedules a drawFrame to the appropriate moment. * Schedules a drawFrame to the appropriate moment.
*/ */
...@@ -88,9 +70,6 @@ actual open class SkiaLayer { ...@@ -88,9 +70,6 @@ actual open class SkiaLayer {
actual val component: Any? actual val component: Any?
get() = this.htmlCanvas get() = this.htmlCanvas
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
private var htmlCanvas: HTMLCanvasElement? = null private var htmlCanvas: HTMLCanvasElement? = null
/** /**
...@@ -110,6 +89,7 @@ actual open class SkiaLayer { ...@@ -110,6 +89,7 @@ actual open class SkiaLayer {
} }
internal actual fun draw(canvas: Canvas) { internal actual fun draw(canvas: Canvas) {
canvas.clear(Color.WHITE)
renderDelegate?.onRender(canvas, state!!.width, state!!.height, currentNanoTime()) renderDelegate?.onRender(canvas, state!!.width, state!!.height, currentNanoTime())
} }
......
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