Unverified Commit 2ef9b920 authored by Nikolay Igotti's avatar Nikolay Igotti Committed by GitHub

Commonize rendering across targets (#273)

parent 1f022991
package org.jetbrains.skiko.sample.js package org.jetbrains.skiko.sample.js
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.browser.window
import org.jetbrains.skia.Canvas import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Color4f import org.jetbrains.skia.Color4f
import org.jetbrains.skia.Paint import org.jetbrains.skia.Paint
import org.jetbrains.skia.PaintMode
import org.jetbrains.skia.Rect import org.jetbrains.skia.Rect
import org.jetbrains.skiko.wasm.api.CanvasRenderer import org.jetbrains.skiko.GenericRenderer
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaRenderer
import org.jetbrains.skiko.wasm.onWasmReady import org.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement import org.w3c.dom.HTMLCanvasElement
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
private data class Circle(var x: Float, var y: Float, var r: Float) private class DemoApp: SkiaRenderer {
private fun Canvas.draw(circle: Circle, paint: Paint) {
drawCircle(circle.x, circle.y, circle.r, paint)
}
private fun Circle.move(s: Float, angle: Float, width: Int, height: Int, r: Float) {
x = (x + s * sin(angle)).coerceAtLeast(r).coerceAtMost(width.toFloat() - r)
y = (y + s * cos(angle)).coerceAtLeast(r).coerceAtMost(height.toFloat() - r)
}
private enum class Position {
INSIDE,
TOUCHES_SOUTH,
TOUCHES_NORTH,
TOUCHES_WEST,
TOUCHES_EAST
}
private fun calculatePosition(circle: Circle, boundingWidth: Int, boundingHeight: Int): Position {
val southmost = circle.y + circle.r
val northmost = circle.y - circle.r
val westmost = circle.x - circle.r
val eastmost = circle.x + circle.r
return when {
southmost >= boundingHeight -> Position.TOUCHES_SOUTH
northmost <= 0 -> Position.TOUCHES_NORTH
eastmost >= boundingWidth -> Position.TOUCHES_EAST
westmost <= 0 -> Position.TOUCHES_WEST
else -> Position.INSIDE
}
}
private class BouncingBall(
val circle: Circle,
val width: Int,
val height: Int,
val velocity: Float,
var angle: Double
) {
fun recalculate(dt: Float) {
val position = calculatePosition(circle, width, height)
when (position) {
Position.TOUCHES_SOUTH -> angle = PI - angle
Position.TOUCHES_EAST -> angle = -angle
Position.TOUCHES_WEST -> angle = -angle
Position.TOUCHES_NORTH -> angle = PI - angle
Position.INSIDE -> angle
}
circle.move(velocity * (dt.coerceAtMost(500f) / 1000), angle.toFloat(), width, height, circle.r)
}
}
private fun Canvas.draw(ball: BouncingBall, paint: Paint) {
draw(ball.circle, paint)
}
private class BouncingBallsApp(htmlCanvas: HTMLCanvasElement, private var prevTimestamp: Double) : CanvasRenderer(htmlCanvas) {
private fun Color4f.asPaint() = Paint().apply {
color4f = this@asPaint
mode = PaintMode.FILL
isAntiAlias = true
}
val data = listOf(
BouncingBall(Circle(200f, 50f, 25f), width, height, 172f, PI / 4),
BouncingBall(Circle(100f, 100f, 10f), width, height, 162f, -PI / 3),
BouncingBall(Circle(150f, 120f, 30f), width, height, 168f, 3 * PI / 4),
BouncingBall(Circle(100f, 10f, 25f), width, height, 208f, -PI / 6),
BouncingBall(Circle(120f, 100f, 40f), width, height, 120f, -1.1 * PI)
).zip(listOf(
Color4f(0f, 1f, 0f, 0.8f).asPaint(),
Color4f(0f, 0f, 1f, 0.8f).asPaint(),
Color4f(1f, 0f, 0f, 0.8f).asPaint(),
Color4f(1f, 0f, 1f, 0.8f).asPaint(),
Color4f(0f, 1f, 1f, 0.8f).asPaint()
))
override fun drawFrame(currentTimestamp: Double) {
val dtime = (currentTimestamp - prevTimestamp)
prevTimestamp = currentTimestamp
data.forEach { (ball, paint) ->
ball.recalculate(dtime.toFloat())
canvas.draw(ball, paint)
}
}
}
private class DemoApp(htmlCanvas: HTMLCanvasElement) : CanvasRenderer(htmlCanvas) {
private val paint = Paint() private val paint = Paint()
override fun drawFrame(currentTimestamp: Double) { override fun onRender(canvas: Canvas, width: Int, height: Int, currentTimestamp: Long) {
canvas.draw(Circle(200f, 50f, 25f), paint) canvas.drawCircle(200f, 50f, 25f, paint)
canvas.drawLine(100f, 100f, 200f, 200f, paint) canvas.drawLine(100f, 100f, 200f, 200f, paint)
canvas.drawRect(Rect(10f, 20f, 50f, 70f), paint) canvas.drawRect(Rect(10f, 20f, 50f, 70f), paint)
...@@ -121,9 +26,17 @@ private class DemoApp(htmlCanvas: HTMLCanvasElement) : CanvasRenderer(htmlCanvas ...@@ -121,9 +26,17 @@ private class DemoApp(htmlCanvas: HTMLCanvasElement) : CanvasRenderer(htmlCanvas
fun main() { fun main() {
onWasmReady { onWasmReady {
val syncNow = window.performance.now() for (index in 1 .. 3) {
BouncingBallsApp(document.getElementById("c") as HTMLCanvasElement, syncNow).draw() val skiaLayer = SkiaLayer()
BouncingBallsApp(document.getElementById("b") as HTMLCanvasElement, syncNow).draw() val canvas = document.getElementById("c$index") as HTMLCanvasElement
DemoApp(document.getElementById("a") as HTMLCanvasElement).draw() val app = if (index == 3) {
DemoApp()
} else {
BouncingBalls()
}
skiaLayer.renderer = GenericRenderer(skiaLayer, app)
skiaLayer.setCanvas(canvas)
skiaLayer.draw()
}
} }
} }
package org.jetbrains.skiko.sample.js
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Color4f
import org.jetbrains.skia.Paint
import org.jetbrains.skia.PaintMode
import org.jetbrains.skiko.SkiaRenderer
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
class BouncingBalls: SkiaRenderer {
private data class Circle(var x: Float, var y: Float, var r: Float)
companion object {
private fun moveCircle(c: Circle, s: Float, angle: Float, width: Int, height: Int, r: Float) {
c.x = (c.x + s * sin(angle)).coerceAtLeast(r).coerceAtMost(width.toFloat() - r)
c.y = (c.y + s * cos(angle)).coerceAtLeast(r).coerceAtMost(height.toFloat() - r)
}
private fun calculatePosition(circle: Circle, boundingWidth: Int, boundingHeight: Int): Position {
val southmost = circle.y + circle.r
val northmost = circle.y - circle.r
val westmost = circle.x - circle.r
val eastmost = circle.x + circle.r
return when {
southmost >= boundingHeight -> Position.TOUCHES_SOUTH
northmost <= 0 -> Position.TOUCHES_NORTH
eastmost >= boundingWidth -> Position.TOUCHES_EAST
westmost <= 0 -> Position.TOUCHES_WEST
else -> Position.INSIDE
}
}
}
private enum class Position {
INSIDE,
TOUCHES_SOUTH,
TOUCHES_NORTH,
TOUCHES_WEST,
TOUCHES_EAST
}
private class BouncingBall(
val circle: Circle,
val velocity: Float,
var angle: Double
) {
fun recalculate(width: Int, height: Int, dt: Float) {
val position = calculatePosition(circle, width, height)
val dtMillis = dt / 1000000
when (position) {
Position.TOUCHES_SOUTH -> angle = PI - angle
Position.TOUCHES_EAST -> angle = -angle
Position.TOUCHES_WEST -> angle = -angle
Position.TOUCHES_NORTH -> angle = PI - angle
Position.INSIDE -> angle
}
moveCircle(circle, velocity * (dtMillis.coerceAtMost(500f) / 1000), angle.toFloat(), width, height, circle.r)
}
}
private var prevTimestamp: Long = 0L
private fun Color4f.asPaint() = Paint().apply {
color4f = this@asPaint
mode = PaintMode.FILL
isAntiAlias = true
}
private val data = listOf(
BouncingBall(Circle(200f, 50f, 25f), 172f, PI / 4),
BouncingBall(Circle(100f, 100f, 10f), 162f, -PI / 3),
BouncingBall(Circle(150f, 120f, 30f), 168f, 3 * PI / 4),
BouncingBall(Circle(100f, 10f, 25f), 208f, -PI / 6),
BouncingBall(Circle(120f, 100f, 40f), 120f, -1.1 * PI)
).zip(listOf(
Color4f(0f, 1f, 0f, 0.8f).asPaint(),
Color4f(0f, 0f, 1f, 0.8f).asPaint(),
Color4f(1f, 0f, 0f, 0.8f).asPaint(),
Color4f(1f, 0f, 1f, 0.8f).asPaint(),
Color4f(0f, 1f, 1f, 0.8f).asPaint()
))
override fun onRender(canvas: Canvas, width: Int, height: Int, currentTimestamp: Long) {
val dtime = (currentTimestamp - prevTimestamp)
prevTimestamp = currentTimestamp
data.forEach { (ball, paint) ->
ball.recalculate(width, height, dtime.toFloat())
canvas.drawCircle(ball.circle.x, ball.circle.y, ball.circle.r, paint)
}
}
}
\ No newline at end of file
...@@ -10,10 +10,10 @@ ...@@ -10,10 +10,10 @@
<body> <body>
<h1>Skiko For Web</h1> <h1>Skiko For Web</h1>
<canvas id="c" width="300" height="300"></canvas> <canvas id="c1" width="300" height="300"></canvas>
<canvas id="b" width="300" height="300"></canvas> <canvas id="c2" width="300" height="300"></canvas>
<div> <div>
<canvas id="a" width="606" height="400"></canvas> <canvas id="c3" width="606" height="400"></canvas>
</div> </div>
</body> </body>
</html> </html>
...@@ -33,6 +33,18 @@ if (project.hasProperty("skiko.version")) { ...@@ -33,6 +33,18 @@ if (project.hasProperty("skiko.version")) {
version = project.properties["skiko.version"] as String version = project.properties["skiko.version"] as String
} }
val resourcesDir = "$buildDir/resources"
val skikoWasm by configurations.creating
dependencies {
skikoWasm("org.jetbrains.skiko:skiko-js-wasm-runtime:$version")
}
val unzipTask = tasks.register("unzipWasm", Copy::class) {
destinationDir = file(resourcesDir)
from(skikoWasm.map { zipTree(it) })
}
kotlin { kotlin {
val targets = mutableListOf<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>() val targets = mutableListOf<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>()
val nativeHostTarget = when (host) { val nativeHostTarget = when (host) {
...@@ -48,6 +60,11 @@ kotlin { ...@@ -48,6 +60,11 @@ kotlin {
targets.add(iosArm64()) targets.add(iosArm64())
} }
js(IR) {
browser()
binaries.executable()
}
targets.forEach { targets.forEach {
it.apply { it.apply {
binaries { binaries {
...@@ -75,6 +92,12 @@ kotlin { ...@@ -75,6 +92,12 @@ kotlin {
dependsOn(commonMain) dependsOn(commonMain)
} }
val jsMain by getting {
dependsOn(commonMain)
resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unzipTask.map { it.destinationDir })
}
val macosMain by creating { val macosMain by creating {
dependsOn(nativeMain) dependsOn(nativeMain)
} }
...@@ -92,7 +115,7 @@ kotlin { ...@@ -92,7 +115,7 @@ kotlin {
} }
macosArm64Main macosArm64Main
} }
else -> throw GradleException("Host OS is not supported in Kotlin/Native.") else -> throw GradleException("Host OS is not supported")
} }
if (hostOs == "macos") { if (hostOs == "macos") {
...@@ -140,6 +163,10 @@ project.tasks.register<Exec>("run") { ...@@ -140,6 +163,10 @@ project.tasks.register<Exec>("run") {
} }
} }
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile>().configureEach {
dependsOn(unzipTask)
}
// Create Xcode integration tasks. // Create Xcode integration tasks.
val sdkName: String? = System.getenv("SDK_NAME") val sdkName: String? = System.getenv("SDK_NAME")
...@@ -196,3 +223,11 @@ val packForXCode = if (sdkName == null || targetBuildDir == null || executablePa ...@@ -196,3 +223,11 @@ val packForXCode = if (sdkName == null || targetBuildDir == null || executablePa
} }
} }
} }
// a temporary workaround for a bug in jsRun invocation - see https://youtrack.jetbrains.com/issue/KT-48273
afterEvaluate {
extensions.configure<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension> {
versions.webpackDevServer.version = "4.0.0"
versions.webpackCli.version = "4.9.0"
}
}
...@@ -8,5 +8,5 @@ pluginManagement { ...@@ -8,5 +8,5 @@ pluginManagement {
} }
} }
rootProject.name = "SkiaNativeSample" rootProject.name = "SkiaMultiplatformSample"
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
import org.jetbrains.skiko.SkiaRenderer
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
class BouncingBalls: SkiaRenderer {
private data class Circle(var x: Float, var y: Float, var r: Float)
companion object {
private fun moveCircle(c: Circle, s: Float, angle: Float, width: Int, height: Int, r: Float) {
c.x = (c.x + s * sin(angle)).coerceAtLeast(r).coerceAtMost(width.toFloat() - r)
c.y = (c.y + s * cos(angle)).coerceAtLeast(r).coerceAtMost(height.toFloat() - r)
}
private fun calculatePosition(circle: Circle, boundingWidth: Int, boundingHeight: Int): Position {
val southmost = circle.y + circle.r
val northmost = circle.y - circle.r
val westmost = circle.x - circle.r
val eastmost = circle.x + circle.r
return when {
southmost >= boundingHeight -> Position.TOUCHES_SOUTH
northmost <= 0 -> Position.TOUCHES_NORTH
eastmost >= boundingWidth -> Position.TOUCHES_EAST
westmost <= 0 -> Position.TOUCHES_WEST
else -> Position.INSIDE
}
}
}
private enum class Position {
INSIDE,
TOUCHES_SOUTH,
TOUCHES_NORTH,
TOUCHES_WEST,
TOUCHES_EAST
}
private class BouncingBall(
val circle: Circle,
val velocity: Float,
var angle: Double
) {
fun recalculate(width: Int, height: Int, dt: Float) {
val position = calculatePosition(circle, width, height)
val dtMillis = dt / 1000000
when (position) {
Position.TOUCHES_SOUTH -> angle = PI - angle
Position.TOUCHES_EAST -> angle = -angle
Position.TOUCHES_WEST -> angle = -angle
Position.TOUCHES_NORTH -> angle = PI - angle
Position.INSIDE -> angle
}
moveCircle(circle, velocity * (dtMillis.coerceAtMost(500f) / 1000), angle.toFloat(), width, height, circle.r)
}
}
private var prevTimestamp: Long = 0L
private fun Color4f.asPaint() = Paint().apply {
color4f = this@asPaint
mode = PaintMode.FILL
isAntiAlias = true
}
private val data = listOf(
BouncingBall(Circle(200f, 50f, 25f), 172f, PI / 4),
BouncingBall(Circle(100f, 100f, 10f), 162f, -PI / 3),
BouncingBall(Circle(150f, 120f, 30f), 168f, 3 * PI / 4),
BouncingBall(Circle(100f, 10f, 25f), 208f, -PI / 6),
BouncingBall(Circle(120f, 100f, 40f), 120f, -1.1 * PI)
).zip(listOf(
Color4f(0f, 1f, 0f, 0.8f).asPaint(),
Color4f(0f, 0f, 1f, 0.8f).asPaint(),
Color4f(1f, 0f, 0f, 0.8f).asPaint(),
Color4f(1f, 0f, 1f, 0.8f).asPaint(),
Color4f(0f, 1f, 1f, 0.8f).asPaint()
))
override fun onRender(canvas: Canvas, width: Int, height: Int, currentTimestamp: Long) {
val dtime = (currentTimestamp - prevTimestamp)
prevTimestamp = currentTimestamp
data.forEach { (ball, paint) ->
ball.recalculate(width, height, dtime.toFloat())
canvas.drawCircle(ball.circle.x, ball.circle.y, ball.circle.r, paint)
}
}
}
\ No newline at end of file
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
import org.jetbrains.skiko.SkiaRenderer
class RotatingSquare : SkiaRenderer {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val angleDeg = (nanoTime / 5_000_000) % 360
val paint = Paint().apply { color = Color.GREEN }
canvas.clear(Color.RED)
canvas.save();
canvas.translate(128.0f, 128.0f)
canvas.rotate(angleDeg.toFloat())
val rect = Rect.makeXYWH(-90.5f, -90.5f, 181.0f, 181.0f)
canvas.drawRect(rect, paint)
canvas.restore()
}
}
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
package org.jetbrains.skiko.sample package org.jetbrains.skiko.sample
import kotlinx.cinterop.* import kotlinx.cinterop.*
import org.jetbrains.skiko.SkiaRenderer
import platform.UIKit.* import platform.UIKit.*
import platform.Foundation.* import platform.Foundation.*
fun makeApp(): SkiaRenderer = BouncingBalls()
fun main() { fun main() {
val args = emptyArray<String>() val args = emptyArray<String>()
memScoped { memScoped {
......
...@@ -26,11 +26,8 @@ class SkikoViewController : UIViewController { ...@@ -26,11 +26,8 @@ class SkikoViewController : UIViewController {
this.size.width to this.size.height this.size.width to this.size.height
} }
view.setFrame(CGRectMake(0.0, 0.0, width, height)) view.setFrame(CGRectMake(0.0, 0.0, width, height))
val layer = SkiaLayer(width.toFloat(), height.toFloat()) val layer = SkiaLayer(width.toFloat(), height.toFloat())
layer.renderer = GenericRenderer(layer) { canvas, w, h, nanoTime -> layer.renderer = GenericRenderer(layer, makeApp())
displayScene(canvas, nanoTime)
}
layer.initLayer(view) layer.initLayer(view)
} }
} }
package org.jetbrains.skiko.sample
import kotlinx.browser.document
import org.jetbrains.skiko.GenericRenderer
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.wasm.onWasmReady
import org.w3c.dom.HTMLCanvasElement
fun main() {
onWasmReady {
val skiaLayer = SkiaLayer()
skiaLayer.renderer = GenericRenderer(skiaLayer, RotatingSquare())
val canvas = document.getElementById("SkikoTarget") as HTMLCanvasElement
skiaLayer.setCanvas(canvas)
skiaLayer.draw()
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>skiko for web demo</title>
<script src="skiko.js"> </script>
<script src="SkiaMultiplatformSample.js"> </script>
<link type="text/css" rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Skiko For Web</h1>
<div>
<canvas id="SkikoTarget" width="800" height="600"></canvas>
</div>
</body>
</html>
canvas {
border: 1px solid black;
}
\ No newline at end of file
...@@ -6,17 +6,13 @@ import org.jetbrains.skia.* ...@@ -6,17 +6,13 @@ import org.jetbrains.skia.*
import org.jetbrains.skiko.* import org.jetbrains.skiko.*
import kotlinx.cinterop.* import kotlinx.cinterop.*
fun makeApp() = BouncingBalls()
fun main() { fun main() {
NSApplication.sharedApplication() NSApplication.sharedApplication()
createWindow()
NSApp?.run()
}
fun createWindow() {
val layer = SkiaLayer() val layer = SkiaLayer()
layer.renderer = GenericRenderer(layer) {
canvas, w, h, nanoTime -> displayScene(canvas, nanoTime)
}
val window = SkiaWindow(layer) val window = SkiaWindow(layer)
layer.renderer = GenericRenderer(layer, makeApp())
window.nsWindow.orderFrontRegardless() window.nsWindow.orderFrontRegardless()
NSApp?.run()
} }
package org.jetbrains.skiko.sample
import org.jetbrains.skia.*
fun displayScene(canvas: Canvas, nanoTime: Long) {
val angleDeg = (nanoTime / 5_000_000) % 360
val paint = Paint().apply { color = Color.GREEN }
canvas.clear(Color.RED)
canvas.save();
canvas.translate(128.0f, 128.0f)
canvas.rotate(angleDeg.toFloat())
val rect = Rect.makeXYWH(-90.5f, -90.5f, 181.0f, 181.0f)
canvas.drawRect(rect, paint)
canvas.restore();
}
package org.jetbrains.skiko package org.jetbrains.skiko
internal expect inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R
internal expect fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties internal expect fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties
\ No newline at end of file
...@@ -20,7 +20,7 @@ internal class RendezvousBroadcastChannel<T> { ...@@ -20,7 +20,7 @@ internal class RendezvousBroadcastChannel<T> {
*/ */
suspend fun sendAll(value: T) { suspend fun sendAll(value: T) {
onRequest.receive() onRequest.receive()
val receiversCopy = synchronized(receivers) { val receiversCopy = maybeSynchronized(receivers) {
mutableListOf<Continuation<T>>().apply { mutableListOf<Continuation<T>>().apply {
addAll(receivers) addAll(receivers)
receivers.clear() receivers.clear()
...@@ -37,7 +37,7 @@ internal class RendezvousBroadcastChannel<T> { ...@@ -37,7 +37,7 @@ internal class RendezvousBroadcastChannel<T> {
* Can be called concurrently from multiple threads. * Can be called concurrently from multiple threads.
*/ */
suspend fun receive(): T = suspendCancellableCoroutine { continuation -> suspend fun receive(): T = suspendCancellableCoroutine { continuation ->
synchronized(receivers) { maybeSynchronized(receivers) {
receivers.add(continuation) receivers.add(continuation)
} }
onRequest.trySend(Unit) onRequest.trySend(Unit)
......
...@@ -3,12 +3,14 @@ package org.jetbrains.skiko ...@@ -3,12 +3,14 @@ package org.jetbrains.skiko
import org.jetbrains.skia.Canvas import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Picture import org.jetbrains.skia.Picture
expect open class SkiaLayer(properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties()) { expect open class SkiaLayer {
var renderApi: GraphicsApi var renderApi: GraphicsApi
val contentScale: Float val contentScale: Float
var fullscreen: Boolean var fullscreen: Boolean
var transparency: Boolean var transparency: Boolean
var renderer: SkiaRenderer?
fun needRedraw() fun needRedraw()
} }
...@@ -18,12 +20,12 @@ interface SkiaRenderer { ...@@ -18,12 +20,12 @@ interface SkiaRenderer {
open class GenericRenderer( open class GenericRenderer(
val layer: SkiaLayer, val layer: SkiaLayer,
val displayScene: (Canvas, Int, Int, Long) -> Unit val app: SkiaRenderer
): SkiaRenderer { ): SkiaRenderer {
override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) { override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
val contentScale = layer.contentScale val contentScale = layer.contentScale
canvas.scale(contentScale, contentScale) canvas.scale(contentScale, contentScale)
displayScene(canvas, (width / contentScale).toInt(), (height / contentScale).toInt(), nanoTime) app.onRender(canvas, (width / contentScale).toInt(), (height / contentScale).toInt(), nanoTime)
layer.needRedraw() layer.needRedraw()
} }
} }
......
...@@ -6,18 +6,11 @@ import org.jetbrains.skiko.context.MetalContextHandler ...@@ -6,18 +6,11 @@ import org.jetbrains.skiko.context.MetalContextHandler
import org.jetbrains.skiko.redrawer.MetalRedrawer import org.jetbrains.skiko.redrawer.MetalRedrawer
import platform.UIKit.* import platform.UIKit.*
actual open class SkiaLayer actual constructor( actual open class SkiaLayer(
val properties: SkiaLayerProperties var width: Float, var height: Float,
) { val properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties()
var width: Float = 0f )
var height: Float = 0f {
constructor(width: Float, height: Float) : this(
makeDefaultSkiaLayerProperties()) {
this.width = width
this.height = height
}
fun isShowing(): Boolean { fun isShowing(): Boolean {
return true return true
} }
...@@ -50,7 +43,7 @@ actual open class SkiaLayer actual constructor( ...@@ -50,7 +43,7 @@ actual open class SkiaLayer actual constructor(
redrawer?.redrawImmediately() redrawer?.redrawImmediately()
} }
var renderer: SkiaRenderer? = null actual var renderer: SkiaRenderer? = null
internal var redrawer: MetalRedrawer? = null internal var redrawer: MetalRedrawer? = null
private var picture: PictureHolder? = null private var picture: PictureHolder? = null
private val pictureRecorder = PictureRecorder() private val pictureRecorder = PictureRecorder()
......
package org.jetbrains.skiko package org.jetbrains.skiko
internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties { internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties {
TODO() return SkiaLayerProperties(true, true)
} }
internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R =
block()
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
actual open class SkiaLayer actual constructor( import org.jetbrains.skiko.wasm.api.CanvasRenderer
properties: SkiaLayerProperties import org.w3c.dom.HTMLCanvasElement
actual open class SkiaLayer(properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties()
) { ) {
private var state: CanvasRenderer? = null
actual var renderApi: GraphicsApi = GraphicsApi.WEBGL actual var renderApi: GraphicsApi = GraphicsApi.WEBGL
actual val contentScale: Float actual val contentScale: Float
get() = 1.0f get() = 1.0f
...@@ -12,7 +16,24 @@ actual open class SkiaLayer actual constructor( ...@@ -12,7 +16,24 @@ actual open class SkiaLayer actual constructor(
actual var transparency: Boolean actual var transparency: Boolean
get() = false get() = false
set(value) = throw Exception("Transparency is not supported!") set(value) = throw Exception("Transparency is not supported!")
actual fun needRedraw() { actual fun needRedraw() {
TODO("unimplemented") draw()
}
actual var renderer: SkiaRenderer? = null
fun setCanvas(htmlCanvas: HTMLCanvasElement) {
state = object: CanvasRenderer(htmlCanvas) {
override fun drawFrame(currentTimestamp: Double) {
// currentTimestamp is milliseconds.
val currentNanos = currentTimestamp * 1000000
renderer?.onRender(canvas, width, height, currentNanos.toLong())
}
}
}
fun draw() {
state?.draw()
} }
} }
...@@ -42,7 +42,6 @@ abstract class CanvasRenderer constructor(htmlCanvas: HTMLCanvasElement, val wid ...@@ -42,7 +42,6 @@ abstract class CanvasRenderer constructor(htmlCanvas: HTMLCanvasElement, val wid
GL.makeContextCurrent(contextPointer) GL.makeContextCurrent(contextPointer)
drawFrame(timestamp) drawFrame(timestamp)
surface.flushAndSubmit() surface.flushAndSubmit()
draw()
} }
} }
} }
\ No newline at end of file
...@@ -3,3 +3,6 @@ package org.jetbrains.skiko ...@@ -3,3 +3,6 @@ package org.jetbrains.skiko
internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties { internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties {
return SkiaLayerProperties(SkikoProperties.vsyncEnabled, SkikoProperties.vsyncFramelimitFallbackEnabled) return SkiaLayerProperties(SkikoProperties.vsyncEnabled, SkikoProperties.vsyncFramelimitFallbackEnabled)
} }
internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R =
synchronized(lock, block)
\ No newline at end of file
...@@ -26,7 +26,7 @@ import javax.swing.SwingUtilities.isEventDispatchThread ...@@ -26,7 +26,7 @@ import javax.swing.SwingUtilities.isEventDispatchThread
actual open class SkiaLayer internal constructor( actual open class SkiaLayer internal constructor(
externalAccessibleFactory: ((Component) -> Accessible)? = null, externalAccessibleFactory: ((Component) -> Accessible)? = null,
private val properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties(), private val properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties(),
private val renderFactory: RenderFactory private val renderFactory: RenderFactory = RenderFactory.Default
) : JPanel() { ) : JPanel() {
companion object { companion object {
...@@ -44,11 +44,6 @@ actual open class SkiaLayer internal constructor( ...@@ -44,11 +44,6 @@ actual open class SkiaLayer internal constructor(
internal val backedLayer: HardwareLayer internal val backedLayer: HardwareLayer
actual constructor(
properties: SkiaLayerProperties
) : this(null, properties, RenderFactory.Default)
constructor( constructor(
properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties(), properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties(),
externalAccessibleFactory: ((Component) -> Accessible)? = null externalAccessibleFactory: ((Component) -> Accessible)? = null
...@@ -128,7 +123,7 @@ actual open class SkiaLayer internal constructor( ...@@ -128,7 +123,7 @@ actual open class SkiaLayer internal constructor(
backedLayer.fullscreen = value backedLayer.fullscreen = value
} }
var renderer: SkiaRenderer? = null actual var renderer: SkiaRenderer? = null
val clipComponents = mutableListOf<ClipRectangle>() val clipComponents = mutableListOf<ClipRectangle>()
@Volatile @Volatile
......
...@@ -22,7 +22,7 @@ internal class Task { ...@@ -22,7 +22,7 @@ internal class Task {
*/ */
fun finish() { fun finish() {
if (!done.getAndSet(true)) { if (!done.getAndSet(true)) {
onFinish.offer(Unit) onFinish.trySend(Unit)
} }
} }
} }
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
actual open class SkiaLayer actual constructor( actual open class SkiaLayer(properties: SkiaLayerProperties) {
properties: SkiaLayerProperties
) {
actual var renderApi: GraphicsApi actual var renderApi: GraphicsApi
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
set(value) {} set(value) {}
...@@ -17,4 +15,5 @@ actual open class SkiaLayer actual constructor( ...@@ -17,4 +15,5 @@ actual open class SkiaLayer actual constructor(
actual fun needRedraw() { actual fun needRedraw() {
TODO("unimplemented") TODO("unimplemented")
} }
actual var renderer: SkiaRenderer? = null
} }
\ No newline at end of file
...@@ -7,8 +7,8 @@ import org.jetbrains.skiko.redrawer.Redrawer ...@@ -7,8 +7,8 @@ import org.jetbrains.skiko.redrawer.Redrawer
import platform.AppKit.NSView import platform.AppKit.NSView
import platform.Foundation.NSMakeRect import platform.Foundation.NSMakeRect
actual open class SkiaLayer actual constructor( actual open class SkiaLayer(
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties = makeDefaultSkiaLayerProperties()
) { ) {
actual var renderApi: GraphicsApi = GraphicsApi.OPENGL actual var renderApi: GraphicsApi = GraphicsApi.OPENGL
actual val contentScale: Float actual val contentScale: Float
...@@ -29,7 +29,7 @@ actual open class SkiaLayer actual constructor( ...@@ -29,7 +29,7 @@ actual open class SkiaLayer actual constructor(
val nsView = NSView(NSMakeRect(0.0, 0.0, 640.0, 480.0)) val nsView = NSView(NSMakeRect(0.0, 0.0, 640.0, 480.0))
var _contentScale: Float = 1.0f var _contentScale: Float = 1.0f
var renderer: SkiaRenderer? = null actual var renderer: SkiaRenderer? = null
private var contextHandler = MacOSOpenGLContextHandler(this) private var contextHandler = MacOSOpenGLContextHandler(this)
......
...@@ -3,3 +3,6 @@ package org.jetbrains.skiko ...@@ -3,3 +3,6 @@ package org.jetbrains.skiko
internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties { internal actual fun makeDefaultSkiaLayerProperties(): SkiaLayerProperties {
return SkiaLayerProperties(true, true) return SkiaLayerProperties(true, true)
} }
internal actual inline fun <R> maybeSynchronized(lock: Any, block: () -> R): R =
block()
\ No newline at end of file
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