Skip to content

Commit a220b2a

Browse files
authored
Merge pull request #49 from silenium-dev/fix/skiko-version-independence
Fix skiko version independence
2 parents 969af87 + 3665471 commit a220b2a

File tree

7 files changed

+92
-54
lines changed

7 files changed

+92
-54
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
[versions]
22
kotlinx-coroutines = "1.10.2"
33
lwjgl = "3.3.6"
4-
skiko = "0.9.22.2"
54
jni-utils = "0.1.6"
6-
compose = "1.9.0"
5+
compose = "1.10.0-alpha02"
76
kotlin = "2.2.20"
87

98
[libraries]
@@ -19,8 +18,7 @@ lwjgl-core = { group = "org.lwjgl", name = "lwjgl", version.ref = "lwjgl" }
1918
lwjgl-glfw = { group = "org.lwjgl", name = "lwjgl-glfw", version.ref = "lwjgl" }
2019
lwjgl-opengl = { group = "org.lwjgl", name = "lwjgl-opengl", version.ref = "lwjgl" }
2120

22-
skiko-awt = { group = "org.jetbrains.skiko", name = "skiko-awt", version.ref = "skiko" }
23-
skiko-awt-runtime-linux-x64 = { group = "org.jetbrains.skiko", name = "skiko-awt-runtime-linux-x64", version.ref = "skiko" }
21+
skiko-awt = { group = "org.jetbrains.skiko", name = "skiko-awt" }
2422

