Unverified Commit 2044a13b authored by Igor Demin's avatar Igor Demin Committed by GitHub

Revert "Pack ANGLE into a separate jar" (#1114)

Reverts JetBrains/skiko#1082

Sign task failed on CI
(`publishSkikoJvmRuntimeWindowsX64PublicationToComposeRepoRepository`):
```

  Some problems were found with the configuration of task ':signSkikoJvmRuntimeWindowsX64Publication' (type 'Sign').
16:29:09     - Gradle detected a problem with the following location: 'C:\buildagent\work\381d14fe560aa354\skiko\build\libs\skiko-0.9.28+debug-javadoc.jar.asc'.

  org.gradle.internal.execution.WorkValidationException: Some problems were found with the configuration of task ':signSkikoJvmRuntimeWindowsX64Publication' (type 'Sign').
    - Gradle detected a problem with the following location: 'C:\buildagent\work\381d14fe560aa354\skiko\build\libs\skiko-0.9.28+debug-javadoc.jar.asc'.
      
      Reason: Task ':publishSkikoJvmRuntimeAngleWindowsX64PublicationToComposeRepoRepository' uses this output of task ':signSkikoJvmRuntimeWindowsX64Publication' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
      
      Possible solutions:
        1. Declare task ':signSkikoJvmRuntimeWindowsX64Publication' as an input of ':publishSkikoJvmRuntimeAngleWindowsX64PublicationToComposeRepoRepository'.
        2. Declare an explicit dependency on ':signSkikoJvmRuntimeWindowsX64Publication' from ':publishSkikoJvmRuntimeAngleWindowsX64PublicationToComposeRepoRepository' using Task#dependsOn.
        3. Declare an explicit dependency on ':signSkikoJvmRuntimeWindowsX64Publication' from ':publishSkikoJvmRuntimeAngleWindowsX64PublicationToComposeRepoRepository' using Task#mustRunAfter.
      
      For more information, please refer to https://docs.gradle.org/8.14.3/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
    - Gradle detected a problem with the following location: 'C:\buildagent\work\381d14fe560aa354\skiko\build\libs\skiko-0.9.28+debug-sources.jar.asc'.
      
      Reason: Task ':publishSkikoJvmRuntimeAngleWindowsX64PublicationToComposeRepoRepository' uses this output of task ':signSkikoJvmRuntimeWindowsX64Publication' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
      
      Possible solutions:
        1. Declare task ':signSkikoJvmRuntimeWindowsX64Publication' as an input of ':publishSkikoJvmRuntimeAngleWindowsX64PublicationToComposeRepoRepository'.
        2. Declare an explicit dependency on ':signSkikoJvmRuntimeWindowsX64Publication' from ':publishSkikoJvmRuntimeAngleWindowsX64PublicationToComposeRepoRepository' using Task#dependsOn.
        3. Declare an explicit dependency on ':signSkikoJvmRuntimeWindowsX64Publication' from ':publishSkikoJvmRuntimeAngleWindowsX64PublicationToComposeRepoRepository' using Task#mustRunAfter.
      
      For more information, please refer to https://docs.gradle.org/8.14.3/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
```
parent 3e870638
...@@ -40,9 +40,6 @@ dependencies { ...@@ -40,9 +40,6 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.0")
implementation("org.jetbrains.skiko:skiko-awt-runtime-$target:$version") implementation("org.jetbrains.skiko:skiko-awt-runtime-$target:$version")
implementation("org.jetbrains.runtime:jbr-api:1.5.0") implementation("org.jetbrains.runtime:jbr-api:1.5.0")
if (System.getProperty("os.name").startsWith("Win")) {
implementation("org.jetbrains.skiko:skiko-awt-runtime-angle-$target:$version")
}
testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
} }
...@@ -71,26 +68,17 @@ val casualRun = tasks.named<JavaExec>("run") { ...@@ -71,26 +68,17 @@ val casualRun = tasks.named<JavaExec>("run") {
additionalArguments.forEach { systemProperty(it.key, it.value) } additionalArguments.forEach { systemProperty(it.key, it.value) }
} }
tasks.register("runWithSoftwareRenderer") { tasks.register("runSoftware") {
additionalArguments += mapOf("skiko.renderApi" to "DIRECT_SOFTWARE") additionalArguments += mapOf("skiko.renderApi" to "DIRECT_SOFTWARE")
dependsOn(casualRun) dependsOn(casualRun)
} }
// Use Angle as a primary renderer for Windows. Renderers for the other OSes are not changing yet
tasks.register("runWithAngleEnabled") {
group = "application"
additionalArguments += mapOf("skiko.rendering.angle.enabled" to "true")
dependsOn(casualRun)
}
tasks.register("runWithTransparency") { tasks.register("runWithTransparency") {
group = "application"
additionalArguments += mapOf("skiko.transparency" to "true") additionalArguments += mapOf("skiko.transparency" to "true")
dependsOn(casualRun) dependsOn(casualRun)
} }
tasks.register("runInterop") { tasks.register("runInterop") {
group = "application"
additionalArguments += mapOf("skiko.swing.interop" to "true") additionalArguments += mapOf("skiko.swing.interop" to "true")
dependsOn(casualRun) dependsOn(casualRun)
} }
......
...@@ -9,14 +9,8 @@ import kotlin.math.PI ...@@ -9,14 +9,8 @@ import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
open class ClocksAwt( open class ClocksAwt(private val scaleProvider: () -> Float) : SkikoRenderDelegate, MouseMotionListener {
private val scaleProvider: () -> Float, constructor(layer: SkiaLayer) : this({ layer.contentScale })
private val renderProvider: () -> GraphicsApi = { GraphicsApi.UNKNOWN }
) : SkikoRenderDelegate, MouseMotionListener {
constructor(layer: SkiaLayer) : this(
{ layer.contentScale },
{ layer.renderApi }
)
private val typeface = FontMgr.default.makeFromFile("fonts/JetBrainsMono-Regular.ttf") private val typeface = FontMgr.default.makeFromFile("fonts/JetBrainsMono-Regular.ttf")
private val font = Font(typeface, 13f).apply { private val font = Font(typeface, 13f).apply {
...@@ -94,7 +88,7 @@ open class ClocksAwt( ...@@ -94,7 +88,7 @@ open class ClocksAwt(
} }
val paragraph = ParagraphBuilder(style, fontCollection) val paragraph = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt())) .pushStyle(TextStyle().setColor(0xFF000000.toInt()))
.addText("Graphic API: ${renderProvider()}, JRE: ${System.getProperty("java.vendor")}, ${System.getProperty("java.runtime.version")} $currentSystemTheme") .addText("JRE: ${System.getProperty("java.vendor")}, ${System.getProperty("java.runtime.version")} $currentSystemTheme")
.popStyle() .popStyle()
.build() .build()
paragraph.layout(Float.POSITIVE_INFINITY) paragraph.layout(Float.POSITIVE_INFINITY)
......
...@@ -16,6 +16,7 @@ plugins { ...@@ -16,6 +16,7 @@ plugins {
id("org.jetbrains.dokka") version "1.9.10" id("org.jetbrains.dokka") version "1.9.10"
`maven-publish` `maven-publish`
signing signing
id("org.gradle.crypto.checksum") version "1.4.0"
} }
if (supportAndroid) { if (supportAndroid) {
...@@ -454,7 +455,6 @@ if (supportAndroid) { ...@@ -454,7 +455,6 @@ if (supportAndroid) {
} }
} }
// TODO now it can be moved, move it if you change this
// Can't be moved to buildSrc because of Checksum dependency // Can't be moved to buildSrc because of Checksum dependency
fun createChecksumsTask( fun createChecksumsTask(
targetOs: OS, targetOs: OS,
...@@ -467,19 +467,13 @@ fun createChecksumsTask( ...@@ -467,19 +467,13 @@ fun createChecksumsTask(
outputDirectory = layout.buildDirectory.dir("checksums-${targetId(targetOs, targetArch)}") outputDirectory = layout.buildDirectory.dir("checksums-${targetId(targetOs, targetArch)}")
} }
val additionalRuntimeLibraries = project.registerAdditionalLibraries(targetOs, targetArch, skiko)
if (supportAwt) { if (supportAwt) {
val skikoAwtJarForTests by project.tasks.registering(Jar::class) { val skikoAwtJarForTests by project.tasks.registering(Jar::class) {
archiveBaseName.set("skiko-awt-test") archiveBaseName.set("skiko-awt-test")
from(kotlin.jvm("awt").compilations["main"].output.allOutputs) from(kotlin.jvm("awt").compilations["main"].output.allOutputs)
} }
skikoProjectContext.setupJvmTestTask( skikoProjectContext.setupJvmTestTask(skikoAwtJarForTests, targetOs, targetArch)
skikoAwtJarForTests,
additionalRuntimeLibraries,
targetOs,
targetArch,
)
} }
afterEvaluate { afterEvaluate {
...@@ -596,10 +590,6 @@ publishing { ...@@ -596,10 +590,6 @@ publishing {
} }
} }
additionalRuntimeLibraries.forEach {
it.registerMavenPublication(this, emptySourcesJar, pomNameForPublication)
}
if (supportWeb) { if (supportWeb) {
create<MavenPublication>("skikoWasmRuntime") { create<MavenPublication>("skikoWasmRuntime") {
pomNameForPublication[name] = "Skiko WASM Runtime" pomNameForPublication[name] = "Skiko WASM Runtime"
...@@ -659,9 +649,6 @@ tasks.findByName("publishSkikoWasmRuntimePublicationToComposeRepoRepository") ...@@ -659,9 +649,6 @@ tasks.findByName("publishSkikoWasmRuntimePublicationToComposeRepoRepository")
tasks.findByName("publishSkikoWasmRuntimePublicationToMavenLocal") tasks.findByName("publishSkikoWasmRuntimePublicationToMavenLocal")
?.dependsOn("publishWasmJsPublicationToMavenLocal") ?.dependsOn("publishWasmJsPublicationToMavenLocal")
additionalRuntimeLibraries.forEach {
it.registerRuntimePublishTaskDependency(listOf("MavenLocal", "ComposeRepoRepository"))
}
tasks.withType<KotlinNativeCompile>().configureEach { tasks.withType<KotlinNativeCompile>().configureEach {
// https://youtrack.jetbrains.com/issue/KT-56583 // https://youtrack.jetbrains.com/issue/KT-56583
......
...@@ -13,5 +13,4 @@ dependencies { ...@@ -13,5 +13,4 @@ dependencies {
val kotlinVersion = project.properties["kotlin.version"] as String val kotlinVersion = project.properties["kotlin.version"] as String
implementation(kotlin("gradle-plugin", kotlinVersion)) implementation(kotlin("gradle-plugin", kotlinVersion))
implementation("de.undercouch:gradle-download-task:5.5.0") implementation("de.undercouch:gradle-download-task:5.5.0")
implementation("org.gradle.crypto.checksum:org.gradle.crypto.checksum.gradle.plugin:1.4.0")
} }
import org.gradle.api.Project
fun Project.registerAdditionalLibraries(
targetOs: OS,
targetArch: Arch,
skikoProperties: SkikoProperties
): List<AdditionalRuntimeLibrary> {
val angleTag = property("dependencies.angle") as String
return listOfNotNull(
if (supportAwt && targetOs == OS.Windows) {
registerAdditionalRuntimeLibrary(
targetOs = targetOs,
targetArch = targetArch,
skikoProperties = skikoProperties,
name = "angle",
archiveUrl = "https://github.com/JetBrains/angle-pack/releases/download/$angleTag/Angle-$angleTag-${targetOs.id}-Release-${targetArch.id}.zip",
filesToInclude = listOf(
"out/Release-${targetOs.id}-${targetArch.id}/libEGL.dll",
"out/Release-${targetOs.id}-${targetArch.id}/libGLESv2.dll"
),
)
} else {
null
}
)
}
import SkikoArtifacts.jvmAdditionalRuntimeArtifactIdFor
import de.undercouch.gradle.tasks.download.Download
import org.gradle.api.Project
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.publish.PublicationContainer
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.crypto.checksum.Checksum
import org.gradle.kotlin.dsl.register
import org.gradle.api.publish.maven.MavenPublication
interface AdditionalRuntimeLibrary {
val jarTask: TaskProvider<Jar>
fun registerMavenPublication(
container: PublicationContainer,
emptySourcesJar: TaskProvider<Jar>,
pomNameForPublication: MutableMap<String, String>
)
fun registerRuntimePublishTaskDependency(repos: List<String>)
}
fun Project.registerAdditionalRuntimeLibrary(
targetOs: OS,
targetArch: Arch,
skikoProperties: SkikoProperties,
name: String,
archiveUrl: String,
filesToInclude: List<String>,
): AdditionalRuntimeLibrary {
val visibleName = "${toTitleCase(name)} Runtime"
val targetId = targetId(targetOs, targetArch)
val taskSuffix = "${toTitleCase(name)}${toTitleCase(targetOs.id)}${toTitleCase(targetArch.id)}"
val archiveFileName = archiveUrl.substringAfterLast('/')
val archiveDir = skikoProperties.dependenciesDir.resolve(name).resolve(archiveFileName.substringBefore("."))
val downloadTask = tasks.register<Download>("download$taskSuffix") {
group = visibleName
description = "Downloads $archiveUrl"
onlyIfModified(true)
src(archiveUrl)
dest(archiveDir.resolve(archiveFileName))
}
val unzipTask = tasks.register<Copy>("unzip$taskSuffix") {
group = visibleName
dependsOn(downloadTask)
from(zipTree(downloadTask.get().dest)) {
filesToInclude.forEach { include(it) }
duplicatesStrategy = DuplicatesStrategy.FAIL
eachFile { path = file.name }
includeEmptyDirs = false
}
into(archiveDir.resolve("extracted"))
}
val checksumTask = tasks.register<Checksum>("createChecksums$taskSuffix") {
group = visibleName
inputFiles.setFrom(unzipTask)
checksumAlgorithm.set(Checksum.Algorithm.SHA256)
outputDirectory.set(layout.buildDirectory.dir("$name/checksums"))
dependsOn(unzipTask)
}
val jarTask = tasks.register<Jar>("skikoRuntimeJar$taskSuffix") {
group = visibleName
dependsOn(unzipTask)
dependsOn(checksumTask)
archiveBaseName.set("skiko-$name")
archiveClassifier.set(targetId)
from(unzipTask)
from(checksumTask)
}
return object : AdditionalRuntimeLibrary {
override val jarTask = jarTask
override fun registerMavenPublication(
container: PublicationContainer,
emptySourcesJar: TaskProvider<Jar>,
pomNameForPublication: MutableMap<String, String>
) {
container.create("skikoJvmRuntime$taskSuffix", MavenPublication::class.java) {
pomNameForPublication[this.name] = "Skiko $visibleName for ${targetOs.id} ${targetArch.id}"
artifactId = jvmAdditionalRuntimeArtifactIdFor(name, targetOs, targetArch)
afterEvaluate {
artifact(jarTask.map { it.archiveFile.get() })
artifact(emptySourcesJar)
}
}
}
override fun registerRuntimePublishTaskDependency(repos: List<String>) {
repos.forEach { repo ->
val mainTaskSuffix = "${toTitleCase(targetOs.id)}${toTitleCase(targetArch.id)}"
project.tasks.findByName("publishSkikoJvmRuntime${mainTaskSuffix}PublicationTo${repo}")
?.dependsOn("publishSkikoJvmRuntime${taskSuffix}PublicationTo${repo}")
}
}
}
}
...@@ -211,8 +211,6 @@ object SkikoArtifacts { ...@@ -211,8 +211,6 @@ object SkikoArtifacts {
"skiko-android-runtime-${arch.id}" "skiko-android-runtime-${arch.id}"
else else
"skiko-awt-runtime-${targetId(os, arch)}" "skiko-awt-runtime-${targetId(os, arch)}"
fun jvmAdditionalRuntimeArtifactIdFor(name: String, os: OS, arch: Arch) =
"skiko-awt-runtime-$name-${os.id}-${arch.id}"
// Using custom name like skiko-<Os>-<Arch> (with a dash) // Using custom name like skiko-<Os>-<Arch> (with a dash)
// does not seem possible (at least without adding a dash to a target's tasks), // does not seem possible (at least without adding a dash to a target's tasks),
// so we're using the default naming pattern instead. // so we're using the default naming pattern instead.
......
package tasks.configuration package tasks.configuration
import AdditionalRuntimeLibrary
import Arch import Arch
import CompileSkikoCppTask import CompileSkikoCppTask
import CompileSkikoObjCTask import CompileSkikoObjCTask
...@@ -25,6 +24,7 @@ import org.gradle.api.tasks.TaskProvider ...@@ -25,6 +24,7 @@ import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.testing.Test import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.withType import org.gradle.kotlin.dsl.withType
import org.gradle.util.internal.VersionNumber
import projectDirs import projectDirs
import registerOrGetSkiaDirProvider import registerOrGetSkiaDirProvider
import registerSkikoTask import registerSkikoTask
...@@ -461,16 +461,11 @@ fun SkikoProjectContext.skikoRuntimeDirForTestsTask( ...@@ -461,16 +461,11 @@ fun SkikoProjectContext.skikoRuntimeDirForTestsTask(
targetOs: OS, targetOs: OS,
targetArch: Arch, targetArch: Arch,
skikoJvmJar: Provider<Jar>, skikoJvmJar: Provider<Jar>,
skikoJvmRuntimeJar: Provider<Jar>, skikoJvmRuntimeJar: Provider<Jar>
additionalRuntimeLibraries: List<AdditionalRuntimeLibrary>,
) = project.registerSkikoTask<Copy>("skikoRuntimeDirForTests", targetOs, targetArch) { ) = project.registerSkikoTask<Copy>("skikoRuntimeDirForTests", targetOs, targetArch) {
dependsOn(skikoJvmJar, skikoJvmRuntimeJar) dependsOn(skikoJvmJar, skikoJvmRuntimeJar)
from(project.zipTree(skikoJvmJar.flatMap { it.archiveFile })) from(project.zipTree(skikoJvmJar.flatMap { it.archiveFile }))
from(project.zipTree(skikoJvmRuntimeJar.flatMap { it.archiveFile })) from(project.zipTree(skikoJvmRuntimeJar.flatMap { it.archiveFile }))
additionalRuntimeLibraries.forEach { lib ->
from(project.zipTree(lib.jarTask.flatMap { it.archiveFile }))
}
duplicatesStrategy = DuplicatesStrategy.WARN duplicatesStrategy = DuplicatesStrategy.WARN
destinationDir = project.layout.buildDirectory.dir("skiko-runtime-for-tests").get().asFile destinationDir = project.layout.buildDirectory.dir("skiko-runtime-for-tests").get().asFile
} }
...@@ -483,14 +478,9 @@ fun SkikoProjectContext.skikoJarForTestsTask( ...@@ -483,14 +478,9 @@ fun SkikoProjectContext.skikoJarForTestsTask(
archiveFileName.set("skiko-runtime-for-tests.jar") archiveFileName.set("skiko-runtime-for-tests.jar")
} }
fun SkikoProjectContext.setupJvmTestTask( fun SkikoProjectContext.setupJvmTestTask(skikoAwtJarForTests: TaskProvider<Jar>, targetOs: OS, targetArch: Arch) = with(project) {
skikoAwtJarForTests: TaskProvider<Jar>,
additionalRuntimeLibraries: List<AdditionalRuntimeLibrary>,
targetOs: OS,
targetArch: Arch
) = with(project) {
val skikoAwtRuntimeJarForTests = createSkikoJvmJarTask(targetOs, targetArch, skikoAwtJarForTests) val skikoAwtRuntimeJarForTests = createSkikoJvmJarTask(targetOs, targetArch, skikoAwtJarForTests)
val skikoRuntimeDirForTests = skikoRuntimeDirForTestsTask(targetOs, targetArch, skikoAwtJarForTests, skikoAwtRuntimeJarForTests, additionalRuntimeLibraries) val skikoRuntimeDirForTests = skikoRuntimeDirForTestsTask(targetOs, targetArch, skikoAwtJarForTests, skikoAwtRuntimeJarForTests)
val skikoJarForTests = skikoJarForTestsTask(skikoRuntimeDirForTests) val skikoJarForTests = skikoJarForTestsTask(skikoRuntimeDirForTests)
tasks.withType<Test>().configureEach { tasks.withType<Test>().configureEach {
......
...@@ -15,8 +15,6 @@ val skikoArtifactIds: List<String> = ...@@ -15,8 +15,6 @@ val skikoArtifactIds: List<String> =
SkikoArtifacts.jvmRuntimeArtifactIdFor(OS.Linux, Arch.Arm64), SkikoArtifacts.jvmRuntimeArtifactIdFor(OS.Linux, Arch.Arm64),
SkikoArtifacts.jvmRuntimeArtifactIdFor(OS.MacOS, Arch.X64), SkikoArtifacts.jvmRuntimeArtifactIdFor(OS.MacOS, Arch.X64),
SkikoArtifacts.jvmRuntimeArtifactIdFor(OS.MacOS, Arch.Arm64), SkikoArtifacts.jvmRuntimeArtifactIdFor(OS.MacOS, Arch.Arm64),
SkikoArtifacts.jvmAdditionalRuntimeArtifactIdFor("angle", OS.Windows, Arch.X64),
SkikoArtifacts.jvmAdditionalRuntimeArtifactIdFor("angle", OS.Windows, Arch.Arm64),
SkikoArtifacts.jsWasmArtifactId, SkikoArtifacts.jsWasmArtifactId,
SkikoArtifacts.jsArtifactId, SkikoArtifacts.jsArtifactId,
SkikoArtifacts.wasmArtifactId, SkikoArtifacts.wasmArtifactId,
......
...@@ -6,12 +6,8 @@ kotlin.mpp.enableCInteropCommonization=true ...@@ -6,12 +6,8 @@ kotlin.mpp.enableCInteropCommonization=true
deploy.version=0.0.0 deploy.version=0.0.0
# a tag from https://github.com/JetBrains/skia-pack
dependencies.skia=m138-80d088a-1 dependencies.skia=m138-80d088a-1
# a tag from https://github.com/JetBrains/angle-pack
dependencies.angle=7fea539cc9
# you can override general skia dependencies by passing platform-specific property: # you can override general skia dependencies by passing platform-specific property:
# dependencies.skia.android-arm64 # dependencies.skia.android-arm64
# dependencies.skia.android-x64 # dependencies.skia.android-x64
......
...@@ -2,13 +2,22 @@ ...@@ -2,13 +2,22 @@
#include <windows.h> #include <windows.h>
#include <shlwapi.h> #include <shlwapi.h>
#include <string>
#define GL_GLES_PROTOTYPES 0 #define GL_GLES_PROTOTYPES 0
#define EGL_EGL_PROTOTYPES 0 #define EGL_EGL_PROTOTYPES 0
#include <GLES/gl.h> #include <GLES/gl.h>
#include <EGL/egl.h> #include <EGL/egl.h>
#include "exceptions_handler.h" #include "exceptions_handler.h"
#define THROW_IF_NULL(action) \
do { \
auto __result { action }; \
if (0 == __result) { \
auto __code = GetLastError(); \
throwJavaRenderExceptionByErrorCode(env, __FUNCTION__, __code); \
return; \
} \
} while ((void)0, 0)
static HINSTANCE AngleEGLLibrary = nullptr; static HINSTANCE AngleEGLLibrary = nullptr;
extern "C" { extern "C" {
...@@ -67,11 +76,20 @@ extern "C" { ...@@ -67,11 +76,20 @@ extern "C" {
return eglGetProcAddress(procname); return eglGetProcAddress(procname);
} }
JNIEXPORT jboolean JNICALL Java_org_jetbrains_skiko_AngleSupport_1jvmKt_initAngleLibraryWindows(JNIEnv *env, jobject, jstring jlibraryName) { JNIEXPORT void JNICALL Java_org_jetbrains_skiko_AngleSupport_1jvmKt_loadAngleLibraryWindows(JNIEnv *env, jobject obj) {
const char* libraryName = env->GetStringUTFChars(jlibraryName, nullptr); TCHAR basePath[MAX_PATH] = TEXT("");
AngleEGLLibrary = GetModuleHandleA(libraryName); TCHAR libEGL[MAX_PATH] = TEXT("");
env->ReleaseStringUTFChars(jlibraryName, libraryName); TCHAR libGLESv2[MAX_PATH] = TEXT("");
return AngleEGLLibrary != nullptr; HMODULE hmodule = nullptr;
THROW_IF_NULL(GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPTSTR) &Java_org_jetbrains_skiko_AngleSupport_1jvmKt_loadAngleLibraryWindows,
&hmodule));
THROW_IF_NULL(GetModuleFileName(hmodule, basePath, sizeof(basePath)));
THROW_IF_NULL(PathRemoveFileSpec(basePath));
THROW_IF_NULL(PathCombine(libEGL, basePath, TEXT("libEGL.dll")));
THROW_IF_NULL(PathCombine(libGLESv2, basePath, TEXT("libGLESv2.dll")));
THROW_IF_NULL(AngleEGLLibrary = LoadLibrary(libEGL));
THROW_IF_NULL(LoadLibrary(libGLESv2));
} }
} }
......
...@@ -11,11 +11,7 @@ internal class AngleRedrawer( ...@@ -11,11 +11,7 @@ internal class AngleRedrawer(
private val properties: SkiaLayerProperties private val properties: SkiaLayerProperties
) : AWTRedrawer(layer, analytics, GraphicsApi.ANGLE) { ) : AWTRedrawer(layer, analytics, GraphicsApi.ANGLE) {
init { init {
try {
loadAngleLibrary() loadAngleLibrary()
} catch (e: Exception) {
throw RenderException("Failed to load ANGLE library", cause = e)
}
} }
private val contextHandler = AngleContextHandler(layer) private val contextHandler = AngleContextHandler(layer)
......
...@@ -21,7 +21,7 @@ internal fun uiTest( ...@@ -21,7 +21,7 @@ internal fun uiTest(
runBlocking(MainUIDispatcher) { runBlocking(MainUIDispatcher) {
if (renderApiProperty == "all") { if (renderApiProperty == "all") {
for (renderApi in SkikoProperties.fallbackRenderApiQueue(initialApi = null)) { for (renderApi in SkikoProperties.fallbackRenderApiQueue(SkikoProperties.renderApi)) {
if (renderApi in excludeRenderApis) { if (renderApi in excludeRenderApis) {
println("Skipping $renderApi renderApi") println("Skipping $renderApi renderApi")
continue continue
......
package org.jetbrains.skiko
/**
* An exception related to inability of loading an optional rendering API
* (e.g. ANGLE on Windows)
*/
internal class OptionalRenderApiException(
message: String? = null,
cause: Throwable? = null
) : RenderException(message, cause)
...@@ -2,25 +2,25 @@ package org.jetbrains.skiko ...@@ -2,25 +2,25 @@ package org.jetbrains.skiko
import org.jetbrains.skia.impl.Library import org.jetbrains.skia.impl.Library
private external fun initAngleLibraryWindows(libraryName: String): Boolean private external fun loadAngleLibraryWindows()
private const val libEGLName = "libEGL" private var isLoaded = false
private var loader = LibraryLoader( @Synchronized
libEGLName, internal actual fun loadAngleLibrary() {
// libGLESv2 is not loaded explicitly in Skiko, it is loaded by libEGL if (!isLoaded) {
additionalFile = System.mapLibraryName("libGLESv2"), when {
init = { hostOs.isWindows -> {
Library.staticLoad() Library.staticLoad()
if (!initAngleLibraryWindows(libEGLName)) { try {
throw LibraryLoadException("Failed to load ANGLE library $libEGLName") loadAngleLibraryWindows()
}
catch (e: Exception) {
throw OptionalRenderApiException("Failed to load ANGLE library: ${e}")
} }
} }
)
internal actual fun loadAngleLibrary() {
when {
hostOs.isWindows -> loader.loadOnce()
else -> Unit else -> Unit
} }
isLoaded = true
}
} }
\ No newline at end of file
package org.jetbrains.skiko package org.jetbrains.skiko
import org.jetbrains.skia.Bitmap import org.jetbrains.skia.Bitmap
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
object Library { object Library {
private var loader = LibraryLoader( private var copyDir: File? = null
name = "skiko-$hostId",
additionalFile = if (hostOs.isWindows) "icudtl.dat" else null, // A native library cannot be loaded in several classloaders, so we have to clone
init = { // the native library to allow Skiko loading to work properly in complex cases, i.e.,
// several IDEA plugins.
private fun loadLibraryOrCopy(library: File) {
try {
System.load(library.absolutePath)
} catch (e: UnsatisfiedLinkError) {
if (e.message?.contains("already loaded in another classloader") == true) {
copyDir = Files.createTempDirectory("skiko").toFile()
val tempFile = copyDir!!.resolve(library.name)
Files.copy(library.toPath(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
tempFile.deleteOnExit()
System.load(tempFile.absolutePath)
} else {
throw e
}
}
}
private fun unpackIfNeeded(dest: File, resourceName: String, deleteOnExit: Boolean): File {
val file = File(dest, resourceName)
if (!file.exists()) {
val tempFile = File.createTempFile("skiko", "", dest)
if (deleteOnExit)
file.deleteOnExit()
Library::class.java.getResourceAsStream("/$resourceName").use { input ->
Files.copy(input, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
}
return file
}
private var loaded = AtomicBoolean(false)
// This function does the following: on request to load given resource,
// it checks if resource with given name is found in content-derived directory
// in Skiko's home, and if not - unpacks it. It could also load additional
// localization resources, on platforms where it is needed.
@Synchronized
fun load() {
if (!loaded.compareAndSet(false, true)) return
// Find/unpack a usable copy of the native library.
findAndLoad()
// TODO move properties to SkikoProperties
Setup.init() Setup.init()
try { try {
...@@ -17,14 +65,59 @@ object Library { ...@@ -17,14 +65,59 @@ object Library {
t.printStackTrace() t.printStackTrace()
} }
} }
private fun findAndLoad() {
val name = "skiko-$hostId"
val platformName = System.mapLibraryName(name)
val icu = if (hostOs.isWindows) "icudtl.dat" else null
if (hostOs == OS.Android) {
System.loadLibrary("skiko-$hostId")
return
}
// First try: system property is set.
val skikoLibraryPath = SkikoProperties.libraryPath
if (skikoLibraryPath != null) {
val library = File(File(skikoLibraryPath), platformName)
loadLibraryOrCopy(library)
if (icu != null && copyDir != null)
unpackIfNeeded(copyDir!!, icu, true)
return
}
// Second try: load it from the bin/ or lib/ directory relative to the JVM home.
// The user might have placed the native files alongside the other JVM libraries
// for signing purposes, so we'll find it here if so.
val jvmFiles = File(System.getProperty("java.home"), if (hostOs.isWindows) "bin" else "lib")
val pathInJvm = jvmFiles.resolve(platformName)
if (pathInJvm.exists() && icu?.let { (jvmFiles.resolve(it)).exists() } != false) {
loadLibraryOrCopy(pathInJvm)
return
}
// Third try: look up in or extract to a local cache directory.
// Key the cache by the hash of the library.
val hashResourceStream = Library::class.java.getResourceAsStream(
"/$platformName.sha256"
) ?: throw LibraryLoadException(
"Cannot find $platformName.sha256, proper native dependency missing."
) )
val hash = hashResourceStream.use { it.bufferedReader().readLine() }
// This function does the following: on request to load given resource, val dataDir = File(File(SkikoProperties.dataPath), hash)
// it checks if resource with given name is found in content-derived directory dataDir.mkdirs()
// in Skiko's home, and if not - unpacks it. It could also load additional val library = unpackIfNeeded(dataDir, platformName, false)
// localization resources, on platforms where it is needed. loadLibraryOrCopy(library)
fun load() { if (icu != null) {
loader.loadOnce() if (copyDir != null) {
// We made a duplicate to resolve classloader conflicts.
unpackIfNeeded(copyDir!!, icu, true)
} else {
// Normal path where Skiko is loaded only once.
unpackIfNeeded(dataDir, icu, false)
}
}
} }
} }
......
...@@ -2,4 +2,4 @@ package org.jetbrains.skiko ...@@ -2,4 +2,4 @@ package org.jetbrains.skiko
import java.lang.RuntimeException import java.lang.RuntimeException
class LibraryLoadException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) class LibraryLoadException(message: String) : RuntimeException(message)
\ No newline at end of file \ No newline at end of file
package org.jetbrains.skiko
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
internal class LibraryLoader(
/**
* Short library name without platform suffix and extension. For example "skiko" or "skiko-angle-libEGL"
*/
private val name: String,
/**
* Additional file to check or unpack after loading the library. For example, "icudtl.dat".
*
* Currently only one file is supported, but it can be extended to support multiple ones.
*/
private val additionalFile: String? = null,
/**
* Additional code that is called after successfully loading
*/
private val init: () -> Unit = {}
) {
// A native library cannot be loaded in several classloaders, so we have to clone
// the native library to allow Skiko loading to work properly in complex cases, i.e.,
// several IDEA plugins.
private fun loadLibraryOrCopy(library: File): File? {
try {
System.load(library.absolutePath)
return null
} catch (e: UnsatisfiedLinkError) {
if (e.message?.contains("already loaded in another classloader") == true) {
val copyDir = Files.createTempDirectory("skiko").toFile()
val tempFile = copyDir.resolve(library.name)
Files.copy(library.toPath(), tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
tempFile.deleteOnExit()
System.load(tempFile.absolutePath)
return copyDir
} else {
throw LibraryLoadException("Failed to loade library $library", cause = e)
}
}
}
private fun unpackIfNeeded(dest: File, resourceName: String, deleteOnExit: Boolean): File {
val file = File(dest, resourceName)
if (!file.exists()) {
val tempFile = File.createTempFile("skiko", "", dest)
if (deleteOnExit)
file.deleteOnExit()
Library::class.java.getResourceAsStream("/$resourceName").use { input ->
Files.copy(input, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
}
return file
}
private var isLoaded = false
/**
* Load a native library finding it in multiple sources:
* - from SkikoProperties.libraryPath
* - java.home
* - jar resources
*
* @throws LibraryLoadException if library wasn't loaded successfully.
* Calling this function again retries the loading.
*/
@Synchronized
fun loadOnce() {
if (!isLoaded) {
findAndLoadLibrary(name, additionalFile)
init()
isLoaded = true
}
}
private fun findAndLoadLibrary(name: String, additionalFile: String? = null) {
val platformName = System.mapLibraryName(name)
if (hostOs == OS.Android) {
System.loadLibrary(name)
return
}
// First try: system property is set.
val skikoLibraryPath = SkikoProperties.libraryPath
if (skikoLibraryPath != null) {
val library = File(File(skikoLibraryPath), platformName)
val copyDir = loadLibraryOrCopy(library)
if (additionalFile != null && copyDir != null)
unpackIfNeeded(copyDir, additionalFile, true)
return
}
// Second try: load it from the bin/ or lib/ directory relative to the JVM home.
// The user might have placed the native files alongside the other JVM libraries
// for signing purposes, so we'll find it here if so.
val jvmFiles = File(System.getProperty("java.home"), if (hostOs.isWindows) "bin" else "lib")
val pathInJvm = jvmFiles.resolve(platformName)
if (pathInJvm.exists() && additionalFile?.let { (jvmFiles.resolve(it)).exists() } != false) {
loadLibraryOrCopy(pathInJvm)
return
}
// Third try: look up in or extract to a local cache directory.
// Key the cache by the hash of the library.
val hashResourceStream = Library::class.java.getResourceAsStream(
"/$platformName.sha256"
) ?: throw LibraryLoadException(
"Cannot find $platformName.sha256, proper native dependency missing."
)
val hash = hashResourceStream.use { it.bufferedReader().readLine() }
val dataDir = File(File(SkikoProperties.dataPath), hash)
dataDir.mkdirs()
val library = unpackIfNeeded(dataDir, platformName, false)
val copyDir = loadLibraryOrCopy(library)
if (additionalFile != null) {
if (copyDir != null) {
// We made a duplicate to resolve classloader conflicts.
unpackIfNeeded(copyDir, additionalFile, true)
} else {
// Normal path where Skiko is loaded only once.
unpackIfNeeded(dataDir, additionalFile, false)
}
}
}
}
...@@ -50,18 +50,6 @@ object SkikoProperties { ...@@ -50,18 +50,6 @@ object SkikoProperties {
return getProperty("skiko.rendering.linux.waitForFrameVsyncOnRedrawImmediately")?.toBoolean() ?: false return getProperty("skiko.rendering.linux.waitForFrameVsyncOnRedrawImmediately")?.toBoolean() ?: false
} }
/**
* Is experimental ANGLE renderer API enabled (https://skia.org/docs/user/special/angle/).
*
* If enabled, Windows uses it as a primary render API and fallbacks to the default APIs.
*
* Other OSes are not supported yet.
*
* If it is enabled, make sure that either:
* - `org.jetbrains.skiko:skiko-awt-runtime-angle-$target:$version` added as a dependency
* - The `skiko.library.path` property is defined and the directory has libEGL, libGLESv2 from
* https://github.com/JetBrains/angle-pack/releases
*/
val renderingAngleEnabled: Boolean get() = getProperty("skiko.rendering.angle.enabled")?.toBoolean() ?: false val renderingAngleEnabled: Boolean get() = getProperty("skiko.rendering.angle.enabled")?.toBoolean() ?: false
/** /**
...@@ -168,7 +156,7 @@ object SkikoProperties { ...@@ -168,7 +156,7 @@ object SkikoProperties {
} }
} }
internal fun fallbackRenderApiQueue(initialApi: GraphicsApi?): List<GraphicsApi> { internal fun fallbackRenderApiQueue(initialApi: GraphicsApi): List<GraphicsApi> {
var fallbackApis = when (hostOs) { var fallbackApis = when (hostOs) {
OS.Linux -> listOf(GraphicsApi.OPENGL, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT) OS.Linux -> listOf(GraphicsApi.OPENGL, GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT)
OS.MacOS -> listOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE_COMPAT) OS.MacOS -> listOf(GraphicsApi.METAL, GraphicsApi.SOFTWARE_COMPAT)
...@@ -194,16 +182,12 @@ object SkikoProperties { ...@@ -194,16 +182,12 @@ object SkikoProperties {
else -> return listOf(GraphicsApi.UNKNOWN) else -> return listOf(GraphicsApi.UNKNOWN)
} }
return if (initialApi != null) {
val indexOfInitialApi = fallbackApis.indexOf(initialApi) val indexOfInitialApi = fallbackApis.indexOf(initialApi)
require(indexOfInitialApi >= 0) { require(indexOfInitialApi >= 0) {
"$hostOs does not support $initialApi rendering API." "$hostOs does not support $initialApi rendering API."
} }
fallbackApis = fallbackApis.drop(indexOfInitialApi + 1) fallbackApis = fallbackApis.drop(indexOfInitialApi + 1)
listOf(initialApi) + fallbackApis return listOf(initialApi) + fallbackApis
} else {
fallbackApis
}
} }
} }
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