Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GraalVM native image support #755

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5985d38
Gradle: Build fat JAR, include slf4j-simple in regular build
skalarproduktraum Jul 25, 2023
1751401
RenderConfigReader: Move configuration data classes to top level
skalarproduktraum Jul 25, 2023
f3d03df
SceneryBase: Temporary changes for quick main method availability
skalarproduktraum Jul 25, 2023
bca13b9
Add native image configuration files
skalarproduktraum Jul 25, 2023
7dbd9f9
Scene: Clean up imports
skalarproduktraum Aug 17, 2023
9a7a8e8
Native image: Update resource configuration
skalarproduktraum Aug 17, 2023
6f1a5e4
VulkanTexture: Adjust for new API usage in fromEmpty()
skalarproduktraum May 21, 2024
7e3b6fb
CycleRenderQualityExample/FauxRenderer: Adjust for changed RenderingQ…
skalarproduktraum May 21, 2024
89bb323
Gradle: Add nativeImage and fullShadowJar tasks
skalarproduktraum May 21, 2024
4787e13
Gradle: Migrate shadow plugin to io.gitub.goooler.shadow for Java 21 …
skalarproduktraum May 21, 2024
d104600
SceneryBase: Skip camera mode switching only on GraalVm
skalarproduktraum May 21, 2024
bbed057
SceneryBase/VulkanNodeHelpers/VulkanRenderer: guard AWT-dependent cal…
skalarproduktraum May 23, 2024
1e80868
Gradle: Change kotlin-reflect to be api() dependency
skalarproduktraum May 23, 2024
5d50cff
Update reflection and resource configuration for native image
skalarproduktraum May 23, 2024
450ef19
Gradle: Improve native image creation
skalarproduktraum May 24, 2024
501f866
Gradle: Have slf4j-simple only as runtime dependency
skalarproduktraum May 29, 2024
f9bebed
SceneryBase: Show a minimal scene when invoking SceneryBase directly
skalarproduktraum May 29, 2024
5a68119
VulkanRenderer: Re-enable SwingSwapchain
skalarproduktraum May 29, 2024
881bd25
Add TexturedCubeExample to reflection config
skalarproduktraum May 29, 2024
8f24cea
Image: Replace BufferedImage-based image reading with SCIFIO (WIP)
skalarproduktraum May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 110 additions & 13 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.kotlin.dsl.api
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import scenery.*
import java.net.URL
import java.io.IOException
import java.net.URI

plugins {
// kotlin and dokka versions are now managed in settings.gradle.kts and gradle.properties
Expand All @@ -12,16 +14,13 @@
scenery.base
scenery.publish
scenery.sign
// id("com.github.elect86.sciJava") version "0.0.4"
jacoco
id("com.github.johnrengelman.shadow") apply false
id("io.github.goooler.shadow")
}

repositories {
mavenCentral()
maven("https://maven.scijava.org/content/groups/public")
// maven("https://jitpack.io")
// mavenLocal()
}