2523
jni-utils = { group = "dev.silenium.libs.jni", name = "jni-utils", version.ref = "jni-utils" }
2624
slf4j-api = { group = "org.slf4j", name = "slf4j-api", version = "2.0.17" }
@@ -44,7 +42,6 @@ kotlinx-coroutines = [
4442

4543
skiko = [
4644
"skiko-awt",
47-
"skiko-awt-runtime-linux-x64",
4845
]
4946

5047
lwjgl = [

native/cmake/skia.cmake

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,17 @@ endforeach ()
6464
message(STATUS "Downloading Skia from ${ASSET_URL} with hash ${ASSET_HASH}")
6565

6666
set(SKIA_URL "${ASSET_URL}")
67-
string(REPLACE ":" "=" ASSET_HASH "${ASSET_HASH}")
67+
string(REGEX REPLACE ":.*" "" ASSET_HASH_ALGO "${ASSET_HASH}")
68+
string(REGEX REPLACE ".*:" "" ASSET_HASH_VALUE "${ASSET_HASH}")
69+
string(TOUPPER "${ASSET_HASH_ALGO}" ASSET_HASH_ALGO)
6870

6971
if (NOT ASSET_HASH)
7072
message(WARNING "Failed to find Skia hash, just checking for the files existence to determine if we need to download Skia again.")
7173
if (NOT EXISTS "${CMAKE_BINARY_DIR}/download/${ASSET_NAME}")
7274
file(DOWNLOAD ${SKIA_URL} "${CMAKE_BINARY_DIR}/download/${ASSET_NAME}" STATUS SKIA_DOWNLOAD_STATUS SHOW_PROGRESS)
7375
endif ()
7476
else ()
75-
file(DOWNLOAD ${SKIA_URL} "${CMAKE_BINARY_DIR}/download/${ASSET_NAME}" STATUS SKIA_DOWNLOAD_STATUS EXPECTED_HASH "${ASSET_HASH}" SHOW_PROGRESS)
77+
file(DOWNLOAD ${SKIA_URL} "${CMAKE_BINARY_DIR}/download/${ASSET_NAME}" STATUS SKIA_DOWNLOAD_STATUS EXPECTED_HASH "${ASSET_HASH_ALGO}=${ASSET_HASH_VALUE}" SHOW_PROGRESS)
7678
endif ()
7779

7880
list(GET SKIA_DOWNLOAD_STATUS 0 SKIA_DOWNLOAD_STATUS_CODE)

src/main/java/dev/silenium/compose/gl/CompositionLocals.kt

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,20 @@ package dev.silenium.compose.gl
22

33
import androidx.compose.runtime.CompositionLocal
44
import org.jetbrains.skia.DirectContext
5-
import org.jetbrains.skia.Surface
65
import org.jetbrains.skia.impl.NativePointer
76
import org.jetbrains.skiko.GraphicsApi
87
import org.jetbrains.skiko.SkiaLayer
9-
import org.jetbrains.skiko.graphicapi.DirectXOffscreenContext
8+
import java.awt.Container
109
import java.awt.Window
10+
import java.util.*
11+
import javax.swing.JComponent
12+
import kotlin.reflect.KClass
13+
import kotlin.reflect.KProperty1
14+
import kotlin.reflect.KType
15+
import kotlin.reflect.full.createType
16+
import kotlin.reflect.full.isSubtypeOf
1117
import kotlin.reflect.full.memberProperties
18+
import kotlin.reflect.full.superclasses
1219
import kotlin.reflect.jvm.isAccessible
1320
import kotlin.reflect.typeOf
1421

@@ -19,59 +26,71 @@ val LocalWindow: CompositionLocal<Window?> by lazy {
1926
method.invoke(null) as CompositionLocal<Window?>
2027
}
2128

22-
fun Window.directXRedrawer(): Any? {
23-
return contextHandler().let {
24-
val getter = it::class.memberProperties.first { it.name == "directXRedrawer" }
25-
getter.isAccessible = true
26-
getter.call(it)
29+
fun Window.directX12Device(): NativePointer? {
30+
return findSkiaLayer()?.let { layer ->
31+
if (layer.graphicsApi() != GraphicsApi.DIRECT3D) return null
32+
layer.redrawer().let { redrawer ->
33+
val getter = redrawer::class.memberProperties.first {
34+
it.name == "device" && it.returnType == typeOf<Long>()
35+
}
36+
getter.isAccessible = true
37+
getter.call(redrawer) as Long?
38+
}
2739
}
2840
}
2941

30-
fun Window.directX12Device(): NativePointer? {
31-
return directXRedrawer()?.let {
32-
val getter = it::class.memberProperties.first { it.name == "device" && it.returnType == typeOf<Long>() }
33-
getter.isAccessible = true
34-
getter.call(it) as Long?
35-
}
42+
fun SkiaLayer.directContext(): DirectContext? {
43+
return contextHandler()?.findProperty<DirectContext?>()
3644
}
3745

38-
fun Window.directContext(): DirectContext? {
39-
val surface = contextHandler().let {
40-
val getter = it::class.java.superclass.superclass.getDeclaredMethod("getSurface")
41-
getter.isAccessible = true
42-
getter.invoke(it) as? Surface
43-
}
44-
return surface?.recordingContext
46+
inline fun <reified T> Any.findProperty(): T? {
47+
return findProperty(typeOf<T>()) as T?
4548
}
4649

47-
fun Window.graphicsApi(): GraphicsApi {
48-
return mediator().let {
49-
val getter = it::class.memberProperties.first { it.name == "renderApi" && it.returnType == typeOf<GraphicsApi>() }
50-
getter.isAccessible = true
51-
getter.call(it) as GraphicsApi
50+
fun Any.findProperty(type: KType): Any? {
51+
val supertypes = LinkedList<KClass<*>>()
52+
supertypes.add(this::class)
53+
var getter: KProperty1<*, *>? = null
54+
while (getter == null && supertypes.isNotEmpty()) {
55+
val klass = supertypes.pop()
56+
for (prop in klass.memberProperties) {
57+
if (prop.returnType.isSubtypeOf(type)) {
58+
getter = prop
59+
break
60+
}
61+
}
62+
klass.superclasses.let(supertypes::addAll)
5263
}
64+
getter?.isAccessible = true
65+
return getter?.call(this)
5366
}
5467

55-
fun Window.mediator(): Any {
56-
val composePanel = this.getFieldValue("composePanel")!!
57-
val composeContainer = composePanel.getFieldValue("_composeContainer")!!
58-
return composeContainer.getFieldValue("mediator")!!
68+
fun SkiaLayer.graphicsApi(): GraphicsApi {
69+
return findProperty<GraphicsApi>() ?: GraphicsApi.UNKNOWN
5970
}
6071

61-
fun Window.contextHandler(): Any {
62-
val contentComponent = mediator().let {
63-
val getter = it::class.java.getMethod("getContentComponent")
64-
getter.invoke(it) as SkiaLayer
65-
}
66-
val redrawer = contentComponent.let {
67-
val getter = it::class.java.getMethod("getRedrawer${'$'}skiko")
68-
getter.invoke(it)
69-
}
70-
return redrawer?.getFieldValue("contextHandler")!!
72+
fun SkiaLayer.contextHandler(): Any? {
73+
val propType = Class.forName("org.jetbrains.skiko.context.ContextHandler")
74+
return redrawer().findProperty(propType.kotlin.createType())
7175
}
7276

73-
private fun Any.getFieldValue(fieldName: String): Any? {
74-
val field = this::class.java.getDeclaredField(fieldName)
75-
field.isAccessible = true
76-
return field.get(this)
77+
fun SkiaLayer.redrawer(): Any = SkikoCompat.getRedrawer(this)
78+
79+
fun Window.findSkiaLayer() = findComponent<SkiaLayer>()
80+
81+
private fun <T : JComponent> findComponent(
82+
container: Container,
83+
klass: Class<T>,
84+
): T? {
85+
val componentSequence = container.components.asSequence()
86+
return componentSequence
87+
.filter { klass.isInstance(it) }
88+
.ifEmpty {
89+
componentSequence
90+
.filterIsInstance<Container>()
91+
.mapNotNull { findComponent(it, klass) }
92+
}.map { klass.cast(it) }
93+
.firstOrNull()
7794
}
95+
96+
private inline fun <reified T : JComponent> Container.findComponent() = findComponent(this, T::class.java)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.silenium.compose.gl;
2+
3+
import org.jetbrains.skiko.SkiaLayer;
4+
5+
class SkikoCompat {
6+
public static Object getRedrawer(SkiaLayer layer) {
7+
return layer.getRedrawer$skiko();
8+
}
9+
}

src/main/java/dev/silenium/compose/gl/canvas/DefaultCanvasDriverFactory.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package dev.silenium.compose.gl.canvas
22

3+
import dev.silenium.compose.gl.findSkiaLayer
34
import dev.silenium.compose.gl.graphicsApi
45
import org.jetbrains.skiko.GraphicsApi
56
import java.awt.Window
67

78
object DefaultCanvasDriverFactory : CanvasDriverFactory<CanvasDriver> {
89
override fun create(window: Window): CanvasDriver {
9-
val factory = apiFactories[window.graphicsApi()]
10-
factory ?: throw UnsupportedOperationException("Unsupported graphics api: ${window.graphicsApi()}")
10+
val factory = apiFactories[window.findSkiaLayer()?.graphicsApi()]
11+
factory ?: throw UnsupportedOperationException(
12+
"Unsupported graphics api: ${
13+
window.findSkiaLayer()?.graphicsApi()
14+
}"
15+
)
1116
return factory.create(window)
1217
}
1318

src/main/java/dev/silenium/compose/gl/canvas/GLCanvas.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import androidx.compose.runtime.DisposableEffect
66
import androidx.compose.runtime.LaunchedEffect
77
import androidx.compose.runtime.remember
88
import androidx.compose.ui.Modifier
9+
import androidx.compose.ui.graphics.nativeCanvas
910
import androidx.compose.ui.unit.IntSize
1011
import dev.silenium.compose.gl.LocalWindow
1112
import dev.silenium.compose.gl.directContext
13+
import dev.silenium.compose.gl.findSkiaLayer
1214
import kotlinx.coroutines.Dispatchers
1315
import kotlinx.coroutines.isActive
1416
import kotlinx.coroutines.withContext
@@ -26,7 +28,7 @@ fun GLCanvas(
2628
LaunchedEffect(window) {
2729
withContext(Dispatchers.IO) {
2830
while (isActive) {
29-
window.directContext()?.let {
31+
window.findSkiaLayer()?.directContext()?.let {
3032
wrapper.setup(it)
3133
return@withContext
3234
}
@@ -39,6 +41,7 @@ fun GLCanvas(
3941
}
4042
}
4143
Canvas(modifier) {
44+
drawContext.canvas.nativeCanvas
4245
wrapper.render(this, onResize) {
4346
drawGL { block() }
4447
}

src/test/kotlin/direct/Main.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import androidx.compose.ui.scene.PlatformLayersComposeScene
1818
import androidx.compose.ui.unit.dp
1919
import androidx.compose.ui.window.Window
2020
import androidx.compose.ui.window.application
21+
import dev.silenium.compose.gl.LocalWindow
2122
import dev.silenium.compose.gl.canvas.GLCanvas
23+
import dev.silenium.compose.gl.findSkiaLayer
2224
import dev.silenium.compose.gl.graphicsApi
2325
import org.jetbrains.skia.*
2426
import org.jetbrains.skiko.Version
@@ -41,6 +43,7 @@ fun main() = application {
4143
val renderer = SampleRenderer()
4244
Window(onCloseRequest = ::exitApplication, title = "Test") {
4345
Box(Modifier.fillMaxSize()) {
46+
println("skia layer: ${LocalWindow.current?.findSkiaLayer()}")
4447
GLCanvas(
4548
modifier = Modifier.fillMaxSize(),
4649
onDispose = {
@@ -91,7 +94,7 @@ fun main() = application {
9194
horizontalAlignment = Alignment.Start,
9295
modifier = Modifier.padding(8.dp),
9396
) {
94-
Text("Skia Graphics API: ${window.graphicsApi()}")
97+
Text("Skia Graphics API: ${window.findSkiaLayer()?.graphicsApi()}")
9598
Text("Skia Version: ${Version.skia}")
9699
Text("Skiko Version: ${Version.skiko}")
97100
Button(onClick = { println("button pressed") }) {

0 commit comments

Comments
 (0)