From fc0503d0991201944cee72af81668b49f44ee2c2 Mon Sep 17 00:00:00 2001 From: Colt Daily Date: Tue, 16 Jul 2024 10:27:33 -0400 Subject: [PATCH] Update template to work with LittleKt 0.10.0 WebGPU update (#6) * update template to work with new WebGPU LittleKt 0.10.0 update * remove SNAPSHOT version of LittleKt * gradle: update to 8.5 --- .gitignore | 3 +- README.md | 40 ++---- build.gradle.kts | 2 +- game/build.gradle.kts | 90 +++----------- game/src/androidMain/AndroidManifest.xml | 23 ---- .../kotlin/com/game/template/AndroidApp.kt | 6 - .../kotlin/com/game/template/AppActivity.kt | 19 --- .../androidMain/res/drawable/ic_launcher.png | Bin 40786 -> 0 bytes game/src/androidMain/res/values/strings.xml | 4 - .../kotlin/com/game/template/Game.kt | 117 ++++++++++++++---- .../jsMain/kotlin/com/game/template/JsApp.kt | 6 +- game/src/jsMain/resources/index.html | 52 +++++++- .../kotlin/com/game/template/LwjglApp.kt | 6 +- .../kotlin/com/template/game/WasmJsApp.kt | 15 --- game/src/wasmJsMain/resources/index.html | 72 ----------- .../webpack.config.d/cleanupSourcemap.js | 37 ------ gradle/libs.versions.toml | 13 +- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 1 + 19 files changed, 194 insertions(+), 314 deletions(-) delete mode 100644 game/src/androidMain/AndroidManifest.xml delete mode 100644 game/src/androidMain/kotlin/com/game/template/AndroidApp.kt delete mode 100644 game/src/androidMain/kotlin/com/game/template/AppActivity.kt delete mode 100644 game/src/androidMain/res/drawable/ic_launcher.png delete mode 100644 game/src/androidMain/res/values/strings.xml delete mode 100644 game/src/wasmJsMain/kotlin/com/template/game/WasmJsApp.kt delete mode 100644 game/src/wasmJsMain/resources/index.html delete mode 100644 game/src/wasmJsMain/webpack.config.d/cleanupSourcemap.js diff --git a/.gitignore b/.gitignore index 250f267..29806e0 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,5 @@ libgdx-*.zip.MD5 .storage .httpCache -kotlin-js-store \ No newline at end of file +kotlin-js-store +.kotlin \ No newline at end of file diff --git a/README.md b/README.md index 9bd8672..86e20ac 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ This template repository contains a base project for creating games with [Little the bare necessities to get a LittleKt project up and running. This includes the necessary plugins, dependencies and source set structure. -This project is set up to use all the available platforms that LittleKt currently supports: **JVM**, **Web**, and **Android**. +This project is set up to use all the available platforms that LittleKt currently supports: **JVM**, **Web**, and * +*Android**. If a certain platform isn't needed, simply deleting the source directory and the source sets in the `build.gradle.kts` file will get rid of it. @@ -15,11 +16,11 @@ respective platform. ### JVM -**Running:** +#### Running Run `LwjglApp` to execute on the desktop. -**Deploying:** +#### Deploying A custom deploy task is created specifically for JVM. Run the `package/packageFatJar` gradle task to create a fat executable JAR. This task can be tinkered with in the `build.gradlek.kts` file. @@ -27,36 +28,19 @@ executable JAR. This task can be tinkered with in the `build.gradlek.kts` file. If and when the packages are renamed from `com.game.template.LwjglApp` to whatever, ensure to update the `jvm.mainClass` property in the `gradle.properties` file to ensure that the `packageFatJar` task will work properly. +A jar will be outputted in the build directory. To run this JAR, use the following +command, replacing the name of the JAR, if needed: `java -jar --enable-preview game-1.0-all.jar` + +If you are running on Mac OS, then the following command can be +used: `java -jar -XstartOnFirstThread --enable-preview game-1.0-all.jar` + ### JS -**Running:** +#### Running Run the `kotlin browser/jsBrowserRun` gradle task like any other **Kotlin/JS** project to run in development mode. -**Deploying:** +#### Deploying Run the `kotlin browser/jsBrowserDistribution` gradle task to create a distribution build. This build will require a webserver in order to run. - -### WASM - -**Running:** - -Run the `kotlin browser/wasmJsBrowserRun` gradle task like any other **Kotlin/Wasm** project to run in development mode. - -**Deploying:** - -Run the `kotlin browser/wasmJsBrowserDistribution` gradle task to create a distribution build. This build will require a -webserver in order to run. - -### Android - -**Running:** - -Run `AndroidApp` class under `src/androidMain/`. - -**Deploying:** - -To create a release build of the Android application, use the `Build/Generate Signed Bundle / APK...` menu option. This -will require knowledge on creating keys for signing the Android app. Going over how to do that is out of scope for this -repository. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 423641c..354af84 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,10 @@ plugins { - alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.multiplatform) apply false } allprojects { repositories { + mavenLocal() google() mavenCentral() } diff --git a/game/build.gradle.kts b/game/build.gradle.kts index 2771712..4df6502 100644 --- a/game/build.gradle.kts +++ b/game/build.gradle.kts @@ -1,20 +1,18 @@ import org.apache.tools.ant.taskdefs.condition.Os -import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType -import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl repositories { maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") } plugins { - alias(libs.plugins.android.application) alias(libs.plugins.kotlin.multiplatform) } kotlin { - androidTarget() + tasks.withType { jvmArgs("--enable-preview", "--enable-native-access=ALL-UNNAMED") } jvm { + compilations.all { kotlinOptions.jvmTarget = "21" } compilations { val main by getting @@ -39,20 +37,19 @@ kotlin { duplicatesStrategy = DuplicatesStrategy.EXCLUDE dependsOn(named("jvmJar")) dependsOn(named("copyResources")) - manifest { - attributes["Main-Class"] = mainClassName - } + manifest { attributes["Main-Class"] = mainClassName } destinationDirectory.set(File("${layout.buildDirectory.asFile.get()}/publish/")) from( main.runtimeDependencyFiles.map { if (it.isDirectory) it else zipTree(it) }, main.output.classesDirs ) doLast { - project.logger.lifecycle("[LittleKt] The packaged jar is available at: ${outputs.files.first().parent}") + logger.lifecycle( + "[LittleKt] The packaged jar is available at: ${outputs.files.first().parent}" + ) } } - // workaround for https://youtrack.jetbrains.com/issue/KT-62214 - if (Os.isFamily(Os.FAMILY_MAC) && mainClassName != null) { + if (Os.isFamily(Os.FAMILY_MAC)) { register("jvmRun") { jvmArgs("-XstartOnFirstThread") mainClass.set(mainClassName) @@ -68,54 +65,32 @@ kotlin { } } } - compilations.all { - kotlinOptions.jvmTarget = "17" - } - testRuns["test"].executionTask.configure { - useJUnit() - } } - js(KotlinJsCompilerType.IR) { + js { binaries.executable() browser { - testTask { - useKarma { - useChromeHeadless() - } + testTask { useKarma { useChromeHeadless() } } + commonWebpackConfig { + devServer = + (devServer + ?: org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + .DevServer()) + .copy( + open = mapOf("app" to mapOf("name" to "chrome")), + ) } } - this.attributes.attribute( - KotlinPlatformType.attribute, - KotlinPlatformType.js - ) + this.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.js) - compilations.all { - kotlinOptions.sourceMap = true - } - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - binaries.executable() - browser { - commonWebpackConfig(Action { - devServer = - (devServer ?: org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.DevServer()).copy( - open = mapOf( - "app" to mapOf( - "name" to "firefox" - ) - ), - ) - }) - } + compilations.all { kotlinOptions.sourceMap = true } } sourceSets { val commonMain by getting { dependencies { implementation(libs.littlekt.core) + implementation(libs.littlekt.scenegraph) implementation(libs.kotlinx.coroutines.core) } } @@ -132,30 +107,5 @@ kotlin { } } val jsTest by getting - val wasmJsMain by getting - val wasmJsTest by getting - val androidMain by getting } } - -android { - namespace = "com.game.template" - sourceSets["main"].apply { - manifest.srcFile("src/androidMain/AndroidManifest.xml") - assets.srcDirs("src/commonMain/resources") - } - compileSdk = (findProperty("android.compileSdk") as String).toInt() - - defaultConfig { - minSdk = (findProperty("android.minSdk") as String).toInt() - targetSdk = (findProperty("android.targetSdk") as String).toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } -} - -rootProject.extensions.configure { - versions.webpackCli.version = "4.10.0" -} diff --git a/game/src/androidMain/AndroidManifest.xml b/game/src/androidMain/AndroidManifest.xml deleted file mode 100644 index a739e00..0000000 --- a/game/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/game/src/androidMain/kotlin/com/game/template/AndroidApp.kt b/game/src/androidMain/kotlin/com/game/template/AndroidApp.kt deleted file mode 100644 index ef46392..0000000 --- a/game/src/androidMain/kotlin/com/game/template/AndroidApp.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.game.template - -import android.app.Application - - -class AndroidApp : Application() \ No newline at end of file diff --git a/game/src/androidMain/kotlin/com/game/template/AppActivity.kt b/game/src/androidMain/kotlin/com/game/template/AppActivity.kt deleted file mode 100644 index 756258d..0000000 --- a/game/src/androidMain/kotlin/com/game/template/AppActivity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.game.template - -import com.lehaine.littlekt.Context -import com.lehaine.littlekt.ContextListener -import com.lehaine.littlekt.LittleKtActivity -import com.lehaine.littlekt.LittleKtProps -import com.lehaine.littlekt.graphics.Color - -class AppActivity : LittleKtActivity() { - - override fun LittleKtProps.configureLittleKt() { - activity = this@AppActivity - backgroundColor = Color.DARK_GRAY - } - - override fun createContextListener(context: Context): ContextListener { - return Game(context) - } -} \ No newline at end of file diff --git a/game/src/androidMain/res/drawable/ic_launcher.png b/game/src/androidMain/res/drawable/ic_launcher.png deleted file mode 100644 index 51f69fb383b49ccdc18b89981af33adc86770e15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40786 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 diff --git a/game/src/androidMain/res/values/strings.xml b/game/src/androidMain/res/values/strings.xml deleted file mode 100644 index 341c6ad..0000000 --- a/game/src/androidMain/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - LittleKt Game Template - diff --git a/game/src/commonMain/kotlin/com/game/template/Game.kt b/game/src/commonMain/kotlin/com/game/template/Game.kt index 1a0291d..65ffd2f 100644 --- a/game/src/commonMain/kotlin/com/game/template/Game.kt +++ b/game/src/commonMain/kotlin/com/game/template/Game.kt @@ -1,25 +1,34 @@ package com.game.template -import com.lehaine.littlekt.Context -import com.lehaine.littlekt.ContextListener -import com.lehaine.littlekt.graph.node.resource.HAlign -import com.lehaine.littlekt.graphics.Color -import com.lehaine.littlekt.graphics.Fonts -import com.lehaine.littlekt.graphics.g2d.SpriteBatch -import com.lehaine.littlekt.graphics.g2d.shape.ShapeRenderer -import com.lehaine.littlekt.graphics.g2d.use -import com.lehaine.littlekt.graphics.gl.ClearBufferMask -import com.lehaine.littlekt.graphics.toFloatBits -import com.lehaine.littlekt.math.geom.degrees -import com.lehaine.littlekt.math.geom.radians -import com.lehaine.littlekt.util.viewport.ExtendViewport +import com.littlekt.Context +import com.littlekt.ContextListener +import com.littlekt.graphics.Color +import com.littlekt.graphics.HAlign +import com.littlekt.graphics.g2d.SpriteBatch +import com.littlekt.graphics.g2d.shape.ShapeRenderer +import com.littlekt.graphics.g2d.use +import com.littlekt.graphics.webgpu.* +import com.littlekt.math.geom.degrees +import com.littlekt.math.geom.radians +import com.littlekt.resources.Fonts +import com.littlekt.util.viewport.ExtendViewport +import com.littlekt.util.viewport.setViewport import kotlin.time.Duration.Companion.milliseconds - class Game(context: Context) : ContextListener(context) { override suspend fun Context.start() { - val batch = SpriteBatch(this) + val device = graphics.device + val surfaceCapabilities = graphics.surfaceCapabilities + val preferredFormat = graphics.preferredFormat + + graphics.configureSurface( + TextureUsage.RENDER_ATTACHMENT, + preferredFormat, + PresentMode.FIFO, + surfaceCapabilities.alphaModes[0] + ) + val batch = SpriteBatch(device, graphics, preferredFormat) val shapeRenderer = ShapeRenderer(batch) val viewport = ExtendViewport(960, 540) val camera = viewport.camera @@ -27,22 +36,86 @@ class Game(context: Context) : ContextListener(context) { var rotationTimer = 0.milliseconds onResize { width, height -> - viewport.update(width, height, context) + viewport.update(width, height) + graphics.configureSurface( + TextureUsage.RENDER_ATTACHMENT, + preferredFormat, + PresentMode.FIFO, + surfaceCapabilities.alphaModes[0] + ) } + onUpdate { dt -> + val surfaceTexture = graphics.surface.getCurrentTexture() + when (val status = surfaceTexture.status) { + TextureStatus.SUCCESS -> { + // all good, could check for `surfaceTexture.suboptimal` here. + } - onRender { dt -> - gl.clearColor(Color.DARK_GRAY) - gl.clear(ClearBufferMask.COLOR_BUFFER_BIT) + TextureStatus.TIMEOUT, + TextureStatus.OUTDATED, + TextureStatus.LOST -> { + surfaceTexture.texture?.release() + logger.info { "getCurrentTexture status=$status" } + return@onUpdate + } - batch.use(camera.viewProjection) { - Fonts.default.draw(it, "Hello LittleKt!", 0f, 0f, align = HAlign.CENTER) - shapeRenderer.filledRectangle(-50f, 50f, 100f, 50f, rotation, color = Color.RED.toFloatBits()) + else -> { + // fatal + logger.fatal { "getCurrentTexture status=$status" } + close() + return@onUpdate + } } + val swapChainTexture = checkNotNull(surfaceTexture.texture) + val frame = swapChainTexture.createView() + + val commandEncoder = device.createCommandEncoder() + val renderPassEncoder = + commandEncoder.beginRenderPass( + desc = + RenderPassDescriptor( + listOf( + RenderPassColorAttachmentDescriptor( + view = frame, + loadOp = LoadOp.CLEAR, + storeOp = StoreOp.STORE, + clearColor = + if (preferredFormat.srgb) Color.DARK_GRAY.toLinear() + else Color.DARK_GRAY + ) + ) + ) + ) + renderPassEncoder.setViewport(viewport) + camera.update() + + batch.use(renderPassEncoder, camera.viewProjection) { + Fonts.default.draw(it, "Hello LittleKt!", 0f, 0f, align = HAlign.CENTER) + shapeRenderer.filledRectangle(-50f, 50f, 100f, 50f, rotation, color = Color.RED) + } + renderPassEncoder.end() + rotationTimer += dt if (rotationTimer > 10.milliseconds) { rotationTimer = 0.milliseconds rotation += 1.degrees } + + val commandBuffer = commandEncoder.finish() + + device.queue.submit(commandBuffer) + graphics.surface.present() + + commandBuffer.release() + renderPassEncoder.release() + commandEncoder.release() + frame.release() + swapChainTexture.release() + } + + onRelease { + batch.release() + device.release() } } } \ No newline at end of file diff --git a/game/src/jsMain/kotlin/com/game/template/JsApp.kt b/game/src/jsMain/kotlin/com/game/template/JsApp.kt index 3277255..0f8e355 100644 --- a/game/src/jsMain/kotlin/com/game/template/JsApp.kt +++ b/game/src/jsMain/kotlin/com/game/template/JsApp.kt @@ -1,12 +1,12 @@ package com.game.template -import com.lehaine.littlekt.createLittleKtApp -import com.lehaine.littlekt.graphics.Color +import com.littlekt.createLittleKtApp fun main() { createLittleKtApp { + width = 960 + height = 540 title = "LittleKt Game Template" - backgroundColor = Color.DARK_GRAY canvasId = "canvas" }.start { Game(it) diff --git a/game/src/jsMain/resources/index.html b/game/src/jsMain/resources/index.html index 35867d2..7cfdebd 100644 --- a/game/src/jsMain/resources/index.html +++ b/game/src/jsMain/resources/index.html @@ -3,10 +3,58 @@ LittleKt Game Template +
- +

+ LittleKt WebGPU Examples +

+

+ Your browser does not currently support WebGPU. You must use Chrome Canary, Edge Canary, or Firefox Nightly and + enable WebGPU (See here + for more details) +

+
+ +
- + + + + + \ No newline at end of file diff --git a/game/src/jvmMain/kotlin/com/game/template/LwjglApp.kt b/game/src/jvmMain/kotlin/com/game/template/LwjglApp.kt index 2cd94e4..a56c6d2 100644 --- a/game/src/jvmMain/kotlin/com/game/template/LwjglApp.kt +++ b/game/src/jvmMain/kotlin/com/game/template/LwjglApp.kt @@ -1,14 +1,14 @@ package com.game.template -import com.lehaine.littlekt.createLittleKtApp -import com.lehaine.littlekt.graphics.Color +import com.littlekt.createLittleKtApp fun main() { createLittleKtApp { width = 960 height = 540 - backgroundColor = Color.DARK_GRAY title = "LittleKt Game Template" + traceWgpu = false + enableWGPULogging = false }.start { Game(it) } diff --git a/game/src/wasmJsMain/kotlin/com/template/game/WasmJsApp.kt b/game/src/wasmJsMain/kotlin/com/template/game/WasmJsApp.kt deleted file mode 100644 index 6f5828d..0000000 --- a/game/src/wasmJsMain/kotlin/com/template/game/WasmJsApp.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.template.game - -import com.game.template.Game -import com.lehaine.littlekt.createLittleKtApp -import com.lehaine.littlekt.graphics.Color - -fun main() { - createLittleKtApp { - title = "LittleKt Game Template" - backgroundColor = Color.DARK_GRAY - canvasId = "canvas" - }.start { - Game(it) - } -} \ No newline at end of file diff --git a/game/src/wasmJsMain/resources/index.html b/game/src/wasmJsMain/resources/index.html deleted file mode 100644 index 144ee4f..0000000 --- a/game/src/wasmJsMain/resources/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - LittleKt - WASM - - - -
- ⚠️ Please make sure that your runtime environment supports the latest version of Wasm GC and Exception-Handling - proposals. - For more information, see https://kotl.in/wasm-help. -
-
-
    -
  • For Chrome and Chromium-based browsers (Edge, Brave etc.), it should just work since - version 119. -
  • -
  • For Firefox 120 it should just work.
  • -
  • For Firefox 119: -
      -
    1. Open about:config in the browser.
    2. -
    3. Enable javascript.options.wasm_gc.
    4. -
    5. Refresh this page.
    6. -
    -
  • -
-
-
- -
- - - - - \ No newline at end of file diff --git a/game/src/wasmJsMain/webpack.config.d/cleanupSourcemap.js b/game/src/wasmJsMain/webpack.config.d/cleanupSourcemap.js deleted file mode 100644 index b45d42b..0000000 --- a/game/src/wasmJsMain/webpack.config.d/cleanupSourcemap.js +++ /dev/null @@ -1,37 +0,0 @@ -// Replace paths unavailable during compilation with `null`, so they will not be shown in devtools -; -(() => { - const fs = require("fs"); - const path = require("path"); - - const outDir = __dirname + "/kotlin/" - const projecName = path.basename(__dirname); - const mapFileLegacy = outDir + projecName + ".map" - const mapFile = outDir + projecName + ".wasm.map" - - let sourcemap - try { - sourcemap = JSON.parse(fs.readFileSync(mapFileLegacy)) - } catch (e) { - sourcemap = JSON.parse(fs.readFileSync(mapFile)) - } - const sources = sourcemap["sources"] - srcLoop: for (let i in sources) { - const srcFilePath = sources[i]; - if (srcFilePath == null) continue; - - const srcFileCandidates = [ - outDir + srcFilePath, - outDir + srcFilePath.substring("../".length), - outDir + "../" + srcFilePath, - ]; - - for (let srcFile of srcFileCandidates) { - if (fs.existsSync(srcFile)) continue srcLoop; - } - - sources[i] = null; - } - - fs.writeFileSync(mapFile, JSON.stringify(sourcemap)); -})(); \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa4a18f..24b7e8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,15 @@ [versions] -agp = "8.2.0" -kotlin = "1.9.23" -kotlinx-html = "0.9.1" -kotlinx-coroutines = "1.8.0" -littlekt = "0.9.0" +kotlin = "2.0.0" +kotlinx-html = "0.11.0" +kotlinx-coroutines = "1.9.0-RC" +littlekt = "0.10.0" [libraries] kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-html-js = { module = "org.jetbrains.kotlinx:kotlinx-html-js", version.ref = "kotlinx-html" } -littlekt-core = { module = "com.lehaine.littlekt:core", version.ref = "littlekt" } +littlekt-core = { module = "com.littlekt:core", version.ref = "littlekt" } +littlekt-scenegraph = { module = "com.littlekt:scene-graph", version.ref = "littlekt" } [plugins] -android-application = { id = "com.android.application", version.ref = "agp" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index db9a6b8..a595206 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 15cb704..9b5a900 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,6 @@ pluginManagement { repositories { + mavenLocal() google() gradlePluginPortal() mavenCentral()