val lwjglArtifacts = listOf(
Expand All @@ -48,7 +47,7 @@
implementation(platform("org.scijava:pom-scijava:$scijavaParentPomVersion"))
annotationProcessor("org.scijava:scijava-common:2.98.0")

implementation(kotlin("reflect"))
api(kotlin("reflect"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")

implementation("org.slf4j:slf4j-api:1.7.36")
Expand Down Expand Up @@ -141,8 +140,11 @@
testImplementation(kotlin("test-junit"))
// implementation("com.github.kotlin-graphics:assimp:25c68811")

// testImplementation(misc.junit4)
testImplementation("org.slf4j:slf4j-simple:1.7.36")
if(properties["buildAsApplication"] != null) {
runtimeOnly("org.slf4j:slf4j-simple:1.7.36")
} else {
testRuntimeOnly("org.slf4j:slf4j-simple:1.7.36")
}
testImplementation("net.imagej:imagej")
testImplementation("net.imagej:ij")
testImplementation("net.imglib2:imglib2-ij")
Expand Down Expand Up @@ -387,7 +389,7 @@
dokkaSourceSets.configureEach {
sourceLink {
localDirectory = file("src/main/kotlin")
remoteUrl = URL("https://github.com/scenerygraphics/scenery/tree/main/src/main/kotlin")
remoteUrl = URI("https://github.com/scenerygraphics/scenery/tree/main/src/main/kotlin").toURL()
remoteLineSuffix = "#L"
}
}
Expand All @@ -397,11 +399,106 @@
enabled = isRelease
}

if(project.properties["buildFatJAR"] == true) {
apply(plugin = "com.github.johnrengelman.shadow")
jar {
isZip64 = true
"shadowJar"(ShadowJar::class) {
enabled = false
isZip64 = true
}

if(project.properties["buildFatJar"] != null) {
apply(plugin = "io.github.goooler.shadow")
}


val shadowJar = register<ShadowJar>("fullShadowJar") {
archiveClassifier.set("everything")
from(sourceSets.test.get().output, sourceSets.main.get().output)
configurations.add(project.configurations.runtimeClasspath.get())
isZip64 = true

// we need to exclude JRuby here, because native-image will
// pick up the RubyFileTypeDetector otherwise on startup, and fail.
val excluded = listOf("org.jruby:jruby-core",
"org.jruby:joni",
"org.jruby:jcodings",
"org.jruby:*")

dependencies {
excluded.forEach { exclude(it) }
}

minimize {
dependencies {
exclude("*.DSA")
exclude("*.RSA")
exclude("*.SF")
exclude("META-INF/*.DSA")
exclude("META-INF/*.RSA")
exclude("META-INF/*.SF")
}
}
}

register("nativeImage") {
val mainClass = "graphics.scenery.SceneryBase"

val outputBinary = project.projectDir.resolve("./scenery-native")
val shadowJarArtifact = shadowJar.get().archiveFile.get().asFile.absolutePath

outputs.file(outputBinary)
inputs.file(shadowJarArtifact)
mustRunAfter("fullShadowJar")
doLast {
val nativeImageCmd = listOf("native-image --no-fallback -cp $shadowJarArtifact",
"-H:Name=${outputBinary.absolutePath} -H:Class=$mainClass",
"-H:+ReportUnsupportedElementsAtRuntime",
"-H:ReflectionConfigurationFiles=src/main/resources/META-INF/native-image/reflect-config.json",
"-H:Log=registerResource:3",
"-H:ResourceConfigurationFiles=src/main/resources/META-INF/native-image/resource-config.json",
"--initialize-at-run-time=org.lwjgl",
"--native-image-info",
"--initialize-at-build-time=org.slf4j.simple.SimpleLogger,org.slf4j.LoggerFactory,org.jruby.util.RubyFileTypeDetector",
"-H:IncludeResources=\".*.frag|.*.vert|.*.geom|.*.comp|.*.spv|.*.conf\""
)
val result = nativeImageCmd.joinToString(" ").runCommand(projectDir, showCommand = true, useStdOut = true)

if(result != null) {
logger.lifecycle("Native image written to ${outputBinary.absolutePath}")
} else {
throw GradleException("Failed to create native image")
}
}
}
}

private fun String.runCommand(workingDir: File, useStdOut: Boolean = false, showCommand: Boolean = false): String? {
try {
val parts = this.split("\\s".toRegex())
if(showCommand) {
logger.lifecycle("Running $this ...")
}

val pb = ProcessBuilder(*parts.toTypedArray())

Check warning on line 480 in build.gradle.kts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

build.gradle.kts#L480

In most cases using a spread operator causes a full copy of the array to be created before calling a method. This may result in a performance penalty.
pb.directory(workingDir)
if(useStdOut) {
pb.inheritIO()
} else {
pb
.redirectErrorStream(true)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
}
val proc = pb.start()

proc.waitFor(60, TimeUnit.MINUTES)
val result = proc.inputStream.bufferedReader().readText()
if(proc.exitValue() != 0) {
logger.error("Non-zero exit code from process, output:")
logger.error(result)
return null
}
return result
} catch(e: IOException) {
logger.error(e.stackTrace.toString())
return null
}
}

Expand Down
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pluginManagement {
kotlin("kapt") version kotlinVersion
id("org.jetbrains.dokka") version dokkaVersion

id("com.github.johnrengelman.shadow") version "8.1.1"
id("io.github.goooler.shadow") version "8.1.7"
}

repositories {
Expand Down
2 changes: 0 additions & 2 deletions src/main/kotlin/graphics/scenery/Scene.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import graphics.scenery.attribute.material.HasMaterial
import graphics.scenery.attribute.renderable.HasRenderable
import graphics.scenery.attribute.spatial.HasSpatial
import graphics.scenery.net.Networkable
import graphics.scenery.net.NodePublisher
import graphics.scenery.net.NodeSubscriber
import graphics.scenery.serialization.*
import graphics.scenery.utils.MaybeIntersects
import graphics.scenery.utils.extensions.plus
Expand Down
50 changes: 50 additions & 0 deletions src/main/kotlin/graphics/scenery/SceneryBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@
* @param[keybinding] The key to trigger the switching.
*/
fun setupCameraModeSwitching(keybinding: String = "C") {
if(System.getProperty("scenery.Headless").toBoolean() == true && SceneryBase.isNative()) {
return
}

val windowWidth = renderer?.window?.width ?: 512
val windowHeight = renderer?.window?.height ?: 512

Expand Down Expand Up @@ -369,6 +373,11 @@
* @param[renderer] A [Renderer] instance or null.
*/
fun loadInputHandler(renderer: Renderer?) {
// TODO: Restore input handling when running as native image
if(isNative()) {
return
}

renderer?.let {
inputHandler = InputHandler(scene, it, hub)
inputHandler?.useDefaultBindings(System.getProperty("user.home") + "/.$applicationName.bindings")
Expand Down Expand Up @@ -606,5 +615,46 @@
}
}
}

@JvmStatic fun main(args: Array<String>) {

Check warning on line 619 in src/main/kotlin/graphics/scenery/SceneryBase.kt

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/main/kotlin/graphics/scenery/SceneryBase.kt#L619

The function main is missing documentation.
class MinimalScene : SceneryBase("Minimal Scene", wantREPL = false) {
override fun init() {
renderer = hub.add(
SceneryElement.Renderer,
Renderer.createRenderer(hub, applicationName, scene, windowWidth, windowHeight)
)

val box = Box(Vector3f(1.0f, 1.0f, 1.0f))
box.name = "le box du win"
box.material {
metallic = 0.3f
roughness = 0.9f
}
scene.addChild(box)

val light = PointLight(radius = 15.0f)
light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f)
light.intensity = 5.0f
light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f)
scene.addChild(light)

val cam: Camera = DetachedHeadCamera()
with(cam) {
spatial {
position = Vector3f(0.0f, 0.0f, 5.0f)
}
perspectiveCamera(50.0f, 512, 512)

scene.addChild(this)
}
}
}

val empty = MinimalScene()
empty.main()
}

/** Returns true if running from a GraalVM native image. */
@JvmStatic fun isNative(): Boolean = System.getProperty("org.graalvm.home") != null
}
}
Loading
Loading