diff --git a/.github/actions/tools/minio.sh b/.github/actions/tools/minio.sh deleted file mode 100644 index 356fbf14d3..0000000000 --- a/.github/actions/tools/minio.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# minio_uploadFile -# -# Upload the specified file to the specified MinIO instance. -function minio_uploadFile { - file="$1" - dest="$2" - url="$3" - access="$4" - secret="$5" - - echo "Install MinIO client" - wget --quiet https://dl.min.io/client/mc/release/linux-amd64/mc - chmod +x ./mc - - echo "Add an alias for the MinIO instance to the MinIO configuration file" - ./mc alias set objects "$url" "$access" "$secret" - - echo "Upload $file to $url/$dest" - ./mc cp "$file" "objects/$dest" -} diff --git a/.github/workflows/android-screenshot.yml b/.github/workflows/android-screenshot.yml new file mode 100644 index 0000000000..46d415bffc --- /dev/null +++ b/.github/workflows/android-screenshot.yml @@ -0,0 +1,84 @@ +name: Android Screenshot Test + +on: + push: + branches: + - master + - v3.7 + - v3.6 + - v3.5 + - v3.4 + - v3.3 + - ios-2024_2 + pull_request: + +jobs: + screenshot-test-android: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: checkout + uses: actions/checkout@v6 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Gradle cache + uses: gradle/actions/setup-gradle@v5 + + - name: AVD cache + uses: actions/cache@v5 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-35 + - name: Run Android Screenshot Test + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + avd-name: test_avd + target: google_apis + arch: x86_64 + force-avd-creation: true + ram-size: 4096M + heap-size: 2048M + profile: pixel_tablet + emulator-options: -no-window -gpu mesa -noaudio -no-boot-anim -camera-back none -no-snapshot -no-snapshot-save -no-snapshot-load + disable-animations: false + script: > + curl -L https://maven.google.com/androidx/test/services/test-services/1.6.0/test-services-1.6.0.apk --output test-services-1.6.0.apk; + adb install -r -g test-services-1.6.0.apk; + adb uninstall org.jmonkeyengine.screenshottests.android || true; + ./gradlew :jme3-screenshot-tests:jme3-screenshot-tests-android:connectedDebugAndroidTest; + exit_code=$?; + mkdir -p logcat; + adb logcat -d > logcat/logcat_full.txt || true; + adb logcat -d | grep org.jmonkeyengine.screenshottests.android > logcat/logcat.txt || true; + mkdir -p report; + adb pull /storage/emulated/0/googletest/test_outputfiles/report report/protoReport || true; + adb pull /storage/emulated/0/googletest/test_outputfiles/changed-images report/changed-images || true; + ./gradlew :jme3-screenshot-tests:jme3-screenshot-tests-proto-report:upgradeProtoReport --args="$(pwd)/report/protoReport $(pwd)/report/extentReport" || true; + echo "GRADLE_EXIT_CODE=$exit_code" >> $GITHUB_ENV; + exit $exit_code + - name: Upload logcat + uses: actions/upload-artifact@v4 + if: always() + with: + name: android-logcat + path: logcat + - name: Upload Screenshot + uses: actions/upload-artifact@v4 + if: always() + with: + name: android-screenshot-report + path: report + if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/bounty.yml b/.github/workflows/bounty.yml index bbd263fae6..3dd7288517 100644 --- a/.github/workflows/bounty.yml +++ b/.github/workflows/bounty.yml @@ -14,7 +14,7 @@ jobs: if: startsWith(github.event.label.name, 'diff:') steps: - name: Comment bounty info - uses: actions/github-script@v8.0.0 + uses: actions/github-script@v9.0.0 env: FORUM_URL: "https://hub.jmonkeyengine.org/t/bounty-program-trial-starts-today/49394/" RESERVE_HOURS: "48" diff --git a/.github/workflows/j3o-scan.yml b/.github/workflows/j3o-scan.yml index 33ccbc1314..bcb1b57a71 100644 --- a/.github/workflows/j3o-scan.yml +++ b/.github/workflows/j3o-scan.yml @@ -34,7 +34,7 @@ jobs: uses: actions/setup-java@v5 with: distribution: temurin - java-version: '17' + java-version: '25' - name: Scan J3O assets run: ./gradlew :jme3-desktop:scanJ3O --console=plain diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1bcc9d07a3..198adbf1bc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,7 @@ # JME CI/CD ###################################################################################### # Quick overview of what is going on in this script: -# - Build natives for android -# - Merge the natives, build the engine, create the zip release, maven artifacts, javadoc and native snapshot -# - (only when native code changes) Deploy the natives snapshot to the MinIO instance +# - Build the engine, create the zip release, maven artifacts and javadoc # - (only when building a release) Deploy everything else to github releases and Sonatype # - (only when building a release) Update javadoc.jmonkeyengine.org # Note: @@ -13,8 +11,6 @@ # running workflow, we use it to store the result of each job since the filesystem # is not maintained between jobs. ################# CONFIGURATIONS ##################################################### -# >> Configure MINIO NATIVES SNAPSHOT -# OBJECTS_KEY=XXXXXX # >> Configure SONATYPE RELEASE # CENTRAL_PASSWORD=XXXXXX # CENTRAL_USERNAME=XXXXXX @@ -69,14 +65,14 @@ jobs: uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Validate the Gradle wrapper uses: gradle/actions/wrapper-validation@v6.1.0 - name: Run Checkstyle run: | ./gradlew checkstyleMain checkstyleTest --console=plain --stacktrace - name: Upload Checkstyle Reports - uses: actions/upload-artifact@v7.0.0 + uses: actions/upload-artifact@v7.0.1 if: always() with: name: checkstyle-report @@ -96,14 +92,14 @@ jobs: uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' - name: Validate the Gradle wrapper uses: gradle/actions/wrapper-validation@v6.1.0 - name: Run SpotBugs run: | ./gradlew -PenableSpotBugs=true spotbugsMain spotbugsTest --console=plain --stacktrace - name: Upload SpotBugs Reports - uses: actions/upload-artifact@v7.0.0 + uses: actions/upload-artifact@v7.0.1 if: always() with: name: spotbugs-report @@ -111,6 +107,29 @@ jobs: path: | **/build/reports/spotbugs/** + JavadocDoclint: + name: Run Javadoc Doclint + runs-on: ubuntu-latest + continue-on-error: true + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v6.1.0 + - name: Run Javadoc doclint + run: | + ./gradlew -PenableJavadocError=true javadoc mergedJavadoc --console=plain --stacktrace + - name: Report Javadoc doclint failure + if: failure() + run: | + echo "::notice title=Javadoc doclint failed::Javadoc warnings were found. Run ./gradlew -PenableJavadocError=true javadoc mergedJavadoc locally to reproduce." + ScreenshotTests: name: Run Screenshot Tests runs-on: ubuntu-latest @@ -120,6 +139,11 @@ jobs: contents: read steps: - uses: actions/checkout@v6 + - name: Setup the java environment + uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' - name: Start xvfb run: | Xvfb :99 -ac -screen 0 1024x768x16 & @@ -138,9 +162,9 @@ jobs: uses: gradle/actions/wrapper-validation@v6.1.0 - name: Test with Gradle Wrapper run: | - ./gradlew :jme3-screenshot-test:screenshotTest + ./gradlew :jme3-screenshot-tests:jme3-screenshot-tests-desktop:screenshotTest - name: Upload Test Reports - uses: actions/upload-artifact@v7.0.0 + uses: actions/upload-artifact@v7.0.1 if: always() with: name: screenshot-test-report @@ -150,91 +174,8 @@ jobs: **/build/changed-images/** **/build/test-results/** - # Build iOS natives - BuildIosNatives: - name: Build natives for iOS - runs-on: macOS-14 - - steps: - - name: Check default JAVAs - run: echo $JAVA_HOME --- $JAVA_HOME_8_X64 --- $JAVA_HOME_11_X64 --- $JAVA_HOME_17_X64 --- $JAVA_HOME_21_X64 --- - - - name: Setup the java environment - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - - - name: Setup the XCode version to 15.1.0 - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 - with: - xcode-version: '15.1.0' - - - name: Clone the repo - uses: actions/checkout@v6 - with: - fetch-depth: 1 - - - name: Validate the Gradle wrapper - uses: gradle/actions/wrapper-validation@v6.1.0 - - - name: Build - run: | - ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ - :jme3-ios-native:build - - - name: Upload natives - uses: actions/upload-artifact@v7.0.0 - with: - name: ios-natives - path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework - - # Build the natives on android - BuildAndroidNatives: - name: Build natives for android - runs-on: ubuntu-latest - container: - image: ghcr.io/cirruslabs/android-sdk:36-ndk - - steps: - - name: Clone the repo - uses: actions/checkout@v6 - with: - fetch-depth: 1 - - - name: Setup Java 17 - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: '17' - - - name: Check java version - run: java -version - - - name: Install CMake - run: | - apt-get update - apt-get install -y cmake - cmake --version - - - name: Validate the Gradle wrapper - uses: gradle/actions/wrapper-validation@v6.1.0 - - - name: Build - run: | - export ANDROID_NDK="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION" - ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ - :jme3-android-native:assemble - - - name: Upload natives - uses: actions/upload-artifact@v7.0.0 - with: - name: android-natives - path: build/native - # Build the engine, we only deploy from ubuntu-latest jdk25 BuildJMonkey: - needs: [BuildAndroidNatives, BuildIosNatives] name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} runs-on: ${{ matrix.os }} strategy: @@ -259,17 +200,11 @@ jobs: with: fetch-depth: 1 - - name: Download natives for android - uses: actions/download-artifact@v8.0.1 - with: - name: android-natives - path: build/native - - - name: Download natives for iOS - uses: actions/download-artifact@v8.0.1 + - name: Setup the java environment + uses: actions/setup-java@v5 with: - name: ios-natives - path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework + distribution: 'temurin' + java-version: ${{ matrix.jdk }} - name: Validate the Gradle wrapper uses: gradle/actions/wrapper-validation@v6.1.0 @@ -277,7 +212,7 @@ jobs: shell: bash run: | # Normal build plus ZIP distribution and merged javadoc - ./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true \ + ./gradlew -PuseCommitHashAsVersionName=true \ -x checkstyleMain -x checkstyleTest \ build createZipDistribution mergedJavadoc @@ -298,44 +233,29 @@ jobs: echo "SIGNING_KEY, SIGNING_PASSWORD" ./gradlew publishMavenPublicationToDistRepository \ - -PskipPrebuildLibraries=true -PuseCommitHashAsVersionName=true \ + -PuseCommitHashAsVersionName=true \ --console=plain --stacktrace else ./gradlew publishMavenPublicationToDistRepository \ -PsigningKey='${{ secrets.SIGNING_KEY }}' \ -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ - -PskipPrebuildLibraries=true -PuseCommitHashAsVersionName=true \ + -PuseCommitHashAsVersionName=true \ --console=plain --stacktrace fi - # Zip the natives into a single archive (we are going to use this to deploy native snapshots) - echo "Create native zip" - cdir="$PWD" - cd "build/native" - zip -r "$cdir/dist/jme3-natives.zip" * - cd "$cdir" - echo "Done" fi - # Used later by DeploySnapshot - - name: Upload merged natives - if: matrix.deploy==true - uses: actions/upload-artifact@v7.0.0 - with: - name: natives - path: dist/jme3-natives.zip - # Upload maven artifacts to be used later by the deploy job - name: Upload maven artifacts if: matrix.deploy==true - uses: actions/upload-artifact@v7.0.0 + uses: actions/upload-artifact@v7.0.1 with: name: maven path: dist/maven - name: Upload javadoc if: matrix.deploy==true - uses: actions/upload-artifact@v7.0.0 + uses: actions/upload-artifact@v7.0.1 with: name: javadoc path: dist/javadoc @@ -343,132 +263,33 @@ jobs: # Upload release archive to be used later by the deploy job - name: Upload release if: github.event_name == 'release' && matrix.deploy==true - uses: actions/upload-artifact@v7.0.0 + uses: actions/upload-artifact@v7.0.1 with: name: release path: dist/release - # This job deploys the native snapshot. - # The snapshot is downloaded when people build the engine without setting buildNativeProject - # this is useful for people that want to build only the java part and don't have - # all the stuff needed to compile natives. - DeployNativeSnapshot: - needs: [BuildJMonkey] - name: "Deploy native snapshot" - runs-on: ubuntu-latest - if: github.event_name == 'push' - steps: - - # We clone the repo manually, since we are going to push back a reference to the snapshot - - name: Clone the repo - run: | - branch="${GITHUB_REF//refs\/heads\//}" - if [ "$branch" != "" ]; - then - git clone --single-branch --branch "$branch" https://github.com/${GITHUB_REPOSITORY}.git . - fi - - - name: Download merged natives - uses: actions/download-artifact@v8.0.1 - with: - name: natives - path: dist/ - - - name: Deploy natives snapshot - run: | - source .github/actions/tools/minio.sh - NATIVE_CHANGES="yes" - branch="${GITHUB_REF//refs\/heads\//}" - if [ "$branch" != "" ]; - then - if [ -f "natives-snapshot.properties" ]; - then - nativeSnapshot=`cat "natives-snapshot.properties"` - nativeSnapshot="${nativeSnapshot#*=}" - - # We deploy ONLY if GITHUB_SHA (the current commit hash) is newer than $nativeSnapshot - if [ "`git rev-list --count $nativeSnapshot..$GITHUB_SHA`" = "0" ]; - then - NATIVE_CHANGES="" - else - # We check if the native code changed. - echo "Detect changes" - NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-android-native/)" - fi - fi - - # We do nothing if there is no change - if [ "$NATIVE_CHANGES" = "" ]; - then - echo "No changes, skip." - else - if [ "${{ secrets.OBJECTS_KEY }}" = "" ]; - then - echo "Configure the OBJECTS_KEY secret to enable natives snapshot deployment to MinIO" - else - # Deploy natives snapshot to a MinIO instance using function in minio.sh - minio_uploadFile dist/jme3-natives.zip \ - native-snapshots/$GITHUB_SHA/jme3-natives.zip \ - https://objects.jmonkeyengine.org \ - jmonkeyengine \ - ${{ secrets.OBJECTS_KEY }} - - # We reference the snapshot by writing its commit hash in natives-snapshot.properties - echo "natives.snapshot=$GITHUB_SHA" > natives-snapshot.properties - - # We commit the updated natives-snapshot.properties - git config --global user.name "Github Actions" - git config --global user.email "actions@users.noreply.github.com" - - git add natives-snapshot.properties - - git commit -m "[skip ci] update natives snapshot" - - # Pull rebase from the remote repo, just in case there was a push in the meantime - git pull -q --rebase - - # We need to calculate the header for git authentication - header=$(echo -n "ad-m:${{ secrets.GITHUB_TOKEN }}" | base64) - - # Push - (git -c http.extraheader="AUTHORIZATION: basic $header" push origin "$branch" || true) - - fi - fi - fi - # This job deploys snapshots on the master branch DeployJavaSnapshot: needs: [BuildJMonkey] name: Deploy Java Snapshot runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref_name == 'master' + permissions: + contents: read steps: # We need to clone everything again for uploadToMaven.sh ... - name: Clone the repo - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 1 - # Setup jdk 21 used for building Maven-style artifacts + # Setup jdk 25 used for building Maven-style artifacts - name: Setup the java environment - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: 'temurin' - java-version: '21' - - - name: Download natives for android - uses: actions/download-artifact@v8.0.1 - with: - name: android-natives - path: build/native - - - name: Download natives for iOS - uses: actions/download-artifact@v8.0.1 - with: - name: ios-natives - path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework + java-version: '25' - name: Rebuild the maven artifacts and upload them to Sonatype's maven-snapshots repo run: | @@ -482,7 +303,6 @@ jobs: -PcentralUsername=${{ secrets.CENTRAL_USERNAME }} \ -PsigningKey='${{ secrets.SIGNING_KEY }}' \ -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ - -PuseCommitHashAsVersionName=true \ --console=plain --stacktrace fi @@ -501,12 +321,12 @@ jobs: with: fetch-depth: 1 - # Setup jdk 21 used for building Sonatype artifacts + # Setup jdk 25 used for building Sonatype artifacts - name: Setup the java environment uses: actions/setup-java@v5 with: distribution: 'temurin' - java-version: '21' + java-version: '25' # Download all the stuff... - name: Download maven artifacts @@ -521,18 +341,6 @@ jobs: name: release path: dist/release - - name: Download natives for android - uses: actions/download-artifact@v8.0.1 - with: - name: android-natives - path: build/native - - - name: Download natives for iOS - uses: actions/download-artifact@v8.0.1 - with: - name: ios-natives - path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework - - name: Rebuild the maven artifacts and upload them to Sonatype's Central Publisher Portal run: | if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; diff --git a/.github/workflows/screenshot-test-comment.yml b/.github/workflows/screenshot-test-comment.yml index 09e3fbf278..a445615273 100644 --- a/.github/workflows/screenshot-test-comment.yml +++ b/.github/workflows/screenshot-test-comment.yml @@ -24,7 +24,7 @@ jobs: run: sleep 120 - name: Wait for Screenshot Tests to complete - uses: lewagon/wait-on-check-action@78dd4dd5d9b337c14c3c81f79e53bf7d222435c1 # v1.6.1 + uses: lewagon/wait-on-check-action@9312864dfbc9fd208e9c0417843430751c042800 # v1.7.0 with: ref: ${{ github.event.pull_request.head.sha }} check-name: 'Run Screenshot Tests' @@ -33,7 +33,7 @@ jobs: allowed-conclusions: success,skipped,failure - name: Check Screenshot Tests status id: check-status - uses: actions/github-script@v8.0.0 + uses: actions/github-script@v9.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -133,6 +133,6 @@ jobs: See https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-screenshot-tests/README.md for more information - Contact @richardTingle (aka richtea) for guidance if required + Contact %40richardTingle (aka richtea) for guidance if required edit-mode: replace comment-id: ${{ steps.existingCommentId.outputs.comment-id }} diff --git a/.gitignore b/.gitignore index a15615de45..c9ee05f2d3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ **/.vscode/** **/out/ /.gradle/ +**/.gradle/ /.nb-gradle/ /.idea/ /dist/ @@ -45,4 +46,9 @@ javadoc_deploy.pub !.vscode/settings.json !.vscode/JME_style.xml !.vscode/extensions.json -joysticks-*.txt \ No newline at end of file +joysticks-*.txt +/jme3-screenshot-tests/jme3-screenshot-tests-desktop/build/ +/jme3-screenshot-tests/jme3-screenshot-tests-android/build/ +/jme3-screenshot-tests/jme3-screenshot-tests-shared/build/ + +/jme3-screenshot-tests/jme3-screenshot-tests-proto-report/build/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 6e9df573c0..e559d94247 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,5 @@ "editor.formatOnPaste": false, "editor.formatOnType": false, "editor.formatOnSave": false, - "java.jdt.ls.vmargs": "-Xms512M -Xmx2G -XX:+UseG1GC -XX:+UseStringDeduplication -XX:AdaptiveSizePolicyWeight=90" + "java.jdt.ls.vmargs": "-Xms512M -Xmx4G -XX:+UseG1GC -XX:+UseStringDeduplication -XX:AdaptiveSizePolicyWeight=90" } diff --git a/README.md b/README.md index aedfb69fe6..3ad9fcc75b 100644 --- a/README.md +++ b/README.md @@ -130,12 +130,12 @@ You can optionally use the `-Pexample` property to specify an example to start w You can run the Android examples on a local android emulator with: ```bash -./gradlew -PbuildAndroidExamples=true -PbuildNativeProjects=true runAndroidExamples +./gradlew runAndroidExamples # or for a specific example: -# ./gradlew -PbuildAndroidExamples=true -PbuildNativeProjects=true runAndroidExamples -Pexample=jme3test.post.TestBloom +# ./gradlew runAndroidExamples -Pexample=jme3test.post.TestBloom ``` -*Make sure to have the SDK and NDK installed and configured properly, and the emulator running before executing the command.* +*Make sure to have the SDK installed and configured properly, and the emulator running before executing the command.* ## Running Tests @@ -151,4 +151,3 @@ This runs all subproject tests and produces two sets of HTML reports: A summary index linking to every per-module report is also generated at: `build/reports/jacoco/index.html` - diff --git a/build.gradle b/build.gradle index 8dade8c48e..0a59e81e5b 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,10 @@ buildscript { } } +plugins { + id 'org.jmonkeyengine.nativeimage' apply false +} + import org.gradle.api.file.RelativePath // Set the license for IDEs that understand this @@ -32,13 +36,14 @@ allprojects { // This is applied to all sub projects subprojects { - if(!project.name.equals('jme3-android-examples')) { - apply from: rootProject.file('common.gradle') - } else { + if(project.name.equals('jme3-android-examples') || project.name.equals('jme3-screenshot-tests-android')) { apply from: rootProject.file('common-android-app.gradle') + } else { + apply from: rootProject.file('common.gradle') } - if (!project.name.endsWith("-native") && enableSpotBugs != "false" ) { + def isAndroidApp = project.plugins.hasPlugin('com.android.application') + if (!project.name.endsWith("-native") && !isAndroidApp && enableSpotBugs != "false" ) { apply plugin: 'com.github.spotbugs' // Currently we only warn about issues and try to fix them as we go, but those aren't mission critical. @@ -56,11 +61,54 @@ subprojects { } } +def nativeImagePluginBuild = gradle.includedBuild('jme3-nativeimage-plugin') +def nativeImagePluginPublishTasks = [ + publish : ':publish', + publishToMavenLocal : ':publishToMavenLocal', + install : ':install', + publishAllPublicationsToCentralRepository : ':publishAllPublicationsToCentralRepository', + publishAllPublicationsToSNAPSHOTRepository : ':publishAllPublicationsToSNAPSHOTRepository', + publishAllPublicationsToDistRepository : ':publishAllPublicationsToDistRepository' +] + +subprojects { + tasks.configureEach { task -> + String includedTaskPath = nativeImagePluginPublishTasks[task.name] + if (includedTaskPath != null) { + task.dependsOn nativeImagePluginBuild.task(includedTaskPath) + } + } +} + tasks.register('run') { dependsOn ':jme3-examples:run' description = 'Run the jME3 examples' } +tasks.register('runAndroidExamples') { + description = 'Run the Android examples selector' + def androidExamplesProject = findProject(':jme3-android-examples') + if (androidExamplesProject != null) { + dependsOn ':jme3-android-examples:runAndroidExamples' + } else { + doLast { + throw new GradleException('Android examples are not available. Request an Android examples task and configure an Android SDK.') + } + } +} + +tasks.register('runIosExamples') { + description = 'Run the iOS examples selector' + def iosExamplesProject = findProject(':jme3-ios-examples') + if (iosExamplesProject != null) { + dependsOn ':jme3-ios-examples:runIosExamples' + } else { + doLast { + throw new GradleException('iOS examples are not available. Request an iOS examples task.') + } + } +} + defaultTasks 'run' def libDist = tasks.register('libDist') { @@ -150,8 +198,13 @@ tasks.register('mergedJavadoc', Javadoc) { title = 'jMonkeyEngine3' destinationDir = file("dist/javadoc") + def javadocErrorsEnabled = findProperty('enableJavadocError') == 'true' + failOnError = javadocErrorsEnabled options.encoding = 'UTF-8' - if (JavaVersion.current().isJava8Compatible()) { + if (javadocErrorsEnabled) { + options.addBooleanOption('Werror', true) + options.addBooleanOption('Xdoclint:all,-missing', true) + } else { options.addStringOption('Xdoclint:none', '-quiet') } @@ -168,130 +221,4 @@ tasks.named('clean') { dependsOn cleanMergedJavadoc } -ext { - ndkCommandPath = "" - ndkExists = false -} - apply from: 'gradle/jacoco.gradle' - -task configureAndroidNDK { - def ndkBuildFile = "ndk-build" - // if windows, use ndk-build.cmd instead - if (System.properties['os.name'].toLowerCase().contains('windows')) { - ndkBuildFile = "ndk-build.cmd" - } - - def ndkCandidates = [] - if (System.env.ANDROID_NDK) { - ndkCandidates << file(System.env.ANDROID_NDK) - } - if (System.env.ANDROID_NDK_HOME) { - ndkCandidates << file(System.env.ANDROID_NDK_HOME) - } - if (project.hasProperty('ndkPath') && ndkPath) { - ndkCandidates << file(ndkPath) - } - - def localProperties = file('local.properties') - if (localProperties.isFile()) { - Properties properties = new Properties() - localProperties.withInputStream { properties.load(it) } - if (properties.getProperty('ndk.dir')) { - ndkCandidates << file(properties.getProperty('ndk.dir')) - } - if (properties.getProperty('sdk.dir')) { - ndkCandidates.addAll(findAndroidNdkDirs(file(properties.getProperty('sdk.dir')))) - } - } - - if (System.env.ANDROID_HOME) { - ndkCandidates.addAll(findAndroidNdkDirs(file(System.env.ANDROID_HOME))) - } - if (System.env.ANDROID_SDK_ROOT) { - ndkCandidates.addAll(findAndroidNdkDirs(file(System.env.ANDROID_SDK_ROOT))) - } - ndkCandidates.addAll(findAndroidNdkDirs(file("${System.properties['user.home']}/Android/Sdk"))) - - def ndkBuildPath = ndkCandidates.collect { new File(it, ndkBuildFile) }.find { it.isFile() } - if (ndkBuildPath != null) { - ndkExists = true - ndkCommandPath = ndkBuildPath.absolutePath - } -} - -def findAndroidNdkDirs(File sdkDir) { - def ndkDirs = [] - def nestedSdkDir = new File(sdkDir, 'Sdk') - if (nestedSdkDir.isDirectory()) { - ndkDirs.addAll(findAndroidNdkDirs(nestedSdkDir)) - } - def sideBySideDir = new File(sdkDir, 'ndk') - if (sideBySideDir.isDirectory()) { - ndkDirs.addAll(sideBySideDir.listFiles()?.findAll { it.isDirectory() }?.sort { a, b -> b.name <=> a.name } ?: []) - } - ndkDirs << new File(sdkDir, 'ndk-bundle') - return ndkDirs -} - -gradle.rootProject.ext.set("usePrebuildNatives", buildNativeProjects!="true"); - -if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { - File nativesSnapshotPropF = file('natives-snapshot.properties') - - if (nativesSnapshotPropF.exists()) { - def readNativesConfig = { - Properties nativesSnapshotProp = new Properties() - nativesSnapshotPropF.withInputStream { nativesSnapshotProp.load(it) } - - String nativesSnapshot = nativesSnapshotProp.getProperty("natives.snapshot") - if (!nativesSnapshot) { - throw new GradleException("Missing 'natives.snapshot' in ${nativesSnapshotPropF}") - } - - [ - snapshot: nativesSnapshot, - url: PREBUILD_NATIVES_URL.replace('${natives.snapshot}', nativesSnapshot), - zipFile: layout.buildDirectory.file("${nativesSnapshot}-natives.zip"), - nativeDir: layout.buildDirectory.dir("native"), - ] - } - - def nativesZipFile = providers.provider { readNativesConfig().zipFile.get().asFile } - def nativesPath = layout.buildDirectory.dir("native") - - def getNativesZipFile = tasks.register('getNativesZipFile') { - outputs.file(nativesZipFile) - doFirst { - def nativesConfig = readNativesConfig() - File target = nativesConfig.zipFile.get().asFile - println("Use natives snapshot: ${nativesConfig.url}") - println("Download natives from ${nativesConfig.url} to ${target}") - target.getParentFile().mkdirs() - ant.get(src: nativesConfig.url, dest: target) - } - } - - def extractPrebuiltNatives = tasks.register('extractPrebuiltNatives', Sync) { - dependsOn(getNativesZipFile) - into(nativesPath) - from({ - zipTree(readNativesConfig().zipFile.get().asFile) - }) { - eachFile { details -> - def segments = details.relativePath.segments - if (segments.length <= 1) { - details.exclude() - } else { - details.relativePath = new RelativePath(details.isDirectory(), segments[1..-1] as String[]) - } - } - includeEmptyDirs = false - } - } - - tasks.named('assemble') { - dependsOn extractPrebuiltNatives - } - } -} diff --git a/common.gradle b/common.gradle index 05400cdaac..30ad1731c7 100644 --- a/common.gradle +++ b/common.gradle @@ -24,9 +24,11 @@ java { } } +apply plugin: 'org.jmonkeyengine.nativeimage' + tasks.withType(JavaCompile) { // compile-time options: //options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings - options.compilerArgs << '-Xlint:unchecked' + options.compilerArgs.addAll(['-Xlint:unchecked', '-Xlint:-options', '-Werror']) options.encoding = 'UTF-8' options.release = 8 } @@ -68,7 +70,14 @@ jar { } javadoc { - failOnError = false + def javadocErrorsEnabled = rootProject.findProperty('enableJavadocError') == 'true' + failOnError = javadocErrorsEnabled + if (javadocErrorsEnabled) { + options.addBooleanOption('Werror', true) + options.addBooleanOption('Xdoclint:all,-missing', true) + } else { + options.addStringOption('Xdoclint:none', '-quiet') + } options.memberLevel = org.gradle.external.javadoc.JavadocMemberLevel.PROTECTED options.docTitle = "jMonkeyEngine ${jmeFullVersion} ${project.name} Javadoc" options.windowTitle = "jMonkeyEngine ${jmeFullVersion} ${project.name} Javadoc" @@ -77,9 +86,6 @@ javadoc { options.use = "true" options.charSet = "UTF-8" options.encoding = "UTF-8" - if (JavaVersion.current().isJava8Compatible()) { - options.addStringOption('Xdoclint:none', '-quiet') - } source = sourceSets.main.allJava // main only, exclude tests } @@ -101,26 +107,26 @@ task javadocJar(type: Jar, dependsOn: javadoc, description: 'Creates a jar from } ext.pomConfig = { - name POM_NAME - description POM_DESCRIPTION - url POM_URL - inceptionYear POM_INCEPTION_YEAR + name = POM_NAME + description = POM_DESCRIPTION + url = POM_URL + inceptionYear = POM_INCEPTION_YEAR scm { - url POM_SCM_URL - connection POM_SCM_CONNECTION - developerConnection POM_SCM_DEVELOPER_CONNECTION + url = POM_SCM_URL + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEVELOPER_CONNECTION } licenses { license { - name POM_LICENSE_NAME - url POM_LICENSE_URL - distribution POM_LICENSE_DISTRIBUTION + name = POM_LICENSE_NAME + url = POM_LICENSE_URL + distribution = POM_LICENSE_DISTRIBUTION } } developers { developer { - name 'jMonkeyEngine Team' - id 'jMonkeyEngine' + name = 'jMonkeyEngine Team' + id = 'jMonkeyEngine' } } } diff --git a/gradle.properties b/gradle.properties index 2af1467273..422a93c8d3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,31 +18,21 @@ buildJavaDoc = true # Let Gradle fetch missing JDKs for toolchains automatically. org.gradle.java.installations.auto-download=true -# specify if SDK and Native libraries get built -buildNativeProjects = false -buildAndroidExamples = false - -# Forcefully ignore prebuilt libraries -skipPrebuildLibraries=false - -# Enable spotbugs -enableSpotBugs=false - -# Path to android NDK for building native libraries -#ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7 -ndkPath = /opt/android-ndk-r16b - -# POM settings -POM_NAME=jMonkeyEngine +# Enable spotbugs +enableSpotBugs=false + +# POM settings +POM_NAME=jMonkeyEngine POM_DESCRIPTION=jMonkeyEngine is a 3-D game engine for adventurous Java developers POM_URL=http://jmonkeyengine.org POM_SCM_URL=https://github.com/jMonkeyEngine/jmonkeyengine POM_SCM_CONNECTION=scm:git:git://github.com/jMonkeyEngine/jmonkeyengine.git POM_SCM_DEVELOPER_CONNECTION=scm:git:git@github.com:jMonkeyEngine/jmonkeyengine.git POM_LICENSE_NAME=New BSD (3-clause) License -POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause -POM_LICENSE_DISTRIBUTION=repo -POM_INCEPTION_YEAR=2009 +POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause +POM_LICENSE_DISTRIBUTION=repo +POM_INCEPTION_YEAR=2009 -PREBUILD_NATIVES_URL=https://objects.jmonkeyengine.org/native-snapshots/${natives.snapshot}/jme3-natives.zip +systemProp.org.gradle.unsafe.repositories=true +org.gradle.jvmargs=-Xmx2048m diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 830a8d203a..aaa18b7ba7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,21 +5,39 @@ checkstyle = "13.3.0" jacoco = "0.8.12" lwjgl3 = "3.4.1" +angle = "2026-05-09" +libjglios = "0.6" +saferalloc = "0.0.10" nifty = "1.4.3" spotbugs = "4.9.8" +jmeAndroidNatives = "3.10.0-xt16kb-alloc" +googleMaterial = "1.4.0" +androidx-fragment-testing = "1.8.9" [libraries] -androidx-annotation = "androidx.annotation:annotation:1.7.1" +androidx-annotation = "androidx.annotation:annotation:1.10.0" androidx-fragment = "androidx.fragment:fragment:1.8.9" androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.7.0" android-build-gradle = "com.android.tools.build:gradle:9.1.0" android-support-appcompat = "com.android.support:appcompat-v7:28.0.0" -androidx-test-runner = "androidx.test:runner:1.7.0" +androidx-test-services = { module = "androidx.test.services:test-services", version = "1.6.0" } +androidx-storage = { module = "androidx.test.services:storage" } # used for peristent android test result storage, file version from androidx-fragment-testing-manifest +androidxAppcompat = { module = "androidx.appcompat:appcompat", version = "1.4.0" } +androidxConstraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.1.1" } +androidx-test-core = "androidx.test:core:1.6.1" +androidx-test-rules = "androidx.test:rules:1.6.1" +androidx-test-ext-junit = "androidx.test.ext:junit:1.2.1" +androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.6.1" +androidx-fragment-testing = { module ="androidx.fragment:fragment-testing", version.ref = "androidx-fragment-testing"} +androidx-fragment-testing-manifest = { module = "androidx.fragment:fragment-testing-manifest", version.ref = "androidx-fragment-testing"} gradle-git = "org.ajoberstar:gradle-git:1.2.0" +androidx-test-runner = "androidx.test:runner:1.7.0" groovy-test = "org.apache.groovy:groovy-test:4.0.31" -gson = "com.google.code.gson:gson:2.13.2" +gson = "com.google.code.gson:gson:2.14.0" +googleMaterial = { module = "com.google.android.material:material", version.ref = "googleMaterial" } j-ogg-vorbis = "com.github.stephengold:j-ogg-vorbis:1.0.6" +jme3-android-natives = { module = "org.jmonkeyengine:jme3-android-native", version.ref = "jmeAndroidNatives" } jbullet = "com.github.stephengold:jbullet:1.0.3" jinput = "net.java.jinput:jinput:2.0.9" jna = "net.java.dev.jna:jna:5.18.1" @@ -36,9 +54,10 @@ lwjgl3-glfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl3" lwjgl3-jawt = { module = "org.lwjgl:lwjgl-jawt", version.ref = "lwjgl3" } lwjgl3-jemalloc = { module = "org.lwjgl:lwjgl-jemalloc", version.ref = "lwjgl3" } lwjgl3-openal = { module = "org.lwjgl:lwjgl-openal", version.ref = "lwjgl3" } -lwjgl3-opencl = { module = "org.lwjgl:lwjgl-opencl", version.ref = "lwjgl3" } lwjgl3-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl3" } lwjgl3-sdl = { module = "org.lwjgl:lwjgl-sdl", version.ref = "lwjgl3" } +lwjgl3-opengles = { module = "org.lwjgl:lwjgl-opengles", version.ref = "lwjgl3" } +lwjgl3-egl = { module = "org.lwjgl:lwjgl-egl", version.ref = "lwjgl3" } mokito-core = "org.mockito:mockito-core:5.23.0" mokito-junit-jupiter = "org.mockito:mockito-junit-jupiter:5.23.0" @@ -51,10 +70,32 @@ nifty-style-black = { module = "com.github.nifty-gui:nifty-style-black", spotbugs-gradle-plugin = "com.github.spotbugs.snom:spotbugs-gradle-plugin:6.4.8" vecmath = "javax.vecmath:vecmath:1.5.2" -stb-image = "org.ngengine:stb-image:2.30.4" +stb-image = "org.ngengine:stb-image:2.30.5" imagewebp = "org.ngengine:image-webp-decoder:1.3.0" +angle = { module = "org.ngengine:angle-natives", version.ref = "angle" } +libjglios-angle-ios = { module = "org.ngengine:libjglios-angle-ios", version.ref = "libjglios" } +libjglios-core-ios = { module = "org.ngengine:libjglios-core-ios", version.ref = "libjglios" } +libjglios-gles-ios = { module = "org.ngengine:libjglios-gles-ios", version.ref = "libjglios" } +libjglios-gradle-plugin = { module = "org.ngengine:libjglios-gradle-plugin", version.ref = "libjglios" } +libjglios-openal-ios = { module = "org.ngengine:libjglios-openal-ios", version.ref = "libjglios" } +libjglios-sdl3-ios = { module = "org.ngengine:libjglios-sdl3-ios", version.ref = "libjglios" } +saferalloc = { module = "org.ngengine:saferalloc", version.ref = "saferalloc" } +saferalloc-natives-linux-x8664 = { module = "org.ngengine:saferalloc-natives-linux-x86_64", version.ref = "saferalloc" } +saferalloc-natives-linux-aarch64 = { module = "org.ngengine:saferalloc-natives-linux-aarch64", version.ref = "saferalloc" } +saferalloc-natives-windows-x8664 = { module = "org.ngengine:saferalloc-natives-windows-x86_64", version.ref = "saferalloc" } +saferalloc-natives-windows-aarch64 = { module = "org.ngengine:saferalloc-natives-windows-aarch64", version.ref = "saferalloc" } +saferalloc-natives-macos-x8664 = { module = "org.ngengine:saferalloc-natives-macos-x86_64", version.ref = "saferalloc" } +saferalloc-natives-macos-aarch64 = { module = "org.ngengine:saferalloc-natives-macos-aarch64", version.ref = "saferalloc" } +saferalloc-natives-android = { module = "org.ngengine:saferalloc-natives-android", version.ref = "saferalloc" } +saferalloc-natives-ios = { module = "org.ngengine:saferalloc-natives-ios", version.ref = "saferalloc" } + +extent-reports = { module = "com.aventstack:extentreports", version = "5.1.2"} + +jackson = {module = "tools.jackson.core:jackson-databind", version ="3.1.3"} [bundles] +saferalloc = ["saferalloc", "saferalloc-natives-linux-x8664", "saferalloc-natives-linux-aarch64", "saferalloc-natives-windows-x8664", "saferalloc-natives-windows-aarch64", "saferalloc-natives-macos-x8664", "saferalloc-natives-macos-aarch64", "saferalloc-natives-android", "saferalloc-natives-ios"] [plugins] jacoco = { id = "jacoco", version.ref = "jacoco" } + diff --git a/javadoc-overview.html b/javadoc-overview.html index 9313bccb1c..83a0dc1466 100644 --- a/javadoc-overview.html +++ b/javadoc-overview.html @@ -12,7 +12,7 @@ in Java aimed at wide accessibility and quick deployment to desktop, web, and mobile platforms. -

Key Features

+

Key Features

  • Free, open-source software (under the New BSD license) – Use our free engine for commercial, educational, or hobby game development
  • Minimal adaptations for cross-compatibility – Create games that run on any OpenGL 2 and 3-ready device with the Java Virtual Machine – web, desktop, or mobile.
  • @@ -23,4 +23,3 @@

    Key Features

    - diff --git a/jme3-android-examples/build.gradle b/jme3-android-examples/build.gradle index a795b54e2d..91e8ecddbf 100644 --- a/jme3-android-examples/build.gradle +++ b/jme3-android-examples/build.gradle @@ -4,7 +4,7 @@ def examplesJar = project(':jme3-examples').tasks.named('jar') android { namespace "org.jmonkeyengine.jme3androidexamples" - compileSdk 34 + compileSdk gradle.ext.androidExamplesCompileSdk lint { // Fix nifty gui referencing "java.awt" package. @@ -43,26 +43,17 @@ android { srcDir '../jme3-testdata/src/main/resources' srcDir '../jme3-examples/src/main/resources' } - jniLibs { - srcDir '../build/native/openalsoft' - srcDir '../build/native/decode' - srcDir '../build/native/allocator' - } } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - testImplementation platform(libs.junit.bom) - testImplementation libs.junit.jupiter - testRuntimeOnly libs.junit.platform.launcher androidTestImplementation libs.junit4 androidTestImplementation libs.androidx.test.runner implementation libs.androidx.fragment implementation project(':jme3-core') implementation project(':jme3-android') - implementation project(':jme3-android-native') implementation project(':jme3-effects') implementation project(':jme3-jbullet') implementation project(':jme3-jogg') @@ -71,21 +62,13 @@ dependencies { implementation project(':jme3-plugins') implementation project(':jme3-plugins-json') implementation project(':jme3-plugins-json-gson') + implementation project(':jme3-saferallocator') implementation project(':jme3-terrain') implementation files(examplesJar.flatMap { it.archiveFile }) } tasks.named('preBuild') { dependsOn examplesJar - if (buildNativeProjects == "true") { - dependsOn ':jme3-android-native:updatePreCompiledOpenAlSoftLibs' - dependsOn ':jme3-android-native:updatePreCompiledLibs' - dependsOn ':jme3-android-native:updatePreCompiledLibsBufferAllocator' - } else if (skipPrebuildLibraries != "true") { - dependsOn ':jme3-android-native:copyPreCompiledOpenAlSoftLibs' - dependsOn ':jme3-android-native:copyPreCompiledLibs' - dependsOn ':jme3-android-native:copyPreCompiledLibsBufferAllocator' - } } tasks.register('installAndroidExamples', Exec) { @@ -123,13 +106,12 @@ tasks.register('runAndroidExamples', Exec) { def exampleClass = project.findProperty('example')?.toString()?.trim() if (exampleClass) { + def verboseLogging = project.findProperty('verboseLogging')?.toString()?.trim() ?: 'false' + args 'shell', 'am', 'start', '-n', 'org.jmonkeyengine.jme3androidexamples/.TestActivity', '--es', 'Selected_App_Class', exampleClass, - '--ez', 'Enable_Mouse_Events', 'true', - '--ez', 'Enable_Joystick_Events', 'false', - '--ez', 'Enable_Key_Events', 'true', - '--ez', 'Verbose_Logging', 'false' + '--ez', 'Verbose_Logging', verboseLogging } else { args 'shell', 'am', 'start', '-n', 'org.jmonkeyengine.jme3androidexamples/.MainActivity' diff --git a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java index b38bba76c5..c6e3bcc8c4 100644 --- a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java +++ b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java @@ -17,6 +17,7 @@ import com.jme3.scene.Mesh; import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Line; +import com.jme3.system.AppSettings; import com.jme3.texture.Texture; import com.jme3.util.IntMap; @@ -79,6 +80,12 @@ public class TestAndroidSensors extends SimpleApplication implements ActionListe // Make sure to set joystickEventsEnabled = true in MainActivity for Android + public static void configureSettings(AppSettings settings) { + settings.setUseJoysticks(true); + settings.setUseAndroidSensorJoystick(true); + settings.setVirtualJoystick(AppSettings.VIRTUAL_JOYSTICK_DISABLED); + } + private float toDegrees(float rad) { return rad * FastMath.RAD_TO_DEG; } @@ -311,4 +318,4 @@ public void onAnalog(String string, float value, float tpf) { } } -} \ No newline at end of file +} diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/JmeFragment.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/JmeFragment.java index 3a3e399930..85528b4eed 100644 --- a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/JmeFragment.java +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/JmeFragment.java @@ -12,9 +12,6 @@ */ public class JmeFragment extends AndroidHarnessFragment { private String appClass; - private boolean joystickEventsEnabled; - private boolean keyEventsEnabled = true; - private boolean mouseEventsEnabled = true; public JmeFragment() { finishOnAppStop = true; @@ -27,13 +24,8 @@ public void onCreate(Bundle savedInstanceState) { appClass = bundle.getString(MainActivity.SELECTED_APP_CLASS); // Log.d(this.getClass().getSimpleName(), "AppClass: " + appClass); - joystickEventsEnabled = bundle.getBoolean(MainActivity.ENABLE_JOYSTICK_EVENTS); -// Log.d(this.getClass().getSimpleName(), "JoystickEventsEnabled: " + joystickEventsEnabled); - keyEventsEnabled = bundle.getBoolean(MainActivity.ENABLE_KEY_EVENTS); -// Log.d(this.getClass().getSimpleName(), "KeyEventsEnabled: " + keyEventsEnabled); - mouseEventsEnabled = bundle.getBoolean(MainActivity.ENABLE_MOUSE_EVENTS); -// Log.d(this.getClass().getSimpleName(), "MouseEventsEnabled: " + mouseEventsEnabled); - boolean verboseLogging = bundle.getBoolean(MainActivity.VERBOSE_LOGGING); + boolean verboseLogging = bundle.getBoolean(MainActivity.VERBOSE_LOGGING, + MainActivity.DEFAULT_VERBOSE_LOGGING); // Log.d(this.getClass().getSimpleName(), "VerboseLogging: " + verboseLogging); if (verboseLogging) { // Set the default logging level (default=Level.INFO, Level.ALL=All Debug Info) @@ -49,21 +41,12 @@ public void onCreate(Bundle savedInstanceState) { @Override protected LegacyApplication createApplication() throws Exception { Class clazz = Class.forName(appClass); - return (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + LegacyApplication application = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + AppSettings settings = new AppSettings(true); + settings.setEmulateMouse(true); + settings.setEmulateKeyboard(true); + application.setSettings(settings); + return application; } - @Override - protected void configureSettings(AppSettings settings) { - settings.setEmulateMouse(mouseEventsEnabled); - settings.setUseJoysticks(joystickEventsEnabled); - settings.setEmulateKeyboard(keyEventsEnabled); - - settings.setBitsPerPixel(24); - settings.setAlphaBits(0); - settings.setGammaCorrection(true); - settings.setDepthBits(16); - settings.setSamples(4); - settings.setStencilBits(0); - settings.setFrameRate(-1); - } } diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/MainActivity.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/MainActivity.java index 960e53bc43..acfe4a89d4 100644 --- a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/MainActivity.java +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/MainActivity.java @@ -48,29 +48,12 @@ public class MainActivity extends Activity implements OnItemClickListener, View. */ public static final String SELECTED_LIST_POSITION = "Selected_List_Position"; - /** - * Static String to pass the key for the setting for enabling mouse events to the - * savedInstanceState Bundle. - */ - public static final String ENABLE_MOUSE_EVENTS = "Enable_Mouse_Events"; - - /** - * Static String to pass the key for the setting for enabling joystick events to the - * savedInstanceState Bundle. - */ - public static final String ENABLE_JOYSTICK_EVENTS = "Enable_Joystick_Events"; - - /** - * Static String to pass the key for the setting for enabling key events to the - * savedInstanceState Bundle. - */ - public static final String ENABLE_KEY_EVENTS = "Enable_Key_Events"; - /** * Static String to pass the key for the setting for verbose logging to the * savedInstanceState Bundle. */ public static final String VERBOSE_LOGGING = "Verbose_Logging"; + public static final boolean DEFAULT_VERBOSE_LOGGING = false; /* Fields to contain the current position and display contents of the spinner */ private int currentPosition = 0; @@ -93,10 +76,7 @@ public class MainActivity extends Activity implements OnItemClickListener, View. EditText editFilterText; /* Custom settings for the test app */ - private boolean enableMouseEvents = true; - private boolean enableJoystickEvents = false; - private boolean enableKeyEvents = true; - private boolean verboseLogging = false; + private boolean verboseLogging = DEFAULT_VERBOSE_LOGGING; /** @@ -113,10 +93,7 @@ public void onCreate(Bundle savedInstanceState) { ); currentPosition = savedInstanceState.getInt(SELECTED_LIST_POSITION, 0); currentSelection = savedInstanceState.getString(SELECTED_APP_CLASS); - enableMouseEvents = savedInstanceState.getBoolean(ENABLE_MOUSE_EVENTS, true); - enableJoystickEvents = savedInstanceState.getBoolean(ENABLE_JOYSTICK_EVENTS, false); - enableKeyEvents = savedInstanceState.getBoolean(ENABLE_KEY_EVENTS, true); - verboseLogging = savedInstanceState.getBoolean(VERBOSE_LOGGING, true); + verboseLogging = savedInstanceState.getBoolean(VERBOSE_LOGGING, DEFAULT_VERBOSE_LOGGING); } @@ -224,25 +201,12 @@ public void onClick(View view) { /* Get selected class, pack it in the intent and start the test app */ Log.d(TAG, "User selected OK for class: " + currentSelection); Intent intent = new Intent(this, TestActivity.class); -// intent.putExtra(SELECTED_APP_CLASS, currentSelection); -// intent.putExtra(ENABLE_MOUSE_EVENTS, enableMouseEvents); -// intent.putExtra(ENABLE_JOYSTICK_EVENTS, enableJoystickEvents); -// intent.putExtra(ENABLE_KEY_EVENTS, enableKeyEvents); Bundle args = new Bundle(); args.putString(MainActivity.SELECTED_APP_CLASS, currentSelection); // Log.d(this.getClass().getSimpleName(), "AppClass="+currentSelection); - args.putBoolean(MainActivity.ENABLE_MOUSE_EVENTS, enableMouseEvents); -// Log.d(TestActivity.class.getSimpleName(), "MouseEnabled="+enableMouseEvents); - - args.putBoolean(MainActivity.ENABLE_JOYSTICK_EVENTS, enableJoystickEvents); -// Log.d(TestActivity.class.getSimpleName(), "JoystickEnabled="+enableJoystickEvents); - - args.putBoolean(MainActivity.ENABLE_KEY_EVENTS, enableKeyEvents); -// Log.d(TestActivity.class.getSimpleName(), "KeyEnabled="+enableKeyEvents); - args.putBoolean(MainActivity.VERBOSE_LOGGING, verboseLogging); // Log.d(TestActivity.class.getSimpleName(), "VerboseLogging="+verboseLogging); @@ -329,9 +293,6 @@ public void onSaveInstanceState(Bundle savedInstanceState) { Log.d(TAG, "Saving selections in onSaveInstanceState: " + "position: " + currentPosition + ", " + "class: " + currentSelection + ", " - + "mouseEvents: " + enableMouseEvents + ", " - + "joystickEvents: " + enableJoystickEvents + ", " - + "keyEvents: " + enableKeyEvents + ", " + "VerboseLogging: " + verboseLogging + ", " ); // Save current selections to the savedInstanceState. @@ -339,9 +300,6 @@ public void onSaveInstanceState(Bundle savedInstanceState) { // killed and restarted. savedInstanceState.putString(SELECTED_APP_CLASS, currentSelection); savedInstanceState.putInt(SELECTED_LIST_POSITION, currentPosition); - savedInstanceState.putBoolean(ENABLE_MOUSE_EVENTS, enableMouseEvents); - savedInstanceState.putBoolean(ENABLE_JOYSTICK_EVENTS, enableJoystickEvents); - savedInstanceState.putBoolean(ENABLE_KEY_EVENTS, enableKeyEvents); savedInstanceState.putBoolean(VERBOSE_LOGGING, verboseLogging); } @@ -388,36 +346,6 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu (Menu menu) { MenuItem item; - item = menu.findItem(R.id.optionMouseEvents); - if (item != null) { - Log.d(TAG, "Found EnableMouseEvents menu item"); - if (enableMouseEvents) { - item.setTitle(R.string.strOptionDisableMouseEventsTitle); - } else { - item.setTitle(R.string.strOptionEnableMouseEventsTitle); - } - } - - item = menu.findItem(R.id.optionJoystickEvents); - if (item != null) { - Log.d(TAG, "Found EnableJoystickEvents menu item"); - if (enableJoystickEvents) { - item.setTitle(R.string.strOptionDisableJoystickEventsTitle); - } else { - item.setTitle(R.string.strOptionEnableJoystickEventsTitle); - } - } - - item = menu.findItem(R.id.optionKeyEvents); - if (item != null) { - Log.d(TAG, "Found EnableKeyEvents menu item"); - if (enableKeyEvents) { - item.setTitle(R.string.strOptionDisableKeyEventsTitle); - } else { - item.setTitle(R.string.strOptionEnableKeyEventsTitle); - } - } - item = menu.findItem(R.id.optionVerboseLogging); if (item != null) { Log.d(TAG, "Found EnableVerboseLogging menu item"); @@ -434,16 +362,7 @@ public boolean onPrepareOptionsMenu (Menu menu) { @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); - if (itemId == R.id.optionMouseEvents) { - enableMouseEvents = !enableMouseEvents; - Log.d(TAG, "enableMouseEvents set to: " + enableMouseEvents); - } else if (itemId == R.id.optionJoystickEvents) { - enableJoystickEvents = !enableJoystickEvents; - Log.d(TAG, "enableJoystickEvents set to: " + enableJoystickEvents); - } else if (itemId == R.id.optionKeyEvents) { - enableKeyEvents = !enableKeyEvents; - Log.d(TAG, "enableKeyEvents set to: " + enableKeyEvents); - } else if (itemId == R.id.optionVerboseLogging) { + if (itemId == R.id.optionVerboseLogging) { verboseLogging = !verboseLogging; Log.d(TAG, "verboseLogging set to: " + verboseLogging); } else { diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestActivity.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestActivity.java index 1251c94118..7473e1403b 100644 --- a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestActivity.java +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/TestActivity.java @@ -30,19 +30,8 @@ protected void onCreate(Bundle savedInstanceState) { args.putString(MainActivity.SELECTED_APP_CLASS, appClass); // Log.d(TestActivity.class.getSimpleName(), "AppClass="+appClass); - boolean mouseEnabled = bundle.getBoolean(MainActivity.ENABLE_MOUSE_EVENTS, true); - args.putBoolean(MainActivity.ENABLE_MOUSE_EVENTS, mouseEnabled); -// Log.d(TestActivity.class.getSimpleName(), "MouseEnabled="+mouseEnabled); - - boolean joystickEnabled = bundle.getBoolean(MainActivity.ENABLE_JOYSTICK_EVENTS, true); - args.putBoolean(MainActivity.ENABLE_JOYSTICK_EVENTS, joystickEnabled); -// Log.d(TestActivity.class.getSimpleName(), "JoystickEnabled="+joystickEnabled); - - boolean keyEnabled = bundle.getBoolean(MainActivity.ENABLE_KEY_EVENTS, true); - args.putBoolean(MainActivity.ENABLE_KEY_EVENTS, keyEnabled); -// Log.d(TestActivity.class.getSimpleName(), "KeyEnabled="+keyEnabled); - - boolean verboseLogging = bundle.getBoolean(MainActivity.VERBOSE_LOGGING, true); + boolean verboseLogging = bundle.getBoolean(MainActivity.VERBOSE_LOGGING, + MainActivity.DEFAULT_VERBOSE_LOGGING); args.putBoolean(MainActivity.VERBOSE_LOGGING, verboseLogging); // Log.d(TestActivity.class.getSimpleName(), "VerboseLogging="+verboseLogging); diff --git a/jme3-android-examples/src/main/res/menu/menu_items.xml b/jme3-android-examples/src/main/res/menu/menu_items.xml index 87c8cc6111..be951960bd 100644 --- a/jme3-android-examples/src/main/res/menu/menu_items.xml +++ b/jme3-android-examples/src/main/res/menu/menu_items.xml @@ -2,22 +2,6 @@ xmlns:tools="http://schemas.android.com/tools" tools:context="org.jmonkeyengine.jme3androidexamples.MainActivity"> - - - - Cancel - Enable Mouse Events - Disable Mouse Events - Enable Joystick Events - Disable Joystick Events - Enable Key Events - Disable Key Events Enable Verbose Logging Disable Verbose Logging diff --git a/jme3-android-examples/src/test/java/org/jmonkeyengine/jme3androidexamples/ExampleUnitTest.java b/jme3-android-examples/src/test/java/org/jmonkeyengine/jme3androidexamples/ExampleUnitTest.java deleted file mode 100644 index 0d59740d8f..0000000000 --- a/jme3-android-examples/src/test/java/org/jmonkeyengine/jme3androidexamples/ExampleUnitTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.jmonkeyengine.jme3androidexamples; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * To work on unit tests, switch the Test Artifact in the Build Variants view. - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/jme3-android-native/.gitignore b/jme3-android-native/.gitignore deleted file mode 100644 index a75c2dfd25..0000000000 --- a/jme3-android-native/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# The headers are autogenerated, and nobody should try to commit them. -src/native/headers diff --git a/jme3-android-native/TremorAndroid.zip b/jme3-android-native/TremorAndroid.zip deleted file mode 100644 index 85f4e734b4..0000000000 Binary files a/jme3-android-native/TremorAndroid.zip and /dev/null differ diff --git a/jme3-android-native/bufferallocator.gradle b/jme3-android-native/bufferallocator.gradle deleted file mode 100644 index d10335a7f2..0000000000 --- a/jme3-android-native/bufferallocator.gradle +++ /dev/null @@ -1,62 +0,0 @@ -// build file for native buffer allocator, created by pavl_g on 5/17/22. - -// directories for native source -String bufferAllocatorAndroidPath = 'src/native/jme_bufferallocator' -String bufferAllocatorHeaders = 'src/native/headers' - -//Pre-compiled libs directory -def rootPath = rootProject.projectDir.absolutePath -String bufferAllocatorPreCompiledLibsDir = - rootPath + File.separator + "build" + File.separator + 'native' + File.separator + 'android' + File.separator + 'allocator' - -// directories for build -String bufferAllocatorBuildDir = "$buildDir" + File.separator + "bufferallocator" -String bufferAllocatorJniDir = bufferAllocatorBuildDir + File.separator + "jni" -String bufferAllocatorHeadersBuildDir = bufferAllocatorJniDir + File.separator + "headers" -String bufferAllocatorBuildLibsDir = bufferAllocatorBuildDir + File.separator + "libs" - -// copy native src to build dir -task copyJmeBufferAllocator(type: Copy) { - from file(bufferAllocatorAndroidPath) - into file(bufferAllocatorJniDir) -} - -// copy native headers to build dir -task copyJmeHeadersBufferAllocator(type: Copy, dependsOn: copyJmeBufferAllocator) { - from file(bufferAllocatorHeaders) - into file(bufferAllocatorHeadersBuildDir) -} - -// compile and build copied natives in build dir -task buildBufferAllocatorNativeLib(type: Exec, dependsOn: [copyJmeBufferAllocator, copyJmeHeadersBufferAllocator]) { - workingDir bufferAllocatorBuildDir - executable rootProject.ndkCommandPath - args "-j" + Runtime.runtime.availableProcessors() -} - -task updatePreCompiledLibsBufferAllocator(type: Copy, dependsOn: buildBufferAllocatorNativeLib) { - from file(bufferAllocatorBuildLibsDir) - into file(bufferAllocatorPreCompiledLibsDir) -} - -// Copy pre-compiled libs to build directory (when not building new libs) -task copyPreCompiledLibsBufferAllocator(type: Copy) { - from file(bufferAllocatorPreCompiledLibsDir) - into file(bufferAllocatorBuildLibsDir) -} -if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { - copyPreCompiledLibsBufferAllocator.dependsOn(rootProject.extractPrebuiltNatives) -} - -// ndkExists is a boolean from the build.gradle in the root project -// buildNativeProjects is a string set to "true" -if (ndkExists && buildNativeProjects == "true") { - // build native libs and update stored pre-compiled libs to commit - compileJava.dependsOn { updatePreCompiledLibsBufferAllocator } -} else { - // use pre-compiled native libs (not building new ones) - compileJava.dependsOn { copyPreCompiledLibsBufferAllocator } -} - -// package the native object files inside the lib folder in a production jar -jar.into("lib") { from bufferAllocatorBuildLibsDir } diff --git a/jme3-android-native/build.gradle b/jme3-android-native/build.gradle deleted file mode 100644 index 5ec15daea1..0000000000 --- a/jme3-android-native/build.gradle +++ /dev/null @@ -1,35 +0,0 @@ -// Note: "common.gradle" in the root project contains additional initialization -// for this project. This initialization is applied in the "build.gradle" -// of the root project. - -sourceSets { - main { - java { - srcDir 'src/native' - } - } -} - -dependencies { - // TODO: Add dependencies here - // but note that JUnit should have already been added in parent.gradle. - // By default, only the Maven Central Repository is specified in - // parent.gradle. - // - // You can read more about how to add dependency here: - // http://www.gradle.org/docs/current/userguide/dependency_management.html#sec:how_to_declare_your_dependencies - api project(':jme3-android') -} - -ext { - // stores the native project classpath to be used in each native - // build to generate native header files - projectClassPath = configurations.runtimeClasspath.asFileTree.matching { - exclude ".gradle" - }.asPath -} - -// add each native lib build file -apply from: file('openalsoft.gradle') -apply from: file('decode.gradle') -apply from: file('bufferallocator.gradle') diff --git a/jme3-android-native/decode.gradle b/jme3-android-native/decode.gradle deleted file mode 100644 index d6a3842f02..0000000000 --- a/jme3-android-native/decode.gradle +++ /dev/null @@ -1,114 +0,0 @@ -String tremorZipFile = "TremorAndroid.zip" -String stbiUrl = 'https://raw.githubusercontent.com/jMonkeyEngine/stb/0224a44a10564a214595797b4c88323f79a5f934/stb_image.h' - -// Working directories for the ndk build. -String decodeBuildDir = "${buildDir}" + File.separator + 'decode' -String decodeClassesBuildDir = "${buildDir}" + File.separator + 'decode_classes' -String decodeBuildJniDir = decodeBuildDir + File.separator + 'jni' -String decodeBuildLibsDir = decodeBuildDir + File.separator + 'libs' - -// Pre-compiled libs directory -def rootPath = rootProject.projectDir.absolutePath -String decodePreCompiledLibsDir = rootPath + File.separator + 'build' + File.separator + 'native' + File.separator + 'android' + File.separator + 'decode' - -// jME Android Native source files path -String decodeSourceDir = 'src/native/jme_decode' -String jmeHeaders = 'src/native/headers' - -task downloadStbImage(type: MyDownload) { - sourceUrl = stbiUrl - target = file(decodeBuildDir + File.separator + 'stb_image.h') -} - -// Copy stb_image.h to the jni directory. -task copyStbiFiles(type: Copy) { - def sourceDir = file(decodeBuildDir + File.separator + 'stb_image.h') - def outputDir = file(decodeBuildJniDir + File.separator + "STBI") - from sourceDir - into outputDir -} -copyStbiFiles.dependsOn { - def stbiFile = file(decodeBuildDir + File.separator + 'stb_image.h') - if (!stbiFile.exists()) { - downloadStbImage - } -} - -// Copy libtremor source to the jni directory. -task copyTremorFiles(type: Copy) { - def zipFile = file(tremorZipFile) - def outputDir = file(decodeBuildJniDir + File.separator + "Tremor") - - from (zipTree(zipFile)) { - include '*.c' - include '*.h' - } - - into outputDir -} - -task copyJmeHeadersDecode(type: Copy) { - from file(jmeHeaders) - into file(decodeBuildJniDir + File.separator + "headers") -} - -// Copy jME Android native files to jni directory -task copySourceToBuild(type: Copy, dependsOn:[copyTremorFiles, copyStbiFiles, copyJmeHeadersDecode]) { - def sourceDir = file(decodeSourceDir) - def outputDir = file(decodeBuildJniDir) - - from sourceDir - into outputDir -} - -task buildNativeLib(type: Exec, dependsOn: copySourceToBuild) { - workingDir decodeBuildDir - executable rootProject.ndkCommandPath - args "-j" + Runtime.runtime.availableProcessors() -} - -task updatePreCompiledLibs(type: Copy, dependsOn: buildNativeLib) { - def sourceDir = new File(decodeBuildLibsDir) - def outputDir = new File(decodePreCompiledLibsDir) - - from sourceDir - into outputDir -} - -// Copy pre-compiled libs to build directory (when not building new libs) -task copyPreCompiledLibs(type: Copy) { - def sourceDir = file(decodePreCompiledLibsDir) - def outputDir = file(decodeBuildLibsDir) - - from sourceDir - into outputDir -} -if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { - copyPreCompiledLibs.dependsOn(rootProject.extractPrebuiltNatives) -} - -// ndkExists is a boolean from the build.gradle in the root project -// buildNativeProjects is a string set to "true" -if (ndkExists && buildNativeProjects == "true") { - // build native libs and update stored pre-compiled libs to commit - compileJava.dependsOn { updatePreCompiledLibs } -} else { - // use pre-compiled native libs (not building new ones) - compileJava.dependsOn { copyPreCompiledLibs } -} - -jar.into("lib") { from decodeBuildLibsDir } - -// Helper class to wrap ant download task -class MyDownload extends DefaultTask { - @Input - String sourceUrl - - @OutputFile - File target - - @TaskAction - void download() { - ant.get(src: sourceUrl, dest: target) - } -} diff --git a/jme3-android-native/openalsoft.gradle b/jme3-android-native/openalsoft.gradle deleted file mode 100644 index 2b91a3a038..0000000000 --- a/jme3-android-native/openalsoft.gradle +++ /dev/null @@ -1,231 +0,0 @@ -// OpenAL Soft r1.21.1 -// TODO: update URL to jMonkeyEngine fork once it's updated with latest kcat's changes -String openALSoftUrl = 'https://github.com/kcat/openal-soft/archive/1.24.3.zip' -String openALSoftZipFile = 'OpenALSoft.zip' - -// OpenAL Soft directory the download is extracted into -// Typically, the downloaded OpenAL Soft zip file will extract to a directory -// called "openal-soft" -String openALSoftFolder = 'openal-soft-1.24.3' - -//Working directories for the ndk build. -String openalsoftBuildDir = "${buildDir}" + File.separator + 'openalsoft' -String openalsoftClassesBuildDir = "${buildDir}" + File.separator + 'openalsoft_classes' -String openalsoftBuildJniDir = openalsoftBuildDir + File.separator + 'jni' -String openalsoftBuildLibsDir = openalsoftBuildDir + File.separator + 'libs' - -//Pre-compiled libs directory -def rootPath = rootProject.projectDir.absolutePath -String openalsoftPreCompiledLibsDir = rootPath + File.separator + 'build' + File.separator + 'native' + File.separator + 'android' + File.separator + 'openalsoft' - -// jME Android Native source files path -String openalsoftJmeAndroidPath = 'src/native/jme_openalsoft' -String jmeHeaders = 'src/native/headers' - -// Download external source files if not available -task downloadOpenALSoft(type: MyDownload) { - sourceUrl = openALSoftUrl - target = file(openalsoftBuildDir + File.separator + openALSoftZipFile) -} - -// Unzip external source files -task unzipOpenALSoft(type: Copy) { - def zipFile = file(openalsoftBuildDir + File.separator + openALSoftZipFile) - def outputDir = file(openalsoftBuildDir) - - from zipTree(zipFile) - into outputDir -} -unzipOpenALSoft.dependsOn { - def zipFilePath = openalsoftBuildDir + File.separator + openALSoftZipFile - def zipFile = new File(zipFilePath) -// println "zipFile path: " + zipFile.absolutePath -// println "zipFile exists: " + zipFile.exists() - if (!zipFile.exists()) { - downloadOpenALSoft - } -} - -// Copy external source files to jni directory -task copyOpenALSoft(type: Copy) { - def sourceDir = file(openalsoftBuildDir + File.separator + openALSoftFolder) - def outputDir = file(openalsoftBuildJniDir) -// println "copyOpenALSoft sourceDir: " + sourceDir -// println "copyOpenALSoft outputDir: " + outputDir - - from sourceDir - into outputDir -} -copyOpenALSoft.dependsOn { - def openALSoftUnzipDir = new File(openalsoftBuildDir + File.separator + openALSoftFolder) -// println "openALSoftUnzipDir path: " + openALSoftUnzipDir.absolutePath -// println "openALSoftUnzipDir exists: " + openALSoftUnzipDir.isDirectory() - if (!openALSoftUnzipDir.isDirectory()) { - unzipOpenALSoft - } -} - -// Copy JME Headers to jni directory -task copyJmeHeadersOpenAL(type: Copy) { - from file(jmeHeaders) - into file(openalsoftBuildJniDir + File.separator + "headers") -} - -// Copy jME Android native files to jni directory -task copyJmeOpenALSoft(type: Copy, dependsOn: [copyOpenALSoft, copyJmeHeadersOpenAL]) { - def sourceDir = file(openalsoftJmeAndroidPath) - def outputDir = file(openalsoftBuildJniDir) -// println "copyJmeOpenALSoft sourceDir: " + sourceDir -// println "copyJmeOpenALSoft outputDir: " + outputDir - - from sourceDir - into outputDir -} -// rootProject.ndkCommandPath must be set to your ndk-build wrapper or full ndk path -def ndkPath = new File(rootProject.ndkCommandPath).getParent() -def cmakeToolchain = "${ndkPath}/build/cmake/android.toolchain.cmake" - -// 1) list your ABIs here -def openalAbis = [ - "arm64-v8a", - "x86_64" -] - -// 2) for each ABI, register a configure/build pair -openalAbis.each { abi -> - - // configure task - tasks.register("configureOpenAlSoft_${abi}", Exec) { - group = "external-native" - description = "Generate CMake build files for OpenAL-Soft [$abi]" - - workingDir file("$openalsoftBuildDir/$openALSoftFolder") - commandLine = [ - "cmake", - "-S", ".", - "-B", "cmake-build-${abi}", - "-G", "Unix Makefiles", // or Ninja - "-DCMAKE_TOOLCHAIN_FILE=${cmakeToolchain}", - "-DANDROID_PLATFORM=android-21", - "-DANDROID_ABI=${abi}", - "-DCMAKE_BUILD_TYPE=Release", - "-DALSOFT_UTILS=OFF", - "-DALSOFT_EXAMPLES=OFF", - "-DALSOFT_TESTS=OFF", - "-DALSOFT_BACKEND_OPENSL=ON", - '-DALSOFT_SHARED=OFF', - '-DBUILD_SHARED_LIBS=OFF', - '-DALSOFT_STATIC=ON', - '-DLIBTYPE=STATIC', - '-DCMAKE_CXX_FLAGS=-stdlib=libc++' - ] - - dependsOn copyOpenALSoft - } - - // build task - tasks.register("buildOpenAlSoft_${abi}", Exec) { - group = "external-native" - description = "Compile OpenAL-Soft into libopenalsoft.a for [$abi]" - - dependsOn "configureOpenAlSoft_${abi}" - workingDir file("$openalsoftBuildDir/$openALSoftFolder") - commandLine = [ - "cmake", - "--build", "cmake-build-${abi}", - "--config", "Release" - ] - } -} - -// 3) optional: aggregate tasks -tasks.register("configureOpenAlSoftAll") { - group = "external-native" - description = "Configure OpenAL-Soft for all ABIs" - dependsOn openalAbis.collect { "configureOpenAlSoft_${it}" } -} - -tasks.register("buildOpenAlSoftAll") { - group = "external-native" - description = "Build OpenAL-Soft for all ABIs" - dependsOn openalAbis.collect { "buildOpenAlSoft_${it}" } -} - -task buildOpenAlSoftNativeLib(type: Exec) { - group = "external-native" - description = "Runs ndk-build on your JNI code, linking in the prebuilt OpenAL-Soft .a files" - - dependsOn copyJmeOpenALSoft, buildOpenAlSoftAll - - // where your Android.mk lives - workingDir openalsoftBuildDir - - // call the NDK build script - executable rootProject.ndkCommandPath - - // pass in all ABIs (so ndk-build will rebuild your shared .so for each one), - // and pass in a custom var OPENALSOFT_BUILD_DIR so your Android.mk can find - // the cmake-build- folders. - args( - // let ndk-build know which ABIs to build for - "APP_ABI=arm64-v8a,x86_64", - - // pass in the path to the CMake output root - "OPENALSOFT_BUILD_ROOT=${openalsoftBuildDir}/${openALSoftFolder}", - - // parallel jobs - "-j${Runtime.runtime.availableProcessors()}" - ) -} - -task updatePreCompiledOpenAlSoftLibs(type: Copy, dependsOn: buildOpenAlSoftNativeLib) { - def sourceDir = new File(openalsoftBuildLibsDir) - def outputDir = new File(openalsoftPreCompiledLibsDir) -// println "updatePreCompiledOpenAlSoftLibs sourceDir: " + sourceDir -// println "updatePreCompiledOpenAlSoftLibs outputDir: " + outputDir - - from sourceDir - into outputDir -} - - -// Copy pre-compiled libs to build directory (when not building new libs) -task copyPreCompiledOpenAlSoftLibs(type: Copy) { - def sourceDir = file(openalsoftPreCompiledLibsDir) - def outputDir = file(openalsoftBuildLibsDir) -// println "copyStbiJmeFiles sourceDir: " + sourceDir -// println "copyStbiJmeFiles outputDir: " + outputDir - - from sourceDir - into outputDir -} -if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { - copyPreCompiledOpenAlSoftLibs.dependsOn(rootProject.extractPrebuiltNatives) -} - -// ndkExists is a boolean from the build.gradle in the root project -// buildNativeProjects is a string set to "true" -if (ndkExists && buildNativeProjects == "true") { - // build native libs and update stored pre-compiled libs to commit - compileJava.dependsOn { updatePreCompiledOpenAlSoftLibs } -} else { - // use pre-compiled native libs (not building new ones) - compileJava.dependsOn { copyPreCompiledOpenAlSoftLibs } -} - -jar.into("lib") { from openalsoftBuildLibsDir } - -// Helper class to wrap ant download task -class MyDownload extends DefaultTask { - @Input - String sourceUrl - - @OutputFile - File target - - @TaskAction - void download() { - ant.get(src: sourceUrl, dest: target) - } -} - diff --git a/jme3-android-native/src/native/jme_bufferallocator/Android.mk b/jme3-android-native/src/native/jme_bufferallocator/Android.mk deleted file mode 100644 index d735478fb6..0000000000 --- a/jme3-android-native/src/native/jme_bufferallocator/Android.mk +++ /dev/null @@ -1,50 +0,0 @@ -# -# Copyright (c) 2009-2022 jMonkeyEngine -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of 'jMonkeyEngine' nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -## -# Created by pavl_g on 5/17/22. -# For more : https://developer.android.com/ndk/guides/android_mk. -## -TARGET_PLATFORM := android-19 - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_LDLIBS := -llog -Wl,-s - -LOCAL_MODULE := bufferallocatorjme - -LOCAL_C_INCLUDES := $(LOCAL_PATH) - -LOCAL_SRC_FILES := com_jme3_util_AndroidNativeBufferAllocator.c - -include $(BUILD_SHARED_LIBRARY) diff --git a/jme3-android-native/src/native/jme_bufferallocator/Application.mk b/jme3-android-native/src/native/jme_bufferallocator/Application.mk deleted file mode 100644 index 6b07cf2873..0000000000 --- a/jme3-android-native/src/native/jme_bufferallocator/Application.mk +++ /dev/null @@ -1,40 +0,0 @@ -# -# Copyright (c) 2009-2022 jMonkeyEngine -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the name of 'jMonkeyEngine' nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -## -# Created by pavl_g on 5/17/22. -# For more : https://developer.android.com/ndk/guides/application_mk. -## -APP_PLATFORM := android-21 -# change this to 'debug' to see android logs -APP_OPTIM := release -APP_ABI := arm64-v8a,x86_64 -APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true diff --git a/jme3-android-native/src/native/jme_bufferallocator/com_jme3_util_AndroidNativeBufferAllocator.c b/jme3-android-native/src/native/jme_bufferallocator/com_jme3_util_AndroidNativeBufferAllocator.c deleted file mode 100644 index 4f5cd66d09..0000000000 --- a/jme3-android-native/src/native/jme_bufferallocator/com_jme3_util_AndroidNativeBufferAllocator.c +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2009-2022 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * @file com_jme3_util_AndroidNativeBufferAllocator.c - * @author pavl_g. - * @brief Creates and releases direct byte buffers for {com.jme3.util.AndroidNativeBufferAllocator}. - * @date 2022-05-17. - * @note - * Find more at : - * - JNI Direct byte buffers : https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#NewDirectByteBuffer. - * - JNI Get Direct byte buffer : https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetDirectBufferAddress. - * - GNU Basic allocation : https://www.gnu.org/software/libc/manual/html_node/Basic-Allocation.html. - * - GNU Allocating Cleared Space : https://www.gnu.org/software/libc/manual/html_node/Allocating-Cleared-Space.html. - * - GNU No Memory error : https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html#index-ENOMEM. - * - GNU Freeing memory : https://www.gnu.org/software/libc/manual/html_node/Freeing-after-Malloc.html. - * - Android logging : https://developer.android.com/ndk/reference/group/logging. - * - Android logging example : https://github.com/android/ndk-samples/blob/7a8ff4c5529fce6ec4c5796efbe773f5d0e569cc/hello-libs/app/src/main/cpp/hello-libs.cpp#L25-L26. - */ - -#include "headers/com_jme3_util_AndroidNativeBufferAllocator.h" -#include -#include -#include - -#ifndef NDEBUG -#include -#define LOG(LOG_ID, ...) __android_log_print(LOG_ID, \ - "AndroidNativeBufferAllocator", ##__VA_ARGS__); -#else -#define LOG(...) -#endif - -bool isDeviceOutOfMemory(void*); - -/** - * @brief Tests if the device is out of memory. - * - * @return true if the buffer to allocate is a NULL pointer and the errno is ENOMEM (Error-no-memory). - * @return false otherwise. - */ -bool isDeviceOutOfMemory(void* buffer) { - return buffer == NULL && errno == ENOMEM; -} - -JNIEXPORT void JNICALL Java_com_jme3_util_AndroidNativeBufferAllocator_releaseDirectByteBuffer -(JNIEnv * env, jobject object, jobject bufferObject) -{ - void* buffer = (*env)->GetDirectBufferAddress(env, bufferObject); - // deallocates the buffer pointer - free(buffer); - // log the destruction by mem address - LOG(ANDROID_LOG_INFO, "Buffer released (mem_address, size) -> (%p, %lu)", buffer, sizeof(buffer)); - // avoid accessing this memory space by resetting the memory address - buffer = NULL; - LOG(ANDROID_LOG_INFO, "Buffer mem_address formatted (mem_address, size) -> (%p, %u)", buffer, sizeof(buffer)); -} - -JNIEXPORT jobject JNICALL Java_com_jme3_util_AndroidNativeBufferAllocator_createDirectByteBuffer -(JNIEnv * env, jobject object, jlong size) -{ - void* buffer = calloc(1, size); - if (isDeviceOutOfMemory(buffer)) { - LOG(ANDROID_LOG_FATAL, "Device is out of memory exiting with %u", errno); - exit(errno); - } else { - LOG(ANDROID_LOG_INFO, "Buffer created successfully (mem_address, size) -> (%p %lli)", buffer, size); - } - return (*env)->NewDirectByteBuffer(env, buffer, size); -} \ No newline at end of file diff --git a/jme3-android-native/src/native/jme_decode/Android.mk b/jme3-android-native/src/native/jme_decode/Android.mk deleted file mode 100644 index 1f2a2dca2d..0000000000 --- a/jme3-android-native/src/native/jme_decode/Android.mk +++ /dev/null @@ -1,39 +0,0 @@ -TARGET_PLATFORM := android-9 - -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) - -LOCAL_MODULE := decodejme - -LOCAL_C_INCLUDES:= \ - $(LOCAL_PATH) \ - $(LOCAL_PATH)/Tremor - -LOCAL_CFLAGS := -std=gnu99 -DLIMIT_TO_64kHz -O0 -LOCAL_LDLIBS := -lz -llog -Wl,-s - -ifeq ($(TARGET_ARCH),arm) -LOCAL_CFLAGS+= -D_ARM_ASSEM_ -endif - -LOCAL_ARM_MODE := arm - -LOCAL_SRC_FILES := \ - Tremor/bitwise.c \ - Tremor/codebook.c \ - Tremor/dsp.c \ - Tremor/floor0.c \ - Tremor/floor1.c \ - Tremor/floor_lookup.c \ - Tremor/framing.c \ - Tremor/info.c \ - Tremor/mapping0.c \ - Tremor/mdct.c \ - Tremor/misc.c \ - Tremor/res012.c \ - Tremor/vorbisfile.c \ - com_jme3_audio_plugins_NativeVorbisFile.c \ - com_jme3_texture_plugins_AndroidNativeImageLoader.c - -include $(BUILD_SHARED_LIBRARY) diff --git a/jme3-android-native/src/native/jme_decode/Application.mk b/jme3-android-native/src/native/jme_decode/Application.mk deleted file mode 100644 index 7ba509726f..0000000000 --- a/jme3-android-native/src/native/jme_decode/Application.mk +++ /dev/null @@ -1,4 +0,0 @@ -APP_PLATFORM := android-21 -APP_OPTIM := release -APP_ABI := arm64-v8a,x86_64 -APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true diff --git a/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c b/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c deleted file mode 100644 index 0ee1caa1d0..0000000000 --- a/jme3-android-native/src/native/jme_decode/com_jme3_audio_plugins_NativeVorbisFile.c +++ /dev/null @@ -1,430 +0,0 @@ -#include -#include -#include -#include -#include - -#include "Tremor/ivorbisfile.h" - -#include "../headers/com_jme3_audio_plugins_NativeVorbisFile.h" - -#ifndef NDEBUG -#include -#include -#define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, \ - "NativeVorbisFile", fmt, ##__VA_ARGS__); -#else -// #error We are building in release mode, arent we? -#define LOGI(fmt, ...) -#endif - -typedef struct -{ - JNIEnv* env; - int fd; - ogg_int64_t start; - ogg_int64_t end; - ogg_int64_t current; -} -FileDescWrapper; - -// size_t read (int fd, void *buf, size_t nbytes) -static size_t FileDesc_read(void *ptr, size_t size, size_t nmemb, void *datasource) -{ - FileDescWrapper* wrapper = (FileDescWrapper*)datasource; - - if (size != 0 && nmemb > SIZE_MAX / size) - { - errno = EOVERFLOW; - return 0; - } - - size_t req_size = size * nmemb; - ogg_int64_t remaining = wrapper->end - wrapper->current; - if (remaining <= 0 || req_size == 0) - { - return 0; - } - size_t to_read = ((uint64_t) remaining < (uint64_t) req_size) ? (size_t) remaining : req_size; - - ssize_t total_read = read(wrapper->fd, ptr, to_read); - if (total_read < 0) - { - return 0; - } - - if (total_read > 0) - { - wrapper->current += total_read; - } - - LOGI("FD read(%zu) = %zd", to_read, total_read); - - return (size_t) total_read; -} - -// off64_t lseek64(int fd, off64_t offset, int whence); -static int FileDesc_seek(void *datasource, ogg_int64_t offset, int whence) -{ - FileDescWrapper* wrapper = (FileDescWrapper*)datasource; - - ogg_int64_t actual_offset; - - switch (whence) - { - case SEEK_SET: - // set the offset relative to start location. - actual_offset = wrapper->start + offset; - break; - case SEEK_END: - // seek from the end of the file. - // offset needs to be negative in this case. - actual_offset = wrapper->end + offset; - break; - case SEEK_CUR: - // seek relative to current position. - actual_offset = wrapper->current + offset; - break; - default: - // invalid whence. - errno = EINVAL; - return (off_t)-1; - } - - if (actual_offset < wrapper->start || - actual_offset > wrapper->end) - { - // actual offset should be within our acceptable range. - errno = EINVAL; - return (off_t)-1; - } - - off64_t result = lseek64(wrapper->fd, (off64_t) actual_offset, SEEK_SET); - - LOGI("FD seek(%lld) = %lld", (long long) actual_offset, (long long) result); - - if (result < 0) - { - // failed, errno should have been set by lseek. - return (off_t)-1; - } - - if (result != actual_offset) - { - // did not seek the expected amount. something wrong here. - errno = EINVAL; - return (off_t)-1; - } - - // seek succeeded. - // update current position - wrapper->current = actual_offset; - return 0; -} - -static int FileDesc_clear(void *datasource) -{ - FileDescWrapper* wrapper = (FileDescWrapper*)datasource; - - LOGI("Clear resources -- delegating closure to the Android ParcelFileDescriptor"); - - /* release the file descriptor wrapper buffer */ - free(wrapper); - - wrapper = NULL; - - return 0; -} - -static long FileDesc_tell(void *datasource) -{ - FileDescWrapper* wrapper = (FileDescWrapper*)datasource; - off64_t result = lseek64(wrapper->fd, 0, SEEK_CUR); - - LOGI("FD tell = %lld", (long long) result); - - if (wrapper->current != result) - { - // Not sure how to deal with this. - LOGI("PROBLEM: stored offset does not match actual: %lld != %lld", - (long long) wrapper->current, (long long) result); - } - - return (long) result; -} - -static ov_callbacks FileDescCallbacks = { - FileDesc_read, - FileDesc_seek, - FileDesc_clear, - FileDesc_tell -}; - -static void throwIOException(JNIEnv* env, const char* message) -{ - jclass ioExClazz = (*env)->FindClass(env, "java/io/IOException"); - (*env)->ThrowNew(env, ioExClazz, message); -} - -static void throwIndexOutOfBoundsException(JNIEnv* env, const char* message) -{ - jclass exClazz = (*env)->FindClass(env, "java/lang/IndexOutOfBoundsException"); - (*env)->ThrowNew(env, exClazz, message); -} - -static void throwNullPointerException(JNIEnv* env, const char* message) -{ - jclass exClazz = (*env)->FindClass(env, "java/lang/NullPointerException"); - (*env)->ThrowNew(env, exClazz, message); -} - -static jfieldID nvf_field_ovf; -static jfieldID nvf_field_seekable; -static jfieldID nvf_field_channels; -static jfieldID nvf_field_sampleRate; -static jfieldID nvf_field_bitRate; -static jfieldID nvf_field_totalBytes; -static jfieldID nvf_field_duration; - -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_preInit - (JNIEnv *env, jclass clazz) -{ - LOGI("preInit"); - - nvf_field_ovf = (*env)->GetFieldID(env, clazz, "ovf", "Ljava/nio/ByteBuffer;");; - nvf_field_seekable = (*env)->GetFieldID(env, clazz, "seekable", "Z"); - nvf_field_channels = (*env)->GetFieldID(env, clazz, "channels", "I"); - nvf_field_sampleRate = (*env)->GetFieldID(env, clazz, "sampleRate", "I"); - nvf_field_bitRate = (*env)->GetFieldID(env, clazz, "bitRate", "I"); - nvf_field_totalBytes = (*env)->GetFieldID(env, clazz, "totalBytes", "I"); - nvf_field_duration = (*env)->GetFieldID(env, clazz, "duration", "F"); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_init - (JNIEnv *env, jobject nvf, jint fd, jlong off, jlong len) -{ - LOGI("init: fd = %d, off = %lld, len = %lld", fd, off, len); - - if (off < 0 || len < 0 || off + len < off) - { - throwIOException(env, "Invalid file descriptor range"); - return; - } - - OggVorbis_File* ovf = (OggVorbis_File*) malloc(sizeof(OggVorbis_File)); - if (ovf == NULL) - { - throwIOException(env, "Failed to allocate OggVorbis_File"); - return; - } - - FileDescWrapper* wrapper = (FileDescWrapper*) malloc(sizeof(FileDescWrapper)); - if (wrapper == NULL) - { - free(ovf); - throwIOException(env, "Failed to allocate file descriptor wrapper"); - return; - } - wrapper->fd = fd; - wrapper->env = env; // NOTE: every java call has to update this - wrapper->start = off; - wrapper->current = off; - wrapper->end = off + len; - - int result = ov_open_callbacks((void*)wrapper, ovf, NULL, 0, FileDescCallbacks); - - if (result != 0) - { - LOGI("init fail"); - - free(ovf); - free(wrapper); - - char err[512]; - sprintf(err, "init failed: %d", result); - throwIOException(env, err); - - return; - } - - LOGI("init OK"); - jobject ovfBuf = (*env)->NewDirectByteBuffer(env, ovf, sizeof(OggVorbis_File)); - - vorbis_info* info = ov_info(ovf, -1); - - // total # of bytes = total samples * bytes per sample * channels - int total_samples = ov_pcm_total(ovf, -1); - jint total_bytes = total_samples * 2 * info->channels; - - jboolean seekable = ov_seekable(ovf) != 0; - - // duration = millis / 1000 - long timeMillis = ov_time_total(ovf, -1); - double timeSeconds = ((double)timeMillis) / 1000.0; - jfloat duration = (jfloat) timeSeconds; - - (*env)->SetObjectField(env, nvf, nvf_field_ovf, ovfBuf); - (*env)->SetBooleanField(env, nvf, nvf_field_seekable, seekable); - (*env)->SetIntField(env, nvf, nvf_field_channels, info->channels); - (*env)->SetIntField(env, nvf, nvf_field_sampleRate, info->rate); - (*env)->SetIntField(env, nvf, nvf_field_bitRate, info->bitrate_nominal); - (*env)->SetIntField(env, nvf, nvf_field_totalBytes, total_bytes); - (*env)->SetFloatField(env, nvf, nvf_field_duration, duration); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_seekTime - (JNIEnv *env, jobject nvf, jdouble time) -{ - jobject nvfBuf = (*env)->GetObjectField(env, nvf, nvf_field_ovf); - OggVorbis_File* ovf = (OggVorbis_File*) (*env)->GetDirectBufferAddress(env, nvfBuf); - FileDescWrapper* wrapper = (FileDescWrapper*) ovf->datasource; - wrapper->env = env; - - LOGI("ov_time_seek(%f)", (double)time); - - int result = ov_time_seek(ovf, (double)time); - - if (result != 0) - { - char err[512]; - sprintf(err, "ov_time_seek failed: %d", result); - throwIOException(env, err); - } -} - -JNIEXPORT jint JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readIntoArray - (JNIEnv *env, jobject nvf, jbyteArray buf, jint off, jint len) -{ - int bitstream = -1; - if (buf == NULL) - { - throwNullPointerException(env, "buffer"); - return 0; - } - - jsize arrayLen = (*env)->GetArrayLength(env, buf); - if (off < 0 || len < 0 || off > arrayLen || len > arrayLen - off) - { - throwIndexOutOfBoundsException(env, "Invalid offset/length for output buffer"); - return 0; - } - if (len == 0) - { - return 0; - } - - jobject nvfBuf = (*env)->GetObjectField(env, nvf, nvf_field_ovf); - OggVorbis_File* ovf = (OggVorbis_File*) (*env)->GetDirectBufferAddress(env, nvfBuf); - if (ovf == NULL) - { - throwIOException(env, "Vorbis file is closed or uninitialized"); - return 0; - } - FileDescWrapper* wrapper = (FileDescWrapper*) ovf->datasource; - wrapper->env = env; - - char nativeBuf[8192]; - jint toRead = len < (jint) sizeof(nativeBuf) ? len : (jint) sizeof(nativeBuf); - - long result = ov_read(ovf, (void*) nativeBuf, toRead, &bitstream); - - LOGI("ov_read(%d) = %ld", toRead, result); - - if (result == 0) - { - return (jint)-1; // EOF - } - else if (result < 0) - { - char err[512]; - sprintf(err, "ov_read failed: %ld", result); - throwIOException(env, err); - return 0; - } - - (*env)->SetByteArrayRegion(env, buf, off, (jsize) result, (const jbyte*) nativeBuf); - if ((*env)->ExceptionCheck(env)) - { - return 0; - } - - return (jint) result; -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readIntoBuffer - (JNIEnv *env, jobject nvf, jobject buf) -{ - int bitstream = -1; - jobject nvfBuf = (*env)->GetObjectField(env, nvf, nvf_field_ovf); - OggVorbis_File* ovf = (OggVorbis_File*) (*env)->GetDirectBufferAddress(env, nvfBuf); - if (ovf == NULL) - { - throwIOException(env, "Vorbis file is closed or uninitialized"); - return; - } - FileDescWrapper* wrapper = (FileDescWrapper*) ovf->datasource; - wrapper->env = env; - - char err[512]; - unsigned char* byteBufferPtr = (unsigned char*)(*env)->GetDirectBufferAddress(env, buf); - jlong byteBufferCap = (*env)->GetDirectBufferCapacity(env, buf); - if (byteBufferPtr == NULL || byteBufferCap < 0 || byteBufferCap > INT32_MAX) - { - throwIOException(env, "Output buffer must be a direct ByteBuffer with valid capacity"); - return; - } - - int offset = 0; - int remaining = (int) byteBufferCap; - - while (remaining > 0) - { - long result = ov_read(ovf, (void*)(byteBufferPtr + offset), remaining, &bitstream); - - LOGI("ov_read(%d, %d) = %ld", offset, remaining, result); - - if (result == 0) - { - sprintf(err, "premature EOF. expected %lld bytes, got %d.", - byteBufferCap, offset); - - throwIOException(env, err); - return; - } - else if (result < 0) - { - sprintf(err, "ov_read failed: %ld", result); - throwIOException(env, err); - return; - } - - remaining -= result; - offset += result; - } -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_clearResources - (JNIEnv *env, jobject nvf) -{ - LOGI("clearResources"); - - jobject ovfBuf = (*env)->GetObjectField(env, nvf, nvf_field_ovf); - if (ovfBuf == NULL) - { - return; - } - OggVorbis_File* ovf = (OggVorbis_File*) (*env)->GetDirectBufferAddress(env, ovfBuf); - if (ovf == NULL) - { - (*env)->SetObjectField(env, nvf, nvf_field_ovf, NULL); - return; - } - - /* release the ovf resources */ - ov_clear(ovf); - /* release the ovf buffer */ - free(ovf); - ovf = NULL; - /* destroy the java reference object */ - (*env)->SetObjectField(env, nvf, nvf_field_ovf, NULL); -} diff --git a/jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.c b/jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.c deleted file mode 100644 index 6d8084e6c0..0000000000 --- a/jme3-android-native/src/native/jme_decode/com_jme3_texture_plugins_AndroidNativeImageLoader.c +++ /dev/null @@ -1,303 +0,0 @@ -#include "../headers/com_jme3_texture_plugins_AndroidNativeImageLoader.h" -#include - -#ifndef NDEBUG -#include -#define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, \ - "NativeImageLoader", fmt, ##__VA_ARGS__); -#else -#define LOGI(fmt, ...) -#endif - -#define STB_IMAGE_IMPLEMENTATION -#define STBI_NO_STDIO -#define STBI_NO_HDR -#include "STBI/stb_image.h" - -typedef struct -{ - JNIEnv* env; - jbyteArray tmp; - int tmpSize; - jobject isObject; - jmethodID isReadMethod; - jmethodID isSkipMethod; - int isEOF; - char* errorMsg; -} -JavaInputStreamWrapper; - -static void throwIOException(JNIEnv* env, const char* message) -{ - jclass ioExClazz = (*env)->FindClass(env, "java/io/IOException"); - (*env)->ThrowNew(env, ioExClazz, message); -} - -static int InputStream_read(void *user, char *nativeData, int nativeSize) { - JavaInputStreamWrapper* wrapper = (JavaInputStreamWrapper*) user; - JNIEnv* env = wrapper->env; - - if (nativeSize <= 0) - { - wrapper->isEOF = 1; - wrapper->errorMsg = "read() requested negative or zero size"; - return 0; - } - - jbyteArray tmp = wrapper->tmp; - jint tmpSize = wrapper->tmpSize; - jint remaining = nativeSize; - jint offset = 0; - - while (offset < nativeSize) - { - // Read data into Java array. - jint toRead = tmpSize < remaining ? tmpSize : remaining; - jint read = (*env)->CallIntMethod(env, wrapper->isObject, - wrapper->isReadMethod, - tmp, (jint)0, (jint)toRead); - - // Check IOException - if ((*env)->ExceptionCheck(env)) - { - wrapper->isEOF = 1; - wrapper->errorMsg = NULL; - return 0; - } - - LOGI("InputStream->read(tmp, 0, %d) = %d", toRead, read); - - // Read -1 bytes = EOF. - if (read < 0) - { - wrapper->isEOF = 1; - wrapper->errorMsg = NULL; - break; - } - else if (read == 0) - { - // Read 0 bytes, give it another try. - continue; - } - - // Read 1 byte or more. - - LOGI("memcpy(native[%d], java, %d)", offset, read); - - // Copy contents of Java array to native array. - jbyte* nativeTmp = (*env)->GetPrimitiveArrayCritical(env, tmp, 0); - - if (nativeTmp == NULL) - { - wrapper->isEOF = 1; - wrapper->errorMsg = "Failed to acquire Java array contents"; - return 0; - } - - memcpy(&nativeData[offset], nativeTmp, read); - - (*env)->ReleasePrimitiveArrayCritical(env, tmp, nativeTmp, 0); - - offset += read; - remaining -= read; - - assert(remaining >= 0); - assert(offset <= nativeSize); - } - - return offset; -} - -static void InputStream_skip(void *user, int n) { - JavaInputStreamWrapper* wrapper = (JavaInputStreamWrapper*) user; - JNIEnv* env = wrapper->env; - - if (n < 0) - { - wrapper->isEOF = 1; - wrapper->errorMsg = "Negative seek attempt detected"; - return; - } - else if (n == 0) - { - return; - } - - // InputStream.skip(n); - jlong result = (*env)->CallLongMethod(env, wrapper->isObject, - wrapper->isSkipMethod, (jlong)n); - - LOGI("InputStream->skip(%lld) = %lld", (jlong)n, result); - - // IOException - if ((*env)->ExceptionCheck(env)) - { - wrapper->isEOF = 1; - wrapper->errorMsg = NULL; - } - else if ((int)result != n) - { - wrapper->isEOF = 1; - wrapper->errorMsg = "Could not skip requested number of bytes"; - } -} - -static int InputStream_eof(void *user) { - JavaInputStreamWrapper* wrapper = (JavaInputStreamWrapper*) user; - LOGI("InputStream->eof() = %s", wrapper->isEOF ? "true" : "false"); - return wrapper->isEOF; -} - -static stbi_io_callbacks JavaInputStreamCallbacks ={ - InputStream_read, - InputStream_skip, - InputStream_eof, -}; - -static JavaInputStreamWrapper createInputStreamWrapper(JNIEnv* env, jobject is, jbyteArray tmpArray) -{ - JavaInputStreamWrapper wrapper; - jclass inputStreamClass = (*env)->FindClass(env, "java/io/InputStream"); - - wrapper.env = env; - wrapper.isObject = is; - wrapper.isEOF = 0; - wrapper.errorMsg = NULL; - wrapper.isReadMethod = (*env)->GetMethodID(env, inputStreamClass, "read", "([BII)I"); - wrapper.isSkipMethod = (*env)->GetMethodID(env, inputStreamClass, "skip", "(J)J"); - wrapper.tmp = (jbyteArray) tmpArray; - wrapper.tmpSize = (*env)->GetArrayLength(env, tmpArray); - - return wrapper; -} - -static jobject createJmeImage(JNIEnv* env, int width, int height, int comps, char* data) -{ - // Convert # of components to jME format. - jclass formatClass = (*env)->FindClass(env, "com/jme3/texture/Image$Format"); - jfieldID formatFieldID; - - switch (comps) - { - case 1: - formatFieldID = (*env)->GetStaticFieldID(env, formatClass, - "Luminance8", "Lcom/jme3/texture/Image$Format;"); - break; - case 2: - formatFieldID = (*env)->GetStaticFieldID(env, formatClass, - "Luminance8Alpha8", "Lcom/jme3/texture/Image$Format;"); - break; - case 3: - formatFieldID = (*env)->GetStaticFieldID(env, formatClass, - "RGB8", "Lcom/jme3/texture/Image$Format;"); - break; - case 4: - formatFieldID = (*env)->GetStaticFieldID(env, formatClass, - "RGBA8", "Lcom/jme3/texture/Image$Format;"); - break; - default: - throwIOException(env, "Unrecognized number of components"); - return NULL; - } - - jobject formatVal = (*env)->GetStaticObjectField(env, formatClass, formatFieldID); - - // Get colorspace sRGB - jclass colorSpaceClass = (*env)->FindClass(env, "com/jme3/texture/image/ColorSpace"); - jfieldID sRGBFieldID = (*env)->GetStaticFieldID(env, colorSpaceClass, - "sRGB", "Lcom/jme3/texture/image/ColorSpace;"); - jobject sRGBVal = (*env)->GetStaticObjectField(env, colorSpaceClass, sRGBFieldID); - - int size = width * height * comps; - - // Stick it in a ByteBuffer - jobject directBuffer = (*env)->NewDirectByteBuffer(env, data, size); - - if (directBuffer == NULL) - { - throwIOException(env, "Failed to allocate ByteBuffer"); - return NULL; - } - - // Create JME image. - jclass jmeImageClass = (*env)->FindClass(env, "com/jme3/texture/Image"); - - // Image(Format format, int width, int height, ByteBuffer data, ColorSpace colorSpace) - jmethodID newImageMethod = (*env)->GetMethodID(env, jmeImageClass, "", - "(Lcom/jme3/texture/Image$Format;IILjava/nio/ByteBuffer;Lcom/jme3/texture/image/ColorSpace;)V"); - - jobject jmeImage = (*env)->NewObject(env, jmeImageClass, newImageMethod, - formatVal, (jint)width, (jint)height, - directBuffer, sRGBVal); - - return jmeImage; -} - -static void flipImage(int scanline, int height, char* data) -{ - char tmp[scanline]; - - for (int y = 0; y < height / 2; y++) - { - int oppY = height - y - 1; - int yOff = y * scanline; - int oyOff = oppY * scanline; - // Copy scanline at Y to tmp - memcpy(tmp, &data[yOff], scanline); - // Copy data at opposite Y to Y - memcpy(&data[yOff], &data[oyOff], scanline); - // Copy tmp to opposite Y - memcpy(&data[oyOff], tmp, scanline); - } -} - -JNIEXPORT jobject JNICALL Java_com_jme3_texture_plugins_AndroidNativeImageLoader_load - (JNIEnv * env, jobject thisObj, jobject inputStream, jboolean flipY, jbyteArray tmpArray) -{ - JavaInputStreamWrapper wrapper = createInputStreamWrapper(env, inputStream, tmpArray); - stbi_uc* imageData; - int width, height, comps; - - LOGI("stbi_load_from_callbacks"); - - imageData = stbi_load_from_callbacks(&JavaInputStreamCallbacks, &wrapper, &width, &height, &comps, STBI_default); - - if ((*env)->ExceptionCheck(env)) - { - // IOException - goto problems; - } - else if (wrapper.errorMsg != NULL) - { - // Misc error - throwIOException(env, wrapper.errorMsg); - goto problems; - } - else if (imageData == NULL) - { - // STBI error - throwIOException(env, stbi_failure_reason()); - goto problems; - } - - // No IOExceptions or errors encountered. We have image data! - - // Maybe we need to flip it. - LOGI("Flipping image"); - if (flipY) - { - flipImage(width * comps, height, imageData); - } - - // Create the jME3 image. - LOGI("Creating jME3 image"); - return createJmeImage(env, width, height, comps, imageData); - -problems: - if (imageData != NULL) - { - stbi_image_free(imageData); - } - - return NULL; -} diff --git a/jme3-android-native/src/native/jme_openalsoft/Android.mk b/jme3-android-native/src/native/jme_openalsoft/Android.mk deleted file mode 100644 index d1f38c1864..0000000000 --- a/jme3-android-native/src/native/jme_openalsoft/Android.mk +++ /dev/null @@ -1,54 +0,0 @@ -# jni/Android.mk - -LOCAL_PATH := $(call my-dir) - -# require the path to cmake-build- -ifndef OPENALSOFT_BUILD_ROOT -$(error OPENALSOFT_BUILD_ROOT not set! pass it via ndk-build OPENALSOFT_BUILD_ROOT=/path/to/cmake-build-root) -endif - -# assemble the path to this ABI's .a -OPENAL_PREBUILT_DIR := $(OPENALSOFT_BUILD_ROOT)/cmake-build-$(TARGET_ARCH_ABI) - -# ----------------------------------------------------------------------------- -# 1) prebuilt static library -include $(CLEAR_VARS) -LOCAL_MODULE := openalsoft_prebuilt -LOCAL_SRC_FILES := $(OPENAL_PREBUILT_DIR)/libopenal.a -LOCAL_EXPORT_C_INCLUDES := $(OPENALSOFT_BUILD_ROOT)/include -include $(PREBUILT_STATIC_LIBRARY) - -# ----------------------------------------------------------------------------- -# 2) your JNI wrapper -include $(CLEAR_VARS) -LOCAL_MODULE := openalsoftjme -LOCAL_SRC_FILES := \ - com_jme3_audio_android_AndroidAL.c \ - com_jme3_audio_android_AndroidALC.c \ - com_jme3_audio_android_AndroidEFX.c - -LOCAL_C_INCLUDES += \ - $(LOCAL_PATH) \ - $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/alc \ - $(LOCAL_PATH)/common - -LOCAL_CPP_FEATURES := exceptions rtti -LOCAL_CFLAGS := -ffast-math \ - -DAL_ALEXT_PROTOTYPES \ - -fcommon \ - -O0 \ - -DRESTRICT="" - -LOCAL_LDLIBS := -lOpenSLES -llog -Wl,-s -lc++_static -lc++abi -ifeq ($(TARGET_ARCH_ABI),arm64-v8a) - LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384" -endif -ifeq ($(TARGET_ARCH_ABI),x86_64) - LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384" -endif -LOCAL_STATIC_LIBRARIES := openalsoft_prebuilt -# (or LOCAL_WHOLE_STATIC_LIBRARIES if you need every object pulled in) - -include $(BUILD_SHARED_LIBRARY) - diff --git a/jme3-android-native/src/native/jme_openalsoft/Application.mk b/jme3-android-native/src/native/jme_openalsoft/Application.mk deleted file mode 100644 index 32f4c893fd..0000000000 --- a/jme3-android-native/src/native/jme_openalsoft/Application.mk +++ /dev/null @@ -1,5 +0,0 @@ -APP_PLATFORM := android-21 -APP_OPTIM := release -APP_ABI := arm64-v8a,x86_64 -APP_STL := c++_static -APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true diff --git a/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidAL.c b/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidAL.c deleted file mode 100644 index 10ba99755b..0000000000 --- a/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidAL.c +++ /dev/null @@ -1,134 +0,0 @@ -#include "../headers/com_jme3_audio_android_AndroidAL.h" -#include "AL/al.h" -#include "AL/alext.h" - -JNIEXPORT jstring JNICALL Java_com_jme3_audio_android_AndroidAL_alGetString - (JNIEnv* env, jobject obj, jint param) -{ - return (*env)->NewStringUTF(env, alGetString(param)); -} - -JNIEXPORT jint JNICALL Java_com_jme3_audio_android_AndroidAL_alGenSources - (JNIEnv *env, jobject obj) -{ - ALuint source; - alGenSources(1, &source); - return source; -} - -JNIEXPORT jint JNICALL Java_com_jme3_audio_android_AndroidAL_alGetError - (JNIEnv *env, jobject obj) -{ - return alGetError(); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alDeleteSources - (JNIEnv* env, jobject obj, jint numSources, jobject intbufSources) -{ - ALuint* pIntBufSources = (ALuint*) (*env)->GetDirectBufferAddress(env, intbufSources); - alDeleteSources((ALsizei)numSources, pIntBufSources); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alGenBuffers - (JNIEnv* env, jobject obj, jint numBuffers, jobject intbufBuffers) -{ - ALuint* pIntBufBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, intbufBuffers); - alGenBuffers((ALsizei)numBuffers, pIntBufBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alDeleteBuffers - (JNIEnv* env, jobject obj, jint numBuffers, jobject intbufBuffers) -{ - ALuint* pIntBufBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, intbufBuffers); - alDeleteBuffers((ALsizei)numBuffers, pIntBufBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourceStop - (JNIEnv *env, jobject obj, jint source) -{ - alSourceStop((ALuint)source); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourcei - (JNIEnv *env, jobject obj, jint source, jint param, jint value) -{ - alSourcei((ALuint)source, (ALenum)param, (ALint)value); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alBufferData - (JNIEnv* env, jobject obj, jint buffer, jint format, jobject bufferData, jint bufferSize, jint frequency) -{ - ALuint* pBufferData = (ALuint*) (*env)->GetDirectBufferAddress(env, bufferData); - alBufferData((ALuint)buffer, (ALenum)format, pBufferData, (ALsizei)bufferSize, (ALsizei)frequency); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourcePlay - (JNIEnv *env, jobject obj, jint source) -{ - alSourcePlay((ALuint)source); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourcePause - (JNIEnv *env, jobject obj, jint source) -{ - alSourcePause((ALuint)source); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourcef - (JNIEnv *env, jobject obj, jint source, jint param, jfloat value) -{ - alSourcef((ALuint)source, (ALenum)param, (ALfloat)value); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSource3f - (JNIEnv *env, jobject obj, jint source, jint param, jfloat value1, jfloat value2, jfloat value3) -{ - alSource3f((ALuint)source, (ALenum)param, (ALfloat)value1, (ALfloat)value2, (ALfloat)value3); -} - -JNIEXPORT jint JNICALL Java_com_jme3_audio_android_AndroidAL_alGetSourcei - (JNIEnv *env, jobject obj, jint source, jint param) -{ - ALint result; - alGetSourcei((ALuint)source, (ALenum)param, &result); - return (jint)result; -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourceUnqueueBuffers - (JNIEnv* env, jobject obj, jint source, jint numBuffers, jobject buffers) -{ - ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffers); - alSourceUnqueueBuffers((ALuint)source, (ALsizei)numBuffers, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSourceQueueBuffers - (JNIEnv* env, jobject obj, jint source, jint numBuffers, jobject buffers) -{ - ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffers); - alSourceQueueBuffers((ALuint)source, (ALsizei)numBuffers, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alListener - (JNIEnv* env, jobject obj, jint param, jobject bufferData) -{ - ALfloat* pBufferData = (ALfloat*) (*env)->GetDirectBufferAddress(env, bufferData); - alListenerfv((ALenum)param, pBufferData); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alListenerf - (JNIEnv *env, jobject obj, jint param, jfloat value) -{ - alListenerf((ALenum)param, (ALfloat)value); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alListener3f - (JNIEnv *env, jobject obj, jint param, jfloat value1, jfloat value2, jfloat value3) -{ - alListener3f((ALenum)param, (ALfloat)value1, (ALfloat)value2, (ALfloat)value3); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidAL_alSource3i - (JNIEnv *env, jobject obj, jint source, jint param, jint value1, jint value2, jint value3) -{ - alSource3i((ALuint)source, (ALenum)param, (ALint)value1, (ALint)value2, (ALint)value3); -} diff --git a/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidALC.c b/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidALC.c deleted file mode 100644 index d1c1000372..0000000000 --- a/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidALC.c +++ /dev/null @@ -1,171 +0,0 @@ -#include "util.h" -#include "../headers/com_jme3_audio_android_AndroidALC.h" -#include "AL/alc.h" -#include "AL/alext.h" - -static jboolean created = JNI_FALSE; - -/* InitAL opens the default device and sets up a context using default - * attributes, making the program ready to call OpenAL functions. */ -static int InitAL() -{ - ALCdevice *device = NULL; - ALCcontext *ctx = NULL; - - /* Open and initialize a device with default settings */ - device = alcOpenDevice(NULL); - - if(device == NULL) - { - fprintf(stderr, "Could not open a device!\n"); - goto cleanup; - } - - ctx = alcCreateContext(device, NULL); - - if (ctx == NULL) - { - fprintf(stderr, "Could not create context!\n"); - goto cleanup; - } - - if (!alcMakeContextCurrent(ctx)) - { - fprintf(stderr, "Could not make context current!\n"); - goto cleanup; - } - - return 0; - -cleanup: - if (ctx != NULL) alcDestroyContext(ctx); - if (device != NULL) alcCloseDevice(device); - return 1; -} - -/* CloseAL closes the device belonging to the current context, and destroys the - * context. */ -static void CloseAL() -{ - ALCdevice *device; - ALCcontext *ctx; - - ctx = alcGetCurrentContext(); - - if (ctx == NULL) - { - return; - } - - device = alcGetContextsDevice(ctx); - - if (device == NULL) - { - return; - } - - if(!alcMakeContextCurrent(NULL)) { - return; - } - - alcDestroyContext(ctx); - alcCloseDevice(device); -} - -static ALCdevice* GetALCDevice() -{ - ALCcontext *ctx = alcGetCurrentContext(); - - if (ctx != NULL) - { - ALCdevice *device = alcGetContextsDevice(ctx); - - if (device != NULL) - { - return device; - } - } - - return NULL; -} - -JNIEXPORT jboolean JNICALL Java_com_jme3_audio_android_AndroidALC_isCreated - (JNIEnv* env, jobject obj) -{ - return created; -} - - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_createALC - (JNIEnv* env, jobject obj) -{ - created = (InitAL() == 0); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_destroyALC - (JNIEnv* env, jobject obj) -{ - CloseAL(); - created = JNI_FALSE; -} - -JNIEXPORT jstring JNICALL Java_com_jme3_audio_android_AndroidALC_alcGetString - (JNIEnv* env, jobject obj, jint param) -{ - ALCdevice* device = GetALCDevice(); - if (device == NULL) return NULL; - return (*env)->NewStringUTF(env, alcGetString(device, param)); -} - -JNIEXPORT jboolean JNICALL Java_com_jme3_audio_android_AndroidALC_alcIsExtensionPresent - (JNIEnv* env, jobject obj, jstring extension) -{ - ALCdevice* device = GetALCDevice(); - - if (device == NULL) return JNI_FALSE; - - const char* strExtension = (*env)->GetStringUTFChars(env, extension, NULL); - - if (strExtension == NULL) - { - return JNI_FALSE; - } - - jboolean result = alcIsExtensionPresent(device, strExtension); - - (*env)->ReleaseStringUTFChars(env, extension, strExtension); - - return result; -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_alcGetInteger - (JNIEnv* env, jobject obj, jint param, jobject buffer, jint bufferSize) -{ - ALCdevice* device = GetALCDevice(); - - if (device == NULL) return; - - ALCint* pBuffers = (ALCint*) (*env)->GetDirectBufferAddress(env, buffer); - - alcGetIntegerv(device, (ALCenum)param, (ALCsizei)bufferSize, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_alcDevicePauseSOFT - (JNIEnv* env, jobject obj) -{ - ALCdevice* device = GetALCDevice(); - - if (device == NULL) return; - - alcDevicePauseSOFT(device); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidALC_alcDeviceResumeSOFT - (JNIEnv* env, jobject obj) -{ - ALCdevice* device = GetALCDevice(); - - if (device == NULL) return; - - alcDeviceResumeSOFT(device); -} diff --git a/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidEFX.c b/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidEFX.c deleted file mode 100644 index 6d3b623db2..0000000000 --- a/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidEFX.c +++ /dev/null @@ -1,75 +0,0 @@ -#include "util.h" -#include "../headers/com_jme3_audio_android_AndroidEFX.h" -#include "AL/alext.h" - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alGenAuxiliaryEffectSlots - (JNIEnv* env, jobject obj, jint numSlots, jobject buffer) -{ - ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); - alGenAuxiliaryEffectSlots((ALsizei)numSlots, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alGenEffects - (JNIEnv* env, jobject obj, jint numEffects, jobject buffer) -{ - ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); - alGenEffects((ALsizei)numEffects, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alEffecti - (JNIEnv* env, jobject obj, jint effect, jint param, jint value) -{ - alEffecti((ALuint)effect, (ALenum)param, (ALint)value); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alAuxiliaryEffectSloti - (JNIEnv* env, jobject obj, jint effectSlot, jint param, jint value) -{ - alAuxiliaryEffectSloti((ALuint)effectSlot, (ALenum)param, (ALint)value); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alDeleteEffects - (JNIEnv* env, jobject obj, jint numEffects, jobject buffer) -{ - ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); - alDeleteEffects((ALsizei)numEffects, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alDeleteAuxiliaryEffectSlots - (JNIEnv* env, jobject obj, jint numEffectSlots, jobject buffer) -{ - ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); - alDeleteAuxiliaryEffectSlots((ALsizei)numEffectSlots, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alGenFilters - (JNIEnv* env, jobject obj, jint numFilters, jobject buffer) -{ - ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); - alGenFilters((ALsizei)numFilters, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alFilteri - (JNIEnv* env, jobject obj, jint filter, jint param, jint value) -{ - alFilteri((ALuint)filter, (ALenum)param, (ALint)value); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alFilterf - (JNIEnv* env, jobject obj, jint filter, jint param, jfloat value) -{ - alFilterf((ALuint)filter, (ALenum)param, (ALfloat)value); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alDeleteFilters - (JNIEnv* env, jobject obj, jint numFilters, jobject buffer) -{ - ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); - alDeleteFilters((ALsizei)numFilters, pBuffers); -} - -JNIEXPORT void JNICALL Java_com_jme3_audio_android_AndroidEFX_alEffectf - (JNIEnv* env, jobject obj, jint effect, jint param, jfloat value) -{ - alEffectf((ALuint)effect, (ALenum)param, (ALfloat)value); -} diff --git a/jme3-android-native/src/native/jme_openalsoft/config.h b/jme3-android-native/src/native/jme_openalsoft/config.h deleted file mode 100644 index e0f5bc73e4..0000000000 --- a/jme3-android-native/src/native/jme_openalsoft/config.h +++ /dev/null @@ -1,225 +0,0 @@ -/* API declaration export attribute */ -#define AL_API __attribute__((visibility("protected"))) -#define ALC_API __attribute__((visibility("protected"))) - -/* Define any available alignment declaration */ -#define ALIGN(x) __attribute__((aligned(x))) - -/* Define a built-in call indicating an aligned data pointer */ -#define ASSUME_ALIGNED(x, y) __builtin_assume_aligned(x, y) - -/* Define if HRTF data is embedded in the library */ -/* #undef ALSOFT_EMBED_HRTF_DATA */ - -/* Define if we have the sysconf function */ -#define HAVE_SYSCONF - -/* Define if we have the C11 aligned_alloc function */ -/* #undef HAVE_ALIGNED_ALLOC */ - -/* Define if we have the posix_memalign function */ -/* #undef HAVE_POSIX_MEMALIGN */ - -/* Define if we have the _aligned_malloc function */ -/* #undef HAVE__ALIGNED_MALLOC */ - -/* Define if we have the proc_pidpath function */ -/* #undef HAVE_PROC_PIDPATH */ - -/* Define if we have the getopt function */ -#define HAVE_GETOPT - -/* Define if we have SSE CPU extensions */ -/* #undef HAVE_SSE */ -/* #undef HAVE_SSE2 */ -/* #undef HAVE_SSE3 */ -/* #undef HAVE_SSE4_1 */ - -/* Define if we have ARM Neon CPU extensions */ -/* #undef HAVE_NEON */ - -/* Define if we have the ALSA backend */ -/* #undef HAVE_ALSA */ - -/* Define if we have the OSS backend */ -/* #undef HAVE_OSS */ - -/* Define if we have the Solaris backend */ -/* #undef HAVE_SOLARIS */ - -/* Define if we have the SndIO backend */ -/* #undef HAVE_SNDIO */ - -/* Define if we have the QSA backend */ -/* #undef HAVE_QSA */ - -/* Define if we have the WASAPI backend */ -/* #undef HAVE_WASAPI */ - -/* Define if we have the DSound backend */ -/* #undef HAVE_DSOUND */ - -/* Define if we have the Windows Multimedia backend */ -/* #undef HAVE_WINMM */ - -/* Define if we have the PortAudio backend */ -/* #undef HAVE_PORTAUDIO */ - -/* Define if we have the PulseAudio backend */ -/* #undef HAVE_PULSEAUDIO */ - -/* Define if we have the JACK backend */ -/* #undef HAVE_JACK */ - -/* Define if we have the CoreAudio backend */ -/* #undef HAVE_COREAUDIO */ - -/* Define if we have the OpenSL backend */ -#define HAVE_OPENSL - -/* Define if we have the Wave Writer backend */ -#define HAVE_WAVE - -/* Define if we have the SDL2 backend */ -/* #undef HAVE_SDL2 */ - -/* Define if we have the stat function */ -#define HAVE_STAT - -/* Define if we have the lrintf function */ -#define HAVE_LRINTF - -/* Define if we have the modff function */ -#define HAVE_MODFF - -/* Define if we have the log2f function */ -#define HAVE_LOG2F - -/* Define if we have the cbrtf function */ -#define HAVE_CBRTF - -/* Define if we have the copysignf function */ -#define HAVE_COPYSIGNF - -/* Define if we have the strtof function */ -/* #undef HAVE_STRTOF */ - -/* Define if we have the strnlen function */ -#define HAVE_STRNLEN - -/* Define if we have the __int64 type */ -/* #undef HAVE___INT64 */ - -/* Define to the size of a long int type */ -#define SIZEOF_LONG 4 - -/* Define to the size of a long long int type */ -#define SIZEOF_LONG_LONG 8 - -/* Define if we have C99 _Bool support */ -#define HAVE_C99_BOOL - -/* Define if we have C11 _Static_assert support */ -#define HAVE_C11_STATIC_ASSERT - -/* Define if we have C11 _Alignas support */ -#define HAVE_C11_ALIGNAS - -/* Define if we have C11 _Atomic support */ -#define HAVE_C11_ATOMIC - -/* Define if we have GCC's destructor attribute */ -#define HAVE_GCC_DESTRUCTOR - -/* Define if we have GCC's format attribute */ -#define HAVE_GCC_FORMAT - -/* Define if we have stdint.h */ -#define HAVE_STDINT_H - -/* Define if we have stdbool.h */ -#define HAVE_STDBOOL_H - -/* Define if we have stdalign.h */ -#define HAVE_STDALIGN_H - -/* Define if we have windows.h */ -/* #undef HAVE_WINDOWS_H */ - -/* Define if we have dlfcn.h */ -#define HAVE_DLFCN_H - -/* Define if we have pthread_np.h */ -/* #undef HAVE_PTHREAD_NP_H */ - -/* Define if we have malloc.h */ -#define HAVE_MALLOC_H - -/* Define if we have dirent.h */ -#define HAVE_DIRENT_H - -/* Define if we have strings.h */ -#define HAVE_STRINGS_H - -/* Define if we have cpuid.h */ -/* #undef HAVE_CPUID_H */ - -/* Define if we have intrin.h */ -/* #undef HAVE_INTRIN_H */ - -/* Define if we have sys/sysconf.h */ -#define HAVE_SYS_SYSCONF_H - -/* Define if we have guiddef.h */ -/* #undef HAVE_GUIDDEF_H */ - -/* Define if we have initguid.h */ -/* #undef HAVE_INITGUID_H */ - -/* Define if we have ieeefp.h */ -/* #undef HAVE_IEEEFP_H */ - -/* Define if we have float.h */ -#define HAVE_FLOAT_H - -/* Define if we have fenv.h */ -#define HAVE_FENV_H - -/* Define if we have GCC's __get_cpuid() */ -/* #undef HAVE_GCC_GET_CPUID */ - -/* Define if we have the __cpuid() intrinsic */ -/* #undef HAVE_CPUID_INTRINSIC */ - -/* Define if we have the _BitScanForward64() intrinsic */ -/* #undef HAVE_BITSCANFORWARD64_INTRINSIC */ - -/* Define if we have the _BitScanForward() intrinsic */ -/* #undef HAVE_BITSCANFORWARD_INTRINSIC */ - -/* Define if we have _controlfp() */ -/* #undef HAVE__CONTROLFP */ - -/* Define if we have __control87_2() */ -/* #undef HAVE___CONTROL87_2 */ - -/* Define if we have pthread_setschedparam() */ -#define HAVE_PTHREAD_SETSCHEDPARAM - -/* Define if we have pthread_setname_np() */ -#define HAVE_PTHREAD_SETNAME_NP - -/* Define if pthread_setname_np() only accepts one parameter */ -/* #undef PTHREAD_SETNAME_NP_ONE_PARAM */ - -/* Define if pthread_setname_np() accepts three parameters */ -/* #undef PTHREAD_SETNAME_NP_THREE_PARAMS */ - -/* Define if we have pthread_set_name_np() */ -/* #undef HAVE_PTHREAD_SET_NAME_NP */ - -/* Define if we have pthread_mutexattr_setkind_np() */ -/* #undef HAVE_PTHREAD_MUTEXATTR_SETKIND_NP */ - -/* Define if we have pthread_mutex_timedlock() */ -/* #undef HAVE_PTHREAD_MUTEX_TIMEDLOCK */ diff --git a/jme3-android-native/src/native/jme_openalsoft/util.h b/jme3-android-native/src/native/jme_openalsoft/util.h deleted file mode 100644 index 5322d5634a..0000000000 --- a/jme3-android-native/src/native/jme_openalsoft/util.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef JME_UTIL_H -#define JME_UTIL_H - -#include -#include - -#ifndef NDEBUG -#include -#define LOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, \ - "OpenALSoft", fmt, ##__VA_ARGS__); -#else -#define LOGI(fmt, ...) -#endif - -#endif \ No newline at end of file diff --git a/jme3-android-native/src/native/jme_openalsoft/version.h b/jme3-android-native/src/native/jme_openalsoft/version.h deleted file mode 100644 index 94ce874a45..0000000000 --- a/jme3-android-native/src/native/jme_openalsoft/version.h +++ /dev/null @@ -1,10 +0,0 @@ -/* Define to the library version */ -#define ALSOFT_VERSION "1.21.1" -#define ALSOFT_VERSION_NUM 1,21,1,0 - -/* Define the branch being built */ -#define ALSOFT_GIT_BRANCH "HEAD" - -/* Define the hash of the head commit */ -#define ALSOFT_GIT_COMMIT_HASH "ae4eacf1" - diff --git a/jme3-android/build.gradle b/jme3-android/build.gradle index 2bf4908a10..9ed242a3e0 100644 --- a/jme3-android/build.gradle +++ b/jme3-android/build.gradle @@ -1,5 +1,5 @@ sourceSets { - main { + androidxStubs { java { srcDir 'src/androidx-stubs/java' } @@ -10,15 +10,13 @@ dependencies { //added annotations used by JmeSurfaceView. compileOnly libs.androidx.annotation compileOnly libs.androidx.lifecycle.common + compileOnly sourceSets.androidxStubs.output + androidxStubsCompileOnly files(rootProject.file('lib/android.jar')) api project(':jme3-core') + implementation libs.jme3.android.natives compileOnly files(rootProject.file('lib/android.jar')) } -compileJava { - // The Android-Native Project requires the jni headers to be generated, so we do that here - options.compilerArgs += ["-h", "${project.rootDir}/jme3-android-native/src/native/headers"] -} - tasks.withType(Jar).configureEach { exclude('androidx/**') } diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java index d3ddab8d2f..68b25b29cd 100644 --- a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java @@ -42,7 +42,7 @@ import androidx.fragment.app.Fragment; import com.jme3.audio.AudioRenderer; import com.jme3.input.JoyInput; -import com.jme3.input.android.AndroidSensorJoyInput; +import com.jme3.input.android.AndroidJoyInput; import com.jme3.system.AppSettings; import com.jme3.system.SystemListener; import com.jme3.system.android.JmeAndroidSystem; @@ -66,6 +66,7 @@ */ public abstract class AndroidHarnessFragment extends Fragment implements SystemListener { private static final Logger logger = Logger.getLogger(AndroidHarnessFragment.class.getName()); + private static final String SAFER_BUFFER_ALLOCATOR_CLASS = "com.jme3.util.SaferBufferAllocator"; protected GLSurfaceView view; protected LegacyApplication app; @@ -90,18 +91,16 @@ public void onCreate(Bundle savedInstanceState) { logger.fine("onCreate"); super.onCreate(savedInstanceState); - System.setProperty( - BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION, - AndroidNativeBufferAllocator.class.getName()); + if (System.getProperty(BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION) == null) { + String allocator = isClassPresent(SAFER_BUFFER_ALLOCATOR_CLASS) + ? SAFER_BUFFER_ALLOCATOR_CLASS + : AndroidNativeBufferAllocator.class.getName(); + System.setProperty(BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION, allocator); + } try { app = createApplication(); - - AppSettings settings = createSettings(); - configureSettings(settings); - app.setSettings(settings); app.start(); - OGLESContext context = (OGLESContext) app.getContext(); context.setSystemListener(this); } catch (Exception exception) { @@ -117,25 +116,15 @@ public void onCreate(Bundle savedInstanceState) { */ protected abstract LegacyApplication createApplication() throws Exception; - /** - * Creates the default Android settings. Subclasses can override this when - * they need to replace the settings object rather than adjust it. - * - * @return default settings for Android - */ - protected AppSettings createSettings() { - AppSettings settings = new AppSettings(true); - settings.setAudioRenderer(AppSettings.ANDROID_OPENAL_SOFT); - return settings; + private static boolean isClassPresent(String className) { + try { + Class.forName(className, false, AndroidHarnessFragment.class.getClassLoader()); + return true; + } catch (Throwable ignored) { + return false; + } } - /** - * Customizes the settings before the application starts. - * - * @param settings the settings to customize - */ - protected void configureSettings(AppSettings settings) { - } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -249,6 +238,11 @@ public void reshape(int width, int height) { app.reshape(width, height); } + @Override + public void reshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) { + app.reshape(logicalWidth, logicalHeight, framebufferWidth, framebufferHeight); + } + @Override public void rescale(float x, float y) { app.rescale(x, y); @@ -288,8 +282,8 @@ public void gainFocus() { } JoyInput joyInput = app.getContext() != null ? app.getContext().getJoyInput() : null; - if (joyInput instanceof AndroidSensorJoyInput) { - ((AndroidSensorJoyInput) joyInput).resumeSensors(); + if (joyInput instanceof AndroidJoyInput) { + ((AndroidJoyInput) joyInput).resumeJoysticks(); } app.gainFocus(); @@ -314,8 +308,8 @@ public void loseFocus() { } JoyInput joyInput = app.getContext() != null ? app.getContext().getJoyInput() : null; - if (joyInput instanceof AndroidSensorJoyInput) { - ((AndroidSensorJoyInput) joyInput).pauseSensors(); + if (joyInput instanceof AndroidJoyInput) { + ((AndroidJoyInput) joyInput).pauseJoysticks(); } } } diff --git a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java b/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java deleted file mode 100644 index 4020295660..0000000000 --- a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisFile.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2009-2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.audio.plugins; - -import java.io.IOException; -import java.nio.ByteBuffer; - -/** - * Represents the android implementation for the native vorbis file decoder. - * This decoder initializes an OggVorbis_File from an already opened file designated by the {@link NativeVorbisFile#fd}. - * - * @author Kirill Vainer - * @author Modified by pavl_g - */ -public class NativeVorbisFile { - - public int fd; - public ByteBuffer ovf; - public boolean seekable; - public int channels; - public int sampleRate; - public int bitRate; - public int totalBytes; - public float duration; - - static { - System.loadLibrary("decodejme"); - preInit(); - } - - /** - * Initializes an ogg vorbis native file from a file descriptor [fd] of an already opened file. - * - * @param fd an integer representing the file descriptor - * @param offset an integer indicating the start of the buffer - * @param length an integer indicating the end of the buffer - * @throws IOException in cases of a failure to initialize the vorbis file - */ - public NativeVorbisFile(int fd, long offset, long length) throws IOException { - init(fd, offset, length); - } - - /** - * Seeks to a playback time relative to the decompressed pcm (Pulse-code modulation) stream. - * - * @param time the playback seek time - * @throws IOException if the seek is not successful - */ - public native void seekTime(double time) throws IOException; - - /** - * Reads the vorbis file into a primitive byte buffer [buf] with an [offset] indicating the start byte and a [length] indicating the end byte on the output buffer. - * - * @param buffer a primitive byte buffer to read the data into it - * @param offset an integer representing the offset or the start byte on the output buffer - * @param length an integer representing the end byte on the output buffer - * @return the number of the read bytes, (-1) if the reading has failed indicating an EOF, - * returns (0) if the reading has failed or the primitive [buffer] passed is null - * @throws IOException if the library has failed to read the file into the [out] buffer - * or if the java primitive byte array [buffer] is inaccessible - */ - public native int readIntoArray(byte[] buffer, int offset, int length) throws IOException; - - /** - * Reads the vorbis file into a direct {@link java.nio.ByteBuffer}, starting from offset [0] till the buffer end on the output buffer. - * - * @param out a reference to the output direct buffer - * @throws IOException if a premature EOF is encountered before reaching the end of the buffer - * or if the library has failed to read the file into the [out] buffer - */ - public native void readIntoBuffer(ByteBuffer out) throws IOException; - - /** - * Clears the native resources and destroys the buffer {@link NativeVorbisFile#ovf} reference. - */ - public native void clearResources(); - - /** - * Prepares the java fields for the native environment. - */ - private static native void preInit(); - - /** - * Initializes an ogg vorbis native file from a file descriptor [fd] of an already opened file. - * - * @param fd an integer representing the file descriptor - * @param offset an integer representing the start of the buffer - * @param length an integer representing the length of the buffer - * @throws IOException in cases of a failure to initialize the vorbis file - */ - private native void init(int fd, long offset, long length) throws IOException; -} diff --git a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java b/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java deleted file mode 100644 index 63517cd3d8..0000000000 --- a/jme3-android/src/main/java/com/jme3/audio/plugins/NativeVorbisLoader.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2009-2023 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.audio.plugins; - -import android.content.res.AssetFileDescriptor; -import com.jme3.asset.AssetInfo; -import com.jme3.asset.AssetLoader; -import com.jme3.asset.plugins.AndroidLocator; -import com.jme3.asset.plugins.AndroidLocator.AndroidAssetInfo; -import com.jme3.audio.AudioBuffer; -import com.jme3.audio.AudioKey; -import com.jme3.audio.AudioStream; -import com.jme3.audio.SeekableStream; -import com.jme3.util.BufferUtils; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -public class NativeVorbisLoader implements AssetLoader { - - private static class VorbisInputStream extends InputStream implements SeekableStream { - - private final AssetFileDescriptor afd; - private final NativeVorbisFile file; - - public VorbisInputStream(AssetFileDescriptor afd, NativeVorbisFile file) { - this.afd = afd; - this.file = file; - } - - @Override - public int read() throws IOException { - return 0; - } - - @Override - public int read(byte[] buf) throws IOException { - return file.readIntoArray(buf, 0, buf.length); - } - - @Override - public int read(byte[] buf, int off, int len) throws IOException { - return file.readIntoArray(buf, off, len); - } - - @Override - public long skip(long n) throws IOException { - throw new IOException("Not supported for audio streams"); - } - - @Override - public void setTime(float time) { - try { - file.seekTime(time); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void close() throws IOException { - file.clearResources(); - afd.close(); - } - } - - private static AudioBuffer loadBuffer(AssetInfo assetInfo) throws IOException { - AndroidAssetInfo aai = (AndroidAssetInfo) assetInfo; - AssetFileDescriptor afd = null; - NativeVorbisFile file = null; - try { - afd = aai.openFileDescriptor(); - int fd = afd.getParcelFileDescriptor().getFd(); - file = new NativeVorbisFile(fd, afd.getStartOffset(), afd.getLength()); - ByteBuffer data = BufferUtils.createByteBuffer(file.totalBytes); - file.readIntoBuffer(data); - AudioBuffer ab = new AudioBuffer(); - ab.setupFormat(file.channels, 16, file.sampleRate); - ab.updateData(data); - return ab; - } finally { - if (file != null) { - file.clearResources(); - } - if (afd != null) { - afd.close(); - } - } - } - - private static AudioStream loadStream(AssetInfo assetInfo) throws IOException { - AndroidAssetInfo aai = (AndroidAssetInfo) assetInfo; - AssetFileDescriptor afd = null; - NativeVorbisFile file = null; - boolean success = false; - - try { - afd = aai.openFileDescriptor(); - int fd = afd.getParcelFileDescriptor().getFd(); - file = new NativeVorbisFile(fd, afd.getStartOffset(), afd.getLength()); - - AudioStream stream = new AudioStream(); - stream.setupFormat(file.channels, 16, file.sampleRate); - stream.updateData(new VorbisInputStream(afd, file), file.duration); - - success = true; - - return stream; - } finally { - if (!success) { - if (file != null) { - file.clearResources(); - } - if (afd != null) { - afd.close(); - } - } - } - } - - @Override - public Object load(AssetInfo assetInfo) throws IOException { - AudioKey key = (AudioKey) assetInfo.getKey(); - if (!(assetInfo instanceof AndroidLocator.AndroidAssetInfo)) { - throw new UnsupportedOperationException("Cannot load audio files from classpath." + - "Place your audio files in " + - "Android's assets directory"); - } - - if (key.isStream()) { - return loadStream(assetInfo); - } else { - return loadBuffer(assetInfo); - } - } -} \ No newline at end of file diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java index fc05da90d6..6db3c95c6b 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java @@ -111,6 +111,7 @@ protected void addListeners(GLSurfaceView view) { public void loadSettings(AppSettings settings) { touchInput.loadSettings(settings); + joyInput.loadSettings(settings); } public TouchInput getTouchInput() { @@ -207,6 +208,10 @@ public boolean onTouch(View view, MotionEvent event) { // logger.log(Level.INFO, "onTouch source: {0}, isTouch: {1}", // new Object[]{source, isTouch}); + if (isTouch && joyInput != null && joyInput.onTouch(event)) { + return true; + } + if (isTouch && touchInput != null) { // send the event to the touch processor consumed = touchInput.onTouch(event); @@ -233,6 +238,10 @@ public boolean onKey(View view, int keyCode, KeyEvent event) { // logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}", // new Object[]{source, isTouch}); + if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD && joyInput != null) { + joyInput.onKeyboardInput(); + } + if (touchInput != null) { consumed = touchInput.onKey(event); } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java index bfa07941bb..355d3c55fa 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java @@ -179,6 +179,10 @@ public boolean onKey(View view, int keyCode, KeyEvent event) { boolean isUnknown = (source & android.view.InputDevice.SOURCE_UNKNOWN) == android.view.InputDevice.SOURCE_UNKNOWN; + if ((source & InputDevice.SOURCE_KEYBOARD) == InputDevice.SOURCE_KEYBOARD && joyInput != null) { + joyInput.onKeyboardInput(); + } + if (touchInput != null && (isTouch || (isUnknown && this.touchInput.isSimulateKeyboard()))) { // logger.log(Level.INFO, "onKey source: {0}, isTouch: {1}", // new Object[]{source, isTouch}); diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java index fbbdc8c831..ad6c1388ba 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java @@ -31,9 +31,8 @@ */ package com.jme3.input.android; -import android.content.Context; import android.opengl.GLSurfaceView; -import android.os.Vibrator; +import android.view.MotionEvent; import com.jme3.input.InputManager; import com.jme3.input.JoyInput; import com.jme3.input.Joystick; @@ -41,7 +40,9 @@ import com.jme3.input.event.InputEvent; import com.jme3.input.event.JoyAxisEvent; import com.jme3.input.event.JoyButtonEvent; +import com.jme3.input.virtual.VirtualJoystick; import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; @@ -49,15 +50,14 @@ import java.util.logging.Logger; /** - * Main class that manages various joystick devices. Joysticks can be many forms - * including a simulated joystick to communicate the device orientation as well - * as physical gamepads.
    + * Main class that manages joystick devices. Joysticks can be physical gamepads, + * the on-screen virtual joystick, or an explicitly-enabled sensor joystick.
    * This class manages all the joysticks and feeds the inputs from each back * to jME's InputManager. * - * This handler also supports the joystick.rumble(rumbleAmount) method. In this - * case, when joystick.rumble(rumbleAmount) is called, the Android device will vibrate - * if the device has a built-in vibrate motor. + * This handler also supports redirecting joystick.rumble(rumbleAmount) to the + * Android device vibrator if AppSettings#setOnDeviceJoystickRumble(boolean) is + * enabled and the device has a built-in vibrate motor. * * Because Android does not allow for the user to define the intensity of the * vibration, the rumble amount (ie strength) is converted into vibration pulses @@ -68,7 +68,7 @@ * * MainActivity needs the following line to enable Joysticks on Android platforms * joystickEventsEnabled = true; - * This is done to allow for battery conservation when sensor data or gamepads + * This is done to allow for battery conservation when sensor data or joysticks * are not required by the application. * * {@code @@ -80,7 +80,6 @@ */ public class AndroidJoyInput implements JoyInput { private static final Logger logger = Logger.getLogger(AndroidJoyInput.class.getName()); - public static boolean disableSensors = false; protected AndroidInputHandler inputHandler; protected List joystickList = new ArrayList<>(); @@ -92,33 +91,37 @@ public class AndroidJoyInput implements JoyInput { private RawInputListener listener = null; private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue<>(); private AndroidSensorJoyInput sensorJoyInput; - private Vibrator vibrator = null; - private boolean vibratorActive = false; - private long maxRumbleTime = 250; // 250ms + private boolean onDeviceJoystickRumble = false; + private String virtualJoystickMode = AppSettings.VIRTUAL_JOYSTICK_AUTO; + private String virtualJoystickDefaultLayout = AppSettings.VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC; + private boolean useJoysticks = true; + private boolean useAndroidSensorJoystick = false; + private boolean physicalJoystickAvailable = false; + private boolean keyboardSuppressedAutoJoystick = false; + private volatile VirtualJoystick virtualJoystick; + private GLSurfaceView view; public AndroidJoyInput(AndroidInputHandler inputHandler) { this.inputHandler = inputHandler; - sensorJoyInput = new AndroidSensorJoyInput(this); } public void setView(GLSurfaceView view) { - if (view == null) { - vibrator = null; - } else { - // Get instance of Vibrator from current Context - vibrator = (Vibrator) view.getContext().getSystemService(Context.VIBRATOR_SERVICE); - if (vibrator == null) { - logger.log(Level.FINE, "Vibrator Service not found."); - } - } - + this.view = view; if (sensorJoyInput != null) { sensorJoyInput.setView(view); } } public void loadSettings(AppSettings settings) { + onDeviceJoystickRumble = settings.isOnDeviceJoystickRumble(); + virtualJoystickMode = settings.getVirtualJoystickMode(); + virtualJoystickDefaultLayout = settings.getVirtualJoystickDefaultLayout(); + useJoysticks = settings.useJoysticks(); + useAndroidSensorJoystick = settings.useAndroidSensorJoystick(); + } + boolean isOnDeviceJoystickRumble() { + return onDeviceJoystickRumble; } public void addEvent(InputEvent event) { @@ -133,8 +136,11 @@ public void pauseJoysticks() { if (sensorJoyInput != null) { sensorJoyInput.pauseSensors(); } - if (vibrator != null && vibratorActive) { - vibrator.cancel(); + if (onDeviceJoystickRumble) { + JmeSystem.stopRumble(); + } + if (virtualJoystick != null) { + virtualJoystick.onPointerCancel(0L); } } @@ -147,7 +153,6 @@ public void resumeJoysticks() { if (sensorJoyInput != null) { sensorJoyInput.resumeSensors(); } - } @Override @@ -163,12 +168,11 @@ public boolean isInitialized() { @Override public void destroy() { initialized = false; - if (sensorJoyInput != null) { sensorJoyInput.destroy(); + sensorJoyInput = null; } - - setView(null); + view = null; } @Override @@ -182,28 +186,16 @@ public long getInputTimeNanos() { } @Override - public void setJoyRumble(int joyId, float amount) { - // convert amount to pulses since Android doesn't allow intensity - if (vibrator != null) { - final long rumbleOnDur = (long)(amount * maxRumbleTime); // ms to pulse vibration on - final long rumbleOffDur = maxRumbleTime - rumbleOnDur; // ms to delay between pulses - final long[] rumblePattern = { - 0, // start immediately - rumbleOnDur, // time to leave vibration on - rumbleOffDur // time to delay between vibrations - }; - final int rumbleRepeatFrom = 0; // index into rumble pattern to repeat from - -// logger.log(Level.FINE, "Rumble amount: {0}, rumbleOnDur: {1}, rumbleOffDur: {2}", -// new Object[]{amount, rumbleOnDur, rumbleOffDur}); - - if (rumbleOnDur > 0) { - vibrator.vibrate(rumblePattern, rumbleRepeatFrom); - vibratorActive = true; - } else { - vibrator.cancel(); - vibratorActive = false; - } + public void setJoyRumble(int joyId, float amountHigh, float amountLow, float duration) { + if (onDeviceJoystickRumble && JmeSystem.isDeviceRumbleSupported()) { + JmeSystem.rumble(amountHigh, amountLow, duration); + } + } + + @Override + public void stopJoyRumble(int joyId) { + if (onDeviceJoystickRumble && JmeSystem.isDeviceRumbleSupported()) { + JmeSystem.stopRumble(); } } @@ -212,12 +204,72 @@ public Joystick[] loadJoysticks(InputManager inputManager) { if (logger.isLoggable(Level.INFO)) { logger.log(Level.INFO, "loading joysticks for {0}", this.getClass().getName()); } - if (!disableSensors) { + joystickList.clear(); + if (useJoysticks && useAndroidSensorJoystick) { + if (sensorJoyInput == null) { + sensorJoyInput = new AndroidSensorJoyInput(this); + sensorJoyInput.setView(view); + } joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); } + physicalJoystickAvailable = false; + if (shouldCreateVirtualJoystick()) { + virtualJoystick = new VirtualJoystick(inputManager, this, joystickList.size()); + virtualJoystick.setLayout(VirtualJoystick.createLayout(virtualJoystickDefaultLayout)); + virtualJoystick.setEnabled(false); + updateVirtualJoystickAutoVisibility(); + joystickList.add(virtualJoystick); + } else { + virtualJoystick = null; + } return joystickList.toArray( new Joystick[joystickList.size()] ); } + public boolean onTouch(MotionEvent event) { + VirtualJoystick joystick = virtualJoystick; + if (joystick == null || inputHandler.getView() == null) { + return false; + } + + boolean consumed = false; + int action = event.getAction() & MotionEvent.ACTION_MASK; + int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + long time = event.getEventTime(); + + switch (action) { + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_DOWN: + consumed = joystick.onPointerDown(event.getPointerId(pointerIndex), + toJmeX(event.getX(pointerIndex)), toJmeY(event.getY(pointerIndex)), time); + break; + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_UP: + consumed = joystick.onPointerUp(event.getPointerId(pointerIndex), + toJmeX(event.getX(pointerIndex)), toJmeY(event.getY(pointerIndex)), time); + break; + case MotionEvent.ACTION_CANCEL: + consumed = joystick.onPointerCancel(time); + break; + case MotionEvent.ACTION_MOVE: + for (int i = 0; i < event.getPointerCount(); i++) { + consumed = joystick.onPointerMove(event.getPointerId(i), + toJmeX(event.getX(i)), toJmeY(event.getY(i)), time) || consumed; + } + break; + default: + break; + } + return consumed; + } + + public void onKeyboardInput() { + if (AppSettings.VIRTUAL_JOYSTICK_AUTO.equals(virtualJoystickMode)) { + keyboardSuppressedAutoJoystick = true; + updateVirtualJoystickAutoVisibility(); + } + } + @Override public void update() { if (sensorJoyInput != null) { @@ -235,7 +287,43 @@ public void update() { } } } + if (virtualJoystick != null) { + updateVirtualJoystickAutoVisibility(); + virtualJoystick.dispatchEvents(listener); + } + + } + private float toJmeX(float x) { + return inputHandler.touchInput.getJmeX(x); + } + + private float toJmeY(float y) { + return inputHandler.touchInput.invertY(inputHandler.touchInput.getJmeY(y)); + } + + protected void setPhysicalJoystickAvailable(boolean available) { + physicalJoystickAvailable = available; + updateVirtualJoystickAutoVisibility(); + } + + private boolean shouldCreateVirtualJoystick() { + return useJoysticks && !AppSettings.VIRTUAL_JOYSTICK_DISABLED.equals(virtualJoystickMode); + } + + private void updateVirtualJoystickAutoVisibility() { + if (virtualJoystick == null) { + return; + } + boolean wasEnabled = virtualJoystick.isEnabled(); + boolean active = AppSettings.VIRTUAL_JOYSTICK_ENABLED.equals(virtualJoystickMode) + || (AppSettings.VIRTUAL_JOYSTICK_AUTO.equals(virtualJoystickMode) + && !physicalJoystickAvailable + && !keyboardSuppressedAutoJoystick + && virtualJoystick.hasInputBindings()); + if (wasEnabled != active) { + virtualJoystick.setEnabled(active); + } } } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java index 050a00e292..f4a4625ed1 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java @@ -35,6 +35,7 @@ import android.view.MotionEvent; import com.jme3.input.InputManager; import com.jme3.input.Joystick; +import com.jme3.system.JmeSystem; import java.util.logging.Logger; /** @@ -89,14 +90,34 @@ public void destroy() { @Override public Joystick[] loadJoysticks(InputManager inputManager) { - // load the simulated joystick for device orientation + // load virtual joystick if enabled super.loadJoysticks(inputManager); // load physical gamepads/joysticks + int beforePhysicalJoysticks = joystickList.size(); joystickList.addAll(joystickJoyInput.loadJoysticks(joystickList.size(), inputManager)); + setPhysicalJoystickAvailable(joystickList.size() > beforePhysicalJoysticks); // return the list of joysticks back to InputManager return joystickList.toArray( new Joystick[joystickList.size()] ); } + @Override + public void setJoyRumble(int joyId, float amountHigh, float amountLow, float duration) { + if (isOnDeviceJoystickRumble() && JmeSystem.isDeviceRumbleSupported()) { + super.setJoyRumble(joyId, amountHigh, amountLow, duration); + } else { + joystickJoyInput.setJoyRumble(joyId, amountHigh, amountLow, duration); + } + } + + @Override + public void stopJoyRumble(int joyId) { + if (isOnDeviceJoystickRumble() && JmeSystem.isDeviceRumbleSupported()) { + super.stopJoyRumble(joyId); + } else { + joystickJoyInput.stopJoyRumble(joyId); + } + } + public boolean onGenericMotion(MotionEvent event) { return joystickJoyInput.onGenericMotion(event); } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java index 6011e9d0a7..0ba2579f9a 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java @@ -47,6 +47,7 @@ import com.jme3.input.JoystickCompatibilityMappings; import com.jme3.input.event.JoyAxisEvent; import com.jme3.input.event.JoyButtonEvent; +import com.jme3.system.android.AndroidHapticFeedback; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -96,7 +97,7 @@ public AndroidJoystickJoyInput14(AndroidJoyInput joyInput) { public void pauseJoysticks() { - + stopAllRumble(); } public void resumeJoysticks() { @@ -104,7 +105,7 @@ public void resumeJoysticks() { } public void destroy() { - + stopAllRumble(); } public List loadJoysticks(int joyId, InputManager inputManager) { @@ -245,6 +246,48 @@ public boolean onKey(KeyEvent event) { return consumed; } + boolean setJoyRumble(int joyId, float amount) { + AndroidJoystick joystick = getJoystick(joyId); + if (joystick == null || !joystick.isHapticFeedbackSupported()) { + return false; + } + joystick.rumble(amount); + return true; + } + + boolean setJoyRumble(int joyId, float amountHigh, float amountLow, float duration) { + AndroidJoystick joystick = getJoystick(joyId); + if (joystick == null || !joystick.isHapticFeedbackSupported()) { + return false; + } + joystick.rumble(amountHigh, amountLow, duration); + return true; + } + + boolean stopJoyRumble(int joyId) { + AndroidJoystick joystick = getJoystick(joyId); + if (joystick == null || !joystick.isHapticFeedbackSupported()) { + return false; + } + joystick.stopRumble(); + return true; + } + + private AndroidJoystick getJoystick(int joyId) { + for (AndroidJoystick joystick : joystickIndex.values()) { + if (joystick.getJoyId() == joyId) { + return joystick; + } + } + return null; + } + + private void stopAllRumble() { + for (AndroidJoystick joystick : joystickIndex.values()) { + joystick.stopRumble(); + } + } + protected class AndroidJoystick extends AbstractJoystick { private JoystickAxis nullAxis; @@ -278,6 +321,25 @@ protected Set getAndroidAxes() { return axisIndex.keySet(); } + @SuppressWarnings("deprecation") + private android.os.Vibrator getVibrator() { + return device.getVibrator(); + } + + private boolean isHapticFeedbackSupported() { + return AndroidHapticFeedback.isSupported(getVibrator()); + } + + @Override + public void rumble(float amountHigh, float amountLow, float duration) { + AndroidHapticFeedback.rumble(getVibrator(), amountHigh, amountLow, duration); + } + + @Override + public void stopRumble() { + AndroidHapticFeedback.stop(getVibrator()); + } + protected JoystickButton getButton(int keyCode) { return buttonIndex.get(keyCode); } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidMouseInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidMouseInput14.java index 859ef595cd..9e76171dfc 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidMouseInput14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidMouseInput14.java @@ -145,7 +145,9 @@ public void loadSettings(AppSettings settings) { if (inputHandler.getView().getWidth() != 0 && inputHandler.getView().getHeight() != 0) { scaleX = settings.getWidth() / (float)inputHandler.getView().getWidth(); scaleY = settings.getHeight() / (float)inputHandler.getView().getHeight(); - currentMouseState.setStartPosition(inputHandler.getView().getWidth()/2, inputHandler.getView().getHeight()/2); + currentMouseState.setStartPosition( + getJmeX(inputHandler.getView().getWidth() / 2f), + getJmeY(inputHandler.getView().getHeight() / 2f)); } if (logger.isLoggable(Level.FINE)) { diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java index 02d9110098..896520c32e 100644 --- a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java +++ b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java @@ -51,6 +51,11 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo { public void resetStats() { } + @Override + public boolean supportsGpuTimerQuery() { + return false; + } + private static int getLimitBytes(ByteBuffer buffer) { checkLimit(buffer); return buffer.limit(); @@ -76,6 +81,62 @@ private static int getLimitCount(Buffer buffer, int elementSize) { return buffer.limit() / elementSize; } + private static int getRemainingBytes(ByteBuffer buffer) { + checkLimit(buffer); + return buffer.remaining(); + } + + private static int getRemainingBytes(IntBuffer buffer) { + checkLimit(buffer); + return checkedLongToInt((long) buffer.remaining() * 4L, "buffer size"); + } + + private static int checkedLongToInt(long value, String name) { + if (value < 0 || value > Integer.MAX_VALUE) { + throw new RendererException(name + " exceeds the range supported by Android GLES bindings: " + value); + } + return (int) value; + } + + private static long getSyncHandle(Object sync) { + if (!(sync instanceof Long)) { + throw new IllegalArgumentException("Expected a sync object returned by glFenceSync"); + } + return (Long) sync; + } + + private static int getBooleanValueCount(int pname) { + if (pname == GLES20.GL_COLOR_WRITEMASK) { + return 4; + } + return 1; + } + + private static String joinShaderSource(String[] strings, IntBuffer lengths) { + StringBuilder builder = new StringBuilder(); + int lengthPosition = lengths == null ? 0 : lengths.position(); + + for (int i = 0; i < strings.length; i++) { + String source = strings[i]; + if (lengths != null) { + int length = lengths.get(lengthPosition + i); + if (length >= 0 && length < source.length()) { + builder.append(source, 0, length); + continue; + } + } + builder.append(source); + } + + return builder.toString(); + } + + private static void unmapBufferAfterRead(int target) { + if (!GLES30.glUnmapBuffer(target)) { + throw new RendererException("Mapped buffer data became corrupted while reading"); + } + } + private static void checkLimit(Buffer buffer) { if (buffer == null) { return; @@ -100,6 +161,9 @@ public void glAttachShader(int program, int shader) { @Override public void glBeginQuery(int target, int query) { + if (target == GL.GL_TIME_ELAPSED) { + throw new UnsupportedOperationException("64-bit GPU timer queries are not exposed by the Android GLES bindings"); + } GLES30.glBeginQuery(target, query); } @@ -140,32 +204,57 @@ public void glBufferData(int target, ByteBuffer data, int usage) { @Override public void glBufferData(int target, long dataSize, int usage) { - GLES20.glBufferData(target, (int) dataSize, null, usage); + GLES20.glBufferData(target, checkedLongToInt(dataSize, "data size"), null, usage); } @Override public void glBufferSubData(int target, long offset, FloatBuffer data) { - GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data); + GLES20.glBufferSubData(target, checkedLongToInt(offset, "offset"), getLimitBytes(data), data); } @Override public void glBufferSubData(int target, long offset, ShortBuffer data) { - GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data); + GLES20.glBufferSubData(target, checkedLongToInt(offset, "offset"), getLimitBytes(data), data); } @Override public void glBufferSubData(int target, long offset, ByteBuffer data) { - GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data); + GLES20.glBufferSubData(target, checkedLongToInt(offset, "offset"), getLimitBytes(data), data); } @Override public void glGetBufferSubData(int target, long offset, ByteBuffer data) { - throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData"); + int byteCount = getRemainingBytes(data); + Buffer mapped = GLES30.glMapBufferRange(target, checkedLongToInt(offset, "offset"), byteCount, GLES30.GL_MAP_READ_BIT); + if (!(mapped instanceof ByteBuffer)) { + throw new RendererException("Unable to map buffer for reading"); + } + + try { + ByteBuffer source = (ByteBuffer) mapped; + source.limit(byteCount); + data.duplicate().put(source); + } finally { + unmapBufferAfterRead(target); + } } @Override public void glGetBufferSubData(int target, long offset, IntBuffer data) { - throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData"); + int byteCount = getRemainingBytes(data); + Buffer mapped = GLES30.glMapBufferRange(target, checkedLongToInt(offset, "offset"), byteCount, GLES30.GL_MAP_READ_BIT); + if (!(mapped instanceof ByteBuffer)) { + throw new RendererException("Unable to map buffer for reading"); + } + + try { + ByteBuffer source = (ByteBuffer) mapped; + source.limit(byteCount); + source.order(data.order()); + data.duplicate().put(source.asIntBuffer()); + } finally { + unmapBufferAfterRead(target); + } } @Override @@ -272,7 +361,7 @@ public void glDrawArrays(int mode, int first, int count) { @Override public void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices) { - GLES20.glDrawElements(mode, count, type, (int)indices); + GLES30.glDrawRangeElements(mode, start, end, count, type, checkedLongToInt(indices, "indices offset")); } @Override @@ -287,6 +376,9 @@ public void glEnableVertexAttribArray(int index) { @Override public void glEndQuery(int target) { + if (target == GL.GL_TIME_ELAPSED) { + throw new UnsupportedOperationException("64-bit GPU timer queries are not exposed by the Android GLES bindings"); + } GLES30.glEndQuery(target); } @@ -314,8 +406,18 @@ public int glGetAttribLocation(int program, String name) { @Override public void glGetBoolean(int pname, ByteBuffer params) { - // GLES20.glGetBoolean(pname, params); - throw new UnsupportedOperationException("Today is not a good day for this"); + checkLimit(params); + int count = getBooleanValueCount(pname); + if (params.remaining() < count) { + throw new RendererException("Insufficient buffer space for boolean query result"); + } + boolean[] values = new boolean[count]; + GLES20.glGetBooleanv(pname, values, 0); + + ByteBuffer destination = params.duplicate(); + for (boolean value : values) { + destination.put((byte) (value ? GLES20.GL_TRUE : GLES20.GL_FALSE)); + } } @Override @@ -348,16 +450,13 @@ public String glGetProgramInfoLog(int program, int maxLength) { @Override public long glGetQueryObjectui64(int query, int pname) { - IntBuffer buff = IntBuffer.allocate(1); - //FIXME This is wrong IMO should be glGetQueryObjectui64v with a LongBuffer but it seems the API doesn't provide it. - GLES30.glGetQueryObjectuiv(query, pname, buff); - return buff.get(0); + throw new UnsupportedOperationException("64-bit query results are not exposed by the Android GLES bindings"); } @Override public int glGetQueryObjectiv(int query, int pname) { - IntBuffer buff = IntBuffer.allocate(1); - GLES30.glGetQueryiv(query, pname, buff); + IntBuffer buff = (IntBuffer)tmpBuff.clear(); + GLES30.glGetQueryObjectuiv(query, pname, buff); return buff.get(0); } @@ -419,10 +518,7 @@ public void glScissor(int x, int y, int width, int height) { @Override public void glShaderSource(int shader, String[] string, IntBuffer length) { - if (string.length != 1) { - throw new UnsupportedOperationException("Today is not a good day"); - } - GLES20.glShaderSource(shader, string[0]); + GLES20.glShaderSource(shader, joinShaderSource(string, length)); } @Override @@ -537,7 +633,7 @@ public void glUseProgram(int program) { @Override public void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) { - GLES20.glVertexAttribPointer(index, size, type, normalized, stride, (int)pointer); + GLES20.glVertexAttribPointer(index, size, type, normalized, stride, checkedLongToInt(pointer, "pointer offset")); } @Override @@ -557,7 +653,7 @@ public void glBufferData(int target, IntBuffer data, int usage) { @Override public void glBufferSubData(int target, long offset, IntBuffer data) { - GLES20.glBufferSubData(target, (int)offset, getLimitBytes(data), data); + GLES20.glBufferSubData(target, checkedLongToInt(offset, "offset"), getLimitBytes(data), data); } @Override @@ -572,12 +668,12 @@ public void glDrawBuffers(IntBuffer bufs) { @Override public void glDrawElementsInstancedARB(int mode, int indicesCount, int type, long indicesBufferOffset, int primcount) { - GLES30.glDrawElementsInstanced(mode, indicesCount, type, (int)indicesBufferOffset, primcount); + GLES30.glDrawElementsInstanced(mode, indicesCount, type, checkedLongToInt(indicesBufferOffset, "indices offset"), primcount); } @Override public void glGetMultisample(int pname, int index, FloatBuffer val) { - GLES31.glGetMultisamplefv(pname, index, val); + throw new UnsupportedOperationException("Multisample textures require OpenGL ES 3.1"); } @Override @@ -587,7 +683,7 @@ public void glRenderbufferStorageMultisampleEXT(int target, int samples, int int @Override public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedSampleLocations) { - GLES31.glTexStorage2DMultisample(target, samples, internalformat, width, height, fixedSampleLocations); + throw new UnsupportedOperationException("Multisample textures require OpenGL ES 3.1"); } @Override @@ -612,15 +708,12 @@ public void glUniformBlockBinding(int program, int uniformBlockIndex, int unifor @Override public int glGetProgramResourceIndex(int program, int programInterface, String name) { - return GLES31.glGetProgramResourceIndex(program, programInterface, name); + throw new UnsupportedOperationException("Shader storage buffer objects require OpenGL ES 3.1"); } @Override public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding) { - /* - * GLES 3.1 exposes shader storage block binding through GLSL layout(binding = N). - * Android's GLES31 Java bindings do not expose glShaderStorageBlockBinding. - */ + throw new UnsupportedOperationException("Shader storage buffer objects require OpenGL ES 3.1"); } @Override @@ -684,23 +777,22 @@ public void glRenderbufferStorageEXT(int param1, int param2, int param3, int par @Override public void glReadPixels(int x, int y, int width, int height, int format, int type, long offset) { - // TODO: no offset??? - GLES20.glReadPixels(x, y, width, height, format, type, null); + GLES30.glReadPixels(x, y, width, height, format, type, checkedLongToInt(offset, "offset")); } @Override public int glClientWaitSync(Object sync, int flags, long timeout) { - throw new UnsupportedOperationException("OpenGL ES 2 does not support sync fences"); + return GLES30.glClientWaitSync(getSyncHandle(sync), flags, timeout); } @Override public void glDeleteSync(Object sync) { - throw new UnsupportedOperationException("OpenGL ES 2 does not support sync fences"); + GLES30.glDeleteSync(getSyncHandle(sync)); } @Override public Object glFenceSync(int condition, int flags) { - throw new UnsupportedOperationException("OpenGL ES 2 does not support sync fences"); + return GLES30.glFenceSync(condition, flags); } @Override @@ -786,4 +878,9 @@ public void glGenVertexArrays(IntBuffer arrays) { } + @Override + public String glGetString(int name, int index) { + return GLES30.glGetStringi(name, index); + } + } diff --git a/jme3-android/src/main/java/com/jme3/system/android/AndroidHapticFeedback.java b/jme3-android/src/main/java/com/jme3/system/android/AndroidHapticFeedback.java new file mode 100644 index 0000000000..75debfc909 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/system/android/AndroidHapticFeedback.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.android; + +import com.jme3.math.FastMath; + +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.VibrationEffect; +import android.os.Vibrator; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * Android haptic helpers shared by device and input-device vibrators. + */ +public final class AndroidHapticFeedback { + + private static final long PULSE_CYCLE_MS = 250L; + private static final Handler MAIN_HANDLER = new Handler(Looper.getMainLooper()); + private static final Map pendingStops = new IdentityHashMap<>(); + + private AndroidHapticFeedback() { + } + + public static boolean isSupported(Vibrator vibrator) { + return vibrator != null && vibrator.hasVibrator(); + } + + @SuppressWarnings("deprecation") + public static void rumble(Vibrator vibrator, float amountHigh, float amountLow, float duration) { + if (!isSupported(vibrator)) { + return; + } + float amount = Math.max(FastMath.clamp(amountHigh, 0f, 1f), FastMath.clamp(amountLow, 0f, 1f)); + if (amount <= 0f || !(duration > 0f)) { + stop(vibrator); + return; + } + + try { + cancelPendingStop(vibrator); + if (duration == Float.POSITIVE_INFINITY) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && vibrator.hasAmplitudeControl()) { + int amplitude = Math.max(1, Math.round(amount * 255f)); + vibrator.vibrate(VibrationEffect.createWaveform( + new long[]{0, 1000}, + new int[]{0, amplitude}, + 0)); + } else { + long rumbleOnDur = Math.max(1L, Math.round(amount * 1000)); + long rumbleOffDur = Math.max(0L, 1000 - rumbleOnDur); + vibrator.vibrate(new long[]{0, rumbleOnDur, rumbleOffDur}, 0); + } + } else { + long durationMs = Math.max(1L, Math.round(duration * 1000f)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && vibrator.hasAmplitudeControl()) { + int amplitude = Math.max(1, Math.round(amount * 255f)); + vibrator.vibrate(VibrationEffect.createOneShot(durationMs, amplitude)); + } else { + vibrateWithoutAmplitudeControl(vibrator, amount, durationMs); + } + } + } catch (SecurityException ignored) { + // Applications without VIBRATE permission should degrade to no-op. + } + } + + @SuppressWarnings("deprecation") + private static void vibrateWithoutAmplitudeControl(Vibrator vibrator, float amount, long durationMs) { + if (amount >= 1f) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(durationMs, VibrationEffect.DEFAULT_AMPLITUDE)); + } else { + vibrator.vibrate(durationMs); + } + return; + } + + long[] rumblePattern = createRepeatingPulsePattern(amount); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createWaveform(rumblePattern, 0)); + } else { + vibrator.vibrate(rumblePattern, 0); + } + scheduleStop(vibrator, durationMs); + } + + private static long[] createRepeatingPulsePattern(float amount) { + long rumbleOnDur = Math.max(1L, Math.round(amount * PULSE_CYCLE_MS)); + long rumbleOffDur = Math.max(0L, PULSE_CYCLE_MS - rumbleOnDur); + return new long[]{0L, rumbleOnDur, rumbleOffDur}; + } + + private static void scheduleStop(final Vibrator vibrator, long durationMs) { + Runnable stopTask = new Runnable() { + @Override + public void run() { + synchronized (pendingStops) { + if (pendingStops.get(vibrator) != this) { + return; + } + pendingStops.remove(vibrator); + } + stop(vibrator); + } + }; + + synchronized (pendingStops) { + Runnable previousStop = pendingStops.put(vibrator, stopTask); + if (previousStop != null) { + MAIN_HANDLER.removeCallbacks(previousStop); + } + } + MAIN_HANDLER.postDelayed(stopTask, durationMs); + } + + private static void cancelPendingStop(Vibrator vibrator) { + synchronized (pendingStops) { + Runnable pendingStop = pendingStops.remove(vibrator); + if (pendingStop != null) { + MAIN_HANDLER.removeCallbacks(pendingStop); + } + } + } + + public static void stop(Vibrator vibrator) { + if (!isSupported(vibrator)) { + return; + } + try { + cancelPendingStop(vibrator); + vibrator.cancel(); + } catch (SecurityException ignored) { + // Applications without VIBRATE permission should degrade to no-op. + } + } +} diff --git a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java index 0ba2d85e0c..c86fc24005 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java +++ b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java @@ -5,6 +5,7 @@ import android.graphics.Bitmap; import android.os.Build; import android.os.Environment; +import android.os.Vibrator; import android.view.View; import android.view.inputmethod.InputMethodManager; import com.jme3.audio.AudioRenderer; @@ -32,7 +33,7 @@ public class JmeAndroidSystem extends JmeSystemDelegate { private static View view; - private static String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; + private static Vibrator vibrator; static { try { @@ -52,6 +53,18 @@ public JmeAndroidSystem(){ }); }); } + + /** + * Returns the default Android audio renderer type. + * + * @return the default Android audio renderer type + * @deprecated Use {@link AppSettings#getAudioRenderer()} and + * {@link AppSettings#OPENAL} instead. + */ + @Deprecated + public String getAudioRendererType() { + return AppSettings.OPENAL; + } @Override public URL getPlatformAssetConfigURL() { @@ -78,17 +91,8 @@ public void writeImageFile(OutputStream outStream, String format, ByteBuffer ima @Override + @SuppressWarnings("deprecation") public JmeContext newContext(AppSettings settings, Type contextType) { - if (settings.getAudioRenderer() == null) { - audioRendererType = null; - } else if (settings.getAudioRenderer().equals(AppSettings.ANDROID_MEDIAPLAYER)) { - audioRendererType = AppSettings.ANDROID_MEDIAPLAYER; - } else if (settings.getAudioRenderer().equals(AppSettings.ANDROID_OPENAL_SOFT)) { - audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; - } else { - logger.log(Level.INFO, "AudioRenderer not set. Defaulting to OpenAL Soft"); - audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; - } initialize(settings); JmeContext ctx = new OGLESContext(); ctx.setSettings(settings); @@ -200,14 +204,39 @@ public synchronized File getStorageFolder(JmeSystem.StorageFolderType type) { public static void setView(View view) { JmeAndroidSystem.view = view; + vibrator = null; } public static View getView() { return view; } - public static String getAudioRendererType() { - return audioRendererType; + @Override + public boolean isDeviceRumbleSupported() { + return AndroidHapticFeedback.isSupported(getVibrator()); + } + + @Override + public void rumble(float amountHigh, float amountLow, float duration) { + AndroidHapticFeedback.rumble(getVibrator(), amountHigh, amountLow, duration); + } + + @Override + public void stopRumble() { + AndroidHapticFeedback.stop(getVibrator()); + } + + private static Vibrator getVibrator() { + if (vibrator != null) { + return vibrator; + } + + View currentView = view; + if (currentView == null || currentView.getContext() == null) { + return null; + } + vibrator = (Vibrator) currentView.getContext().getSystemService(Context.VIBRATOR_SERVICE); + return vibrator; } @Override diff --git a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java index a553afa25a..4b27f4decb 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java +++ b/jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java @@ -37,13 +37,10 @@ import android.content.DialogInterface; import android.content.pm.ConfigurationInfo; import android.graphics.PixelFormat; -import android.graphics.Rect; import android.opengl.GLSurfaceView; import android.os.Build; import android.text.InputType; import android.view.Gravity; -import android.view.SurfaceHolder; -import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.EditText; @@ -58,7 +55,9 @@ import com.jme3.input.controls.SoftTextDialogInputListener; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.material.Material; +import com.jme3.math.Vector2f; import com.jme3.renderer.Caps; +import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.android.AndroidGL; import com.jme3.renderer.opengl.*; @@ -67,6 +66,7 @@ import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; import com.jme3.texture.image.ColorSpace; import com.jme3.ui.Picture; @@ -98,9 +98,16 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex protected AndroidInputHandler androidInput; protected long minFrameDuration = 0; // No FPS cap protected long lastUpdateTime = 0; + private int logicalWidth = 1; + private int logicalHeight = 1; + private int framebufferWidth = 1; + private int framebufferHeight = 1; + private final Vector2f displayScale = new Vector2f(1f, 1f); + private float appliedDisplayScaleMode = Float.NaN; private Application application; private Material blitMaterial; private Picture blitGeometry; + private final Camera blitCamera = new Camera(1, 1); private FrameBuffer linearFrameBuffer; private Texture2D linearFrameBufferColorTexture; private boolean linearFrameBufferDirty; @@ -450,16 +457,62 @@ public void onSurfaceChanged(GL10 gl, int width, int height) { new Object[] { width, height } ); } - // update the application settings with the new resolution - settings.setResolution(width, height); - // Reload settings in androidInput so the correct touch event scaling can be - // calculated in case the surface resolution is different than the view. - androidInput.loadSettings(settings); + framebufferWidth = Math.max(width, 1); + framebufferHeight = Math.max(height, 1); + updateDisplayScaleMetrics(); // if the application has already been initialized (ie renderable is set) // then call reshape so the app can adjust to the new resolution. if (renderable.get()) { logger.log(Level.FINE, "App already initialized, calling reshape"); - listener.reshape(width, height); + listener.reshape(logicalWidth, logicalHeight, getRenderFramebufferWidth(), getRenderFramebufferHeight()); + listener.rescale(displayScale.x, displayScale.y); + } + } + + private float getAndroidDisplayDensity() { + if (androidInput != null && androidInput.getView() != null + && androidInput.getView().getResources() != null) { + android.util.DisplayMetrics metrics = androidInput.getView().getResources().getDisplayMetrics(); + if (metrics != null) { + if (metrics.density > 0f) { + return metrics.density; + } + if (metrics.densityDpi > 0) { + return metrics.densityDpi / 160f; + } + } + } + return 1f; + } + + private void updateDisplayScaleMetrics() { + float density = DisplayScaleUtils.sanitizeScale(getAndroidDisplayDensity()); + displayScale.set(density, density); + appliedDisplayScaleMode = settings.getDisplayScaleMode(); + if (DisplayScaleUtils.isNativePixelsMode(appliedDisplayScaleMode)) { + logicalWidth = framebufferWidth; + logicalHeight = framebufferHeight; + } else { + logicalWidth = Math.max(Math.round(framebufferWidth / density), 1); + logicalHeight = Math.max(Math.round(framebufferHeight / density), 1); + } + settings.setResolution(logicalWidth, logicalHeight); + // Reload settings in androidInput so the correct touch event scaling can be + // calculated in case the surface resolution is different than the view. + if (androidInput != null) { + androidInput.loadSettings(settings); + } + } + + private void applyDisplayScaleModeIfNeeded() { + if (Float.compare(settings.getDisplayScaleMode(), appliedDisplayScaleMode) == 0) { + return; + } + + updateDisplayScaleMetrics(); + if (renderable.get()) { + listener.reshape(logicalWidth, logicalHeight, getRenderFramebufferWidth(), getRenderFramebufferHeight()); + listener.rescale(displayScale.x, displayScale.y); } } @@ -473,8 +526,13 @@ public void onDrawFrame(GL10 gl) { if (!renderable.get()) { if (created.get()) { + applyDisplayScaleModeIfNeeded(); logger.fine("GL Surface is setup, initializing application"); listener.initialize(); + if (framebufferWidth > 0 && framebufferHeight > 0) { + listener.reshape(logicalWidth, logicalHeight, getRenderFramebufferWidth(), getRenderFramebufferHeight()); + listener.rescale(displayScale.x, displayScale.y); + } renderable.set(true); } } else { @@ -482,6 +540,7 @@ public void onDrawFrame(GL10 gl) { throw new IllegalStateException("onDrawFrame without create"); } + applyDisplayScaleModeIfNeeded(); if (!renderFrameWithBlitSrgbConversion()) { listener.update(); } @@ -547,12 +606,43 @@ private boolean useBlitSrgbConversion() { return settings.isGammaCorrection() && application != null; } + private boolean useBlitFrameBuffer() { + float mode = settings.getDisplayScaleMode(); + return application != null && (useBlitSrgbConversion() + || DisplayScaleUtils.isDisabledMode(mode) || DisplayScaleUtils.isEmulatedScaleMode(mode)); + } + + private int getRenderFramebufferWidth() { + float mode = settings.getDisplayScaleMode(); + if (DisplayScaleUtils.isDisabledMode(mode)) { + return Math.max(logicalWidth, 1); + } + if (DisplayScaleUtils.isEmulatedScaleMode(mode)) { + return Math.max(Math.round(framebufferWidth * mode), 1); + } + return Math.max(framebufferWidth, 1); + } + + private int getRenderFramebufferHeight() { + float mode = settings.getDisplayScaleMode(); + if (DisplayScaleUtils.isDisabledMode(mode)) { + return Math.max(logicalHeight, 1); + } + if (DisplayScaleUtils.isEmulatedScaleMode(mode)) { + return Math.max(Math.round(framebufferHeight * mode), 1); + } + return Math.max(framebufferHeight, 1); + } + private int getLinearFrameBufferSampleCount() { int samples = Math.max(settings.getSamples(), 1); - if (samples > 1 && renderer != null && !renderer.getCaps().contains(Caps.TextureMultisample)) { + if (samples > 1 && renderer != null + && (!renderer.getCaps().contains(Caps.TextureMultisample) + || !renderer.getCaps().contains(Caps.OpenGL32))) { if (!multisampleTextureWarningIssued) { - logger.warning("sRGB blit conversion requires multisampled textures for MSAA. " - + "Falling back to a single-sample linear framebuffer."); + logger.log(Level.WARNING, + "Display scale blit requested {0}x MSAA, but this backend cannot sample multisample textures for the blit path. Falling back to a single-sample linear framebuffer.", + samples); multisampleTextureWarningIssued = true; } return 1; @@ -561,13 +651,13 @@ private int getLinearFrameBufferSampleCount() { } private void rebuildLinearFrameBufferIfNeeded() { - if (!useBlitSrgbConversion()) { + if (!useBlitFrameBuffer()) { destroyLinearFrameBufferResources(); return; } - int width = Math.max(settings.getWidth(), 1); - int height = Math.max(settings.getHeight(), 1); + int width = getRenderFramebufferWidth(); + int height = getRenderFramebufferHeight(); int samples = getLinearFrameBufferSampleCount(); if (linearFrameBuffer != null && linearFrameBuffer.getWidth() == width @@ -583,6 +673,8 @@ private void rebuildLinearFrameBufferIfNeeded() { Texture2D colorTexture = new Texture2D( new Image(getLinearFrameBufferColorFormat(), width, height, null, ColorSpace.Linear)); + colorTexture.setMagFilter(Texture.MagFilter.Bilinear); + colorTexture.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); if (samples > 1) { colorTexture.getImage().setMultiSamples(samples); } @@ -608,7 +700,7 @@ private Format getLinearFrameBufferColorFormat() { } private boolean ensureBlitResources() { - if (!useBlitSrgbConversion()) { + if (!useBlitFrameBuffer()) { return false; } @@ -620,10 +712,10 @@ private boolean ensureBlitResources() { if (blitMaterial == null) { blitMaterial = new Material(assetManager, BLIT_MATERIAL); - blitMaterial.setBoolean("Srgb", true); blitMaterial.getAdditionalRenderState().setDepthTest(false); blitMaterial.getAdditionalRenderState().setDepthWrite(false); } + blitMaterial.setBoolean("Srgb", useBlitSrgbConversion()); if (blitGeometry == null) { blitGeometry = new Picture("Linear to sRGB Blit"); @@ -665,7 +757,12 @@ private void destroyLinearFrameBufferResources() { } private boolean renderFrameWithBlitSrgbConversion() { - if (!useBlitSrgbConversion()) { + if (!useBlitFrameBuffer()) { + return false; + } + + FrameBuffer previousMainFramebuffer = renderer.getCurrentFrameBuffer(); + if (previousMainFramebuffer != null) { return false; } @@ -674,16 +771,42 @@ private boolean renderFrameWithBlitSrgbConversion() { return false; } + FrameBuffer restoreMainFramebuffer = previousMainFramebuffer; + renderer.setMainFrameBufferOverride(linearFrameBuffer); try { listener.update(); + FrameBuffer currentMainFramebuffer = renderer.getCurrentFrameBuffer(); + if (currentMainFramebuffer != linearFrameBuffer) { + restoreMainFramebuffer = currentMainFramebuffer; + } } finally { - renderer.setMainFrameBufferOverride(null); + renderer.setMainFrameBufferOverride(restoreMainFramebuffer); } - renderer.setFrameBuffer(null); - blitGeometry.updateGeometricState(); - application.getRenderManager().renderGeometry(blitGeometry); + renderer.setMainFrameBufferOverride(null); + RenderManager renderManager = application.getRenderManager(); + Camera previousCamera = renderManager.getCurrentCamera(); + try { + renderer.setFrameBuffer(null); + int blitWidth = Math.max(getFramebufferWidth(), 1); + int blitHeight = Math.max(getFramebufferHeight(), 1); + if (blitCamera.getWidth() != blitWidth || blitCamera.getHeight() != blitHeight) { + blitCamera.resize(blitWidth, blitHeight, true); + } + renderManager.setCamera(blitCamera, true); + if (blitGeometry.getWidth() != blitWidth || blitGeometry.getHeight() != blitHeight) { + blitGeometry.setWidth(blitWidth); + blitGeometry.setHeight(blitHeight); + } + blitGeometry.updateGeometricState(); + renderManager.renderGeometry(blitGeometry); + } finally { + renderer.setMainFrameBufferOverride(restoreMainFramebuffer); + if (previousCamera != null) { + renderManager.setCamera(previousCamera, false); + } + } return true; } @@ -711,8 +834,8 @@ public void requestDialog( public void run() { final FrameLayout layoutTextDialogInput = new FrameLayout(view.getContext()); final EditText editTextDialogInput = new EditText(view.getContext()); - editTextDialogInput.setWidth(LayoutParams.FILL_PARENT); - editTextDialogInput.setHeight(LayoutParams.FILL_PARENT); + editTextDialogInput.setWidth(LayoutParams.MATCH_PARENT); + editTextDialogInput.setHeight(LayoutParams.MATCH_PARENT); editTextDialogInput.setPadding(20, 20, 20, 20); editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL); //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); @@ -778,22 +901,9 @@ public void onClick(DialogInterface dialog, int whichButton) { ); } - @Override - public com.jme3.opencl.Context getOpenCLContext() { - logger.warning("OpenCL is not yet supported on android"); - return null; - } - - /** - * Returns the height of the input surface. - * - * @return the height (in pixels) - */ @Override public int getFramebufferHeight() { - Rect rect = getSurfaceFrame(); - int result = rect.height(); - return result; + return framebufferHeight; } /** @@ -803,9 +913,7 @@ public int getFramebufferHeight() { */ @Override public int getFramebufferWidth() { - Rect rect = getSurfaceFrame(); - int result = rect.width(); - return result; + return framebufferWidth; } /** @@ -828,19 +936,6 @@ public int getWindowYPosition() { throw new UnsupportedOperationException("not implemented yet"); } - /** - * Retrieves the dimensions of the input surface. Note: do not modify the - * returned object. - * - * @return the dimensions (in pixels, left and top are 0) - */ - private Rect getSurfaceFrame() { - SurfaceView view = (SurfaceView) androidInput.getView(); - SurfaceHolder holder = view.getHolder(); - Rect result = holder.getSurfaceFrame(); - return result; - } - @Override public Displays getDisplays() { // TODO Auto-generated method stub diff --git a/jme3-android/src/main/java/com/jme3/util/AndroidNativeBufferAllocator.java b/jme3-android/src/main/java/com/jme3/util/AndroidNativeBufferAllocator.java index f91334db7e..a0ca781327 100644 --- a/jme3-android/src/main/java/com/jme3/util/AndroidNativeBufferAllocator.java +++ b/jme3-android/src/main/java/com/jme3/util/AndroidNativeBufferAllocator.java @@ -31,8 +31,15 @@ */ package com.jme3.util; +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; import java.nio.Buffer; import java.nio.ByteBuffer; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Allocates and destroys direct byte buffers using native code. @@ -40,28 +47,88 @@ * @author pavl_g. */ public final class AndroidNativeBufferAllocator implements BufferAllocator { + private static final Logger LOGGER = Logger.getLogger(AndroidNativeBufferAllocator.class.getName()); + private static final ReferenceQueue REFERENCE_QUEUE = new ReferenceQueue<>(); + private static final Map DEALLOCATORS = new ConcurrentHashMap<>(); + private static final Thread CLEAN_THREAD = new Thread(AndroidNativeBufferAllocator::freeCollectedBuffers); static { System.loadLibrary("bufferallocatorjme"); + CLEAN_THREAD.setDaemon(true); + CLEAN_THREAD.setName("Android Native Buffer Deallocator"); + CLEAN_THREAD.start(); } @Override public void destroyDirectBuffer(Buffer toBeDestroyed) { - releaseDirectByteBuffer(toBeDestroyed); + long address = directBufferAddress(toBeDestroyed); + if (address == 0L) { + LOGGER.log(Level.WARNING, "Not found address of the {0}", toBeDestroyed); + return; + } + Deallocator deallocator = DEALLOCATORS.remove(address); + if (deallocator == null) { + LOGGER.log(Level.WARNING, "Not found a deallocator for address {0}", address); + return; + } + deallocator.freeNow(); } @Override public ByteBuffer allocate(int size) { - return createDirectByteBuffer(size); + ByteBuffer buffer = createDirectByteBuffer(size); + if (buffer == null) { + throw new OutOfMemoryError("Could not allocate " + size + " bytes through Android native allocator"); + } + long address = directBufferAddress(buffer); + if (address != 0L) { + DEALLOCATORS.put(address, new Deallocator(buffer, address)); + } + return buffer; + } + + private static void freeCollectedBuffers() { + for (;;) { + try { + Deallocator deallocator = (Deallocator) REFERENCE_QUEUE.remove(); + deallocator.freeNow(); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + break; + } catch (Throwable throwable) { + LOGGER.log(Level.SEVERE, "Error deallocating direct buffer", throwable); + } + } + } + + private static final class Deallocator extends PhantomReference { + private final long address; + private final AtomicBoolean freed = new AtomicBoolean(false); + + private Deallocator(ByteBuffer referent, long address) { + super(referent, REFERENCE_QUEUE); + this.address = address; + } + + private void freeNow() { + if (!freed.compareAndSet(false, true)) { + return; + } + DEALLOCATORS.remove(address, this); + clear(); + releaseDirectByteBufferAddress(address); + } } /** - * Releases the memory of a direct buffer using a buffer object reference. + * Releases the memory of a direct buffer using its native address. * - * @param buffer the buffer reference to release its memory. + * @param address the native address to release * @see AndroidNativeBufferAllocator#destroyDirectBuffer(Buffer) */ - private native void releaseDirectByteBuffer(Buffer buffer); + private static native void releaseDirectByteBufferAddress(long address); + + private static native long directBufferAddress(Buffer buffer); /** * Creates a new direct byte buffer explicitly with a specific size. diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java deleted file mode 100644 index 67d73f4b5c..0000000000 --- a/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java +++ /dev/null @@ -1,1029 +0,0 @@ -/* - * Copyright (c) 2009-2025 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.view.surfaceview; - -import android.app.Activity; -import android.app.ActivityManager; -import android.app.AlertDialog; -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.pm.ConfigurationInfo; -import android.opengl.GLSurfaceView; -import android.os.Handler; -import android.util.AttributeSet; -import android.widget.RelativeLayout; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleEventObserver; -import androidx.lifecycle.LifecycleOwner; -import com.jme3.app.LegacyApplication; -import com.jme3.asset.AssetLoader; -import com.jme3.audio.AudioNode; -import com.jme3.audio.AudioRenderer; -import com.jme3.input.JoyInput; -import com.jme3.input.android.AndroidSensorJoyInput; -import com.jme3.scene.Spatial; -import com.jme3.system.AppSettings; -import com.jme3.system.SystemListener; -import com.jme3.system.android.JmeAndroidSystem; -import com.jme3.system.android.OGLESContext; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A RelativeLayout class holder that wraps a {@link GLSurfaceView} as a renderer UI component and uses {@link OGLESContext} as a renderer context to render - * a jme game on an android view for custom xml designs. - * The main idea of {@link JmeSurfaceView} class is to start a jMonkeyEngine application in a {@link SystemListener} context on a GL_ES thread, - * then the game is rendered and updated through a {@link GLSurfaceView} component with a delay of user's choice using a {@link Handler}, during the delay, - * the user has the ability to handle a couple of actions asynchronously as displaying a progress bar on a SplashScreen or an image or even play a preface game music of choice. - * - * @author pavl_g. - */ -public class JmeSurfaceView extends RelativeLayout implements SystemListener, DialogInterface.OnClickListener, LifecycleEventObserver { - - private static final Logger jmeSurfaceViewLogger = Logger.getLogger(JmeSurfaceView.class.getName()); - /*AppSettings attributes*/ - protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; - /*using {@link LegacyApplication} instead of {@link SimpleApplication} to include all classes extends LegacyApplication*/ - private LegacyApplication legacyApplication; - private AppSettings appSettings; - private int eglBitsPerPixel = 24; - private int eglAlphaBits = 0; - private int eglDepthBits = 16; - private int eglSamples = 0; - private int eglStencilBits = 0; - private int frameRate = -1; - private boolean emulateKeyBoard = true; - private boolean emulateMouse = true; - private boolean useJoyStickEvents = true; - private boolean isGLThreadPaused; - /*Late-init instances -- nullable objects*/ - private GLSurfaceView glSurfaceView; - private OGLESContext oglesContext; - private ConfigurationInfo configurationInfo; - private OnRendererCompleted onRendererCompleted; - private OnRendererStarted onRendererStarted; - private OnExceptionThrown onExceptionThrown; - private OnLayoutDrawn onLayoutDrawn; - /*Global Objects*/ - private Handler handler = new Handler(); - private RendererThread rendererThread = new RendererThread(); - private StringWriter crashLogWriter = new StringWriter(150); - /*Global flags*/ - private boolean showErrorDialog = true; - private boolean bindAppState = true; - private boolean showEscExitPrompt = true; - private boolean exitOnEscPressed = true; - /*Destruction policy flag*/ - private DestructionPolicy destructionPolicy = DestructionPolicy.DESTROY_WHEN_FINISH; - /*extra messages/data*/ - private String crashLog = ""; - private String glEsVersion = ""; - - /** - * Instantiates a default surface view holder without XML attributes. - * On instantiating this surface view, the holder is bound directly to the - * parent context life cycle. - * - * @param context the parent context. - */ - public JmeSurfaceView(@NonNull Context context) { - super(context); - //binds the view component to the holder activity life cycle - bindAppStateToActivityLifeCycle(bindAppState); - } - - /** - * Instantiates a surface view holder with XML attributes from an XML document. - * On instantiating this surface view, the holder is bound directly to the - * parent context life cycle. - * - * @param context the parent context. - * @param attrs a collection of attributes describes the tags in an XML document. - * @see android.content.res.Resources.Theme#obtainAttributes(AttributeSet, int[]) - */ - public JmeSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - //binds the view component to the holder activity life cycle - bindAppStateToActivityLifeCycle(bindAppState); - } - - /** - * Instantiates a surface view holder with XML attributes and a default style attribute. - * On instantiating this surface view, the holder is bound directly to the - * parent context life cycle. - * - * @param context the parent context. - * @param attrs a collection of attributes describes the tags in an XML document. - * @param defStyleAttr an attribute in the current theme that contains a - * reference to a style resource that supplies - * defaults values. Can be 0 to not look for defaults. - * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) - */ - public JmeSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - //binds the view component to the holder activity life cycle - bindAppStateToActivityLifeCycle(bindAppState); - } - - /** - * Instantiates a surface view holder with XML attributes, default style attribute and a default style resource. - * On instantiating this surface view, the holder is bound directly to the - * parent context life cycle. - * - * @param context the parent context. - * @param attrs a collection of attributes describes the tags in an XML document. - * @param defStyleAttr an attribute in the current theme that contains defaults. Can be 0 to not look for defaults. - * @param defStyleRes a resource identifier of a style resource that - * supplies default values, used only if defStyleAttr is 0 or can not be found in the theme. - * Can be 0 to not look for defaults. - * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) - */ - public JmeSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - //binds the view component to the holder activity life cycle - bindAppStateToActivityLifeCycle(bindAppState); - } - - /** - * Starts the jmeRenderer on a GlSurfaceView attached to a RelativeLayout. - * - * @param delayMillis delays the attachment of the surface view to the UI (RelativeLayout). - */ - public void startRenderer(int delayMillis) { - delayMillis = Math.max(0, delayMillis); - /*gets the device configuration attributes from the activity manager*/ - configurationInfo = ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE)).getDeviceConfigurationInfo(); - glEsVersion = "GL_ES Version : " + configurationInfo.getGlEsVersion(); - /*sanity check the app instance*/ - if (legacyApplication == null) { - throw new IllegalStateException("Cannot build a SurfaceView for a null app, make sure to use setLegacyApplication() to pass in your app !"); - } - /*initialize App Settings and start the Game*/ - appSettings = new AppSettings(true); - appSettings.setAudioRenderer(audioRendererType); - appSettings.setResolution(JmeSurfaceView.this.getLayoutParams().width, JmeSurfaceView.this.getLayoutParams().height); - appSettings.setAlphaBits(eglAlphaBits); - appSettings.setDepthBits(eglDepthBits); - appSettings.setSamples(eglSamples); - appSettings.setStencilBits(eglStencilBits); - appSettings.setBitsPerPixel(eglBitsPerPixel); - appSettings.setEmulateKeyboard(emulateKeyBoard); - appSettings.setEmulateMouse(emulateMouse); - appSettings.setUseJoysticks(useJoyStickEvents); - /*fetch and sanity check the static memory*/ - if (GameState.getLegacyApplication() != null) { - this.legacyApplication = GameState.getLegacyApplication(); - jmeSurfaceViewLogger.log(Level.INFO, "Old game state has been assigned as the current game state, skipping the first update"); - } else { - legacyApplication.setSettings(appSettings); - jmeSurfaceViewLogger.log(Level.INFO, "Starting a new Game State"); - /*start jme game context*/ - legacyApplication.start(); - /*fire the onStart() listener*/ - if (onRendererStarted != null) { - onRendererStarted.onRenderStart(legacyApplication, this); - } - } - /*attach the game to JmE OpenGL.Renderer context*/ - oglesContext = (OGLESContext) legacyApplication.getContext(); - /*create a glSurfaceView that will hold the renderer thread*/ - glSurfaceView = oglesContext.createView(JmeSurfaceView.this.getContext()); - /*set the current view as the system engine thread view for future uses*/ - JmeAndroidSystem.setView(JmeSurfaceView.this); - /*set JME system Listener to initialize game, update, requestClose and destroy on closure*/ - oglesContext.setSystemListener(JmeSurfaceView.this); - /*set the glSurfaceView to fit the widget*/ - glSurfaceView.setLayoutParams(new LayoutParams(JmeSurfaceView.this.getLayoutParams().width, JmeSurfaceView.this.getLayoutParams().height)); - if (GameState.getLegacyApplication() != null) { - addGlSurfaceView(); - } else { - /*post delay the attachment of the surface view on the UI*/ - handler.postDelayed(rendererThread, delayMillis); - } - } - - private void removeGLSurfaceView() { - ((Activity) getContext()).runOnUiThread(() -> JmeSurfaceView.this.removeView(glSurfaceView)); - } - - @Override - public void handleError(String errorMsg, Throwable throwable) { - throwable.printStackTrace(); - showErrorDialog(throwable, throwable.getClass().getName()); - if (onExceptionThrown != null) { - onExceptionThrown.onExceptionThrown(throwable); - } - } - - /** - * A state change observer to the holder Activity life cycle, used to keep this android view up-to-date with the holder activity life cycle. - * - * @param source the life cycle source, aka the observable object. - * @param event the fired event by the observable object, which is dispatched and sent to the observers. - */ - @Override - public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { - switch (event) { - case ON_DESTROY: - // activity is off the foreground stack - // activity is being destructed completely as a result of Activity#finish() - // this is a killable automata state! - jmeSurfaceViewLogger.log(Level.INFO, "Hosting Activity has been destructed."); - break; - case ON_PAUSE: - // activity is still on the foreground stack but not - // on the topmost level or before transition to stopped/hidden or destroyed state - // as a result of dispatch to Activity#finish() - // activity is no longer visible and is out of foreground - if (((Activity) getContext()).isFinishing()) { - if (destructionPolicy == DestructionPolicy.DESTROY_WHEN_FINISH) { - legacyApplication.stop(!isGLThreadPaused()); - } else if (destructionPolicy == DestructionPolicy.KEEP_WHEN_FINISH) { - jmeSurfaceViewLogger.log(Level.INFO, "Context stops, but game is still running."); - } - } else { - loseFocus(); - } - break; - case ON_RESUME: - // activity is back to the topmost of the - // foreground stack - gainFocus(); - break; - case ON_STOP: - // activity is out off the foreground stack or being destructed by a finishing dispatch - // this is a killable automata state! - break; - } - } - - @Override - public void initialize() { - /*Invoking can be delayed by delaying the draw of GlSurfaceView component on the screen*/ - if (legacyApplication == null) { - return; - } - legacyApplication.initialize(); - /*log for display*/ - jmeSurfaceViewLogger.log(Level.INFO, "JmeGame started in GLThread Asynchronously......."); - } - - @Override - public void reshape(int width, int height) { - if (legacyApplication == null) { - return; - } - legacyApplication.reshape(width, height); - jmeSurfaceViewLogger.log(Level.INFO, "Requested reshaping from the system listener"); - } - - @Override - public void rescale(float x, float y) { - if (legacyApplication == null) { - return; - } - legacyApplication.rescale(x, y); - jmeSurfaceViewLogger.log(Level.INFO, "Requested rescaling from the system listener"); - } - - @Override - public void update() { - /*Invoking can be delayed by delaying the draw of GlSurfaceView component on the screen*/ - if (legacyApplication == null || glSurfaceView == null) { - return; - } - legacyApplication.update(); - if (!GameState.isFirstUpdatePassed()) { - ((Activity) getContext()).runOnUiThread(() -> { - jmeSurfaceViewLogger.log(Level.INFO, "User delay finishes with 0 errors"); - if (onRendererCompleted != null) { - onRendererCompleted.onRenderCompletion(legacyApplication, legacyApplication.getContext().getSettings()); - } - }); - GameState.setFirstUpdatePassed(true); - } - } - - @Override - public void requestClose(boolean esc) { - /*skip if it's not enabled or the input is null*/ - if (legacyApplication == null || (!isExitOnEscPressed())) { - return; - } - if (isShowEscExitPrompt()) { - final AlertDialog alertDialog = new AlertDialog.Builder(getContext()).create(); - alertDialog.setTitle("Exit Prompt"); - alertDialog.setMessage("Are you sure you want to quit ?"); - alertDialog.setCancelable(false); - alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "No", (dialogInterface, i) -> alertDialog.dismiss()); - alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Yes", (dialogInterface, i) -> legacyApplication.requestClose(esc)); - alertDialog.show(); - } else { - legacyApplication.requestClose(esc); - } - } - - @Override - public void gainFocus() { - /*skip the block if the instances are nullptr*/ - if (legacyApplication == null || glSurfaceView == null) { - return; - } - glSurfaceView.onResume(); - /*resume the audio*/ - final AudioRenderer audioRenderer = legacyApplication.getAudioRenderer(); - if (audioRenderer != null) { - audioRenderer.resumeAll(); - } - /*resume the sensors (aka joysticks)*/ - if (legacyApplication.getContext() != null) { - final JoyInput joyInput = legacyApplication.getContext().getJoyInput(); - if (joyInput != null) { - if (joyInput instanceof AndroidSensorJoyInput) { - final AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; - androidJoyInput.resumeSensors(); - } - } - legacyApplication.gainFocus(); - } - setGLThreadPaused(false); - jmeSurfaceViewLogger.log(Level.INFO, "Game returns from the idle mode"); - } - - @Override - public void loseFocus() { - /*skip the block if the invoking instances are nullptr*/ - if (legacyApplication == null || glSurfaceView == null) { - return; - } - glSurfaceView.onPause(); - /*pause the audio*/ - legacyApplication.loseFocus(); - final AudioRenderer audioRenderer = legacyApplication.getAudioRenderer(); - if (audioRenderer != null) { - audioRenderer.pauseAll(); - } - /*pause the sensors (aka joysticks)*/ - if (legacyApplication.getContext() != null) { - final JoyInput joyInput = legacyApplication.getContext().getJoyInput(); - if (joyInput != null) { - if (joyInput instanceof AndroidSensorJoyInput) { - final AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; - androidJoyInput.pauseSensors(); - } - } - } - setGLThreadPaused(true); - jmeSurfaceViewLogger.log(Level.INFO, "Game goes idle"); - } - - @Override - public void destroy() { - if (glSurfaceView != null) { - removeGLSurfaceView(); - } - if (legacyApplication != null) { - legacyApplication.destroy(); - } - /*help the Dalvik Garbage collector to destruct the objects, by releasing their references*/ - /*context instances*/ - legacyApplication = null; - appSettings = null; - oglesContext = null; - configurationInfo = null; - /*extra data instances*/ - crashLogWriter = null; - crashLog = null; - /*nullifying helper instances and flags*/ - rendererThread = null; - destructionPolicy = null; - audioRendererType = null; - handler = null; - glEsVersion = null; - /*nullifying the event handlers*/ - onRendererStarted = null; - onRendererCompleted = null; - onExceptionThrown = null; - onLayoutDrawn = null; - GameState.setLegacyApplication(null); - GameState.setFirstUpdatePassed(false); - JmeAndroidSystem.setView(null); - jmeSurfaceViewLogger.log(Level.INFO, "Context and Game have been destructed."); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_NEGATIVE: - dialog.dismiss(); - ((Activity) getContext()).finish(); - break; - case DialogInterface.BUTTON_POSITIVE: - dialog.dismiss(); - break; - case DialogInterface.BUTTON_NEUTRAL: - /*copy crash log button*/ - final ClipboardManager clipboardManager = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); - final ClipData clipData = ClipData.newPlainText("Crash Log", crashLog); - clipboardManager.setPrimaryClip(clipData); - Toast.makeText(getContext(), "Crash Log copied to clipboard", Toast.LENGTH_SHORT).show(); - break; - } - } - - /** - * Adds the glSurfaceView to the screen immediately, saving the current app instance. - */ - protected void addGlSurfaceView() { - /*jme Renderer joins the UIThread at that point*/ - JmeSurfaceView.this.addView(glSurfaceView); - /*dispatch the layout drawn event*/ - if (onLayoutDrawn != null) { - onLayoutDrawn.onLayoutDrawn(legacyApplication, this); - } - /*set the static memory to hold the game state, only if the destruction policy uses KEEP_WHEN_FINISHED policy*/ - if (destructionPolicy == DestructionPolicy.KEEP_WHEN_FINISH) { - GameState.setLegacyApplication(legacyApplication); - } else { - GameState.setLegacyApplication(null); - } - } - - /** - * Displays an error dialog with a throwable title(error/exception), message and 3 buttons. - * 1st button is : EXIT to exit the activity and terminates the app. - * 2nd button is : DISMISS to dismiss the dialog and ignore the exception. - * 3rd button is : CopyCrashLog to copy the crash log to the clipboard. - * - * @param throwable the throwable stack. - * @param title the message title. - */ - protected void showErrorDialog(Throwable throwable, String title) { - if (!isShowErrorDialog()) { - return; - } - ((Activity) getContext()).runOnUiThread(() -> { - throwable.printStackTrace(new PrintWriter(crashLogWriter)); - crashLog = glEsVersion + "\n" + crashLogWriter.toString(); - - final AlertDialog alertDialog = new AlertDialog.Builder(getContext()).create(); - alertDialog.setTitle(glEsVersion + ", " + title); - alertDialog.setMessage(crashLog); - alertDialog.setCancelable(false); - alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Exit", this); - alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Dismiss", this); - alertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, "Copy crash log", this); - alertDialog.show(); - }); - } - - /** - * Binds/Unbinds the game life cycle to the holder activity life cycle. - * Unbinding the game life cycle, would disable {@link JmeSurfaceView#gainFocus()}, {@link JmeSurfaceView#loseFocus()} - * and {@link JmeSurfaceView#destroy()} from being invoked by the System Listener. - * The Default value is : true, and the view component is pre-bounded to its activity lifeCycle when initialized. - * - * @param condition true if you want to bind them, false otherwise. - */ - public void bindAppStateToActivityLifeCycle(final boolean condition) { - this.bindAppState = condition; - if (condition) { - /*register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner*/ - if (getContext() instanceof LifecycleOwner) { - ((LifecycleOwner) getContext()).getLifecycle().addObserver(JmeSurfaceView.this); - jmeSurfaceViewLogger.log(Level.INFO, "Command binding SurfaceView to the Activity Lifecycle."); - } - } else { - /*un-register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner*/ - if (getContext() instanceof LifecycleOwner) { - ((LifecycleOwner) getContext()).getLifecycle().removeObserver(JmeSurfaceView.this); - jmeSurfaceViewLogger.log(Level.INFO, "Command removing SurfaceView from the Activity Lifecycle."); - } - } - } - - /** - * Gets the current destruction policy. - * Default value is : {@link DestructionPolicy#DESTROY_WHEN_FINISH}. - * - * @return the destruction policy, either {@link DestructionPolicy#DESTROY_WHEN_FINISH} or {@link DestructionPolicy#KEEP_WHEN_FINISH}. - * @see DestructionPolicy - * @see GameState - */ - public DestructionPolicy getDestructionPolicy() { - return destructionPolicy; - } - - /** - * Sets the current destruction policy, destruction policy {@link DestructionPolicy#KEEP_WHEN_FINISH} ensures that we protect the app state - * using {@link GameState#legacyApplication} static memory when the activity finishes, while - * {@link DestructionPolicy#DESTROY_WHEN_FINISH} destroys the game context with the activity onDestroy(). - * Default value is : {@link DestructionPolicy#DESTROY_WHEN_FINISH}. - * - * @param destructionPolicy a destruction policy to set. - * @see DestructionPolicy - * @see GameState - */ - public void setDestructionPolicy(DestructionPolicy destructionPolicy) { - this.destructionPolicy = destructionPolicy; - } - - /** - * Checks whether the current game application life cycle is bound to the activity life cycle. - * - * @return true it matches the condition, false otherwise. - */ - public boolean isAppStateBoundToActivityLifeCycle() { - return bindAppState; - } - - /** - * Checks whether the system would show an exit prompt dialog when the esc keyboard input is invoked. - * - * @return ture if the exit prompt dialog is activated on exit, false otherwise. - */ - public boolean isShowEscExitPrompt() { - return showEscExitPrompt; - } - - /** - * Determines whether to show an exit prompt dialog when the esc keyboard button is invoked. - * - * @param showEscExitPrompt true to show the exit prompt dialog before exiting, false otherwise. - */ - public void setShowEscExitPrompt(boolean showEscExitPrompt) { - this.showEscExitPrompt = showEscExitPrompt; - } - - /** - * Checks whether the exit on esc press is activated. - * - * @return true if the exit on escape is activated, false otherwise. - */ - public boolean isExitOnEscPressed() { - return exitOnEscPressed; - } - - /** - * Determines whether the system would exit on pressing the keyboard esc button. - * - * @param exitOnEscPressed true to activate exiting on Esc button press, false otherwise. - */ - public void setExitOnEscPressed(boolean exitOnEscPressed) { - this.exitOnEscPressed = exitOnEscPressed; - } - - /** - * Gets the jme app instance. - * - * @return legacyApplication instance representing your game enclosure. - */ - public LegacyApplication getLegacyApplication() { - return legacyApplication; - } - - /** - * Sets the jme game instance that will be engaged into the {@link SystemListener}. - * - * @param legacyApplication your jme game instance. - */ - public void setLegacyApplication(@NonNull LegacyApplication legacyApplication) { - this.legacyApplication = legacyApplication; - } - - /** - * Gets the game window settings. - * - * @return app settings instance. - */ - public AppSettings getAppSettings() { - return appSettings; - } - - /** - * Sets the appSettings instance. - * - * @param appSettings the custom appSettings instance - */ - public void setAppSettings(@NonNull AppSettings appSettings) { - this.appSettings = appSettings; - } - - /** - * Gets the bits/pixel for Embedded gL - * - * @return integer representing it. - */ - public int getEglBitsPerPixel() { - return eglBitsPerPixel; - } - - /** - * Sets the memory representing each pixel in bits. - * - * @param eglBitsPerPixel the bits for each pixel. - */ - public void setEglBitsPerPixel(int eglBitsPerPixel) { - this.eglBitsPerPixel = eglBitsPerPixel; - } - - /** - * Gets the Embedded gL alpha(opacity) bits. - * - * @return integer representing it. - */ - public int getEglAlphaBits() { - return eglAlphaBits; - } - - /** - * Sets the memory representing the alpha of embedded gl in bits. - * - * @param eglAlphaBits the alpha bits. - */ - public void setEglAlphaBits(int eglAlphaBits) { - this.eglAlphaBits = eglAlphaBits; - } - - /** - * Gets the memory representing the EGL depth in bits. - * - * @return the depth bits. - */ - public int getEglDepthBits() { - return eglDepthBits; - } - - /** - * Sets the EGL depth in bits. - * The depth buffer or Z-buffer is basically coupled with stencil buffer, - * usually 8bits stencilBuffer + 24bits depthBuffer = 32bits shared memory. - * - * @param eglDepthBits the depth bits. - * @see JmeSurfaceView#setEglStencilBits(int) - */ - public void setEglDepthBits(int eglDepthBits) { - this.eglDepthBits = eglDepthBits; - } - - /** - * Gets the number of samples to use for multi-sampling. - * - * @return number of samples to use for multi-sampling. - */ - public int getEglSamples() { - return eglSamples; - } - - /** - * Sets the number of samples to use for multi-sampling. - * Leave 0 (default) to disable multi-sampling. - * Set to 2 or 4 to enable multi-sampling. - * - * @param eglSamples embedded gl samples bits to set. - */ - public void setEglSamples(int eglSamples) { - this.eglSamples = eglSamples; - } - - /** - * Gets the number of stencil buffer bits. - * Default is : 0. - * - * @return the stencil buffer bits. - */ - public int getEglStencilBits() { - return eglStencilBits; - } - - /** - * Sets the number of stencil buffer bits. - * Stencil buffer is used in depth-based shadow maps and shadow rendering as it limits rendering, - * it's coupled with Z-buffer or depth buffer, usually 8bits stencilBuffer + 24bits depthBuffer = 32bits shared memory. - * (default = 0) - * - * @param eglStencilBits the desired number of stencil bits. - * @see JmeSurfaceView#setEglDepthBits(int) - */ - public void setEglStencilBits(int eglStencilBits) { - this.eglStencilBits = eglStencilBits; - } - - /** - * Gets the limited FrameRate level for egl INFO. - * Default is : -1, for a device based limited value (determined by hardware). - * - * @return the limit frameRate in integers. - */ - public int getFrameRate() { - return frameRate; - } - - /** - * Limits the frame rate (fps) in the second. - * Default is : -1, for a device based limited value (determined by hardware). - * - * @param frameRate the limitation in integers. - */ - public void setFrameRate(int frameRate) { - this.frameRate = frameRate; - } - - /** - * Gets the audio renderer in String. - * Default is : {@link AppSettings#ANDROID_OPENAL_SOFT}. - * - * @return string representing audio renderer framework. - */ - public String getAudioRendererType() { - return audioRendererType; - } - - /** - * Sets the audioRenderer type. - * Default is : {@link AppSettings#ANDROID_OPENAL_SOFT}. - * - * @param audioRendererType string representing audioRenderer type. - */ - public void setAudioRendererType(String audioRendererType) { - this.audioRendererType = audioRendererType; - } - - /** - * Checks if the keyboard interfacing is enabled. - * Default is : true. - * - * @return true if the keyboard interfacing is enabled. - */ - public boolean isEmulateKeyBoard() { - return emulateKeyBoard; - } - - /** - * Enables keyboard interfacing. - * Default is : true. - * - * @param emulateKeyBoard true to enable keyboard interfacing. - */ - public void setEmulateKeyBoard(boolean emulateKeyBoard) { - this.emulateKeyBoard = emulateKeyBoard; - } - - /** - * Checks whether the mouse interfacing is enabled or not. - * Default is : true. - * - * @return true if the mouse interfacing is enabled. - */ - public boolean isEmulateMouse() { - return emulateMouse; - } - - /** - * Enables mouse interfacing. - * Default is : true. - * - * @param emulateMouse true to enable the mouse interfacing. - */ - public void setEmulateMouse(boolean emulateMouse) { - this.emulateMouse = emulateMouse; - } - - /** - * Checks whether joystick interfacing is enabled or not. - * Default is : true. - * - * @return true if the joystick interfacing is enabled. - */ - public boolean isUseJoyStickEvents() { - return useJoyStickEvents; - } - - /** - * Enables joystick interfacing for a jme-game - * - * @param useJoyStickEvents true to enable the joystick interfacing. - */ - public void setUseJoyStickEvents(boolean useJoyStickEvents) { - this.useJoyStickEvents = useJoyStickEvents; - } - - /** - * Checks whether the GLThread is paused or not. - * - * @return true/false - */ - public boolean isGLThreadPaused() { - return isGLThreadPaused; - } - - /** - * Sets GL Thread paused. - * - * @param GLThreadPaused true if you want to pause the GLThread. - */ - protected void setGLThreadPaused(boolean GLThreadPaused) { - isGLThreadPaused = GLThreadPaused; - } - - /** - * Sets the listener for the completion of rendering, ie : when the GL thread holding the {@link JmeSurfaceView} - * joins the UI thread, after asynchronous rendering. - * - * @param onRendererCompleted an instance of the interface {@link OnRendererCompleted}. - */ - public void setOnRendererCompleted(OnRendererCompleted onRendererCompleted) { - this.onRendererCompleted = onRendererCompleted; - } - - /** - * Sets the listener that will fire when an exception is thrown. - * - * @param onExceptionThrown an instance of the interface {@link OnExceptionThrown}. - */ - public void setOnExceptionThrown(OnExceptionThrown onExceptionThrown) { - this.onExceptionThrown = onExceptionThrown; - } - - /** - * Sets the listener that will fire after initializing the game. - * - * @param onRendererStarted an instance of the interface {@link OnRendererStarted}. - */ - public void setOnRendererStarted(OnRendererStarted onRendererStarted) { - this.onRendererStarted = onRendererStarted; - } - - /** - * Sets the listener that will dispatch an event when the layout is drawn by {@link JmeSurfaceView#addGlSurfaceView()}. - * - * @param onLayoutDrawn the event to be dispatched. - * @see JmeSurfaceView#addGlSurfaceView() - */ - public void setOnLayoutDrawn(OnLayoutDrawn onLayoutDrawn) { - this.onLayoutDrawn = onLayoutDrawn; - } - - /** - * Gets the current device GL_ES version. - * - * @return the current gl_es version in a string format. - */ - public String getGlEsVersion() { - return configurationInfo.getGlEsVersion(); - } - - /** - * Checks whether the error dialog is enabled upon encountering exceptions/errors. - * Default is : true. - * - * @return true if the error dialog is activated, false otherwise. - */ - public boolean isShowErrorDialog() { - return showErrorDialog; - } - - /** - * Determines whether the error dialog would be shown on encountering exceptions. - * Default is : true. - * - * @param showErrorDialog true to activate the error dialog, false otherwise. - */ - public void setShowErrorDialog(boolean showErrorDialog) { - this.showErrorDialog = showErrorDialog; - } - - /** - * Determines whether the app context would be destructed as a result of dispatching {@link Activity#finish()} - * with the holder activity context in case of {@link DestructionPolicy#DESTROY_WHEN_FINISH} or be - * spared for a second use in case of {@link DestructionPolicy#KEEP_WHEN_FINISH}. - * Default value is : {@link DestructionPolicy#DESTROY_WHEN_FINISH}. - * - * @see JmeSurfaceView#setDestructionPolicy(DestructionPolicy) - */ - public enum DestructionPolicy { - /** - * Finishes the game context with the activity context (ignores the static memory {@link GameState#legacyApplication}) - * as a result of dispatching {@link Activity#finish()}. - */ - DESTROY_WHEN_FINISH, - /** - * Spares the game context inside a static memory {@link GameState#legacyApplication} - * when the activity context is destroyed dispatching {@link Activity#finish()}, but the {@link android.app.Application} - * stills in the background. - */ - KEEP_WHEN_FINISH - } - - /** - * Used as a static memory to protect the game context from destruction by Activity#onDestroy(). - * - * @see DestructionPolicy - * @see JmeSurfaceView#setDestructionPolicy(DestructionPolicy) - */ - protected static final class GameState { - - private static LegacyApplication legacyApplication; - private static boolean firstUpdatePassed = false; - - /** - * Private constructor to inhibit instantiation of this class. - */ - private GameState() { - } - - /** - * Returns the current application state. - * - * @return game state instance, holding jME3 states (JmeContext, AssetManager, StateManager, Graphics, Sound, Input, Spatial/Nodes in place, etcetera). - */ - protected static LegacyApplication getLegacyApplication() { - return legacyApplication; - } - - /** - * Replaces the current application state. - * - * @param legacyApplication the new app instance holding the game state (including {@link AssetLoader}s, {@link AudioNode}s, {@link Spatial}s, etcetera). - */ - protected static void setLegacyApplication(LegacyApplication legacyApplication) { - GameState.legacyApplication = legacyApplication; - } - - /** - * Tests the first update flag. - * - * @return true if the firstUpdate has passed, false otherwise. - */ - protected static boolean isFirstUpdatePassed() { - return firstUpdatePassed; - } - - /** - * Adjusts the first update flag. - * - * @param firstUpdatePassed set to true to determine whether the firstUpdate has passed, false otherwise. - */ - protected static void setFirstUpdatePassed(boolean firstUpdatePassed) { - GameState.firstUpdatePassed = firstUpdatePassed; - } - } - - /** - * Delays the attachment surface view on the UI for the sake of initial frame pacing and splash screens, - * delaying the display of the game (GlSurfaceView) would lead to a substantial delay in the - * {@link android.opengl.GLSurfaceView.Renderer#onDrawFrame(javax.microedition.khronos.opengles.GL10)} which would - * delay invoking both {@link LegacyApplication#initialize()} and {@link LegacyApplication#update()}. - * - * @see JmeSurfaceView#startRenderer(int) - * @see com.jme3.system.android.OGLESContext#onDrawFrame(javax.microedition.khronos.opengles.GL10) - */ - private class RendererThread implements Runnable { - /** - * Delays the {@link GLSurfaceView} attachment on the UI thread. - * - * @see JmeSurfaceView#startRenderer(int) - */ - @Override - public void run() { - addGlSurfaceView(); - jmeSurfaceViewLogger.log(Level.INFO, "JmeSurfaceView's joined the UI thread"); - } - } -} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java deleted file mode 100644 index d92fd19479..0000000000 --- a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2009-2022 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.view.surfaceview; - -import android.view.View; -import com.jme3.app.LegacyApplication; - -/** - * An interface used for invoking an event when the application is started explicitly from {@link JmeSurfaceView#startRenderer(int)}. - * NB : This listener must be utilized before using {@link JmeSurfaceView#startRenderer(int)}, ie : it would be ignored if you try to use {@link JmeSurfaceView#setOnRendererStarted(OnRendererStarted)} after - * {@link JmeSurfaceView#startRenderer(int)}. - * - * @author pavl_g. - * @see JmeSurfaceView#setOnRendererStarted(OnRendererStarted) - */ -public interface OnRendererStarted { - /** - * Invoked when the game application is started by the {@link LegacyApplication#start()}, the event is dispatched on the - * holder Activity context thread. - * - * @param application the game instance. - * @param layout the enclosing layout. - * @see JmeSurfaceView#startRenderer(int) - */ - void onRenderStart(LegacyApplication application, View layout); -} diff --git a/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java index efbb721054..c8c90fc054 100644 --- a/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java +++ b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java @@ -129,6 +129,7 @@ public interface SelectionListener { private JComboBox colorDepthCombo = null; private JComboBox displayFreqCombo = null; private JComboBox antialiasCombo = null; + private JComboBox rendererCombo = null; private JLabel icon = null; private int selection = 0; private SelectionListener selectionListener = null; @@ -463,6 +464,8 @@ public void keyPressed(KeyEvent e) { displayFreqCombo.addKeyListener(aListener); antialiasCombo = new JComboBox<>(); antialiasCombo.addKeyListener(aListener); + rendererCombo = setUpRendererChooser(); + rendererCombo.addKeyListener(aListener); fullscreenBox = new JCheckBox(resourceBundle.getString("checkbox.fullscreen")); fullscreenBox.setSelected(source.isFullscreen()); fullscreenBox.addActionListener(new ActionListener() { @@ -549,6 +552,20 @@ public void actionPerformed(ActionEvent e) { gbc.anchor = GridBagConstraints.WEST; mainPanel.add(antialiasCombo, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 4, 4, 4); + gbc.gridx = 0; + gbc.gridy = 4; + gbc.anchor = GridBagConstraints.EAST; + gbc.weightx = 0.5; + mainPanel.add(new JLabel(resourceBundle.getString("label.renderer")), gbc); + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 4; + gbc.gridwidth = 3; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(rendererCombo, gbc); + // Set the button action listeners. Cancel disposes without saving, OK // saves. ok.addActionListener(new ActionListener() { @@ -583,14 +600,14 @@ public void actionPerformed(ActionEvent e) { gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridwidth = 2; - gbc.gridy = 4; + gbc.gridy = 5; gbc.anchor = GridBagConstraints.EAST; mainPanel.add(ok, gbc); gbc = new GridBagConstraints(); gbc.insets = new Insets(4, 16, 4, 4); gbc.gridx = 2; gbc.gridwidth = 2; - gbc.gridy = 4; + gbc.gridy = 5; gbc.anchor = GridBagConstraints.WEST; mainPanel.add(cancel, gbc); @@ -662,6 +679,7 @@ private boolean verifyAndSaveCurrentSelection() { boolean fullscreen = fullscreenBox.isSelected(); boolean vsync = vsyncBox.isSelected(); boolean gamma = gammaBox.isSelected(); + String renderer = (String) rendererCombo.getSelectedItem(); String[] parts = display.split(" x "); int width = Integer.parseInt(parts[0]); @@ -721,7 +739,7 @@ private boolean verifyAndSaveCurrentSelection() { source.setFullscreen(fullscreen); source.setVSync(vsync); source.setGammaCorrection(gamma); - // source.setRenderer(renderer); + source.setRenderer(renderer); source.setSamples(multisample); String appTitle = source.getTitle(); @@ -762,6 +780,30 @@ public void actionPerformed(ActionEvent e) { return resolutionBox; } + private JComboBox setUpRendererChooser() { + JComboBox rendererBox = new JComboBox<>(); + Set renderers = new LinkedHashSet<>(Arrays.asList( + AppSettings.ANGLE_GLES3, + AppSettings.LWJGL_OPENGL32, + AppSettings.LWJGL_OPENGL33, + AppSettings.LWJGL_OPENGL40, + AppSettings.LWJGL_OPENGL41, + AppSettings.LWJGL_OPENGL42, + AppSettings.LWJGL_OPENGL43, + AppSettings.LWJGL_OPENGL44, + AppSettings.LWJGL_OPENGL45 + )); + String currentRenderer = source.getRenderer(); + if (currentRenderer != null) { + renderers.add(currentRenderer); + } + for (String renderer : renderers) { + rendererBox.addItem(renderer); + } + rendererBox.setSelectedItem(currentRenderer); + return rendererBox; + } + /** * updateDisplayChoices updates the available color depth and * display frequency options to match the currently selected resolution. diff --git a/jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java b/jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java index 7c5b12c97d..c0d87711ee 100644 --- a/jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java +++ b/jme3-awt-dialogs/src/main/java/com/jme3/system/JmeDialogsFactoryImpl.java @@ -32,16 +32,26 @@ package com.jme3.system; +import java.util.logging.Logger; + import com.jme3.awt.AWTErrorDialog; import com.jme3.awt.AWTSettingsDialog; public class JmeDialogsFactoryImpl implements JmeDialogsFactory { - + private final static Logger logger = Logger.getLogger(JmeDialogsFactoryImpl.class.getName()); public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry){ + if(JmeSystem.getPlatform()==Platform.MacOSX64||JmeSystem.getPlatform()==Platform.MacOSX_ARM64){ + logger.warning("AWT settings dialog skipped in MacOS"); + return true; + } return AWTSettingsDialog.showDialog(settings,loadFromRegistry); } public void showErrorDialog(String message){ + if(JmeSystem.getPlatform()==Platform.MacOSX64||JmeSystem.getPlatform()==Platform.MacOSX_ARM64){ + logger.warning("AWT error dialog skipped in MacOS"); + return; + } AWTErrorDialog.showDialog(message); } diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java index 344a69b216..a28fea51f8 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java @@ -39,12 +39,12 @@ /** * Wraps an array of Tween actions into an action object. * - *

    - * Notes : + *

    Notes:

    + *
      *
    • The sequence of tweens is determined by {@link com.jme3.anim.tween.Tweens} utility class and the {@link BaseAction} interpolates that sequence.
    • *
    • This implementation mimics the {@link com.jme3.anim.tween.AbstractTween}, but it delegates the interpolation method {@link Tween#interpolate(double)} * to the {@link BlendableAction} class.
    • - *

      + *
    * * Created by Nehon. * @@ -64,12 +64,12 @@ public abstract class Action implements JmeCloneable, Tween { /** * Instantiates an action object that wraps a tween actions array by extracting their actions to the collection {@link Action#actions}. - *

    - * Notes : + *

    Notes:

    + *
      *
    • If intentions are to wrap some tween actions, then subclasses have to call this constructor, examples : {@link BlendableAction} and {@link BlendAction}.
    • *
    • If intentions are to make an implementation of {@link Action} that shouldn't wrap tweens of actions, then subclasses shouldn't call this * constructor, examples : {@link ClipAction} and {@link BaseAction}.
    • - *

      + *
    * * @param tweens the tween actions to be wrapped (not null). */ @@ -117,13 +117,13 @@ public double getSpeed() { /** * Alters the speedup factor applied by the layer running this action. - *

    - * Notes: + *

    Notes:

    + *
      *
    • This factor controls the animation direction, if the speed is a positive value then the animation will run forward and vice versa.
    • *
    • The speed factor gets applied, inside the {@link com.jme3.anim.AnimLayer}, on each interpolation step by this formula : time += tpf * action.getSpeed() * composer.globalSpeed.
    • *
    • Default speed is 1.0, it plays the animation clips at their normal speed.
    • *
    • Setting the speed factor to Zero will stop the animation, while setting it to a negative number will play the animation in a backward fashion.
    • - *

      + *
    * * @param speed the speed of frames. */ diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java index edc819f7ff..928c168bc0 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BaseAction.java @@ -55,7 +55,6 @@ * //run the action within this layer * animComposer.setCurrentAction("basicAction", ActionState.class.getSimpleName()); * - *

    * Created by Nehon. */ public class BaseAction extends Action { diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java index 17b66f8b07..56231de5bf 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java @@ -31,37 +31,34 @@ */ package com.jme3.anim.tween.action; +import com.jme3.anim.util.HasLocalTransform; +import com.jme3.math.Transform; + /** * A provider interface which provides a value {@link BlendSpace#getWeight()} to control the blending between 2 successive actions in a {@link BlendAction}. * The blending weight is a read-only value, and it can be manipulated using the arbitrary value {@link BlendSpace#setValue(float)} during the application runtime. * - *

    - * Notes about the blending action and its relations with the blending weight: + *

    Notes about the blending action and its relations with the blending weight:

    *
      *
    • Blending is the action of mixing between 2 successive animation {@link BlendableAction}s by interpolating their transforms and * then applying the result on the assigned {@link HasLocalTransform} object, the {@link BlendSpace} provides this blending action with a blend weight value.
    • *
    • The blend weight is the value for the interpolation for the target transforms.
    • *
    • The blend weight value must be in this interval [0, 1].
    • *
    - *

    * - *

    - * Different blending weight case scenarios managed by {@link BlendAction} internally: + *

    Different blending weight case scenarios managed by {@link BlendAction} internally:

    *
      - *
    • In case of (0 < Blending weight < 1), the blending is executed each update among 2 actions, the first action will use + *
    • In case of (0 < Blending weight < 1), the blending is executed each update among 2 actions, the first action will use * a blend value of 1 and the second action will use the blend space weight as a value for the interpolation.
    • *
    • In case of (Blending weight = 0), the blending hasn't started yet, only the first action will be interpolated at (weight = 1).
    • *
    • In case of (Blending weight = 1), the blending is finished and only the second action will continue to run at (weight = 1).
    • *
    - *

    * - *

    - * Notes about the blending weight value: + *

    Notes about the blending weight value:

    *
      *
    • Negative values and values greater than 1 aren't allowed (i.e., extrapolations aren't allowed).
    • *
    • For more details, see {@link BlendAction#doInterpolate(double)} and {@link BlendAction#collectTransform(HasLocalTransform, Transform, float, BlendableAction)}.
    • *
    - *

    * * Created by Nehon. * @see LinearBlendSpace an example of blendspace implementation diff --git a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java index 2936a30072..bd6194dd2f 100644 --- a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java +++ b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java @@ -32,6 +32,7 @@ package com.jme3.app; import com.jme3.profile.*; +import com.jme3.renderer.Caps; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; @@ -86,14 +87,16 @@ public void appStep(AppStep step) { frameTime.setNewFrameValueCpu(System.nanoTime()); frameEnded = false; - for (StatLine statLine : data.values()) { - for (Iterator i = statLine.taskIds.iterator(); i.hasNext(); ) { - int id = i.next(); - if (renderer.isTaskResultAvailable(id)) { - long val = renderer.getProfilingTime(id); - statLine.setValueGpu(val); - i.remove(); - idsPool.push(id); + if (renderer != null) { + for (StatLine statLine : data.values()) { + for (Iterator i = statLine.taskIds.iterator(); i.hasNext(); ) { + int id = i.next(); + if (renderer.isTaskResultAvailable(id)) { + long val = renderer.getProfilingTime(id); + statLine.setValueGpu(val); + i.remove(); + idsPool.push(id); + } } } } @@ -222,8 +225,8 @@ private void addStep(String path, long value) { int id = getUnusedTaskId(); line.taskIds.add(id); renderer.startProfiling(id); + ongoingGpuProfiling = true; } - ongoingGpuProfiling = true; prevPath = path; } @@ -239,8 +242,13 @@ private String getPath(String step, String... subPath) { } public void setRenderer(Renderer renderer) { - this.renderer = renderer; - poolTaskIds(renderer); + idsPool.clear(); + if (renderer != null && renderer.getCaps().contains(Caps.GpuTimerQuery)) { + this.renderer = renderer; + poolTaskIds(renderer); + } else { + this.renderer = null; + } } private void poolTaskIds(Renderer renderer) { diff --git a/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java b/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java index 7a3e0393cc..6de1dd4c78 100644 --- a/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java +++ b/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java @@ -108,8 +108,8 @@ protected void initialize(Application app) { ui.attachChild(darkenStats); ui.setLocalTranslation(app.getCamera().getWidth() - PANEL_WIDTH, app.getCamera().getHeight(), 0); - font = app.getAssetManager().loadFont("Interface/Fonts/Console.fnt"); - bigFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt"); + font = app.getAssetManager().loadFont("Interface/Fonts/Console.j3o"); + bigFont = app.getAssetManager().loadFont("Interface/Fonts/Default.j3o"); prof.setRenderer(app.getRenderer()); rootLine = new StatLineView("Frame"); rootLine.attachTo(ui); diff --git a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java index 1e0a75daf7..5d482c8719 100644 --- a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java @@ -593,8 +593,13 @@ public void startCanvas(boolean waitFor) { */ @Override public void reshape(int w, int h) { + reshape(w, h, w, h); + } + + @Override + public void reshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) { if (renderManager != null) { - renderManager.notifyReshape(w, h); + renderManager.notifyReshape(logicalWidth, logicalHeight, framebufferWidth, framebufferHeight); } } diff --git a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java index 7850b4b218..3400dc0cb6 100644 --- a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java @@ -37,10 +37,13 @@ import com.jme3.font.BitmapFont; import com.jme3.font.BitmapText; import com.jme3.input.FlyByCamera; +import com.jme3.input.Joystick; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.virtual.VirtualJoystick; import com.jme3.profile.AppStep; +import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Node; @@ -245,7 +248,7 @@ public void setShowSettings(boolean showSettings) { * @return the loaded BitmapFont */ protected BitmapFont loadGuiFont() { - return assetManager.loadFont("Interface/Fonts/Default.fnt"); + return assetManager.loadFont("Interface/Fonts/Default.j3o"); } @Override @@ -336,6 +339,7 @@ public void update() { if (prof != null) { prof.appStep(AppStep.SpatialUpdate); } + updateVirtualJoystickVisuals(tpf); rootNode.updateLogicalState(tpf); guiNode.updateLogicalState(tpf); @@ -410,4 +414,22 @@ public void simpleUpdate(float tpf) { public void simpleRender(RenderManager rm) { // Default empty implementation; subclasses can override } + + private void updateVirtualJoystickVisuals(float tpf) { + if (inputManager == null || assetManager == null || guiViewPort == null) { + return; + } + Joystick[] joysticks = inputManager.getJoysticks(); + if (joysticks == null) { + return; + } + Camera guiCamera = guiViewPort.getCamera(); + int width = guiCamera != null ? guiCamera.getWidth() : settings.getWidth(); + int height = guiCamera != null ? guiCamera.getHeight() : settings.getHeight(); + for (Joystick joystick : joysticks) { + if (joystick instanceof VirtualJoystick) { + ((VirtualJoystick) joystick).updateVisuals(guiNode, assetManager, width, height, tpf); + } + } + } } diff --git a/jme3-core/src/main/java/com/jme3/app/StatsAppState.java b/jme3-core/src/main/java/com/jme3/app/StatsAppState.java index 2078626a9c..54ca80fb93 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsAppState.java @@ -155,7 +155,7 @@ public void initialize(AppStateManager stateManager, Application app) { } if (guiFont == null) { - guiFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt"); + guiFont = app.getAssetManager().loadFont("Interface/Fonts/Default.j3o"); } loadFpsText(); diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java index 8e274e97b5..514cae82b8 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsView.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -82,7 +82,7 @@ public StatsView(String name, AssetManager manager, Statistics stats) { statLabels = statistics.getLabels(); statData = new int[statLabels.length]; - BitmapFont font = manager.loadFont("Interface/Fonts/Console.fnt"); + BitmapFont font = manager.loadFont("Interface/Fonts/Console.j3o"); statText = new BitmapText(font); statText.setLocalTranslation(0, statText.getLineHeight() * statLabels.length, 0); attachChild(statText); diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index 338894bbdf..9e6cf2ae29 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -187,6 +187,7 @@ public ParticleEmitter clone(boolean cloneMaterial) { * The old clone() method that did not use the new Cloner utility. */ @Override + @Deprecated public ParticleEmitter oldClone(boolean cloneMaterial) { ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial); clone.shape = shape.deepClone(); diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java index cd27573d02..10c42df8da 100644 --- a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java @@ -309,7 +309,7 @@ protected void initialize(Application app) { viewports[i] = createOffViewPort("EnvView" + i, cameras[i]); framebuffers[i] = createOffScreenFrameBuffer(size, viewports[i]); textures[i] = new Texture2D(size, size, colorFormat); - framebuffers[i].setColorTexture(textures[i]); + framebuffers[i].addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(textures[i])); } } @@ -416,7 +416,7 @@ protected FrameBuffer createOffScreenFrameBuffer(int mapSize, ViewPort offView) protected FrameBuffer createOffScreenFrameBuffer(int mapSize, ViewPort offView, Image.Format depthFormat) { // create offscreen framebuffer final FrameBuffer offBuffer = new FrameBuffer(mapSize, mapSize, 1); - offBuffer.setDepthBuffer(depthFormat); + offBuffer.setDepthTarget(FrameBuffer.FrameBufferTarget.newTarget(depthFormat)); offView.setOutputFrameBuffer(offBuffer); return offBuffer; } diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java index 82d45f32de..655456b16e 100644 --- a/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java @@ -36,7 +36,6 @@ import java.util.function.Predicate; import com.jme3.asset.AssetManager; import com.jme3.environment.baker.IBLGLEnvBakerLight; -import com.jme3.environment.baker.IBLHybridEnvBakerLight; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; @@ -49,7 +48,6 @@ import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; -import com.jme3.texture.Image.Format; /** * A control that automatically handles environment bake and rebake including @@ -87,10 +85,12 @@ public class EnvironmentProbeControl extends LightProbe implements Control { private float frustumNear = 0.001f, frustumFar = 1000f; private String uuid = "none"; private boolean enabled = true; - + private IBLGLEnvBakerLight.SphericalHarmonicsMode sphericalHarmonicsMode = + IBLGLEnvBakerLight.SphericalHarmonicsMode.FAST; private Predicate filter = (s) -> { return s.getUserData("tags.env") != null || s.getUserData("tags.env.env" + uuid) != null; }; + private transient IBLGLEnvBakerLight baker; protected EnvironmentProbeControl() { super(); @@ -186,6 +186,7 @@ public static void untagGlobal(Spatial s) { } @Override + @Deprecated public Control cloneForSpatial(Spatial spatial) { throw new UnsupportedOperationException(); } @@ -211,11 +212,47 @@ public boolean isRequiredSavableResults() { return requiredSavableResults; } + /** + * Sets how spherical harmonics coefficients are baked by this control. + * + * @param mode the spherical harmonics bake mode + */ + public void setSphericalHarmonicsMode(IBLGLEnvBakerLight.SphericalHarmonicsMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode cannot be null"); + } + sphericalHarmonicsMode = mode; + } + + /** + * Returns the spherical harmonics bake mode used by this control. + * + * @return the spherical harmonics bake mode + */ + public IBLGLEnvBakerLight.SphericalHarmonicsMode getSphericalHarmonicsMode() { + return sphericalHarmonicsMode; + } + + /** + * Enables or disables the spherical harmonics fast path explicitly. + * + * @param enabled true to use the fast path, false to use the quality path + */ + public void setSphericalHarmonicsFastPathEnabled(boolean enabled) { + setSphericalHarmonicsMode(enabled + ? IBLGLEnvBakerLight.SphericalHarmonicsMode.FAST + : IBLGLEnvBakerLight.SphericalHarmonicsMode.QUALITY); + } + @Override public void setSpatial(Spatial spatial) { if (this.spatial != null && spatial != null && spatial != this.spatial) { throw new IllegalStateException("This control has already been added to a Spatial"); } + if (spatial == null && baker != null) { + baker.clean(); + baker = null; + } this.spatial = spatial; if (spatial != null) spatial.addLight(this); } @@ -230,7 +267,12 @@ public void render(RenderManager rm, ViewPort vp) { if (!isEnabled()) return; if (bakeNeeded) { bakeNeeded = false; - rebakeNow(rm); + try { + rebakeNow(rm); + } finally { + rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer()); + rm.setCamera(vp.getCamera(), false); + } } } @@ -284,11 +326,23 @@ public float getFrustumFar() { */ public void setAssetManager(AssetManager assetManager) { this.assetManager = assetManager; + if (baker != null) { + baker.clean(); + baker = null; + } + } + + private IBLGLEnvBakerLight getBaker(RenderManager renderManager) { + if (baker == null) { + baker = new IBLGLEnvBakerLight(renderManager, assetManager, null, + null, envMapSize, envMapSize); + } + return baker; } void rebakeNow(RenderManager renderManager) { - IBLHybridEnvBakerLight baker = new IBLGLEnvBakerLight(renderManager, assetManager, null, - null, envMapSize, envMapSize); + IBLGLEnvBakerLight baker = getBaker(renderManager); + baker.setSphericalHarmonicsMode(sphericalHarmonicsMode); baker.setTexturePulling(isRequiredSavableResults()); baker.bakeEnvironment(spatial, getPosition(), frustumNear, frustumFar, filter); @@ -303,8 +357,6 @@ void rebakeNow(RenderManager renderManager) { setShCoeffs(baker.getSphericalHarmonicsCoefficients()); setPosition(Vector3f.ZERO); setReady(true); - - baker.clean(); } public void setEnabled(boolean enabled) { @@ -319,6 +371,18 @@ public Spatial getSpatial() { return spatial; } + static IBLGLEnvBakerLight.SphericalHarmonicsMode readSphericalHarmonicsMode(String modeName) { + if (modeName == null) { + return IBLGLEnvBakerLight.SphericalHarmonicsMode.FAST; + } + try { + return IBLGLEnvBakerLight.SphericalHarmonicsMode.valueOf(modeName); + } catch (IllegalArgumentException ex) { + // Legacy AUTO and unknown values now fall back to FAST. + return IBLGLEnvBakerLight.SphericalHarmonicsMode.FAST; + } + } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); @@ -331,6 +395,8 @@ public void write(JmeExporter ex) throws IOException { oc.write(frustumFar, "frustumFar", 1000f); oc.write(frustumNear, "frustumNear", 0.001f); oc.write(uuid, "envProbeControlUUID", "none"); + oc.write(sphericalHarmonicsMode, "sphericalHarmonicsMode", + IBLGLEnvBakerLight.SphericalHarmonicsMode.FAST); } @Override @@ -346,6 +412,15 @@ public void read(JmeImporter im) throws IOException { frustumFar = ic.readFloat("frustumFar", 1000f); frustumNear = ic.readFloat("frustumNear", 0.001f); uuid = ic.readString("envProbeControlUUID", "none"); + try { + sphericalHarmonicsMode = ic.readEnum("sphericalHarmonicsMode", + IBLGLEnvBakerLight.SphericalHarmonicsMode.class, + IBLGLEnvBakerLight.SphericalHarmonicsMode.FAST); + } catch (IllegalArgumentException ex) { + sphericalHarmonicsMode = readSphericalHarmonicsMode( + ic.readString("sphericalHarmonicsMode", + IBLGLEnvBakerLight.SphericalHarmonicsMode.FAST.name())); + } } } diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java index d79a40c00f..9e80bab227 100644 --- a/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java +++ b/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java @@ -105,6 +105,7 @@ public abstract class GenericEnvBaker implements EnvBaker { protected final Camera cam; protected boolean texturePulling = false; protected List bos = new ArrayList<>(); + private FrameBuffer[] envBakers; protected GenericEnvBaker(RenderManager rm, AssetManager am, Format colorFormat, Format depthFormat, int env_size) { this.depthFormat = depthFormat; @@ -178,18 +179,35 @@ protected Camera updateAndGetInternalCamera(int faceId, int w, int h, Vector3f p @Override public void clean() { + if (envBakers != null) { + for (FrameBuffer envBaker : envBakers) { + if (envBaker != null) { + envBaker.dispose(); + } + } + envBakers = null; + } } - @Override - public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar, Predicate filter) { - FrameBuffer envbakers[] = new FrameBuffer[6]; + private FrameBuffer[] getEnvBakers() { + if (envBakers != null) { + return envBakers; + } + + envBakers = new FrameBuffer[6]; for (int i = 0; i < 6; i++) { - envbakers[i] = new FrameBuffer(envMap.getImage().getWidth(), envMap.getImage().getHeight(), 1); - envbakers[i].setDepthTarget(FrameBufferTarget.newTarget(getDepthFormat())); - envbakers[i].setSrgb(false); - envbakers[i].addColorTarget(FrameBufferTarget.newTarget(envMap).face(TextureCubeMap.Face.values()[i])); + envBakers[i] = new FrameBuffer(envMap.getImage().getWidth(), envMap.getImage().getHeight(), 1); + envBakers[i].setDepthTarget(FrameBufferTarget.newTarget(getDepthFormat())); + envBakers[i].setSrgb(false); + envBakers[i].addColorTarget(FrameBufferTarget.newTarget(envMap).face(TextureCubeMap.Face.values()[i])); } + return envBakers; + } + + @Override + public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar, Predicate filter) { + FrameBuffer envbakers[] = getEnvBakers(); if (isTexturePulling()) { startPulling(); @@ -212,8 +230,11 @@ public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, Predicate ofilter = renderManager.getRenderFilter(); renderManager.setRenderFilter(filter); - renderManager.renderViewPort(viewPort, 0.16f); - renderManager.setRenderFilter(ofilter); + try { + renderManager.renderViewPort(viewPort, 0.16f); + } finally { + renderManager.setRenderFilter(ofilter); + } if (isTexturePulling()) { pull(envbaker, envMap, i); @@ -226,10 +247,6 @@ public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, } envMap.getImage().clearUpdateNeeded(); - - for (int i = 0; i < 6; i++) { - envbakers[i].dispose(); - } } /** diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java index 54e0bac574..109a2e68a5 100644 --- a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java @@ -63,8 +63,28 @@ */ public class IBLGLEnvBakerLight extends IBLHybridEnvBakerLight { private static final int NUM_SH_COEFFICIENT = 9; + private static final int DEFAULT_FAST_SH_SAMPLE_COUNT = 8192; private static final Logger LOG = Logger.getLogger(IBLGLEnvBakerLight.class.getName()); + /** + * Selects how spherical harmonics coefficients are baked on the GPU. + */ + public enum SphericalHarmonicsMode { + /** + * Use Hammersley sampling. + */ + FAST, + /** + * Integrate every cubemap texel in a single shader pass. + */ + QUALITY + } + + private SphericalHarmonicsMode sphericalHarmonicsMode = SphericalHarmonicsMode.FAST; + private int sphericalHarmonicsFastPathSampleCount = DEFAULT_FAST_SH_SAMPLE_COUNT; + private Texture2D shCoefTexture; + private FrameBuffer shBaker; + /** * Create a new IBL env baker * @@ -91,6 +111,78 @@ public boolean isTexturePulling() { return this.texturePulling; } + /** + * Sets how spherical harmonics coefficients are baked. + * + * @param mode the spherical harmonics bake mode + */ + public void setSphericalHarmonicsMode(SphericalHarmonicsMode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode cannot be null"); + } + this.sphericalHarmonicsMode = mode; + } + + /** + * Returns the current spherical harmonics bake mode. + * + * @return the spherical harmonics bake mode + */ + public SphericalHarmonicsMode getSphericalHarmonicsMode() { + return sphericalHarmonicsMode; + } + + /** + * Sets the sample count used by the Hammersley spherical harmonics fast path. + * + * @param sampleCount the number of samples, must be positive + */ + public void setSphericalHarmonicsFastPathSampleCount(int sampleCount) { + if (sampleCount <= 0) { + throw new IllegalArgumentException("sampleCount must be greater than zero"); + } + this.sphericalHarmonicsFastPathSampleCount = sampleCount; + } + + /** + * Returns the sample count used by the Hammersley spherical harmonics fast path. + * + * @return the Hammersley sample count + */ + public int getSphericalHarmonicsFastPathSampleCount() { + return sphericalHarmonicsFastPathSampleCount; + } + + @Override + public void clean() { + super.clean(); + if (shBaker != null) { + shBaker.dispose(); + shBaker = null; + } + shCoefTexture = null; + } + + private Texture2D getShCoefTexture(Format format) { + if (shCoefTexture == null || shCoefTexture.getImage().getFormat() != format) { + if (shBaker != null) { + shBaker.dispose(); + shBaker = null; + } + shCoefTexture = new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format); + } + return shCoefTexture; + } + + private FrameBuffer getShBaker(Texture2D shCoefTexture) { + if (shBaker == null) { + shBaker = new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1); + shBaker.setSrgb(false); + shBaker.addColorTarget(FrameBufferTarget.newTarget(shCoefTexture)); + } + return shBaker; + } + @Override public void bakeSphericalHarmonicsCoefficients() { Box boxm = new Box(1, 1, 1); @@ -99,8 +191,20 @@ public void bakeSphericalHarmonicsCoefficients() { Material mat = new Material(assetManager, "Common/IBLSphH/IBLSphH.j3md"); mat.setTexture("Texture", envMap); mat.setVector2("Resolution", new Vector2f(envMap.getImage().getWidth(), envMap.getImage().getHeight())); + mat.setInt("SampleCount", sphericalHarmonicsFastPathSampleCount); screen.setMaterial(mat); + switch (sphericalHarmonicsMode) { + case FAST: { + mat.setBoolean("UseFastSphericalHarmonics", true); + break; + } + case QUALITY: { + mat.setBoolean("UseFastSphericalHarmonics", false); + break; + } + } + float remapMaxValue = 0; Format format = Format.RGBA32F; if (!renderManager.getRenderer().getCaps().contains(Caps.FloatColorBufferRGBA)) { @@ -117,38 +221,18 @@ public void bakeSphericalHarmonicsCoefficients() { mat.clearParam("RemapMaxValue"); } - Texture2D shCoefTx[] = { new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format), new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format) }; - - FrameBuffer shbaker[] = { new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1), new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1) }; - shbaker[0].setSrgb(false); - shbaker[0].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[0])); - - shbaker[1].setSrgb(false); - shbaker[1].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[1])); + Texture2D shCoefTx = getShCoefTexture(format); + FrameBuffer shbaker = getShBaker(shCoefTx); - int renderOnT = -1; + screen.updateLogicalState(0); + screen.updateGeometricState(); - for (int faceId = 0; faceId < 6; faceId++) { - if (renderOnT != -1) { - int s = renderOnT; - renderOnT = renderOnT == 0 ? 1 : 0; - mat.setTexture("ShCoef", shCoefTx[s]); - } else { - renderOnT = 0; - } - - mat.setInt("FaceId", faceId); - - screen.updateLogicalState(0); - screen.updateGeometricState(); - - renderManager.setCamera(updateAndGetInternalCamera(0, shbaker[renderOnT].getWidth(), shbaker[renderOnT].getHeight(), Vector3f.ZERO, 1, 1000), false); - renderManager.getRenderer().setFrameBuffer(shbaker[renderOnT]); - renderManager.renderGeometry(screen); - } + renderManager.setCamera(updateAndGetInternalCamera(0, shbaker.getWidth(), shbaker.getHeight(), Vector3f.ZERO, 1, 1000), false); + renderManager.getRenderer().setFrameBuffer(shbaker); + renderManager.renderGeometry(screen); - ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker[renderOnT].getColorTarget().getFormat().getBitsPerPixel() / 8)); - renderManager.getRenderer().readFrameBufferWithFormat(shbaker[renderOnT], shCoefRaw, shbaker[renderOnT].getColorTarget().getFormat()); + ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker.getColorTarget().getFormat().getBitsPerPixel() / 8)); + renderManager.getRenderer().readFrameBufferWithFormat(shbaker, shCoefRaw, shbaker.getColorTarget().getFormat()); shCoefRaw.rewind(); Image img = new Image(format, NUM_SH_COEFFICIENT, 1, shCoefRaw, ColorSpace.Linear); diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java index 606a894699..123d3ac440 100644 --- a/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java @@ -64,6 +64,7 @@ public class IBLHybridEnvBakerLight extends GenericEnvBaker implements IBLEnvBak private static final Logger LOGGER = Logger.getLogger(IBLHybridEnvBakerLight.class.getName()); protected TextureCubeMap specular; protected Vector3f[] shCoef; + private FrameBuffer[][] specularBakers; /** * Create a new IBL env baker @@ -104,6 +105,24 @@ public IBLHybridEnvBakerLight(RenderManager rm, AssetManager am, Format format, } + @Override + public void clean() { + super.clean(); + if (specularBakers != null) { + for (FrameBuffer[] mipBakers : specularBakers) { + if (mipBakers == null) { + continue; + } + for (FrameBuffer specularBaker : mipBakers) { + if (specularBaker != null) { + specularBaker.dispose(); + } + } + } + specularBakers = null; + } + } + @Override public boolean isTexturePulling() { // always pull textures from gpu return true; @@ -123,19 +142,36 @@ private float roughnessFromMip(int mip) { return mipNorm * mipNorm; } - private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry screen) throws Exception { - mat.setFloat("Roughness", roughness); + private FrameBuffer[] getSpecularBakers(int mip, int mipWidth, int mipHeight) { + if (specularBakers == null) { + specularBakers = new FrameBuffer[specular.getImage().getMipMapSizes().length][]; + } - int mipWidth = (int) (specular.getImage().getWidth() * FastMath.pow(0.5f, mip)); - int mipHeight = (int) (specular.getImage().getHeight() * FastMath.pow(0.5f, mip)); + FrameBuffer[] specularbakers = specularBakers[mip]; + if (specularbakers != null + && specularbakers[0].getWidth() == mipWidth + && specularbakers[0].getHeight() == mipHeight) { + return specularbakers; + } - FrameBuffer specularbakers[] = new FrameBuffer[6]; + specularbakers = new FrameBuffer[6]; for (int i = 0; i < 6; i++) { specularbakers[i] = new FrameBuffer(mipWidth, mipHeight, 1); specularbakers[i].setSrgb(false); specularbakers[i].addColorTarget(FrameBufferTarget.newTarget(specular).level(mip).face(i)); specularbakers[i].setMipMapsGenerationHint(false); } + specularBakers[mip] = specularbakers; + return specularbakers; + } + + private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry screen) throws Exception { + mat.setFloat("Roughness", roughness); + + int mipWidth = (int) (specular.getImage().getWidth() * FastMath.pow(0.5f, mip)); + int mipHeight = (int) (specular.getImage().getHeight() * FastMath.pow(0.5f, mip)); + + FrameBuffer specularbakers[] = getSpecularBakers(mip, mipWidth, mipHeight); for (int i = 0; i < 6; i++) { FrameBuffer specularbaker = specularbakers[i]; @@ -153,9 +189,6 @@ private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry sc } } - for (int i = 0; i < 6; i++) { - specularbakers[i].dispose(); - } } @Override diff --git a/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java index 7eab91ee12..b26eef13c3 100644 --- a/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java +++ b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java @@ -82,14 +82,14 @@ protected void addButton(JoystickButton button) { buttons.add(button); } - /** - * Rumbles the joystick for the given amount/magnitude. - * - * @param amount The amount to rumble. Should be between 0 and 1. - */ @Override - public void rumble(float amount) { - joyInput.setJoyRumble(joyId, amount); + public void rumble(float amountHigh, float amountLow, float duration) { + joyInput.setJoyRumble(joyId, amountHigh, amountLow, duration); + } + + @Override + public void stopRumble() { + joyInput.stopJoyRumble(joyId); } /** diff --git a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java index fe494801b3..3ff89e8166 100644 --- a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java @@ -50,7 +50,7 @@ * A camera that follows a spatial and can turn around it by dragging the mouse * @author nehon */ -public class ChaseCamera implements ActionListener, AnalogListener, Control, JmeCloneable { +public class ChaseCamera implements ActionListener, AnalogListener, Control, JmeCloneable, JoystickConnectionListener { protected Spatial target = null; protected float minVerticalRotation = 0.00f; @@ -134,8 +134,15 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control, Jme /** * @deprecated use {@link CameraInput#CHASECAM_TOGGLEROTATE} */ - @Deprecated - public static final String ChaseCamToggleRotate = "ChaseCamToggleRotate"; + @Deprecated + public static final String ChaseCamToggleRotate = "ChaseCamToggleRotate"; + + private static final String CHASECAM_JOYDOWN = "ChaseCamJoyDown"; + private static final String CHASECAM_JOYUP = "ChaseCamJoyUp"; + private static final String CHASECAM_JOYMOVELEFT = "ChaseCamJoyMoveLeft"; + private static final String CHASECAM_JOYMOVERIGHT = "ChaseCamJoyMoveRight"; + private static final String CHASECAM_JOYZOOMIN = "ChaseCamJoyZoomIn"; + private static final String CHASECAM_JOYZOOMOUT = "ChaseCamJoyZoomOut"; protected boolean zoomin; protected boolean hideCursorOnRotate = true; @@ -219,14 +226,34 @@ public void onAnalog(String name, float value, float tpf) { distanceLerpFactor = 0; } zoomin = true; - } else if (name.equals(CameraInput.CHASECAM_ZOOMOUT)) { - zoomCamera(+value); - if (zoomin == true) { - distanceLerpFactor = 0; - } - zoomin = false; - } - } + } else if (name.equals(CameraInput.CHASECAM_ZOOMOUT)) { + zoomCamera(+value); + if (zoomin == true) { + distanceLerpFactor = 0; + } + zoomin = false; + } else if (name.equals(CHASECAM_JOYMOVELEFT)) { + rotateCamera(-value, true); + } else if (name.equals(CHASECAM_JOYMOVERIGHT)) { + rotateCamera(value, true); + } else if (name.equals(CHASECAM_JOYUP)) { + vRotateCamera(value, true); + } else if (name.equals(CHASECAM_JOYDOWN)) { + vRotateCamera(-value, true); + } else if (name.equals(CHASECAM_JOYZOOMIN)) { + zoomCamera(-value); + if (zoomin == false) { + distanceLerpFactor = 0; + } + zoomin = true; + } else if (name.equals(CHASECAM_JOYZOOMOUT)) { + zoomCamera(+value); + if (zoomin == true) { + distanceLerpFactor = 0; + } + zoomin = false; + } + } /** * Registers inputs with the input manager @@ -237,10 +264,16 @@ public final void registerWithInput(InputManager inputManager) { String[] inputs = {CameraInput.CHASECAM_TOGGLEROTATE, CameraInput.CHASECAM_DOWN, CameraInput.CHASECAM_UP, - CameraInput.CHASECAM_MOVELEFT, - CameraInput.CHASECAM_MOVERIGHT, - CameraInput.CHASECAM_ZOOMIN, - CameraInput.CHASECAM_ZOOMOUT}; + CameraInput.CHASECAM_MOVELEFT, + CameraInput.CHASECAM_MOVERIGHT, + CameraInput.CHASECAM_ZOOMIN, + CameraInput.CHASECAM_ZOOMOUT, + CHASECAM_JOYDOWN, + CHASECAM_JOYUP, + CHASECAM_JOYMOVELEFT, + CHASECAM_JOYMOVERIGHT, + CHASECAM_JOYZOOMIN, + CHASECAM_JOYZOOMOUT}; this.inputManager = inputManager; if (!invertYaxis) { @@ -271,11 +304,66 @@ public final void registerWithInput(InputManager inputManager) { } inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); - inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, - new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); - - inputManager.addListener(this, inputs); - } + inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, + new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); + + Joystick[] joysticks = inputManager.getJoysticks(); + if (joysticks != null) { + for (Joystick joystick : joysticks) { + mapJoystick(joystick); + } + } + + inputManager.addListener(this, inputs); + inputManager.addJoystickConnectionListener(this); + } + + /** + * Configures joystick input mappings for the chase camera. + * + * @param joystick joystick to map + */ + protected void mapJoystick(Joystick joystick) { + JoystickAxis xAxis = joystick.getAxis(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X); + JoystickAxis yAxis = joystick.getAxis(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_Y); + + if (xAxis == null) { + xAxis = joystick.getXAxis(); + } + if (yAxis == null) { + yAxis = joystick.getYAxis(); + } + + if (xAxis != null) { + xAxis.assignAxis(CHASECAM_JOYMOVERIGHT, CHASECAM_JOYMOVELEFT); + } + if (yAxis != null) { + yAxis.assignAxis(CHASECAM_JOYDOWN, CHASECAM_JOYUP); + } + + JoystickAxis leftTrigger = joystick.getAxis(JoystickAxis.AXIS_XBOX_LEFT_TRIGGER); + if (leftTrigger != null) { + inputManager.addMapping(CHASECAM_JOYZOOMOUT, + new JoyAxisTrigger(joystick.getJoyId(), leftTrigger.getAxisId(), false)); + } + + JoystickAxis rightTrigger = joystick.getAxis(JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER); + if (rightTrigger != null) { + inputManager.addMapping(CHASECAM_JOYZOOMIN, + new JoyAxisTrigger(joystick.getJoyId(), rightTrigger.getAxisId(), false)); + } + } + + @Override + public void onConnected(Joystick joystick) { + if (inputManager != null) { + mapJoystick(joystick); + } + } + + @Override + public void onDisconnected(Joystick joystick) { + } /** * Cleans up the input mappings from the input manager. @@ -287,11 +375,18 @@ public void cleanupWithInput(InputManager mgr) { mgr.deleteMapping(CameraInput.CHASECAM_DOWN); mgr.deleteMapping(CameraInput.CHASECAM_UP); mgr.deleteMapping(CameraInput.CHASECAM_MOVELEFT); - mgr.deleteMapping(CameraInput.CHASECAM_MOVERIGHT); - mgr.deleteMapping(CameraInput.CHASECAM_ZOOMIN); - mgr.deleteMapping(CameraInput.CHASECAM_ZOOMOUT); - mgr.removeListener(this); - } + mgr.deleteMapping(CameraInput.CHASECAM_MOVERIGHT); + mgr.deleteMapping(CameraInput.CHASECAM_ZOOMIN); + mgr.deleteMapping(CameraInput.CHASECAM_ZOOMOUT); + mgr.deleteMapping(CHASECAM_JOYDOWN); + mgr.deleteMapping(CHASECAM_JOYUP); + mgr.deleteMapping(CHASECAM_JOYMOVELEFT); + mgr.deleteMapping(CHASECAM_JOYMOVERIGHT); + mgr.deleteMapping(CHASECAM_JOYZOOMIN); + mgr.deleteMapping(CHASECAM_JOYZOOMOUT); + mgr.removeListener(this); + mgr.removeJoystickConnectionListener(this); + } /** * Sets custom triggers for toggling the rotation of the cam @@ -340,12 +435,16 @@ protected void computePosition() { pos.addLocal(target.getWorldTranslation()); } - //rotate the camera around the target on the horizontal plane - protected void rotateCamera(float value) { - if (!canRotate || !enabled) { - return; - } - rotating = true; + //rotate the camera around the target on the horizontal plane + protected void rotateCamera(float value) { + rotateCamera(value, false); + } + + protected void rotateCamera(float value, boolean forceRotate) { + if ((!forceRotate && !canRotate) || !enabled) { + return; + } + rotating = true; targetRotation += value * rotationSpeed; @@ -372,12 +471,16 @@ protected void zoomCamera(float value) { } } - //rotate the camera around the target on the vertical plane - protected void vRotateCamera(float value) { - if (!canRotate || !enabled) { - return; - } - vRotating = true; + //rotate the camera around the target on the vertical plane + protected void vRotateCamera(float value) { + vRotateCamera(value, false); + } + + protected void vRotateCamera(float value, boolean forceRotate) { + if ((!forceRotate && !canRotate) || !enabled) { + return; + } + vRotating = true; float lastGoodRot = targetVRotation; targetVRotation += value * rotationSpeed; if (targetVRotation > maxVerticalRotation) { diff --git a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java index 1a39ee9820..f5d1076664 100644 --- a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java @@ -54,7 +54,12 @@ * - WASD keys for moving forward/backward and strafing * - QZ keys raise or lower the camera */ -public class FlyByCamera implements AnalogListener, ActionListener { +public class FlyByCamera implements AnalogListener, ActionListener, JoystickConnectionListener { + + private static final String FLYCAM_JOYSTICK_LEFT = "FLYCAM_JoystickLeft"; + private static final String FLYCAM_JOYSTICK_RIGHT = "FLYCAM_JoystickRight"; + private static final String FLYCAM_JOYSTICK_UP = "FLYCAM_JoystickUp"; + private static final String FLYCAM_JOYSTICK_DOWN = "FLYCAM_JoystickDown"; private static final String[] mappings = new String[]{ CameraInput.FLYCAM_LEFT, @@ -74,7 +79,12 @@ public class FlyByCamera implements AnalogListener, ActionListener { CameraInput.FLYCAM_RISE, CameraInput.FLYCAM_LOWER, - CameraInput.FLYCAM_INVERTY + CameraInput.FLYCAM_INVERTY, + + FLYCAM_JOYSTICK_LEFT, + FLYCAM_JOYSTICK_RIGHT, + FLYCAM_JOYSTICK_UP, + FLYCAM_JOYSTICK_DOWN }; /** * camera controlled by this controller (not null) @@ -105,9 +115,10 @@ public class FlyByCamera implements AnalogListener, ActionListener { * drag-to-rotate mode flag */ protected boolean dragToRotate = false; - protected boolean canRotate = false; - protected boolean invertY = false; - protected InputManager inputManager; + protected boolean canRotate = false; + protected boolean invertY = false; + protected InputManager inputManager; + private boolean inputMappingsRegistered; // Reusable temporary objects to reduce allocations during updates private final Matrix3f tempMat = new Matrix3f(); @@ -203,14 +214,21 @@ public float getZoomSpeed() { * * @param enable true to enable, false to disable */ - public void setEnabled(boolean enable) { - if (enabled && !enable) { - if (inputManager != null && (!dragToRotate || (dragToRotate && canRotate))) { - inputManager.setCursorVisible(true); - } - } - enabled = enable; - } + public void setEnabled(boolean enable) { + if (enabled == enable) { + return; + } + if (enabled && !enable) { + if (inputManager != null && (!dragToRotate || (dragToRotate && canRotate))) { + inputManager.setCursorVisible(true); + } + unregisterInputMappings(); + } + enabled = enable; + if (enabled) { + registerInputMappings(); + } + } /** * Checks whether this camera controller is currently enabled. @@ -243,12 +261,12 @@ public boolean isDragToRotate() { * * @param dragToRotate true to enable, false to disable */ - public void setDragToRotate(boolean dragToRotate) { - this.dragToRotate = dragToRotate; - if (inputManager != null) { - inputManager.setCursorVisible(dragToRotate); - } - } + public void setDragToRotate(boolean dragToRotate) { + this.dragToRotate = dragToRotate; + if (inputManager != null) { + inputManager.setCursorVisible(dragToRotate || !isEnabled()); + } + } /** * Registers this controller to receive input events from the specified @@ -257,12 +275,23 @@ public void setDragToRotate(boolean dragToRotate) { * * @param inputManager The InputManager instance to register with (must not be null). */ - public void registerWithInput(InputManager inputManager) { - this.inputManager = inputManager; - - // Mouse and Keyboard Mappings for Rotation - inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true), - new KeyTrigger(KeyInput.KEY_LEFT)); + public void registerWithInput(InputManager inputManager) { + this.inputManager = inputManager; + if (enabled) { + registerInputMappings(); + } else { + inputManager.setCursorVisible(true); + } + inputManager.addJoystickConnectionListener(this); + } + + private void registerInputMappings() { + if (inputManager == null || inputMappingsRegistered) { + return; + } + // Mouse and Keyboard Mappings for Rotation + inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true), + new KeyTrigger(KeyInput.KEY_LEFT)); inputManager.addMapping(CameraInput.FLYCAM_RIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false), new KeyTrigger(KeyInput.KEY_RIGHT)); @@ -291,11 +320,12 @@ public void registerWithInput(InputManager inputManager) { Joystick[] joysticks = inputManager.getJoysticks(); if (joysticks != null && joysticks.length > 0) { - for (Joystick j : joysticks) { - mapJoystick(j); - } - } - } + for (Joystick j : joysticks) { + mapJoystick(j); + } + } + inputMappingsRegistered = true; + } /** * Configures joystick input mappings for the camera controller. This method @@ -304,58 +334,63 @@ public void registerWithInput(InputManager inputManager) { * @param joystick The {@link Joystick} to map (not null). */ protected void mapJoystick(Joystick joystick) { - // Map it differently if there are Z axis - if (joystick.getAxis(JoystickAxis.Z_ROTATION) != null - && joystick.getAxis(JoystickAxis.Z_AXIS) != null) { - - // Make the left stick move - joystick.getXAxis().assignAxis(CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT); - joystick.getYAxis().assignAxis(CameraInput.FLYCAM_BACKWARD, CameraInput.FLYCAM_FORWARD); - - // And the right stick control the camera - joystick.getAxis(JoystickAxis.Z_ROTATION) - .assignAxis(CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP); - joystick.getAxis(JoystickAxis.Z_AXIS) - .assignAxis(CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT); - - // And let the dpad be up and down - joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_RISE, CameraInput.FLYCAM_LOWER); - - if (joystick.getButton("Button 8") != null) { - // Let the standard select button be the y invert toggle - joystick.getButton("Button 8").assignButton(CameraInput.FLYCAM_INVERTY); - } + JoystickAxis xAxis = joystick.getXAxis(); + JoystickAxis yAxis = joystick.getYAxis(); + JoystickAxis rightXAxis = joystick.getAxis(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X); + JoystickAxis rightYAxis = joystick.getAxis(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_Y); - } else { - joystick.getPovXAxis().assignAxis(CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT); - joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_FORWARD, CameraInput.FLYCAM_BACKWARD); - joystick.getXAxis().assignAxis(CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT); - joystick.getYAxis().assignAxis(CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP); + if (xAxis != null) { + xAxis.assignAxis(CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT); } - } - - /** - * Unregisters this controller from its currently associated {@link InputManager}. - */ - public void unregisterInput() { - if (inputManager == null) { - return; + if (yAxis != null) { + yAxis.assignAxis(CameraInput.FLYCAM_BACKWARD, CameraInput.FLYCAM_FORWARD); } - - for (String s : mappings) { - if (inputManager.hasMapping(s)) { - inputManager.deleteMapping(s); - } + if (rightXAxis != null && rightYAxis != null && rightXAxis != xAxis && rightYAxis != yAxis) { + rightYAxis.assignAxis(FLYCAM_JOYSTICK_DOWN, FLYCAM_JOYSTICK_UP); + rightXAxis.assignAxis(FLYCAM_JOYSTICK_RIGHT, FLYCAM_JOYSTICK_LEFT); } - inputManager.removeListener(this); - inputManager.setCursorVisible(!dragToRotate); + } + + @Override + public void onConnected(Joystick joystick) { + if (inputManager != null && inputMappingsRegistered) { + mapJoystick(joystick); + } + } - // Joysticks cannot be "unassigned" in the same way, but mappings are removed with listener. - // Joystick-specific mapping might persist but won't trigger this listener. - inputManager = null; // Clear reference + @Override + public void onDisconnected(Joystick joystick) { } + /** + * Unregisters this controller from its currently associated {@link InputManager}. + */ + public void unregisterInput() { + if (inputManager == null) { + return; + } + + unregisterInputMappings(); + inputManager.removeJoystickConnectionListener(this); + inputManager.setCursorVisible(!dragToRotate); + inputManager = null; // Clear reference + } + + private void unregisterInputMappings() { + if (inputManager == null || !inputMappingsRegistered) { + return; + } + for (String s : mappings) { + if (inputManager.hasMapping(s)) { + inputManager.deleteMapping(s); + } + } + + inputManager.removeListener(this); + inputMappingsRegistered = false; + } + /** * Rotates the camera by the specified amount around the given axis. * @@ -363,7 +398,11 @@ public void unregisterInput() { * @param axis The axis around which to rotate (a unit vector, unaffected). */ protected void rotateCamera(float value, Vector3f axis) { - if (dragToRotate && !canRotate) { + rotateCamera(value, axis, false); + } + + private void rotateCamera(float value, Vector3f axis, boolean forceRotate) { + if (!forceRotate && dragToRotate && !canRotate) { return; // In drag-to-rotate mode, only rotate if canRotate is true. } @@ -479,6 +518,14 @@ public void onAnalog(String name, float value, float tpf) { rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft(tempLeft)); } else if (name.equals(CameraInput.FLYCAM_DOWN)) { rotateCamera(value * (invertY ? -1 : 1), cam.getLeft(tempLeft)); + } else if (name.equals(FLYCAM_JOYSTICK_LEFT)) { + rotateCamera(value, initialUpVec, true); + } else if (name.equals(FLYCAM_JOYSTICK_RIGHT)) { + rotateCamera(-value, initialUpVec, true); + } else if (name.equals(FLYCAM_JOYSTICK_UP)) { + rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft(tempLeft), true); + } else if (name.equals(FLYCAM_JOYSTICK_DOWN)) { + rotateCamera(value * (invertY ? -1 : 1), cam.getLeft(tempLeft), true); } else if (name.equals(CameraInput.FLYCAM_FORWARD)) { moveCamera(value, false); } else if (name.equals(CameraInput.FLYCAM_BACKWARD)) { diff --git a/jme3-core/src/main/java/com/jme3/opencl/Event.java b/jme3-core/src/main/java/com/jme3/input/HapticDevice.java similarity index 62% rename from jme3-core/src/main/java/com/jme3/opencl/Event.java rename to jme3-core/src/main/java/com/jme3/input/HapticDevice.java index 32d78714aa..dc94ae0258 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/Event.java +++ b/jme3-core/src/main/java/com/jme3/input/HapticDevice.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2020 jMonkeyEngine + * Copyright (c) 2009-2026 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,39 +29,39 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package com.jme3.opencl; +package com.jme3.input; /** - * Wrapper for an OpenCL Event object. - * Events are returned from kernel launches and all asynchronous operations. - * They allow us to test whether an action has completed and block until the operation - * is done. - * - * @author shaman + * A device that can produce haptic feedback. */ -public abstract class Event extends AbstractOpenCLObject { - - protected Event(ObjectReleaser releaser) { - super(releaser); - } +public interface HapticDevice { - @Override - public Event register() { - super.register(); - return this; + /** + * Rumbles the device with the given amount. + *

    + * This starts a continuous rumble that keeps playing until + * {@code rumble(0)} or {@link #stopRumble()} is called. + * + * @param amount the amount to rumble, between 0 and 1 + */ + default void rumble(float amount) { + rumble(amount, amount, Float.POSITIVE_INFINITY); } /** - * Waits until the action has finished (blocking). - * This automatically releases the event. + * Rumbles the device with separate high and low frequency amounts for the + * given duration. + * + * @param amountHigh the high frequency amount, between 0 and 1 + * @param amountLow the low frequency amount, between 0 and 1 + * @param duration the duration in seconds */ - public abstract void waitForFinished(); + void rumble(float amountHigh, float amountLow, float duration); /** - * Tests if the action is completed. - * If the action is completed, the event is released. - * - * @return {@code true} if the action is completed + * Stops any rumble currently playing on the device. */ - public abstract boolean isCompleted(); + default void stopRumble() { + rumble(0f); + } } diff --git a/jme3-core/src/main/java/com/jme3/input/InputManager.java b/jme3-core/src/main/java/com/jme3/input/InputManager.java index b45433d1d9..7928f33ffc 100644 --- a/jme3-core/src/main/java/com/jme3/input/InputManager.java +++ b/jme3-core/src/main/java/com/jme3/input/InputManager.java @@ -606,6 +606,20 @@ public boolean hasMapping(String mappingName) { return mappings.containsKey(mappingName); } + + + /** + * Returns true if at least one mapping is registered for the specified + * trigger hash. + * + * @param triggerHash hash returned by {@link Trigger#triggerHashCode()} + * @return true if the trigger hash is registered to at least one mapping + */ + public boolean hasTriggerMapping(int triggerHash) { + ArrayList maps = bindings.get(triggerHash); + return maps != null && !maps.isEmpty(); + } + /** * Deletes a mapping from receiving trigger events. * diff --git a/jme3-core/src/main/java/com/jme3/input/JoyInput.java b/jme3-core/src/main/java/com/jme3/input/JoyInput.java index 0041b86942..8fc0ba7d02 100644 --- a/jme3-core/src/main/java/com/jme3/input/JoyInput.java +++ b/jme3-core/src/main/java/com/jme3/input/JoyInput.java @@ -49,11 +49,36 @@ public interface JoyInput extends Input { /** * Causes the joystick at joyId index to rumble with * the given amount. + *

    + * The rumble continues until this method is called with 0 or + * {@link #stopJoyRumble(int)} is called. * * @param joyId The joystick index * @param amount Rumble amount. Should be between 0 and 1. */ - public void setJoyRumble(int joyId, float amount); + public default void setJoyRumble(int joyId, float amount) { + setJoyRumble(joyId, amount, amount, Float.POSITIVE_INFINITY); + } + + /** + * Causes the joystick at joyId index to rumble with + * separate high and low frequency amounts for the given duration. + * + * @param joyId The joystick index + * @param amountHigh High frequency rumble amount. Should be between 0 and 1. + * @param amountLow Low frequency rumble amount. Should be between 0 and 1. + * @param duration Rumble duration in seconds. + */ + public void setJoyRumble(int joyId, float amountHigh, float amountLow, float duration); + + /** + * Stops any rumble currently playing on the joystick at joyId. + * + * @param joyId the joystick index + */ + public default void stopJoyRumble(int joyId) { + setJoyRumble(joyId, 0f); + } /** * Loads a list of joysticks from the system. diff --git a/jme3-core/src/main/java/com/jme3/input/Joystick.java b/jme3-core/src/main/java/com/jme3/input/Joystick.java index 967422fc6f..b93ce0e65c 100644 --- a/jme3-core/src/main/java/com/jme3/input/Joystick.java +++ b/jme3-core/src/main/java/com/jme3/input/Joystick.java @@ -38,14 +38,8 @@ * * @author Paul Speed, Kirill Vainer */ -public interface Joystick { +public interface Joystick extends HapticDevice { - /** - * Rumbles the joystick for the given amount/magnitude. - * - * @param amount The amount to rumble. Should be between 0 and 1. - */ - public void rumble(float amount); /** * Assign the mapping name to receive events from the given button index diff --git a/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java index 95a7871ae1..af484978bf 100644 --- a/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java +++ b/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java @@ -47,7 +47,7 @@ public class MouseAxisTrigger implements Trigger { /** * Create a new MouseAxisTrigger. - *

    + * * @param mouseAxis Mouse axis. See AXIS_*** constants in {@link MouseInput} * @param negative True if listen to negative axis events, false if * listen to positive axis events. diff --git a/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystick.java b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystick.java new file mode 100644 index 0000000000..e364ef9bd7 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystick.java @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.virtual; + +import com.jme3.asset.AssetManager; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.input.AbstractJoystick; +import com.jme3.input.DefaultJoystickAxis; +import com.jme3.input.DefaultJoystickButton; +import com.jme3.input.InputManager; +import com.jme3.input.JoyInput; +import com.jme3.input.JoystickAxis; +import com.jme3.input.JoystickButton; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListener; +import com.jme3.input.TouchInput; +import com.jme3.input.controls.JoyAxisTrigger; +import com.jme3.input.controls.JoyButtonTrigger; +import com.jme3.input.controls.MouseAxisTrigger; +import com.jme3.input.controls.TouchTrigger; +import com.jme3.input.event.InputEvent; +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import com.jme3.input.virtual.VirtualJoystickLayout.Element; +import com.jme3.input.virtual.VirtualJoystickTheme.TextureKey; +import com.jme3.math.FastMath; +import com.jme3.scene.Node; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; +import com.jme3.ui.Picture; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; + +/** + * A joystick implementation driven by on-screen controls. + */ +public class VirtualJoystick extends AbstractJoystick { + + private static final int AXIS_LEFT_X = 0; + private static final int AXIS_LEFT_Y = 1; + private static final int AXIS_RIGHT_X = 2; + private static final int AXIS_RIGHT_Y = 3; + private static final int AXIS_LEFT_TRIGGER = 4; + private static final int AXIS_RIGHT_TRIGGER = 5; + private static final int AXIS_POV_X_ID = 6; + + private static final String ROOT_NAME = "Virtual Joystick"; + + private final Map axesByLogicalId = new HashMap<>(); + private final Map buttonsByLogicalId = new HashMap<>(); + private final Map captures = new HashMap<>(); + private ArrayDeque events = new ArrayDeque<>(); + private ArrayDeque readyEvents = new ArrayDeque<>(); + private final float[] axisValues = new float[7]; + private final boolean[] buttonValues = new boolean[16]; + private final Object inputLock = new Object(); + private final Element.BoundsSnapshot inputBounds = new Element.BoundsSnapshot(); + + private JoystickAxis xAxis; + private JoystickAxis yAxis; + private JoystickAxis povXAxis; + private volatile boolean enabled = true; + private volatile int buttonStateMask; + private volatile boolean hasEvents; + private volatile VirtualJoystickTheme theme = new VirtualJoystickTheme(); + private volatile VirtualJoystickLayout layout = new VirtualJoystickDynamicLayout(true); + private volatile int visualWidth; + private volatile int visualHeight; + private Node visualRoot; + private Node visualParent; + private BitmapFont font; + + public VirtualJoystick(InputManager inputManager, JoyInput joyInput, int joyId) { + super(inputManager, joyInput, joyId, "Virtual Joystick"); + addAxes(); + addButtons(inputManager); + releaseAllLocked(0L); + } + + /** + * Returns true if this joystick accepts pointer input. + * + * @return true if enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Enables or disables pointer processing for this joystick. + * + * @param enabled true to accept pointer input + */ + public void setEnabled(boolean enabled) { + synchronized (inputLock) { + this.enabled = enabled; + if (!enabled) { + releaseAllLocked(0L); + } + } + } + + public VirtualJoystickTheme getTheme() { + return theme; + } + + public void setTheme(VirtualJoystickTheme theme) { + this.theme = theme == null ? new VirtualJoystickTheme() : theme; + this.theme.markUpdateNeeded(); + } + + public VirtualJoystickLayout getLayout() { + return layout; + } + + public void setLayout(VirtualJoystickLayout layout) { + synchronized (inputLock) { + releaseAllLocked(0L); + this.layout = layout == null ? new VirtualJoystickDynamicLayout(true) : layout; + this.layout.markUpdateNeeded(); + } + } + + public static VirtualJoystickLayout createLayout(String layout) { + if (AppSettings.VIRTUAL_JOYSTICK_LAYOUT_XBOX.equalsIgnoreCase(layout)) { + return new VirtualJoystickXboxLayout(); + } + return new VirtualJoystickDynamicLayout(true); + } + + @Override + public void rumble(float amountHigh, float amountLow, float duration) { + if (JmeSystem.isDeviceRumbleSupported()) { + JmeSystem.rumble(amountHigh, amountLow, duration); + } + } + + @Override + public void stopRumble() { + JmeSystem.stopRumble(); + } + + /** + * Processes a pointer-down event. + * + * @return true if the pointer was captured by a virtual control + */ + public boolean onPointerDown(int pointerId, float x, float y, long time) { + synchronized (inputLock) { + Capture existingCapture = captures.get(pointerId); + if (!enabled || existingCapture != null) { + return existingCapture != null; + } + + for (Element element : layout.getAxisElements()) { + if (element.visible && element.contains(x, y)) { + captures.put(pointerId, new Capture(element, true)); + updateAxisCapture(element, x, y, time); + return true; + } + } + + for (Element element : layout.getButtons()) { + if (element.visible && element.contains(x, y)) { + captures.put(pointerId, new Capture(element, false)); + if (isToggleButton(element.id)) { + pressButton(element.id, !isButtonPressed(element.id), time); + } else { + pressButton(element.id, true, time); + } + return true; + } + } + + return false; + } + } + + /** + * Processes a pointer-move event. + * + * @return true if the pointer is captured by a virtual control + */ + public boolean onPointerMove(int pointerId, float x, float y, long time) { + synchronized (inputLock) { + Capture capture = captures.get(pointerId); + if (capture == null) { + return false; + } + if (capture.axis) { + updateAxisCapture(capture.element, x, y, time); + } + return true; + } + } + + /** + * Processes a pointer-up event. + * + * @return true if the pointer was captured by a virtual control + */ + public boolean onPointerUp(int pointerId, float x, float y, long time) { + synchronized (inputLock) { + Capture capture = captures.remove(pointerId); + if (capture == null) { + return false; + } + if (capture.axis) { + centerAxisCapture(capture.element, time); + } else if (!isToggleButton(capture.element.id)) { + pressButton(capture.element.id, false, time); + } + return true; + } + } + + /** + * Releases all active pointer captures. + * + * @return true if at least one pointer was captured + */ + public boolean onPointerCancel(long time) { + synchronized (inputLock) { + boolean captured = !captures.isEmpty(); + releaseAllLocked(time); + return captured; + } + } + + /** + * Dispatches pending joystick events to the backend listener. + * + * @param listener listener that receives joystick events + */ + public void dispatchEvents(RawInputListener listener) { + if (!hasEvents) { + return; + } + + synchronized (inputLock) { + if (!hasEvents) { + return; + } + if (listener == null) { + events.clear(); + hasEvents = false; + return; + } + ArrayDeque pendingEvents = events; + events = readyEvents; + readyEvents = pendingEvents; + hasEvents = false; + } + + InputEvent event; + while ((event = readyEvents.poll()) != null) { + if (event instanceof JoyAxisEvent) { + listener.onJoyAxisEvent((JoyAxisEvent) event); + } else if (event instanceof JoyButtonEvent) { + listener.onJoyButtonEvent((JoyButtonEvent) event); + } + } + } + + /** + * Synchronizes GUI spatials with the current joystick state. + * + * @param parent GUI node that should contain the controls + * @param assetManager asset manager used to load default textures + * @param width GUI width in pixels + * @param height GUI height in pixels + * @param tpf time per frame + */ + public void updateVisuals(Node parent, AssetManager assetManager, int width, int height, float tpf) { + if (parent == null || assetManager == null || width <= 0 || height <= 0) { + return; + } + if (visualRoot == null) { + visualRoot = new Node(ROOT_NAME); + } + attachVisualRootOnTop(parent); + if (width != visualWidth || height != visualHeight) { + synchronized (inputLock) { + if (width != visualWidth || height != visualHeight) { + visualWidth = width; + visualHeight = height; + releaseAllLocked(0L); + } + } + } + + if (!enabled) { + if (visualRoot.getQuantity() > 0) { + visualRoot.detachAllChildren(); + } + return; + } + + VirtualJoystickTheme currentTheme = theme; + VirtualJoystickLayout currentLayout = layout; + currentLayout.update(this); + boolean themeUpdateNeeded = currentTheme.isUpdateNeeded(); + if (themeUpdateNeeded || currentLayout.isUpdateNeeded()) { + clearVisuals(currentLayout); + if (themeUpdateNeeded) { + font = null; + } + currentTheme.clearUpdateNeeded(); + currentLayout.clearUpdateNeeded(); + } + String fontPath = currentTheme.getFontPath(); + if (font == null && fontPath != null) { + font = assetManager.loadFont(fontPath); + } + + float scale = currentLayout.getScale(); + + for (Element element : currentLayout.getButtons()) { + syncElement(element, assetManager, width, height, scale); + } + for (Element element : currentLayout.getAxisElements()) { + syncElement(element, assetManager, width, height, scale); + } + } + + private void attachVisualRootOnTop(Node parent) { + if (visualParent != parent || visualRoot.getParent() != parent) { + visualRoot.removeFromParent(); + parent.attachChild(visualRoot); + visualParent = parent; + return; + } + + int childIndex = parent.getChildIndex(visualRoot); + int topIndex = parent.getQuantity() - 1; + if (childIndex >= 0 && childIndex < topIndex) { + visualRoot.removeFromParent(); + parent.attachChild(visualRoot); + } + } + + @Override + public JoystickAxis getXAxis() { + return xAxis; + } + + @Override + public JoystickAxis getYAxis() { + return yAxis; + } + + @Override + public JoystickAxis getPovXAxis() { + return povXAxis; + } + + @Override + public JoystickAxis getPovYAxis() { + return null; + } + + public boolean hasInputBindings() { + for (JoystickAxis axis : getAxes()) { + if (isAxisBound(axis.getLogicalId())) { + return true; + } + } + for (JoystickButton button : getButtons()) { + if (isButtonBound(button.getLogicalId())) { + return true; + } + } + return false; + } + + public boolean isAxisBound(String logicalId) { + JoystickAxis axis = axesByLogicalId.get(logicalId); + if (axis == null) { + return false; + } + InputManager manager = getInputManager(); + return manager != null + && (manager.hasTriggerMapping(JoyAxisTrigger.joyAxisHash(getJoyId(), axis.getAxisId(), false)) + || manager.hasTriggerMapping(JoyAxisTrigger.joyAxisHash(getJoyId(), axis.getAxisId(), true))); + } + + public boolean isButtonBound(String logicalId) { + JoystickButton button = buttonsByLogicalId.get(logicalId); + if (button == null) { + return false; + } + InputManager manager = getInputManager(); + return manager != null + && manager.hasTriggerMapping(JoyButtonTrigger.joyButtonHash(getJoyId(), button.getButtonId())); + } + + public boolean hasPointerLookBindings() { + InputManager manager = getInputManager(); + if (manager == null) { + return false; + } + boolean mouseLook = hasMouseAxisBinding(manager, MouseInput.AXIS_X) + && hasMouseAxisBinding(manager, MouseInput.AXIS_Y); + boolean touchLook = manager.hasTriggerMapping(TouchTrigger.touchHash(TouchInput.ALL)); + return touchLook || (mouseLook && manager.isSimulateMouse()); + } + + private boolean hasMouseAxisBinding(InputManager manager, int axis) { + return manager.hasTriggerMapping(MouseAxisTrigger.mouseAxisHash(axis, false)) + || manager.hasTriggerMapping(MouseAxisTrigger.mouseAxisHash(axis, true)); + } + + private void addAxes() { + xAxis = addAxis(AXIS_LEFT_X, "LEFT THUMB STICK (X)", JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_X); + yAxis = addAxis(AXIS_LEFT_Y, "LEFT THUMB STICK (Y)", JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_Y); + addAxis(AXIS_RIGHT_X, "RIGHT THUMB STICK (X)", JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X); + addAxis(AXIS_RIGHT_Y, "RIGHT THUMB STICK (Y)", JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_Y); + addAxis(AXIS_LEFT_TRIGGER, "LEFT TRIGGER", JoystickAxis.AXIS_XBOX_LEFT_TRIGGER); + addAxis(AXIS_RIGHT_TRIGGER, "RIGHT TRIGGER", JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER); + povXAxis = addAxis(AXIS_POV_X_ID, JoystickAxis.POV_X, JoystickAxis.POV_X); + } + + private JoystickAxis addAxis(int id, String name, String logicalId) { + JoystickAxis axis = new DefaultJoystickAxis(getInputManager(), this, id, name, logicalId, true, false, 0f); + axesByLogicalId.put(logicalId, axis); + super.addAxis(axis); + return axis; + } + + private void addButtons(InputManager inputManager) { + addButton(inputManager, 0, "A", JoystickButton.BUTTON_XBOX_A); + addButton(inputManager, 1, "B", JoystickButton.BUTTON_XBOX_B); + addButton(inputManager, 2, "X", JoystickButton.BUTTON_XBOX_X); + addButton(inputManager, 3, "Y", JoystickButton.BUTTON_XBOX_Y); + addButton(inputManager, 4, "LB", JoystickButton.BUTTON_XBOX_LB); + addButton(inputManager, 5, "RB", JoystickButton.BUTTON_XBOX_RB); + addButton(inputManager, 6, "LT", JoystickButton.BUTTON_XBOX_LT); + addButton(inputManager, 7, "RT", JoystickButton.BUTTON_XBOX_RT); + addButton(inputManager, 8, "BACK", JoystickButton.BUTTON_XBOX_BACK); + addButton(inputManager, 9, "START", JoystickButton.BUTTON_XBOX_START); + addButton(inputManager, 10, "L3", JoystickButton.BUTTON_XBOX_L3); + addButton(inputManager, 11, "R3", JoystickButton.BUTTON_XBOX_R3); + addButton(inputManager, 12, "D-PAD UP", JoystickButton.BUTTON_XBOX_DPAD_UP); + addButton(inputManager, 13, "D-PAD DOWN", JoystickButton.BUTTON_XBOX_DPAD_DOWN); + addButton(inputManager, 14, "D-PAD LEFT", JoystickButton.BUTTON_XBOX_DPAD_LEFT); + addButton(inputManager, 15, "D-PAD RIGHT", JoystickButton.BUTTON_XBOX_DPAD_RIGHT); + } + + private void addButton(InputManager inputManager, int id, String name, String logicalId) { + JoystickButton button = new DefaultJoystickButton(inputManager, this, id, name, logicalId); + buttonsByLogicalId.put(logicalId, button); + super.addButton(button); + } + + private void updateAxisCapture(Element element, float x, float y, long time) { + element.copyBoundsTo(inputBounds); + float radius = inputBounds.size * 0.5f; + if (radius <= 0f) { + return; + } + float dx = x - inputBounds.x; + float dy = y - inputBounds.y; + float length = FastMath.sqrt(dx * dx + dy * dy); + if (length > radius && length > 0f) { + dx *= radius / length; + dy *= radius / length; + } + + float valueX = FastMath.clamp(dx / radius, -1f, 1f); + float valueY = FastMath.clamp(dy / radius, -1f, 1f); + element.nubX = valueX; + element.nubY = valueY; + + setAxisValue(element.id, valueX, time); + setAxisValue(element.yAxisLogicalId, -valueY, time); + } + + private void centerAxisCapture(Element element, long time) { + element.nubX = 0f; + element.nubY = 0f; + setAxisValue(element.id, 0f, time); + setAxisValue(element.yAxisLogicalId, 0f, time); + } + + private void pressButton(String logicalId, boolean pressed, long time) { + JoystickButton button = buttonsByLogicalId.get(logicalId); + if (button == null) { + return; + } + int buttonId = button.getButtonId(); + if (buttonValues[buttonId] == pressed) { + return; + } + buttonValues[buttonId] = pressed; + if (pressed) { + buttonStateMask |= 1 << buttonId; + } else { + buttonStateMask &= ~(1 << buttonId); + } + JoyButtonEvent event = new JoyButtonEvent(button, pressed); + event.setTime(time); + enqueueEvent(event); + + if (JoystickButton.BUTTON_XBOX_LT.equals(logicalId)) { + setAxisValue(JoystickAxis.AXIS_XBOX_LEFT_TRIGGER, pressed ? 1f : 0f, time); + } else if (JoystickButton.BUTTON_XBOX_RT.equals(logicalId)) { + setAxisValue(JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER, pressed ? 1f : 0f, time); + } else if (isDpad(logicalId)) { + updatePovXAxis(time); + } + } + + private boolean isButtonPressed(String logicalId) { + JoystickButton button = buttonsByLogicalId.get(logicalId); + return button != null && buttonValues[button.getButtonId()]; + } + + private boolean isToggleButton(String logicalId) { + return JoystickButton.BUTTON_XBOX_L3.equals(logicalId) + || JoystickButton.BUTTON_XBOX_R3.equals(logicalId); + } + + private boolean isDpad(String logicalId) { + return JoystickButton.BUTTON_XBOX_DPAD_UP.equals(logicalId) + || JoystickButton.BUTTON_XBOX_DPAD_DOWN.equals(logicalId) + || JoystickButton.BUTTON_XBOX_DPAD_LEFT.equals(logicalId) + || JoystickButton.BUTTON_XBOX_DPAD_RIGHT.equals(logicalId); + } + + private void updatePovXAxis(long time) { + float x = 0f; + if (buttonValues[buttonsByLogicalId.get(JoystickButton.BUTTON_XBOX_DPAD_LEFT).getButtonId()]) { + x -= 1f; + } + if (buttonValues[buttonsByLogicalId.get(JoystickButton.BUTTON_XBOX_DPAD_RIGHT).getButtonId()]) { + x += 1f; + } + setAxisValue(JoystickAxis.POV_X, x, time); + } + + private void setAxisValue(String logicalId, float value, long time) { + JoystickAxis axis = axesByLogicalId.get(logicalId); + if (axis == null) { + return; + } + value = FastMath.clamp(value, -1f, 1f); + int axisId = axis.getAxisId(); + if (axisValues[axisId] == value) { + return; + } + axisValues[axisId] = value; + JoyAxisEvent event = new JoyAxisEvent(axis, value, value); + event.setTime(time); + enqueueEvent(event); + } + + private void enqueueEvent(InputEvent event) { + events.add(event); + hasEvents = true; + } + + private void releaseAllLocked(long time) { + captures.clear(); + for (Element element : layout.getAxisElements()) { + centerAxisCapture(element, time); + } + for (String logicalId : buttonsByLogicalId.keySet()) { + pressButton(logicalId, false, time); + } + } + + private void syncElement(Element element, AssetManager assetManager, int width, int height, float scale) { + if (element == null) { + return; + } + if (!element.visible) { + if (element.node != null && element.node.getParent() != null) { + element.node.removeFromParent(); + } + return; + } + if (element.node == null) { + createVisual(element, assetManager); + } + if (element.node.getParent() != visualRoot) { + visualRoot.attachChild(element.node); + } + JoystickButton button = buttonsByLogicalId.get(element.id); + boolean pressed = button != null && (buttonStateMask & (1 << button.getButtonId())) != 0; + element.sync(width, height, scale, pressed); + } + + private void createVisual(Element element, AssetManager assetManager) { + element.node = new Node("Virtual Joystick " + element.id); + element.base = new Picture(element.id); + TextureKey baseTextureKey; + TextureKey nubTextureKey; + TextureKey iconTextureKey; + String label; + baseTextureKey = element.textureKey; + nubTextureKey = element.nubTextureKey; + iconTextureKey = element.iconTextureKey; + label = element.label; + element.base.setImage(assetManager, texture(baseTextureKey), true); + element.node.attachChild(element.base); + + if (nubTextureKey != null) { + element.nub = new Picture(element.id + " Nub"); + element.nub.setImage(assetManager, texture(nubTextureKey), true); + element.node.attachChild(element.nub); + } + + if (iconTextureKey != null) { + element.icon = new Picture(element.id + " Icon"); + element.icon.setImage(assetManager, texture(iconTextureKey), true); + element.node.attachChild(element.icon); + } + + if (font != null && !label.isEmpty()) { + element.text = new BitmapText(font, false); + element.text.setText(label); + element.node.attachChild(element.text); + } + } + + private String texture(TextureKey textureKey) { + String texturePath = theme.getTexture(textureKey); + if (texturePath == null) { + throw new IllegalStateException("No virtual joystick texture bound for key: " + textureKey); + } + return texturePath; + } + + private void clearVisuals(VirtualJoystickLayout layout) { + for (Element element : layout.getButtons()) { + element.clearVisuals(); + } + for (Element element : layout.getAxisElements()) { + element.clearVisuals(); + } + if (visualRoot != null) { + visualRoot.detachAllChildren(); + } + } + + private static final class Capture { + final Element element; + final boolean axis; + + Capture(Element element, boolean axis) { + this.element = element; + this.axis = axis; + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickDynamicLayout.java b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickDynamicLayout.java new file mode 100644 index 0000000000..a8f972d0b5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickDynamicLayout.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.virtual; + +import com.jme3.input.JoystickAxis; +import com.jme3.input.JoystickButton; + +/** + * Xbox-like layout that only shows controls currently bound in the input manager. + */ +public class VirtualJoystickDynamicLayout extends VirtualJoystickXboxLayout { + + private final boolean useTouchForRightAnalog; + private long visibilityMask = -1L; + + public VirtualJoystickDynamicLayout(boolean useTouchForRightAnalog) { + this.useTouchForRightAnalog = useTouchForRightAnalog; + } + + @Override + public void update(VirtualJoystick joystick) { + if (joystick == null) { + return; + } + + boolean leftTrigger = joystick.isButtonBound(JoystickButton.BUTTON_XBOX_LT) + || joystick.isAxisBound(JoystickAxis.AXIS_XBOX_LEFT_TRIGGER); + boolean rightTrigger = joystick.isButtonBound(JoystickButton.BUTTON_XBOX_RT) + || joystick.isAxisBound(JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER); + + boolean leftStick = joystick.isAxisBound(JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_X) + || joystick.isAxisBound(JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_Y); + boolean rightStick = joystick.isAxisBound(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X) + || joystick.isAxisBound(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_Y); + if (useTouchForRightAnalog && joystick.hasPointerLookBindings()) { + rightStick = false; + } + + long nextVisibilityMask = 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_A) ? 1L : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_B) ? 1L << 1 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_X) ? 1L << 2 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_Y) ? 1L << 3 : 0L; + nextVisibilityMask |= leftTrigger ? 1L << 4 : 0L; + nextVisibilityMask |= rightTrigger ? 1L << 5 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_LB) ? 1L << 6 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_RB) ? 1L << 7 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_BACK) ? 1L << 8 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_START) ? 1L << 9 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_L3) ? 1L << 10 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_R3) ? 1L << 11 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_DPAD_UP) ? 1L << 12 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_DPAD_DOWN) ? 1L << 13 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_DPAD_LEFT) + || joystick.isAxisBound(JoystickAxis.POV_X) ? 1L << 14 : 0L; + nextVisibilityMask |= joystick.isButtonBound(JoystickButton.BUTTON_XBOX_DPAD_RIGHT) + || joystick.isAxisBound(JoystickAxis.POV_X) ? 1L << 15 : 0L; + nextVisibilityMask |= leftStick ? 1L << 16 : 0L; + nextVisibilityMask |= rightStick ? 1L << 17 : 0L; + + if (visibilityMask == nextVisibilityMask) { + return; + } + visibilityMask = nextVisibilityMask; + + setButtonVisible(JoystickButton.BUTTON_XBOX_A, (nextVisibilityMask & 1L) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_B, (nextVisibilityMask & (1L << 1)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_X, (nextVisibilityMask & (1L << 2)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_Y, (nextVisibilityMask & (1L << 3)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_LT, (nextVisibilityMask & (1L << 4)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_RT, (nextVisibilityMask & (1L << 5)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_LB, (nextVisibilityMask & (1L << 6)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_RB, (nextVisibilityMask & (1L << 7)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_BACK, (nextVisibilityMask & (1L << 8)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_START, (nextVisibilityMask & (1L << 9)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_L3, (nextVisibilityMask & (1L << 10)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_R3, (nextVisibilityMask & (1L << 11)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_DPAD_UP, (nextVisibilityMask & (1L << 12)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_DPAD_DOWN, (nextVisibilityMask & (1L << 13)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_DPAD_LEFT, (nextVisibilityMask & (1L << 14)) != 0L); + setButtonVisible(JoystickButton.BUTTON_XBOX_DPAD_RIGHT, (nextVisibilityMask & (1L << 15)) != 0L); + setAxisVisible(JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_X, leftStick); + setAxisVisible(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X, rightStick); + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickLayout.java b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickLayout.java new file mode 100644 index 0000000000..9b84f62133 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickLayout.java @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.virtual; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.font.BitmapText; +import com.jme3.input.virtual.VirtualJoystickTheme.TextureKey; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.scene.Node; +import com.jme3.ui.Picture; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Virtual joystick controls layout. + */ +public abstract class VirtualJoystickLayout implements Savable { + + private final Map buttons = new LinkedHashMap<>(); + private final Map axisElements = new LinkedHashMap<>(); + private volatile float scale = 1.15f; + private transient volatile boolean updateNeeded = true; + private transient volatile Element[] buttonSnapshot = new Element[0]; + private transient volatile Element[] axisSnapshot = new Element[0]; + protected VirtualJoystickLayout() { + } + + protected final synchronized void addButtonElement(String logicalId, String label, float x, float y, float size, + TextureKey textureKey) { + buttons.put(logicalId, new Element(logicalId, label, x, y, size, textureKey)); + layoutChanged(); + } + + protected final synchronized void addButtonElement(String logicalId, String label, float x, float y, float shortOffsetX, + float shortOffsetY, float size, TextureKey textureKey) { + buttons.put(logicalId, new Element(logicalId, label, x, y, size, textureKey) + .setShortOffset(shortOffsetX, shortOffsetY)); + layoutChanged(); + } + + protected final synchronized void addButtonElement(String logicalId, String label, float x, float y, float shortOffsetX, + float shortOffsetY, float size, TextureKey textureKey, TextureKey iconTextureKey) { + buttons.put(logicalId, new Element(logicalId, label, x, y, size, textureKey) + .setShortOffset(shortOffsetX, shortOffsetY) + .setIconTextureKey(iconTextureKey)); + layoutChanged(); + } + + protected final synchronized void addButtonElement(String logicalId, String label, float x, float y, float size, float aspect, + TextureKey textureKey) { + buttons.put(logicalId, new Element(logicalId, label, x, y, size, textureKey).setAspect(aspect)); + layoutChanged(); + } + + protected final synchronized void addButtonElement(String logicalId, String label, float x, float y, float size, float aspect, + TextureKey textureKey, TextureKey iconTextureKey) { + buttons.put(logicalId, new Element(logicalId, label, x, y, size, textureKey) + .setAspect(aspect) + .setIconTextureKey(iconTextureKey)); + layoutChanged(); + } + + protected final synchronized void addAxisElement(String xAxisLogicalId, String yAxisLogicalId, String label, float x, float y, + float size, TextureKey textureKey, TextureKey nubTextureKey) { + axisElements.put(xAxisLogicalId, new Element(xAxisLogicalId, label, x, y, size, textureKey) + .setYAxisLogicalId(yAxisLogicalId) + .setNubTextureKey(nubTextureKey)); + layoutChanged(); + } + + protected final synchronized void setButtonPosition(String logicalId, float x, float y) { + element(buttons, logicalId).setPosition(x, y); + markUpdateNeeded(); + } + + public synchronized Vector2f getButtonPosition(String logicalId) { + Element element = element(buttons, logicalId); + return new Vector2f(element.positionX, element.positionY); + } + + protected final synchronized void setButtonVisible(String logicalId, boolean visible) { + Element element = element(buttons, logicalId); + if (element.visible == visible) { + return; + } + element.visible = visible; + markUpdateNeeded(); + } + + public synchronized boolean isButtonVisible(String logicalId) { + return element(buttons, logicalId).visible; + } + + protected final synchronized void setAxisPosition(String logicalId, float x, float y) { + axisElement(logicalId).setPosition(x, y); + markUpdateNeeded(); + } + + public synchronized Vector2f getAxisPosition(String logicalId) { + Element element = axisElement(logicalId); + return new Vector2f(element.positionX, element.positionY); + } + + protected final synchronized void setAxisVisible(String logicalId, boolean visible) { + Element element = axisElement(logicalId); + if (element.visible == visible) { + return; + } + element.visible = visible; + markUpdateNeeded(); + } + + public synchronized boolean isAxisVisible(String logicalId) { + return axisElement(logicalId).visible; + } + + protected final synchronized void setButtonSize(String logicalId, float size) { + Element element = element(buttons, logicalId); + element.size = FastMath.clamp(size, 0.01f, 1f); + markUpdateNeeded(); + } + + protected final synchronized void setButtonTextureKey(String logicalId, TextureKey textureKey) { + Element element = element(buttons, logicalId); + element.textureKey = textureKey; + markUpdateNeeded(); + } + + protected final synchronized void setButtonIconTextureKey(String logicalId, TextureKey iconTextureKey) { + Element element = element(buttons, logicalId); + element.iconTextureKey = iconTextureKey; + markUpdateNeeded(); + } + + protected final synchronized void setAxisSize(String logicalId, float size) { + Element element = axisElement(logicalId); + element.size = FastMath.clamp(size, 0.01f, 1f); + markUpdateNeeded(); + } + + protected final synchronized void setAxisTextureKey(String logicalId, TextureKey textureKey) { + Element element = axisElement(logicalId); + element.textureKey = textureKey; + markUpdateNeeded(); + } + + protected final synchronized void setAxisNubTextureKey(String logicalId, TextureKey nubTextureKey) { + Element element = axisElement(logicalId); + element.nubTextureKey = nubTextureKey; + markUpdateNeeded(); + } + + protected final synchronized void setScale(float scale) { + this.scale = FastMath.clamp(scale, 0.25f, 3f); + markUpdateNeeded(); + } + + public float getScale() { + return scale; + } + + boolean isUpdateNeeded() { + return updateNeeded; + } + + void markUpdateNeeded() { + updateNeeded = true; + } + + void clearUpdateNeeded() { + updateNeeded = false; + } + + Element[] getButtons() { + return buttonSnapshot; + } + + Element[] getAxisElements() { + return axisSnapshot; + } + + public void update(VirtualJoystick joystick) { + } + + public synchronized Element getButtonElement(String logicalId) { + return buttons.get(logicalId); + } + + public synchronized Element getAxisElement(String logicalId) { + return findAxisElement(logicalId); + } + + private Element axisElement(String logicalId) { + Element element = findAxisElement(logicalId); + if (element == null) { + throw new IllegalArgumentException("Unknown virtual joystick axis element: " + logicalId); + } + return element; + } + + private Element findAxisElement(String logicalId) { + Element element = axisElements.get(logicalId); + if (element != null) { + return element; + } + for (Element axisElement : axisElements.values()) { + if (logicalId.equals(axisElement.yAxisLogicalId)) { + return axisElement; + } + } + return null; + } + + private Element element(Map elements, String logicalId) { + Element element = elements.get(logicalId); + if (element == null) { + throw new IllegalArgumentException("Unknown virtual joystick element: " + logicalId); + } + return element; + } + + @Override + public synchronized void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(scale, "scale", 1.15f); + capsule.write(buttons.values().toArray(new Element[0]), "buttons", null); + capsule.write(axisElements.values().toArray(new Element[0]), "axes", null); + } + + @Override + public synchronized void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + scale = capsule.readFloat("scale", 1.15f); + buttons.clear(); + axisElements.clear(); + Savable[] readButtons = capsule.readSavableArray("buttons", null); + if (readButtons != null) { + for (Savable savable : readButtons) { + Element element = (Element) savable; + buttons.put(element.id, element); + } + } + Savable[] readAxes = capsule.readSavableArray("axes", null); + if (readAxes != null) { + for (Savable savable : readAxes) { + Element element = (Element) savable; + axisElements.put(element.id, element); + } + } + rebuildSnapshots(); + markUpdateNeeded(); + } + + private void layoutChanged() { + rebuildSnapshots(); + markUpdateNeeded(); + } + + private void rebuildSnapshots() { + buttonSnapshot = buttons.values().toArray(new Element[0]); + axisSnapshot = axisElements.values().toArray(new Element[0]); + } + + public static class Element implements Savable { + private static final ColorRGBA DEFAULT_COLOR = new ColorRGBA(1f, 1f, 1f, 0.72f); + + volatile String id; + volatile String label; + volatile String yAxisLogicalId; + volatile float positionX; + volatile float positionY; + volatile TextureKey textureKey; + volatile TextureKey nubTextureKey; + volatile TextureKey iconTextureKey; + volatile float size; + volatile float aspect = 1f; + volatile float shortOffsetX; + volatile float shortOffsetY; + volatile boolean visible = true; + private transient volatile Bounds bounds = Bounds.EMPTY; + transient volatile float nubX; + transient volatile float nubY; + transient Node node; + transient Picture base; + transient Picture nub; + transient Picture icon; + transient BitmapText text; + private transient boolean baseGeometrySynced; + private transient boolean baseColorSynced; + private transient boolean nubGeometrySynced; + private transient boolean iconGeometrySynced; + private transient boolean iconColorSynced; + private transient boolean textGeometrySynced; + private transient boolean textColorSynced; + private transient boolean nodePositionSynced; + private transient boolean lastPressed; + private transient float lastBaseWidth; + private transient float lastBaseHeight; + private transient float lastBaseX; + private transient float lastBaseY; + private transient float lastNubSize; + private transient float lastNubX; + private transient float lastNubY; + private transient float lastIconSize; + private transient float lastTextSize; + private transient float lastNodeX; + private transient float lastNodeY; + + public Element() { + } + + public Element(String id, String label, float x, float y, float size, TextureKey textureKey) { + this.id = id; + this.label = label == null ? "" : label; + this.size = size; + this.textureKey = textureKey; + setPosition(x, y); + } + + synchronized Element setPosition(float x, float y) { + positionX = FastMath.clamp(x, 0f, 1f); + positionY = FastMath.clamp(y, 0f, 1f); + shortOffsetX = 0f; + shortOffsetY = 0f; + return this; + } + + synchronized Element setShortOffset(float x, float y) { + shortOffsetX = x; + shortOffsetY = y; + return this; + } + + synchronized Element setAspect(float aspect) { + this.aspect = aspect; + return this; + } + + synchronized Element setYAxisLogicalId(String yAxisLogicalId) { + this.yAxisLogicalId = yAxisLogicalId; + return this; + } + + synchronized Element setNubTextureKey(TextureKey nubTextureKey) { + this.nubTextureKey = nubTextureKey; + return this; + } + + synchronized Element setIconTextureKey(TextureKey iconTextureKey) { + this.iconTextureKey = iconTextureKey; + return this; + } + + boolean contains(float x, float y) { + Bounds current = bounds; + return Math.abs(x - current.x) <= current.width * 0.5f + && Math.abs(y - current.y) <= current.height * 0.5f; + } + + void copyBoundsTo(BoundsSnapshot target) { + Bounds current = bounds; + target.x = current.x; + target.y = current.y; + target.size = current.size; + target.width = current.width; + target.height = current.height; + } + + void sync(int width, int height, float scale, boolean pressed) { + float shortSide = Math.min(width, height); + float scaledShortSide = shortSide * scale; + float pixelSize = Math.max(scaledShortSide * size, 1f); + float pixelWidth = pixelSize * aspect; + float pixelHeight = pixelSize; + float pixelX = positionX * width + shortOffsetX * scaledShortSide; + float pixelY = positionY * height + shortOffsetY * scaledShortSide; + if (pixelWidth < width) { + pixelX = FastMath.clamp(pixelX, pixelWidth * 0.5f, width - pixelWidth * 0.5f); + } else { + pixelX = width * 0.5f; + } + if (pixelHeight < height) { + pixelY = FastMath.clamp(pixelY, pixelHeight * 0.5f, height - pixelHeight * 0.5f); + } else { + pixelY = height * 0.5f; + } + float baseX = -pixelWidth * 0.5f; + float baseY = -pixelHeight * 0.5f; + if (!baseGeometrySynced || lastBaseWidth != pixelWidth) { + base.setWidth(pixelWidth); + lastBaseWidth = pixelWidth; + } + if (!baseGeometrySynced || lastBaseHeight != pixelHeight) { + base.setHeight(pixelHeight); + lastBaseHeight = pixelHeight; + } + if (!baseGeometrySynced || lastBaseX != baseX || lastBaseY != baseY) { + base.setPosition(baseX, baseY); + lastBaseX = baseX; + lastBaseY = baseY; + } + baseGeometrySynced = true; + if (!baseColorSynced || lastPressed != pressed) { + setColor(base, pressed ? ColorRGBA.White : DEFAULT_COLOR); + lastPressed = pressed; + baseColorSynced = true; + } + + if (nub != null) { + float nubSize = pixelHeight * 0.42f; + float nubPixelX = (nubX * pixelHeight * 0.32f) - nubSize * 0.5f; + float nubPixelY = (nubY * pixelHeight * 0.32f) - nubSize * 0.5f; + if (!nubGeometrySynced || lastNubSize != nubSize) { + nub.setWidth(nubSize); + nub.setHeight(nubSize); + lastNubSize = nubSize; + } + if (!nubGeometrySynced || lastNubX != nubPixelX || lastNubY != nubPixelY) { + nub.setPosition(nubPixelX, nubPixelY); + lastNubX = nubPixelX; + lastNubY = nubPixelY; + } + nubGeometrySynced = true; + } + + if (icon != null) { + float iconSize = pixelHeight * (aspect > 1f ? 0.38f : 0.46f); + if (!iconGeometrySynced || lastIconSize != iconSize) { + icon.setWidth(iconSize); + icon.setHeight(iconSize); + icon.setPosition(-iconSize * 0.5f, -iconSize * 0.5f); + lastIconSize = iconSize; + iconGeometrySynced = true; + } + if (!iconColorSynced) { + setColor(icon, ColorRGBA.White); + iconColorSynced = true; + } + } + + if (text != null) { + float textSize = pixelHeight * (label.length() > 2 ? 0.2f : 0.32f); + if (!textGeometrySynced || lastTextSize != textSize) { + text.setSize(textSize); + lastTextSize = textSize; + text.setLocalTranslation(-text.getLineWidth() * 0.5f, + text.getLineHeight() * 0.48f, 1f); + textGeometrySynced = true; + } + if (!textColorSynced) { + text.setColor(ColorRGBA.White); + textColorSynced = true; + } + } + + if (!nodePositionSynced || lastNodeX != pixelX || lastNodeY != pixelY) { + node.setLocalTranslation(pixelX, pixelY, 0f); + lastNodeX = pixelX; + lastNodeY = pixelY; + nodePositionSynced = true; + } + Bounds current = bounds; + if (current.x != pixelX || current.y != pixelY || current.size != pixelSize + || current.width != pixelWidth || current.height != pixelHeight) { + publishBounds(pixelX, pixelY, pixelSize, pixelWidth, pixelHeight); + } + } + + synchronized void clearVisuals() { + if (node != null) { + node.removeFromParent(); + } + node = null; + base = null; + nub = null; + icon = null; + text = null; + publishBounds(0f, 0f, 0f, 0f, 0f); + clearSyncState(); + } + + private void setColor(Picture picture, ColorRGBA color) { + if (picture.getMaterial() != null) { + picture.getMaterial().setColor("Color", color); + } + } + + private void clearSyncState() { + baseGeometrySynced = false; + baseColorSynced = false; + nubGeometrySynced = false; + iconGeometrySynced = false; + iconColorSynced = false; + textGeometrySynced = false; + textColorSynced = false; + nodePositionSynced = false; + } + + private void publishBounds(float x, float y, float size, float width, float height) { + bounds = x == 0f && y == 0f && size == 0f && width == 0f && height == 0f + ? Bounds.EMPTY + : new Bounds(x, y, size, width, height); + } + + private static final class Bounds { + static final Bounds EMPTY = new Bounds(0f, 0f, 0f, 0f, 0f); + + final float x; + final float y; + final float size; + final float width; + final float height; + + Bounds(float x, float y, float size, float width, float height) { + this.x = x; + this.y = y; + this.size = size; + this.width = width; + this.height = height; + } + } + + static final class BoundsSnapshot { + float x; + float y; + float size; + float width; + float height; + } + + @Override + public synchronized void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(id, "id", null); + capsule.write(label, "label", ""); + capsule.write(yAxisLogicalId, "yAxisLogicalId", null); + capsule.write(new Vector2f(positionX, positionY), "position", null); + capsule.write(textureKey, "textureKey", null); + capsule.write(nubTextureKey, "nubTextureKey", null); + capsule.write(iconTextureKey, "iconTextureKey", null); + capsule.write(size, "size", 0f); + capsule.write(aspect, "aspect", 1f); + capsule.write(shortOffsetX, "shortOffsetX", 0f); + capsule.write(shortOffsetY, "shortOffsetY", 0f); + capsule.write(visible, "visible", true); + } + + @Override + public synchronized void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + id = capsule.readString("id", null); + label = capsule.readString("label", ""); + yAxisLogicalId = capsule.readString("yAxisLogicalId", null); + Vector2f position = (Vector2f) capsule.readSavable("position", new Vector2f()); + positionX = position.x; + positionY = position.y; + textureKey = capsule.readEnum("textureKey", TextureKey.class, null); + nubTextureKey = capsule.readEnum("nubTextureKey", TextureKey.class, null); + iconTextureKey = capsule.readEnum("iconTextureKey", TextureKey.class, null); + size = capsule.readFloat("size", 0f); + aspect = capsule.readFloat("aspect", 1f); + shortOffsetX = capsule.readFloat("shortOffsetX", 0f); + shortOffsetY = capsule.readFloat("shortOffsetY", 0f); + visible = capsule.readBoolean("visible", true); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickTheme.java b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickTheme.java new file mode 100644 index 0000000000..cb37a9333d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickTheme.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.virtual; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.util.EnumMap; +import java.util.Map; + +/** + * Virtual joystick look and feel + */ +public class VirtualJoystickTheme implements Savable { + + private static final String DEFAULT_FONT = "Interface/Fonts/Default.j3o"; + + public enum TextureKey { + BUTTON, + BUTTON_WIDE, + BUTTON_A_ICON, + BUTTON_B_ICON, + BUTTON_X_ICON, + BUTTON_Y_ICON, + BUTTON_BACK_ICON, + BUTTON_START_ICON, + STICK_PAD, + STICK_NUB, + DPAD_UP, + DPAD_DOWN, + DPAD_LEFT, + DPAD_RIGHT + } + + private final Map textures = new EnumMap<>(TextureKey.class); + private volatile String fontPath = DEFAULT_FONT; + private transient volatile boolean updateNeeded = true; + + public VirtualJoystickTheme() { + resetToDefault(); + } + + public synchronized final void resetToDefault() { + textures.clear(); + fontPath = DEFAULT_FONT; + textures.put(TextureKey.BUTTON, "Common/VirtualJoystick/button_circle.png"); + textures.put(TextureKey.BUTTON_WIDE, "Common/VirtualJoystick/button_circle_wide.png"); + textures.put(TextureKey.BUTTON_A_ICON, "Common/VirtualJoystick/icon_button_a.png"); + textures.put(TextureKey.BUTTON_B_ICON, "Common/VirtualJoystick/icon_button_b.png"); + textures.put(TextureKey.BUTTON_X_ICON, "Common/VirtualJoystick/icon_button_x.png"); + textures.put(TextureKey.BUTTON_Y_ICON, "Common/VirtualJoystick/icon_button_y.png"); + textures.put(TextureKey.BUTTON_BACK_ICON, "Common/VirtualJoystick/icon_menu.png"); + textures.put(TextureKey.BUTTON_START_ICON, "Common/VirtualJoystick/icon_star.png"); + textures.put(TextureKey.STICK_PAD, "Common/VirtualJoystick/joystick_circle_pad_a.png"); + textures.put(TextureKey.STICK_NUB, "Common/VirtualJoystick/joystick_circle_nub_a.png"); + textures.put(TextureKey.DPAD_UP, "Common/VirtualJoystick/dpad_element_north.png"); + textures.put(TextureKey.DPAD_DOWN, "Common/VirtualJoystick/dpad_element_south.png"); + textures.put(TextureKey.DPAD_LEFT, "Common/VirtualJoystick/dpad_element_west.png"); + textures.put(TextureKey.DPAD_RIGHT, "Common/VirtualJoystick/dpad_element_east.png"); + markUpdateNeeded(); + } + + public String getFontPath() { + return fontPath; + } + + public void setFontPath(String fontPath) { + this.fontPath = fontPath; + markUpdateNeeded(); + } + + public synchronized String getTexture(TextureKey key) { + return textures.get(key); + } + + public synchronized void setTexture(TextureKey key, String texturePath) { + if (key == null) { + throw new IllegalArgumentException("Texture key cannot be null."); + } + if (texturePath == null) { + textures.remove(key); + } else { + textures.put(key, texturePath); + } + markUpdateNeeded(); + } + + boolean isUpdateNeeded() { + return updateNeeded; + } + + void markUpdateNeeded() { + updateNeeded = true; + } + + void clearUpdateNeeded() { + updateNeeded = false; + } + + @Override + public synchronized void write(JmeExporter ex) throws IOException { + OutputCapsule capsule = ex.getCapsule(this); + capsule.write(fontPath, "fontPath", DEFAULT_FONT); + String[] keys = new String[textures.size()]; + int index = 0; + for (TextureKey key : textures.keySet()) { + keys[index++] = key.name(); + } + capsule.write(keys, "keys", null); + capsule.write(textures.values().toArray(new String[0]), "paths", null); + } + + @Override + public synchronized void read(JmeImporter im) throws IOException { + InputCapsule capsule = im.getCapsule(this); + fontPath = capsule.readString("fontPath", DEFAULT_FONT); + String[] keys = capsule.readStringArray("keys", null); + String[] paths = capsule.readStringArray("paths", null); + textures.clear(); + if (keys != null && paths != null) { + int count = Math.min(keys.length, paths.length); + for (int i = 0; i < count; i++) { + textures.put(TextureKey.valueOf(keys[i]), paths[i]); + } + } + markUpdateNeeded(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickXboxLayout.java b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickXboxLayout.java new file mode 100644 index 0000000000..4b90d40ab4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/virtual/VirtualJoystickXboxLayout.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input.virtual; + +import com.jme3.input.JoystickAxis; +import com.jme3.input.JoystickButton; +import com.jme3.input.virtual.VirtualJoystickTheme.TextureKey; + +/** + * Default Xbox-like virtual joystick layout. + */ +public class VirtualJoystickXboxLayout extends VirtualJoystickLayout { + + public VirtualJoystickXboxLayout() { + float leftStickColumn = 0.145f; + float dpadColumn = 0.255f; + float rightStickColumn = 0.745f; + float faceColumn = 0.855f; + float leftStickRow = 0.565f; + float faceRow = 0.535f; + float lowerRow = 0.285f; + float faceButtonSize = 0.086f; + float faceButtonOffset = 0.09f; + float shoulderSize = 0.09f; + + addButtonElement(JoystickButton.BUTTON_XBOX_A, "", faceColumn, faceRow, 0f, -faceButtonOffset, + faceButtonSize, TextureKey.BUTTON, TextureKey.BUTTON_A_ICON); + addButtonElement(JoystickButton.BUTTON_XBOX_B, "", faceColumn, faceRow, faceButtonOffset, 0f, + faceButtonSize, TextureKey.BUTTON, TextureKey.BUTTON_B_ICON); + addButtonElement(JoystickButton.BUTTON_XBOX_X, "", faceColumn, faceRow, -faceButtonOffset, 0f, + faceButtonSize, TextureKey.BUTTON, TextureKey.BUTTON_X_ICON); + addButtonElement(JoystickButton.BUTTON_XBOX_Y, "", faceColumn, faceRow, 0f, faceButtonOffset, + faceButtonSize, TextureKey.BUTTON, TextureKey.BUTTON_Y_ICON); + + addButtonElement(JoystickButton.BUTTON_XBOX_LT, "LT", leftStickColumn, 0.94f, shoulderSize, 2f, + TextureKey.BUTTON_WIDE); + addButtonElement(JoystickButton.BUTTON_XBOX_RT, "RT", faceColumn, 0.94f, shoulderSize, 2f, + TextureKey.BUTTON_WIDE); + addButtonElement(JoystickButton.BUTTON_XBOX_LB, "LB", leftStickColumn, 0.79f, shoulderSize, 2f, + TextureKey.BUTTON_WIDE); + addButtonElement(JoystickButton.BUTTON_XBOX_RB, "RB", faceColumn, 0.79f, shoulderSize, 2f, + TextureKey.BUTTON_WIDE); + + addButtonElement(JoystickButton.BUTTON_XBOX_BACK, "", 0.44f, 0.06f, 0.078f, 2f, + TextureKey.BUTTON_WIDE, TextureKey.BUTTON_BACK_ICON); + addButtonElement(JoystickButton.BUTTON_XBOX_START, "", 0.56f, 0.06f, 0.078f, 2f, + TextureKey.BUTTON_WIDE, TextureKey.BUTTON_START_ICON); + addButtonElement(JoystickButton.BUTTON_XBOX_L3, "L3", leftStickColumn, leftStickRow, -0.145f, -0.07f, 0.065f, + TextureKey.BUTTON); + addButtonElement(JoystickButton.BUTTON_XBOX_R3, "R3", rightStickColumn, lowerRow, 0.145f, -0.07f, 0.065f, + TextureKey.BUTTON); + + addButtonElement(JoystickButton.BUTTON_XBOX_DPAD_UP, "", dpadColumn, lowerRow, 0f, 0.064f, 0.085f, + TextureKey.DPAD_UP); + addButtonElement(JoystickButton.BUTTON_XBOX_DPAD_DOWN, "", dpadColumn, lowerRow, 0f, -0.064f, 0.085f, + TextureKey.DPAD_DOWN); + addButtonElement(JoystickButton.BUTTON_XBOX_DPAD_LEFT, "", dpadColumn, lowerRow, -0.064f, 0f, 0.085f, + TextureKey.DPAD_LEFT); + addButtonElement(JoystickButton.BUTTON_XBOX_DPAD_RIGHT, "", dpadColumn, lowerRow, 0.064f, 0f, 0.085f, + TextureKey.DPAD_RIGHT); + + addAxisElement(JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_X, JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_Y, + "", leftStickColumn, leftStickRow, 0.235f, TextureKey.STICK_PAD, TextureKey.STICK_NUB); + addAxisElement(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X, JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_Y, + "", rightStickColumn, lowerRow, 0.235f, TextureKey.STICK_PAD, TextureKey.STICK_NUB); + } +} diff --git a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java index 31f970a176..badda09108 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java @@ -91,7 +91,7 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager * {@link #makeCurrent(com.jme3.asset.AssetManager, com.jme3.renderer.RenderManager, java.util.EnumSet, com.jme3.light.LightList, com.jme3.shader.DefineList)}. * @param geometry The geometry to render * @param lights Lights which influence the geometry. - * @param lastTexUnit the index of the most recently used texture unit + * @param lastBindUnits the most recently used bind units */ public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits); } diff --git a/jme3-core/src/main/java/com/jme3/opencl/Buffer.java b/jme3-core/src/main/java/com/jme3/opencl/Buffer.java deleted file mode 100644 index bbe9a8c1c9..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/Buffer.java +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import java.nio.ByteBuffer; - -/** - * Wrapper for an OpenCL buffer object. - * A buffer object stores a one-dimensional collection of elements. Elements of a buffer object can - * be a scalar data type (such as an int, float), vector data type, or a user-defined structure. - *
    - * Buffers are created by the {@link Context}. - *
    - * All access methods (read/write/copy/map) are available in both synchronized/blocking versions - * and in async/non-blocking versions. The later ones always return an {@link Event} object - * and have the prefix -Async in their name. - * - * @see Context#createBuffer(long, com.jme3.opencl.MemoryAccess) - * @author shaman - */ -public abstract class Buffer extends AbstractOpenCLObject { - - protected Buffer(ObjectReleaser releaser) { - super(releaser); - } - - @Override - public Buffer register() { - super.register(); - return this; - } - - /** - * @return the size of the buffer in bytes. - * @see Context#createBuffer(long) - */ - public abstract long getSize(); - - /** - * @return the memory access flags set on creation. - * @see Context#createBuffer(long, com.jme3.opencl.MemoryAccess) - */ - public abstract MemoryAccess getMemoryAccessFlags(); - - /** - * Performs a blocking read of the buffer. - * The target buffer must have at least {@code size} bytes remaining. - * This method may set the limit to the last byte read. - * - * @param queue the command queue - * @param dest the target buffer - * @param size the size in bytes being read - * @param offset the offset in bytes in the buffer to read from - */ - public abstract void read(CommandQueue queue, ByteBuffer dest, long size, long offset); - - /** - * Alternative version of {@link #read(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) }, - * sets {@code offset} to zero. - * - * @param queue the command queue - * @param dest the target buffer - * @param size the number of bytes to read - */ - public void read(CommandQueue queue, ByteBuffer dest, long size) { - read(queue, dest, size, 0); - } - - /** - * Alternative version of {@link #read(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) }, - * sets {@code size} to {@link #getSize() }. - * - * @param queue the command queue - * @param dest the target buffer - */ - public void read(CommandQueue queue, ByteBuffer dest) { - read(queue, dest, getSize()); - } - - /** - * Performs an async/non-blocking read of the buffer. - * The target buffer must have at least {@code size} bytes remaining. - * This method may set the limit to the last byte read. - * - * @param queue the command queue - * @param dest the target buffer - * @param size the size in bytes being read - * @param offset the offset in bytes in the buffer to read from - * @return the event indicating when the memory has been fully read into the provided buffer - */ - public abstract Event readAsync(CommandQueue queue, ByteBuffer dest, long size, long offset); - - /** - * Alternative version of {@link #readAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) }, - * sets {@code offset} to zero. - * - * @param queue the command queue - * @param dest the target buffer - * @param size the number of bytes to read - * @return an Event to indicate completion - */ - public Event readAsync(CommandQueue queue, ByteBuffer dest, long size) { - return readAsync(queue, dest, size, 0); - } - - /** - * Alternative version of {@link #readAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) }, - * sets {@code size} to {@link #getSize() } - * - * @param queue the command queue - * @param dest the target buffer - * @return an Event to indicate completion - */ - public Event readAsync(CommandQueue queue, ByteBuffer dest) { - return readAsync(queue, dest, getSize()); - } - - /** - * Performs a blocking write to the buffer. - * The target buffer must have at least {@code size} bytes remaining. - * This method may set the limit to the last byte that will be written. - * - * @param queue the command queue - * @param src the source buffer, its data is written to this buffer - * @param size the size in bytes to write - * @param offset the offset into the target buffer - */ - public abstract void write(CommandQueue queue, ByteBuffer src, long size, long offset); - - /** - * Alternative version of {@link #write(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) }, - * sets {@code offset} to zero. - * - * @param queue the command queue - * @param src the source buffer, its data is written to this buffer - * @param size the number of bytes to write - */ - public void write(CommandQueue queue, ByteBuffer src, long size) { - write(queue, src, size, 0); - } - - /** - * Alternative version of {@link #write(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) }, - * sets {@code size} to {@link #getSize() }. - * - * @param queue the command queue - * @param src the source buffer, its data is written to this buffer - */ - public void write(CommandQueue queue, ByteBuffer src) { - write(queue, src, getSize()); - } - - /** - * Performs an async/non-blocking write to the buffer. - * The target buffer must have at least {@code size} bytes remaining. - * This method may set the limit to the last byte that will be written. - * - * @param queue the command queue - * @param src the source buffer, its data is written to this buffer - * @param size the size in bytes to write - * @param offset the offset into the target buffer - * @return an Event to indicate completion - */ - public abstract Event writeAsync(CommandQueue queue, ByteBuffer src, long size, long offset); - - /** - * Alternative version of {@link #writeAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) }, - * sets {@code offset} to zero. - * - * @param queue the command queue - * @param src the source buffer, its data is written to this buffer - * @param size the number of bytes to write - * @return an Event to indicate completion - */ - public Event writeAsync(CommandQueue queue, ByteBuffer src, long size) { - return writeAsync(queue, src, size, 0); - } - - /** - * Alternative version of {@link #writeAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) }, - * sets {@code size} to {@link #getSize() }. - * - * @param queue the command queue - * @param src the source buffer, its data is written to this buffer - * @return an Event to indicate completion - */ - public Event writeAsync(CommandQueue queue, ByteBuffer src) { - return writeAsync(queue, src, getSize()); - } - - /** - * Performs a blocking copy operation from this buffer to the specified buffer. - * - * @param queue the command queue - * @param dest the target buffer - * @param size the size in bytes to copy - * @param srcOffset offset in bytes into this buffer - * @param destOffset offset in bytes into the target buffer - */ - public abstract void copyTo(CommandQueue queue, Buffer dest, long size, long srcOffset, long destOffset); - - /** - * Alternative version of {@link #copyTo(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long, long, long) }, - * sets {@code srcOffset} and {@code destOffset} to zero. - * - * @param queue the command queue - * @param dest the target buffer - * @param size the number of bytes to copy - */ - public void copyTo(CommandQueue queue, Buffer dest, long size) { - copyTo(queue, dest, size, 0, 0); - } - - /** - * Alternative version of {@link #copyTo(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long) }, - * sets {@code size} to {@code this.getSize()}. - * - * @param queue the command queue - * @param dest the target buffer - */ - public void copyTo(CommandQueue queue, Buffer dest) { - copyTo(queue, dest, getSize()); - } - - /** - * Performs an async/non-blocking copy operation from this buffer to the specified buffer. - * - * @param queue the command queue - * @param dest the target buffer - * @param size the size in bytes to copy - * @param srcOffset offset in bytes into this buffer - * @param destOffset offset in bytes into the target buffer - * @return the event object indicating when the copy operation is finished - */ - public abstract Event copyToAsync(CommandQueue queue, Buffer dest, long size, long srcOffset, long destOffset); - - /** - * Alternative version of {@link #copyToAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long, long, long) }, - * sets {@code srcOffset} and {@code destOffset} to zero. - * - * @param queue the command queue - * @param dest the target buffer - * @param size the number of bytes to copy - * @return an Event to indicate completion - */ - public Event copyToAsync(CommandQueue queue, Buffer dest, long size) { - return copyToAsync(queue, dest, size, 0, 0); - } - - /** - * Alternative version of {@link #copyToAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long) }, - * sets {@code size} to {@code this.getSize()}. - * - * @param queue the command queue - * @param dest the target buffer - * @return an Event to indicate completion - */ - public Event copyToAsync(CommandQueue queue, Buffer dest) { - return copyToAsync(queue, dest, getSize()); - } - - /** - * Maps this buffer directly into host memory. This might be the fastest method - * to access the contents of the buffer since the OpenCL implementation directly - * provides the memory.
    - * Important: The mapped memory MUST be released by calling - * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. - * - * @param queue the command queue - * @param size the size in bytes to map - * @param offset the offset into this buffer - * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE - * @return the byte buffer directly reflecting the buffer contents - */ - public abstract ByteBuffer map(CommandQueue queue, long size, long offset, MappingAccess access); - - /** - * Alternative version of {@link #map(com.jme3.opencl.CommandQueue, long, long, com.jme3.opencl.MappingAccess) }, - * sets {@code offset} to zero. - * Important: The mapped memory MUST be released by calling - * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. - * - * @param queue the command queue - * @param size the number of bytes to map - * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE - * @return the byte buffer directly reflecting the buffer contents - */ - public ByteBuffer map(CommandQueue queue, long size, MappingAccess access) { - return map(queue, size, 0, access); - } - - /** - * Alternative version of {@link #map(com.jme3.opencl.CommandQueue, long, com.jme3.opencl.MappingAccess) }, - * sets {@code size} to {@link #getSize() }. - * Important: The mapped memory MUST be released by calling - * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. - * - * @param queue the command queue - * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE - * @return the byte buffer directly reflecting the buffer contents - */ - public ByteBuffer map(CommandQueue queue, MappingAccess access) { - return map(queue, getSize(), access); - } - - /** - * Unmaps a previously mapped memory. - * This releases the native resources and for WRITE_ONLY or READ_WRITE access, - * the memory content is sent back to the GPU. - * - * @param queue the command queue - * @param ptr the buffer that was previously mapped - */ - public abstract void unmap(CommandQueue queue, ByteBuffer ptr); - - /** - * Maps this buffer asynchronously into host memory. This might be the fastest method - * to access the contents of the buffer since the OpenCL implementation directly - * provides the memory.
    - * Important: The mapped memory MUST be released by calling - * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. - * - * @param queue the command queue - * @param size the size in bytes to map - * @param offset the offset into this buffer - * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE - * @return the byte buffer directly reflecting the buffer contents - * and the event indicating when the buffer contents are available - */ - public abstract AsyncMapping mapAsync(CommandQueue queue, long size, long offset, MappingAccess access); - - /** - * Alternative version of {@link #mapAsync(com.jme3.opencl.CommandQueue, long, long, com.jme3.opencl.MappingAccess) }, - * sets {@code offset} to zero. - * Important: The mapped memory MUST be released by calling - * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. - * - * @param queue the command queue - * @param size the size in bytes to map - * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE - * @return the byte buffer directly reflecting the buffer contents - * and the event indicating when the buffer contents are available - */ - public AsyncMapping mapAsync(CommandQueue queue, long size, MappingAccess access) { - return mapAsync(queue, size, 0, access); - } - - /** - * Alternative version of {@link #mapAsync(com.jme3.opencl.CommandQueue, long, com.jme3.opencl.MappingAccess) }, - * sets {@code size} to {@link #getSize() }. - * Important: The mapped memory MUST be released by calling - * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. - * - * @param queue the command queue - * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE - * @return the byte buffer directly reflecting the buffer contents - * and the event indicating when the buffer contents are available - */ - public AsyncMapping mapAsync(CommandQueue queue, MappingAccess access) { - return mapAsync(queue, getSize(), 0, access); - } - - /** - * Enqueues a fill operation. This method can be used to initialize or clear - * a buffer with a certain value. - * - * @param queue the command queue - * @param pattern the buffer containing the filling pattern. - * The remaining bytes specify the pattern length - * @param size the size in bytes to fill, must be a multiple of the pattern length - * @param offset the offset in bytes into the buffer, must be a multiple of the pattern length - * @return an event indicating when this operation is finished - */ - public abstract Event fillAsync(CommandQueue queue, ByteBuffer pattern, long size, long offset); - - /** - * Result of an async mapping operation, contains the event and the target byte buffer. - * This is a work-around since no generic pair-structure is available. - * - * @author shaman - */ - public static class AsyncMapping { - - public final Event event; - public final ByteBuffer buffer; - - public AsyncMapping(Event event, ByteBuffer buffer) { - super(); - this.event = event; - this.buffer = buffer; - } - - /** - * @return the event object indicating when the data in the mapped buffer - * is available - */ - public Event getEvent() { - return event; - } - - /** - * @return the mapped buffer, only valid when the event object signals completion - */ - public ByteBuffer getBuffer() { - return buffer; - } - } - - /** - * Copies this buffer to the specified image. - * Note that no format conversion is done. - *
    - * For detailed description of the origin and region parameter, see the - * documentation of the {@link Image} class. - * - * @param queue the command queue - * @param dest the target image - * @param srcOffset the offset in bytes into this buffer - * @param destOrigin the origin of the copied area - * @param destRegion the size of the copied area - * @return the event object - */ - public abstract Event copyToImageAsync(CommandQueue queue, Image dest, long srcOffset, long[] destOrigin, long[] destRegion); - - /** - * Acquires this buffer object for using. Only call this method if this buffer - * represents a shared object from OpenGL, created with e.g. - * {@link Context#bindVertexBuffer(com.jme3.scene.VertexBuffer, com.jme3.opencl.MemoryAccess) }. - * This method must be called before the buffer is used. After the work is - * done, the buffer must be released by calling - * {@link #releaseBufferForSharingAsync(com.jme3.opencl.CommandQueue) } - * so that OpenGL can use the VertexBuffer again. - * - * @param queue the command queue - * @return the event object - */ - public abstract Event acquireBufferForSharingAsync(CommandQueue queue); - - /** - * Acquires this buffer object for using. Only call this method if this buffer - * represents a shared object from OpenGL, created with e.g. - * {@link Context#bindVertexBuffer(com.jme3.scene.VertexBuffer, com.jme3.opencl.MemoryAccess) }. - * This method must be called before the buffer is used. After the work is - * done, the buffer must be released by calling - * {@link #releaseBufferForSharingAsync(com.jme3.opencl.CommandQueue) } - * so that OpenGL can use the VertexBuffer again. - * - * The generated event object is directly released. - * This brings a performance improvement when the resource is e.g. directly - * used by a kernel afterwards on the same queue (this implicitly waits for - * this action). If you need the event, use - * {@link #acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } instead. - * - * @param queue the command queue - */ - public void acquireBufferForSharingNoEvent(CommandQueue queue) { - //default implementation, overwrite for better performance - acquireBufferForSharingAsync(queue).release(); - } - - /** - * Releases a shared buffer object. - * Call this method after the buffer object was acquired by - * {@link #acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } - * to hand the control back to OpenGL. - * - * @param queue the command queue - * @return the event object - */ - public abstract Event releaseBufferForSharingAsync(CommandQueue queue); - - /** - * Releases a shared buffer object. - * Call this method after the buffer object was acquired by - * {@link #acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } - * to hand the control back to OpenGL. - * The generated event object is directly released, resulting in - * performance improvements. - * @param queue the command queue - */ - public void releaseBufferForSharingNoEvent(CommandQueue queue) { - //default implementation, overwrite for better performance - releaseBufferForSharingAsync(queue).release(); - } - - @Override - public String toString() { - return "Buffer (" + getSize() + "B)"; - } -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/CommandQueue.java b/jme3-core/src/main/java/com/jme3/opencl/CommandQueue.java deleted file mode 100644 index 001e74292b..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/CommandQueue.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -/** - * Wrapper for an OpenCL command queue. - * The command queue serializes every GPU function call: By passing the same - * queue to OpenCL function (buffer, image operations, kernel calls), it is - * ensured that they are executed in the order in which they are passed. - *
    - * Each command queue is associated with exactly one device: that device - * is specified on creation ({@link Context#createQueue(com.jme3.opencl.Device) }) - * and all commands are sent to this device. - * - * @author shaman - */ -public abstract class CommandQueue extends AbstractOpenCLObject { - protected Device device; - - protected CommandQueue(ObjectReleaser releaser, Device device) { - super(releaser); - this.device = device; - } - - @Override - public CommandQueue register() { - super.register(); - return this; - } - - /** - * Returns the device associated with this command queue. - * It can be used to query properties of the device that is used to execute - * the commands issued to this command queue. - * - * @return the associated device - */ - public Device getDevice() { - return device; - } - - /** - * Issues all previously queued OpenCL commands in command_queue to the - * device associated with command queue. Flush only guarantees that all - * queued commands to command_queue will eventually be submitted to the - * appropriate device. There is no guarantee that they will be complete - * after flush returns. - */ - public abstract void flush(); - - /** - * Blocks until all previously queued OpenCL commands in command queue are - * issued to the associated device and have completed. Finish does not - * return until all previously queued commands in command queue have been - * processed and completed. Finish is also a synchronization point. - */ - public abstract void finish(); -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/Context.java b/jme3-core/src/main/java/com/jme3/opencl/Context.java deleted file mode 100644 index e25f893e24..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/Context.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import com.jme3.asset.AssetInfo; -import com.jme3.asset.AssetKey; -import com.jme3.asset.AssetManager; -import com.jme3.asset.AssetNotFoundException; -import com.jme3.opencl.Image.ImageDescriptor; -import com.jme3.opencl.Image.ImageFormat; -import com.jme3.opencl.Image.ImageType; -import com.jme3.scene.VertexBuffer; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Texture; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.StringReader; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The central OpenCL context. Every action starts from here. - * The context can be obtained by {@link com.jme3.system.JmeContext#getOpenCLContext() }. - *

    - * The context is used to: - *

      - *
    • Query the available devices
    • - *
    • Create a command queue
    • - *
    • Create buffers and images
    • - *
    • Created buffers and images shared with OpenGL vertex buffers, textures and renderbuffers
    • - *
    • Create program objects from source code and source files
    • - *
    - * - * @author shaman - */ -public abstract class Context extends AbstractOpenCLObject { - private static final Logger LOG = Logger.getLogger(Context.class.getName()); - - protected Context(ObjectReleaser releaser) { - super(releaser); - } - - @Override - public Context register() { - super.register(); - return this; - } - - /** - * Returns all available devices for this context. - * These devices all belong to the same {@link Platform}. - * They are used to create a command queue sending commands to a particular - * device, see {@link #createQueue(com.jme3.opencl.Device) }. - * Also, device capabilities, like the supported OpenCL version, extensions, - * memory size and so on, are queried over the Device instances. - *
    - * The available devices were specified by a {@link PlatformChooser}. - * - * @return a list of devices - */ - public abstract List getDevices(); - - /** - * Alternative version of {@link #createQueue(com.jme3.opencl.Device) }, - * just uses the first device returned by {@link #getDevices() }. - * - * @return the command queue - */ - public CommandQueue createQueue() { - return createQueue(getDevices().get(0)); - } - - /** - * Creates a command queue sending commands to the specified device. - * The device must be an entry of {@link #getDevices() }. - * - * @param device the target device - * @return the command queue - */ - public abstract CommandQueue createQueue(Device device); - - /** - * Allocates a new buffer of the specific size and access type on the device. - * - * @param size the size of the buffer in bytes - * @param access the allowed access of this buffer from kernel code - * @return the new buffer - */ - public abstract Buffer createBuffer(long size, MemoryAccess access); - - /** - * Alternative version of {@link #createBuffer(long, com.jme3.opencl.MemoryAccess) }, - * creates a buffer with read and write access. - * - * @param size the size of the buffer in bytes - * @return the new buffer - */ - public Buffer createBuffer(long size) { - return createBuffer(size, MemoryAccess.READ_WRITE); - } - - /** - * Creates a new buffer wrapping the specific host memory. This host memory - * specified by a ByteBuffer can then be used directly by kernel code, - * although the access might be slower than with native buffers - * created by {@link #createBuffer(long, com.jme3.opencl.MemoryAccess) }. - * - * @param data the host buffer to use - * @param access the allowed access of this buffer from kernel code - * @return the new buffer - */ - public abstract Buffer createBufferFromHost(ByteBuffer data, MemoryAccess access); - - /** - * Alternative version of {@link #createBufferFromHost(java.nio.ByteBuffer, com.jme3.opencl.MemoryAccess) }, - * creates a buffer with read and write access. - * - * @param data the host buffer to use - * @return the new buffer - */ - public Buffer createBufferFromHost(ByteBuffer data) { - return createBufferFromHost(data, MemoryAccess.READ_WRITE); - } - - /** - * Creates a new 1D, 2D, 3D image.
    - * {@code ImageFormat} specifies the element type and order, like RGBA of floats.
    - * {@code ImageDescriptor} specifies the dimension of the image.
    - * Furthermore, a ByteBuffer can be specified in the ImageDescriptor together - * with row and slice pitches. This buffer is then used to store the image. - * If no ByteBuffer is specified, a new buffer is allocated (this is the - * normal behaviour). - * - * @param access the allowed access of this image from kernel code - * @param format the image format - * @param descr the image descriptor - * @return the new image object - */ - public abstract Image createImage(MemoryAccess access, ImageFormat format, ImageDescriptor descr); - //TODO: add simplified methods for 1D, 2D, 3D textures - - /** - * Queries all supported image formats for a specified memory access and - * image type. - *
    - * Note that the returned array may contain {@code ImageFormat} objects - * where {@code ImageChannelType} or {@code ImageChannelOrder} are {@code null} - * (or both). This is the case when the device supports new formats that - * are not included in this wrapper yet. - * - * @param access the memory access type - * @param type the image type (1D, 2D, 3D, ...) - * @return an array of all supported image formats - */ - public abstract ImageFormat[] querySupportedFormats(MemoryAccess access, ImageType type); - - //Interop - /** - * Creates a shared buffer from a VertexBuffer. - * The returned buffer and the vertex buffer operate on the same memory, - * changes in one view are visible in the other view. - * This can be used to modify meshes directly from OpenCL (e.g. for particle systems). - *
    - * Note: The vertex buffer must already been uploaded to the GPU, - * i.e. it must be used at least once for drawing. - *

    - * Before the returned buffer can be used, it must be acquired explicitly - * by {@link Buffer#acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } - * and after modifying it, released by {@link Buffer#releaseBufferForSharingAsync(com.jme3.opencl.CommandQueue) }. - * This is needed so that OpenGL and OpenCL operations do not interfere with each other. - * - * @param vb the vertex buffer to share - * @param access the memory access for the kernel - * @return the new buffer - */ - public abstract Buffer bindVertexBuffer(VertexBuffer vb, MemoryAccess access); - - /** - * Creates a shared image object from a jME3-image. - * The returned image shares the same memory with the jME3-image, changes - * in one view are visible in the other view. - * This can be used to modify textures and images directly from OpenCL - * (e.g. for post-processing effects and other texture effects). - *
    - * Note: The image must already been uploaded to the GPU, - * i.e. it must be used at least once for drawing. - *

    - * Before the returned image can be used, it must be acquired explicitly - * by {@link Image#acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * and after modifying it, released by {@link Image#releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * This is needed so that OpenGL and OpenCL operations do not interfere with each other. - * - * @param image the jME3 image object - * @param textureType the texture type (1D, 2D, 3D), since this is not stored in the image - * @param miplevel the mipmap level that should be shared - * @param access the allowed memory access for kernels - * @return the OpenCL image - */ - public abstract Image bindImage(com.jme3.texture.Image image, Texture.Type textureType, int miplevel, MemoryAccess access); - - /** - * Creates a shared image object from a jME3 texture. - * The returned image shares the same memory with the jME3 texture, changes - * in one view are visible in the other view. - * This can be used to modify textures and images directly from OpenCL - * (e.g. for post-processing effects and other texture effects). - *
    - * Note: The image must already been uploaded to the GPU, - * i.e. it must be used at least once for drawing. - *

    - * Before the returned image can be used, it must be acquired explicitly - * by {@link Image#acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * and after modifying it, released by {@link Image#releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * This is needed so that OpenGL and OpenCL operations do not interfere with each other. - *

    - * This method is equivalent to calling - * {@code bindImage(texture.getImage(), texture.getType(), miplevel, access)}. - * - * @param texture the jME3 texture - * @param miplevel the mipmap level that should be shared - * @param access the allowed memory access for kernels - * @return the OpenCL image - */ - public Image bindImage(Texture texture, int miplevel, MemoryAccess access) { - return bindImage(texture.getImage(), texture.getType(), miplevel, access); - } - - /** - * Alternative version to {@link #bindImage(com.jme3.texture.Texture, int, com.jme3.opencl.MemoryAccess) }, - * uses {@code miplevel=0}. - * - * @param texture the jME3 texture - * @param access the allowed memory access for kernels - * @return the OpenCL image - */ - public Image bindImage(Texture texture, MemoryAccess access) { - return bindImage(texture, 0, access); - } - - /** - * Creates a shared image object from a jME3 render buffer. - * The returned image shares the same memory with the jME3 render buffer, changes - * in one view are visible in the other view. - *
    - * This can be used as an alternative to post-processing effects - * (e.g. reduce sum operations, needed e.g. for tone mapping). - *
    - * Note: The renderbuffer must already been uploaded to the GPU, - * i.e. it must be used at least once for drawing. - *

    - * Before the returned image can be used, it must be acquired explicitly - * by {@link Image#acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * and after modifying it, released by {@link Image#releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * This is needed so that OpenGL and OpenCL operations do not interfere with each other. - * - * @param buffer the buffer to bind - * @param access the kernel access permissions - * @return an image - */ - public Image bindRenderBuffer(FrameBuffer.RenderBuffer buffer, MemoryAccess access) { - if (buffer.getTexture() == null) { - return bindPureRenderBuffer(buffer, access); - } else { - return bindImage(buffer.getTexture(), access); - } - } - - protected abstract Image bindPureRenderBuffer(FrameBuffer.RenderBuffer buffer, MemoryAccess access); - - /** - * Creates a program object from the provided source code. - * The program still needs to be compiled using {@link Program#build() }. - * - * @param sourceCode the source code - * @return the program object - */ - public abstract Program createProgramFromSourceCode(String sourceCode); - - /** - * Resolves dependencies (using {@code #include } in the source code) - * and delegates the combined source code to - * {@link #createProgramFromSourceCode(java.lang.String) }. - * Important: only absolute paths are allowed. - * - * @param sourceCode the original source code - * @param assetManager the asset manager to load the files - * @return the created program object - * @throws AssetNotFoundException if a dependency could not be loaded - */ - public Program createProgramFromSourceCodeWithDependencies(String sourceCode, AssetManager assetManager) { - StringBuilder builder = new StringBuilder(sourceCode.length()); - BufferedReader reader = new BufferedReader(new StringReader(sourceCode)); - try { - buildSourcesRec(reader, builder, assetManager); - } catch (IOException ex) { - throw new AssetNotFoundException("Unable to read a dependency file", ex); - } - return createProgramFromSourceCode(builder.toString()); - } - - private void buildSourcesRec(BufferedReader reader, StringBuilder builder, AssetManager assetManager) throws IOException { - String ln; - while ((ln = reader.readLine()) != null) { - if (ln.trim().startsWith("#import ")) { - ln = ln.trim().substring(8).trim(); - if (ln.startsWith("\"")) { - ln = ln.substring(1); - } - if (ln.endsWith("\"")) { - ln = ln.substring(0, ln.length() - 1); - } - AssetInfo info = assetManager.locateAsset(new AssetKey(ln)); - if (info == null) { - throw new AssetNotFoundException("Unable to load source file \"" + ln + "\""); - } - try (BufferedReader r = new BufferedReader(new InputStreamReader(info.openStream()))) { - builder.append("//-- begin import ").append(ln).append(" --\n"); - buildSourcesRec(r, builder, assetManager); - builder.append("//-- end import ").append(ln).append(" --\n"); - } - } else { - builder.append(ln).append('\n'); - } - } - } - - /** - * Creates a program object from the provided source code and files. - * The source code is made up from the specified include string first, - * then all files specified by the resource array (array of asset paths) - * are loaded by the provided asset manager and appended to the source code. - *

    - * The typical use case is: - *

      - *
    • The include string contains some compiler constants like the grid size
    • - *
    • Some common OpenCL files used as libraries (Convention: file names end with {@code .clh}
    • - *
    • One main OpenCL file containing the actual kernels (Convention: file name ends with {@code .cl})
    • - *
    - * - * After the files were combined, additional include statements are resolved - * by {@link #createProgramFromSourceCodeWithDependencies(java.lang.String, com.jme3.asset.AssetManager) }. - * - * @param assetManager the asset manager used to load the files - * @param include an additional include string - * @param resources an array of asset paths pointing to OpenCL source files - * @return the new program objects - * @throws AssetNotFoundException if a file could not be loaded - */ - public Program createProgramFromSourceFilesWithInclude(AssetManager assetManager, String include, String... resources) { - return createProgramFromSourceFilesWithInclude(assetManager, include, Arrays.asList(resources)); - } - - /** - * Creates a program object from the provided source code and files. - * The source code is made up from the specified include string first, - * then all files specified by the resource array (array of asset paths) - * are loaded by the provided asset manager and appended to the source code. - *

    - * The typical use case is: - *

      - *
    • The include string contains some compiler constants like the grid size
    • - *
    • Some common OpenCL files used as libraries (Convention: file names end with {@code .clh}
    • - *
    • One main OpenCL file containing the actual kernels (Convention: file name ends with {@code .cl})
    • - *
    - * - * After the files were combined, additional include statements are resolved - * by {@link #createProgramFromSourceCodeWithDependencies(java.lang.String, com.jme3.asset.AssetManager) }. - * - * @param assetManager the asset manager used to load the files - * @param include an additional include string - * @param resources an array of asset paths pointing to OpenCL source files - * @return the new program objects - * @throws AssetNotFoundException if a file could not be loaded - */ - public Program createProgramFromSourceFilesWithInclude(AssetManager assetManager, String include, List resources) { - StringBuilder str = new StringBuilder(); - str.append(include); - for (String res : resources) { - AssetInfo info = assetManager.locateAsset(new AssetKey(res)); - if (info == null) { - throw new AssetNotFoundException("Unable to load source file \"" + res + "\""); - } - try (BufferedReader reader = new BufferedReader(new InputStreamReader(info.openStream()))) { - while (true) { - String line = reader.readLine(); - if (line == null) { - break; - } - str.append(line).append('\n'); - } - } catch (IOException ex) { - LOG.log(Level.WARNING, "unable to load source file '" + res + "'", ex); - } - } - return createProgramFromSourceCodeWithDependencies(str.toString(), assetManager); - } - - /** - * Alternative version of {@link #createProgramFromSourceFilesWithInclude(com.jme3.asset.AssetManager, java.lang.String, java.lang.String...) } - * with an empty include string - * - * @param assetManager for loading assets - * @param resources asset paths pointing to OpenCL source files - * @return a new instance - * @throws AssetNotFoundException if a file could not be loaded - */ - public Program createProgramFromSourceFiles(AssetManager assetManager, String... resources) { - return createProgramFromSourceFilesWithInclude(assetManager, "", resources); - } - - /** - * Alternative version of {@link #createProgramFromSourceFilesWithInclude(com.jme3.asset.AssetManager, java.lang.String, java.util.List) } - * with an empty include string - * - * @param assetManager for loading assets - * @param resources a list of asset paths pointing to OpenCL source files - * @return a new instance - * @throws AssetNotFoundException if a file could not be loaded - */ - public Program createProgramFromSourceFiles(AssetManager assetManager, List resources) { - return createProgramFromSourceFilesWithInclude(assetManager, "", resources); - } - - /** - * Creates a program from the specified binaries. - * The binaries are created by {@link Program#getBinary(com.jme3.opencl.Device) }. - * The returned program still needs to be build using - * {@link Program#build(java.lang.String, com.jme3.opencl.Device...) }. - * Important:The device passed to {@code Program.getBinary(..)}, - * this method and {@code Program#build(..)} must be the same. - * - * The binaries are used to build a program cache across multiple launches - * of the application. The programs build much faster from binaries than - * from sources. - * - * @param binaries the binaries - * @param device the device to use - * @return the new program - */ - public abstract Program createProgramFromBinary(ByteBuffer binaries, Device device); - - @Override - public String toString() { - return "Context (" + getDevices() + ')'; - } -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/DefaultPlatformChooser.java b/jme3-core/src/main/java/com/jme3/opencl/DefaultPlatformChooser.java deleted file mode 100644 index 3f3cb2596f..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/DefaultPlatformChooser.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; - -/** - * A default implementation of {@link PlatformChooser}. - * It favors GPU devices with OpenGL sharing, then any devices with OpenGL sharing, - * then any possible device. - * @author shaman - */ -public class DefaultPlatformChooser implements PlatformChooser { - private static final Logger LOG = Logger.getLogger(DefaultPlatformChooser.class.getName()); - - @Override - public List chooseDevices(List platforms) { - ArrayList result = new ArrayList<>(); - for (Platform p : platforms) { - if (!p.hasOpenGLInterop()) { - continue; //must support interop - } - for (Device d : p.getDevices()) { - if (d.hasOpenGLInterop() && d.getDeviceType()==Device.DeviceType.GPU) { - result.add(d); //GPU preferred - } - } - if (!result.isEmpty()) { - return result; - } - } - //no GPU devices found, try all - for (Platform p : platforms) { - if (!p.hasOpenGLInterop()) { - continue; //must support interop - } - for (Device d : p.getDevices()) { - if (d.hasOpenGLInterop()) { - result.add(d); //just interop needed - } - } - if (!result.isEmpty()) { - return result; - } - } - //still no one found, try without interop - LOG.warning("No device with OpenCL-OpenGL-interop found, try without"); - for (Platform p : platforms) { - for (Device d : p.getDevices()) { - result.add(d); - } - if (!result.isEmpty()) { - return result; - } - } - //no devices available at all! - return result; //result is empty - } - -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/Device.java b/jme3-core/src/main/java/com/jme3/opencl/Device.java deleted file mode 100644 index b78d555bf9..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/Device.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import java.util.Collection; - -/** - * Represents a hardware device actually running the OpenCL kernels. - * A {@link Context} can be associated with multiple {@code Devices} - * that all belong to the same {@link Platform}. - * For execution, a single device must be chosen and passed to a command - * queue ({@link Context#createQueue(com.jme3.opencl.Device) }). - *

    - * This class is used to query the capabilities of the underlying device. - * - * @author shaman - */ -public interface Device { - /** - * @return the platform associated with this device - */ - Platform getPlatform(); - /** - * The device type - */ - public static enum DeviceType { - DEFAULT, - CPU, - GPU, - ACCELEARTOR, - ALL - } - - /** - * @return queries the device type - */ - DeviceType getDeviceType(); - - /** - * @return the vendor id - */ - int getVendorId(); - - /** - * checks if this device is available at all, must always be tested - * - * @return checks if this device is available at all, must always be tested - */ - boolean isAvailable(); - - /** - * @return if this device has a compiler for kernel code - */ - boolean hasCompiler(); - - /** - * @return supports double precision floats (64 bit) - */ - boolean hasDouble(); - - /** - * @return supports half precision floats (16 bit) - */ - boolean hasHalfFloat(); - - /** - * @return supports error correction for every access to global or constant memory - */ - boolean hasErrorCorrectingMemory(); - - /** - * @return supports unified virtual memory (OpenCL 2.0) - */ - boolean hasUnifiedMemory(); - - /** - * @return supports images - */ - boolean hasImageSupport(); - - /** - * @return supports writes to 3d images (this is an extension) - */ - boolean hasWritableImage3D(); - - /** - * @return supports sharing with OpenGL - */ - boolean hasOpenGLInterop(); - - /** - * Explicitly tests for the availability of the specified extension - * - * @param extension the name of the extension - * @return {@code true} iff this extension is supported - */ - boolean hasExtension(String extension); - - /** - * Lists all available extensions - * - * @return all available extensions - */ - Collection getExtensions(); - - /** - * Returns the number of parallel compute units on - * the OpenCL device. A work-group - * executes on a single compute unit. The - * minimum value is 1. - * @return the number of parallel compute units - * @see #getMaximumWorkItemDimensions() - * @see #getMaximumWorkItemSizes() - */ - int getComputeUnits(); - - /** - * @return maximum clock frequency of the device in MHz - */ - int getClockFrequency(); - - /** - * Returns the default compute device address space - * size specified as an unsigned integer value - * in bits. The values currently supported are 32 - * and 64. - * - * @return the size of an address - */ - int getAddressBits(); - - /** - * @return {@code true} if this device is little endian - */ - boolean isLittleEndian(); - - /** - * The maximum dimension that specify the local and global work item ids. - * You can always assume to be this at least 3. - * Therefore, the ids are always three integers x,y,z. - * - * @return the maximum dimension of work item ids - */ - long getMaximumWorkItemDimensions(); - - /** - * Maximum number of work-items that can be specified in each dimension of the - * work-group to {@link Kernel#Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...)}. - * The array has a length of at least 3. - * - * @return the maximum size of the work group in each dimension - */ - long[] getMaximumWorkItemSizes(); - - /** - * Maximum number of work-items in a - * work-group executing a kernel on a single - * compute unit, using the data parallel - * execution model. - * - * @return maximum number of work-items in a work-group - */ - long getMaxiumWorkItemsPerGroup(); - - /** - * @return the maximum number of samples that can be used in a kernel - */ - int getMaximumSamplers(); - - /** - * @return the maximum number of images that can be used for reading in a kernel - */ - int getMaximumReadImages(); - - /** - * @return the maximum number of images that can be used for writing in a kernel - */ - int getMaximumWriteImages(); - - /** - * Queries the maximal size of a 2D image - * - * @return an array of length 2 with the maximal size of a 2D image - */ - long[] getMaximumImage2DSize(); - - /** - * Queries the maximal size of a 3D image - * - * @return an array of length 3 with the maximal size of a 3D image - */ - long[] getMaximumImage3DSize(); - - /** - * @return the maximal size of a memory object (buffer and image) in bytes - */ - long getMaximumAllocationSize(); - - /** - * @return the total available global memory in bytes - */ - long getGlobalMemorySize(); - - /** - * @return the total available local memory in bytes - */ - long getLocalMemorySize(); - - /** - * Returns the maximal size of a constant buffer. - *
    - * Constant buffers are normal buffer objects, but passed to the kernel - * with the special declaration {@code __constant BUFFER_TYPE* BUFFER_NAME}. - * Because they have a special caching, their size is usually very limited. - * - * @return the maximal size of a constant buffer - */ - long getMaximumConstantBufferSize(); - - /** - * @return the maximal number of constant buffer arguments in a kernel call - */ - int getMaximumConstantArguments(); - - //TODO: cache, preferred sizes properties - /** - * OpenCL profile string. Returns the profile name supported by the device. - * The profile name returned can be one of the following strings:
    - * FULL_PROFILE – if the device supports the OpenCL specification - * (functionality defined as part of the core specification and does not - * require any extensions to be supported).
    - * EMBEDDED_PROFILE - if the device supports the OpenCL embedded profile. - * - * @return the profile string - */ - String getProfile(); - - /** - * OpenCL version string. Returns the OpenCL version supported by the - * device. This version string has the following format: OpenCL space - * major_version.minor_version space vendor-specific information. - *
    - * E.g. OpenCL 1.1, OpenCL 1.2, OpenCL 2.0 - * - * @return the version string - */ - String getVersion(); - - /** - * Extracts the major version from the version string - * - * @return the major version - * @see #getVersion() - */ - int getVersionMajor(); - - /** - * Extracts the minor version from the version string - * - * @return the minor version - * @see #getVersion() } - */ - int getVersionMinor(); - - /** - * OpenCL C version string. Returns the highest OpenCL C version supported - * by the compiler for this device that is not of type - * CL_DEVICE_TYPE_CUSTOM. This version string has the following format: - * OpenCL space C space major_version.minor_version space vendor-specific - * information.
    - * The major_version.minor_version value returned must be 1.2 if - * CL_DEVICE_VERSION is OpenCL 1.2. The major_version.minor_version value - * returned must be 1.1 if CL_DEVICE_VERSION is OpenCL 1.1. The - * major_version.minor_version value returned can be 1.0 or 1.1 if - * CL_DEVICE_VERSION is OpenCL 1.0. - * - * @return the compiler version - */ - String getCompilerVersion(); - - /** - * Extracts the major version from the compiler version - * - * @return the major compiler version - * @see #getCompilerVersion() - */ - int getCompilerVersionMajor(); - - /** - * Extracts the minor version from the compiler version - * - * @return the minor compiler version - * @see #getCompilerVersion() - */ - int getCompilerVersionMinor(); - - /** - * @return the OpenCL software driver version string in the form - * major_number.minor_number - */ - String getDriverVersion(); - - /** - * Extracts the major version from the driver version - * - * @return the major driver version - * @see #getDriverVersion() - */ - int getDriverVersionMajor(); - - /** - * Extracts the minor version from the driver version - * - * @return the minor driver version - * @see #getDriverVersion() - */ - int getDriverVersionMinor(); - - /** - * @return the device name - */ - String getName(); - - /** - * @return the vendor - */ - String getVendor(); -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/Image.java b/jme3-core/src/main/java/com/jme3/opencl/Image.java deleted file mode 100644 index 5b07d0f376..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/Image.java +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Copyright (c) 2009-2020 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import com.jme3.math.ColorRGBA; -import java.nio.ByteBuffer; -import java.util.Objects; - -/** - * Wrapper for an OpenCL image. - *
    - * An image object is similar to a {@link Buffer}, but with a specific element - * format and buffer structure. - *
    - * The image is specified by the {@link ImageDescriptor}, specifying - * the type and dimensions of the image, and {@link ImageFormat}, specifying - * the type of each pixel. - *
    - * An image is created from scratch using - * {@link Context#createImage(com.jme3.opencl.MemoryAccess, com.jme3.opencl.Image.ImageFormat, com.jme3.opencl.Image.ImageDescriptor) } - * or from OpenGL by - * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) } - * (and alternative versions). - * - *

    - * Most methods take long arrays as input: {@code long[] origin} and {@code long[] region}. - * Both are arrays of length 3. - *
    - * origin defines the (x, y, z) offset in pixels in the 1D, 2D or 3D - * image, the (x, y) offset and the image index in the 2D image array or the (x) - * offset and the image index in the 1D image array. If image is a 2D image - * object, origin[2] must be 0. If image is a 1D image or 1D image buffer - * object, origin[1] and origin[2] must be 0. If image is a 1D image array - * object, origin[2] must be 0. If image is a 1D image array object, origin[1] - * describes the image index in the 1D image array. If image is a 2D image array - * object, origin[2] describes the image index in the 2D image array. - *
    - * region defines the (width, height, depth) in pixels of the 1D, 2D or - * 3D rectangle, the (width, height) in pixels of the 2D rectangle and the - * number of images of a 2D image array or the (width) in pixels of the 1D - * rectangle and the number of images of a 1D image array. If image is a 2D - * image object, region[2] must be 1. If image is a 1D image or 1D image buffer - * object, region[1] and region[2] must be 1. If image is a 1D image array - * object, region[2] must be 1. The values in region cannot be 0. - * - * @author shaman - */ -public abstract class Image extends AbstractOpenCLObject { - /** - * {@code ImageChannelType} describes the size of the channel data type. - */ - public static enum ImageChannelType { - SNORM_INT8, - SNORM_INT16, - UNORM_INT8, - UNORM_INT16, - UNORM_SHORT_565, - UNORM_SHORT_555, - UNORM_INT_101010, - SIGNED_INT8, - SIGNED_INT16, - SIGNED_INT32, - UNSIGNED_INT8, - UNSIGNED_INT16, - UNSIGNED_INT32, - HALF_FLOAT, - FLOAT - } - - /** - * {@code ImageChannelOrder} specifies the number of channels and the channel layout i.e. the - * memory layout in which channels are stored in the image. - */ - public static enum ImageChannelOrder { - R, Rx, A, - INTENSITY, - LUMINANCE, - RG, RGx, RA, - RGB, RGBx, - RGBA, - ARGB, BGRA - } - - /** - * Describes the image format, consisting of - * {@link ImageChannelOrder} and {@link ImageChannelType}. - */ - public static class ImageFormat { //Struct - public ImageChannelOrder channelOrder; - public ImageChannelType channelType; - - public ImageFormat() { - } - - public ImageFormat(ImageChannelOrder channelOrder, ImageChannelType channelType) { - this.channelOrder = channelOrder; - this.channelType = channelType; - } - - @Override - public String toString() { - return "ImageFormat{" + "channelOrder=" + channelOrder + ", channelType=" + channelType + '}'; - } - - @Override - public int hashCode() { - int hash = 5; - hash = 61 * hash + Objects.hashCode(this.channelOrder); - hash = 61 * hash + Objects.hashCode(this.channelType); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ImageFormat other = (ImageFormat) obj; - if (this.channelOrder != other.channelOrder) { - return false; - } - if (this.channelType != other.channelType) { - return false; - } - return true; - } - } - - /** - * The possible image types / dimensions. - */ - public static enum ImageType { - IMAGE_1D, - IMAGE_1D_BUFFER, - IMAGE_2D, - IMAGE_3D, - IMAGE_1D_ARRAY, - IMAGE_2D_ARRAY - } - - /** - * The image descriptor structure describes the type and dimensions of the image or image array. - *

    - * There exist two constructors:
    - * ImageDescriptor(ImageType, long, long, long, long) - * is used when an image with new memory should be created (used most often).
    - * ImageDescriptor(ImageType, long, long, long, long, long, long, ByteBuffer) - * creates an image using the provided {@code ByteBuffer} as source. - */ - public static class ImageDescriptor { //Struct - public ImageType type; - public long width; - public long height; - public long depth; - public long arraySize; - public long rowPitch; - public long slicePitch; - public ByteBuffer hostPtr; - /* - public int numMipLevels; //They must always be set to zero - public int numSamples; - */ - - public ImageDescriptor() { - } - - /** - * Used to specify an image with the provided ByteBuffer as source - * - * @param type the image type - * @param width the width - * @param height the height, unused for image types {@code ImageType.IMAGE_1D*} - * @param depth the depth of the image, only used for image type {@code ImageType.IMAGE_3D} - * @param arraySize the number of array elements for image type {@code ImageType.IMAGE_1D_ARRAY} and {@code ImageType.IMAGE_2D_ARRAY} - * @param rowPitch the row pitch of the provided buffer - * @param slicePitch the slice pitch of the provided buffer - * @param hostPtr host buffer used as image memory - */ - public ImageDescriptor(ImageType type, long width, long height, long depth, long arraySize, long rowPitch, long slicePitch, ByteBuffer hostPtr) { - this.type = type; - this.width = width; - this.height = height; - this.depth = depth; - this.arraySize = arraySize; - this.rowPitch = rowPitch; - this.slicePitch = slicePitch; - this.hostPtr = hostPtr; - } - - /** - * Specifies an image without a host buffer, a new chunk of memory - * will be allocated. - * - * @param type the image type - * @param width the width - * @param height the height, unused for image types {@code ImageType.IMAGE_1D*} - * @param depth the depth of the image, only used for image type {@code ImageType.IMAGE_3D} - * @param arraySize the number of array elements for image type {@code ImageType.IMAGE_1D_ARRAY} and {@code ImageType.IMAGE_2D_ARRAY} - */ - public ImageDescriptor(ImageType type, long width, long height, long depth, long arraySize) { - this.type = type; - this.width = width; - this.height = height; - this.depth = depth; - this.arraySize = arraySize; - this.rowPitch = 0; - this.slicePitch = 0; - hostPtr = null; - } - - @Override - public String toString() { - return "ImageDescriptor{" + "type=" + type + ", width=" + width + ", height=" + height + ", depth=" + depth + ", arraySize=" + arraySize + ", rowPitch=" + rowPitch + ", slicePitch=" + slicePitch + '}'; - } - } - - protected Image(ObjectReleaser releaser) { - super(releaser); - } - - @Override - public Image register() { - super.register(); - return this; - } - - /** - * @return the width of the image - */ - public abstract long getWidth(); - - /** - * @return the height of the image - */ - public abstract long getHeight(); - - /** - * @return the depth of the image - */ - public abstract long getDepth(); - - /** - * @return the row pitch when the image was created from a host buffer - */ - public abstract long getRowPitch(); - - /** - * @return the slice pitch when the image was created from a host buffer - */ - public abstract long getSlicePitch(); - - /** - * @return the number of elements in the image array - * @see ImageType#IMAGE_1D_ARRAY - * @see ImageType#IMAGE_2D_ARRAY - */ - public abstract long getArraySize(); - - /** - * @return the image format - */ - public abstract ImageFormat getImageFormat(); - - /** - * @return the image type - */ - public abstract ImageType getImageType(); - - /** - * @return the number of bytes per pixel - */ - public abstract int getElementSize(); - - /** - * Performs a blocking read of the image into the specified byte buffer. - * - * @param queue the command queue - * @param dest the target byte buffer - * @param origin the image origin location, see class description for the format - * @param region the copied region, see class description for the format - * @param rowPitch the row pitch of the target buffer, must be set to 0 if the image is 1D. - * If set to 0 for 2D and 3D image, the row pitch is calculated as {@code bytesPerElement * width} - * @param slicePitch the slice pitch of the target buffer, must be set to 0 for 1D and 2D images. - * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height} - */ - public abstract void readImage(CommandQueue queue, ByteBuffer dest, long[] origin, long[] region, long rowPitch, long slicePitch); - - /** - * Performs an async/non-blocking read of the image into the specified byte buffer. - * - * @param queue the command queue - * @param dest the target byte buffer - * @param origin the image origin location, see class description for the format - * @param region the copied region, see class description for the format - * @param rowPitch the row pitch of the target buffer, must be set to 0 if the image is 1D. - * If set to 0 for 2D and 3D image, the row pitch is calculated as {@code bytesPerElement * width} - * @param slicePitch the slice pitch of the target buffer, must be set to 0 for 1D and 2D images. - * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height} - * @return the event object indicating the status of the operation - */ - public abstract Event readImageAsync(CommandQueue queue, ByteBuffer dest, long[] origin, long[] region, long rowPitch, long slicePitch); - - /** - * Performs a blocking write from the specified byte buffer into the image. - * - * @param queue the command queue - * @param src the source buffer - * @param origin the image origin location, see class description for the format - * @param region the copied region, see class description for the format - * @param rowPitch the row pitch of the target buffer, must be set to 0 if the image is 1D. - * If set to 0 for 2D and 3D image, the row pitch is calculated as {@code bytesPerElement * width} - * @param slicePitch the slice pitch of the target buffer, must be set to 0 for 1D and 2D images. - * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height} - */ - public abstract void writeImage(CommandQueue queue, ByteBuffer src, long[] origin, long[] region, long rowPitch, long slicePitch); - - /** - * Performs an async/non-blocking write from the specified byte buffer into the image. - * - * @param queue the command queue - * @param src the source buffer - * @param origin the image origin location, see class description for the format - * @param region the copied region, see class description for the format - * @param rowPitch the row pitch of the target buffer, must be set to 0 if the image is 1D. - * If set to 0 for 2D and 3D image, the row pitch is calculated as {@code bytesPerElement * width} - * @param slicePitch the slice pitch of the target buffer, must be set to 0 for 1D and 2D images. - * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height} - * @return the event object indicating the status of the operation - */ - public abstract Event writeImageAsync(CommandQueue queue, ByteBuffer src, long[] origin, long[] region, long rowPitch, long slicePitch); - - /** - * Performs a blocking copy operation from one image to another. - * Important: Both images must have the same format! - * - * @param queue the command queue - * @param dest the target image - * @param srcOrigin the source image origin, see class description for the format - * @param destOrigin the target image origin, see class description for the format - * @param region the copied region, see class description for the format - */ - public abstract void copyTo(CommandQueue queue, Image dest, long[] srcOrigin, long[] destOrigin, long[] region); - - /** - * Performs an async/non-blocking copy operation from one image to another. - * Important: Both images must have the same format! - * - * @param queue the command queue - * @param dest the target image - * @param srcOrigin the source image origin, see class description for the format - * @param destOrigin the target image origin, see class description for the format - * @param region the copied region, see class description for the format - * @return the event object indicating the status of the operation - */ - public abstract Event copyToAsync(CommandQueue queue, Image dest, long[] srcOrigin, long[] destOrigin, long[] region); - - /** - * Maps the image into host memory. - * The returned structure contains the mapped byte buffer and row and slice pitch. - * The event object is set to {@code null}, it is needed for the async - * version {@link #mapAsync(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) }. - * - * @param queue the command queue - * @param origin the image origin, see class description for the format - * @param region the mapped region, see class description for the format - * @param access the allowed memory access to the mapped memory - * @return a structure describing the mapped memory - * @see #unmap(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image.ImageMapping) - */ - public abstract ImageMapping map(CommandQueue queue, long[] origin, long[] region, MappingAccess access); - - /** - * Non-blocking version of {@link #map(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) }. - * The returned structure contains the mapped byte buffer and row and slice pitch. - * The event object is used to detect when the mapped memory is available. - * @param queue the command queue - * @param origin the image origin, see class description for the format - * @param region the mapped region, see class description for the format - * @param access the allowed memory access to the mapped memory - * @return a structure describing the mapped memory - * @see #unmap(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image.ImageMapping) - */ - public abstract ImageMapping mapAsync(CommandQueue queue, long[] origin, long[] region, MappingAccess access); - - /** - * Unmaps the mapped memory - * - * @param queue the command queue - * @param mapping the mapped memory - */ - public abstract void unmap(CommandQueue queue, ImageMapping mapping); - - /** - * Describes a mapped region of the image - */ - public static class ImageMapping { - /** - * The raw byte buffer - */ - public final ByteBuffer buffer; - /** - * The row pitch in bytes. - * This value is at least {@code bytesPerElement * width} - */ - public final long rowPitch; - /** - * The slice pitch in bytes. - * This value is at least {@code rowPitch * height} - */ - public final long slicePitch; - /** - * The event object used to detect when the memory is available. - * - * @see #mapAsync(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) - */ - public final Event event; - - public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch, Event event) { - this.buffer = buffer; - this.rowPitch = rowPitch; - this.slicePitch = slicePitch; - this.event = event; - } - - public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch) { - this.buffer = buffer; - this.rowPitch = rowPitch; - this.slicePitch = slicePitch; - this.event = null; - } - } - - /** - * Fills the image with the specified color. - * Does only work if the image channel is {@link ImageChannelType#FLOAT} - * or {@link ImageChannelType#HALF_FLOAT}. - * - * @param queue the command queue - * @param origin the image origin, see class description for the format - * @param region the size of the region, see class description for the format - * @param color the color to fill - * @return an event object to detect for the completion - */ - public abstract Event fillAsync(CommandQueue queue, long[] origin, long[] region, ColorRGBA color); - /** - * Fills the image with the specified color given as four integer variables. - * Does not work if the image channel is {@link ImageChannelType#FLOAT} - * or {@link ImageChannelType#HALF_FLOAT}. - * - * @param queue the command queue - * @param origin the image origin, see class description for the format - * @param region the size of the region, see class description for the format - * @param color the color to fill, must be an array of length 4 - * @return an event object to detect for the completion - */ - public abstract Event fillAsync(CommandQueue queue, long[] origin, long[] region, int[] color); - - /** - * Copies this image into the specified buffer, no format conversion is done. - * This is the dual function to - * {@link Buffer#copyToImageAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image, long, long[], long[]) }. - * - * @param queue the command queue - * @param dest the target buffer - * @param srcOrigin the image origin, see class description for the format - * @param srcRegion the copied region, see class description for the format - * @param destOffset an offset into the target buffer - * @return the event object to detect the completion of the operation - */ - public abstract Event copyToBufferAsync(CommandQueue queue, Buffer dest, long[] srcOrigin, long[] srcRegion, long destOffset); - - /** - * Acquires this image object for using. Only call this method if this image - * represents a shared object from OpenGL, created with e.g. - * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) } - * or variations. - * This method must be called before the image is used. After the work is - * done, the image must be released by calling - * {@link #releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * so that OpenGL can use the image/texture/renderbuffer again. - * - * @param queue the command queue - * @return the event object - */ - public abstract Event acquireImageForSharingAsync(CommandQueue queue); - - /** - * Acquires this image object for using. Only call this method if this image - * represents a shared object from OpenGL, created with e.g. - * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) } - * or variations. - * This method must be called before the image is used. After the work is - * done, the image must be released by calling - * {@link #releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * so that OpenGL can use the image/texture/renderbuffer again. - * - * The generated event object is directly released. - * This brings a performance improvement when the resource is e.g. directly - * used by a kernel afterwards on the same queue (this implicitly waits for - * this action). If you need the event, use - * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) }. - * - * @param queue the command queue - */ - public void acquireImageForSharingNoEvent(CommandQueue queue) { - //Default implementation, overwrite for performance - acquireImageForSharingAsync(queue).release(); - } - - /** - * Releases a shared image object. - * Call this method after the image object was acquired by - * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * to hand the control back to OpenGL. - * - * @param queue the command queue - * @return the event object - */ - public abstract Event releaseImageForSharingAsync(CommandQueue queue); - - /** - * Releases a shared image object. - * Call this method after the image object was acquired by - * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } - * to hand the control back to OpenGL. - * The generated event object is directly released, resulting in - * performance improvements. - * - * @param queue the command queue - */ - public void releaseImageForSharingNoEvent(CommandQueue queue) { - //default implementation, overwrite it for performance improvements - releaseImageForSharingAsync(queue).release(); - } - - @Override - public String toString() { - StringBuilder str = new StringBuilder(); - str.append("Image ("); - ImageType t = getImageType(); - str.append(t); - str.append(", w=").append(getWidth()); - if (t == ImageType.IMAGE_2D || t == ImageType.IMAGE_3D) { - str.append(", h=").append(getHeight()); - } - if (t == ImageType.IMAGE_3D) { - str.append(", d=").append(getDepth()); - } - if (t == ImageType.IMAGE_1D_ARRAY || t == ImageType.IMAGE_2D_ARRAY) { - str.append(", arrays=").append(getArraySize()); - } - str.append(", ").append(getImageFormat()); - str.append(')'); - return str.toString(); - } -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/Kernel.java b/jme3-core/src/main/java/com/jme3/opencl/Kernel.java deleted file mode 100644 index a1882e7776..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/Kernel.java +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (c) 2009-2020 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import com.jme3.math.*; -import com.jme3.util.TempVars; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * Wrapper for an OpenCL kernel, a piece of executable code on the GPU. - *

    - * Terminology:
    - * A Kernel is executed in parallel. In total number of parallel threads, - * called work items, are specified by the global work size (of type - * {@link WorkSize}). These threads are organized in a 1-D, 2-D or 3-D grid - * (of course, this is only a logical view). Inside each kernel, - * the id of each thread (i.e. the index inside this grid) can be requested - * by {@code get_global_id(dimension)} with {@code dimension=0,1,2}. - *
    - * Not all threads can always be executed in parallel because there simply might - * not be enough processor cores. - * Therefore, the concept of a work group is introduced. The work group - * specifies the actual number of threads that are executed in parallel. - * The maximal size of it can be queried by {@link Device#getMaxiumWorkItemsPerGroup() }. - * Again, the threads inside the work group can be organized in a 1D, 2D or 3D - * grid, but this is also just a logical view (specifying how the threads are - * indexed). - * The work group is important for another concept: shared memory - * Unlike the normal global or constant memory (passing a {@link Buffer} object - * as argument), shared memory can't be set from outside. Shared memory is - * allocated by the kernel and is only valid within the kernel. It is used - * to quickly share data between threads within a work group. - * The size of the shared memory is specified by setting an instance of - * {@link LocalMem} or {@link LocalMemPerElement} as argument.
    - * Due to heavy register usage or other reasons, a kernel might not be able - * to utilize a whole work group. Therefore, the actual number of threads - * that can be executed in a work group can be queried by - * {@link #getMaxWorkGroupSize(com.jme3.opencl.Device) }, which might differ from the - * value returned from the Device. - * - *

    - * There are two ways to launch a kernel:
    - * First, arguments and the work group sizes can be set in advance - * ({@code setArg(index, ...)}, {@code setGlobalWorkSize(...)} and {@code setWorkGroupSize(...)}. - * Then a kernel is launched by {@link #Run(com.jme3.opencl.CommandQueue) }.
    - * Second, two convenient functions are provided that set the arguments - * and work sizes in one call: - * {@link #Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) } - * and {@link #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. - * - * @author shaman - * @see Program#createKernel(java.lang.String) - */ -public abstract class Kernel extends AbstractOpenCLObject { - /** - * The current global work size - */ - protected final WorkSize globalWorkSize; - /** - * The current local work size - */ - protected final WorkSize workGroupSize; - - protected Kernel(ObjectReleaser releaser) { - super(releaser); - this.globalWorkSize = new WorkSize(0); - this.workGroupSize = new WorkSize(0); - } - - @Override - public Kernel register() { - super.register(); - return this; - } - - /** - * @return the name of the kernel as defined in the program source code - */ - public abstract String getName(); - - /** - * @return the number of arguments - */ - public abstract int getArgCount(); - - /** - * @return the current global work size - */ - public WorkSize getGlobalWorkSize() { - return globalWorkSize; - } - - /** - * Sets the global work size. - * - * @param ws the work size to set - */ - public void setGlobalWorkSize(WorkSize ws) { - globalWorkSize.set(ws); - } - - /** - * Sets the global work size to a 1D grid - * - * @param size the size in 1D - */ - public void setGlobalWorkSize(int size) { - globalWorkSize.set(1, size); - } - - /** - * Sets the global work size to be a 2D grid - * - * @param width the width - * @param height the height - */ - public void setGlobalWorkSize(int width, int height) { - globalWorkSize.set(2, width, height); - } - - /** - * Sets the global work size to be a 3D grid - * - * @param width the width - * @param height the height - * @param depth the depth - */ - public void setGlobalWorkSize(int width, int height, int depth) { - globalWorkSize.set(3, width, height, depth); - } - - /** - * @return the current work group size - */ - public WorkSize getWorkGroupSize() { - return workGroupSize; - } - - /** - * Sets the work group size - * - * @param ws the work group size to set - */ - public void setWorkGroupSize(WorkSize ws) { - workGroupSize.set(ws); - } - - /** - * Sets the work group size to be a 1D grid - * - * @param size the size to set - */ - public void setWorkGroupSize(int size) { - workGroupSize.set(1, size); - } - - /** - * Sets the work group size to be a 2D grid - * - * @param width the width - * @param height the height - */ - public void setWorkGroupSize(int width, int height) { - workGroupSize.set(2, width, height); - } - - /** - * Sets the work group size to be a 3D grid - * - * @param width the width - * @param height the height - * @param depth the depth - */ - public void setWorkGroupSdize(int width, int height, int depth) { - workGroupSize.set(3, width, height, depth); - } - - /** - * Tells the driver to figure out the work group size on their own. - * Use this if you do not rely on specific work group layouts, i.e. - * because shared memory is not used. - * {@link #Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) } - * implicitly calls this method. - */ - public void setWorkGroupSizeToNull() { - workGroupSize.set(1, 0, 0, 0); - } - - /** - * Returns the maximal work group size when this kernel is executed on - * the specified device - * - * @param device the device - * @return the maximal work group size - */ - public abstract long getMaxWorkGroupSize(Device device); - - public abstract void setArg(int index, LocalMemPerElement t); - - public abstract void setArg(int index, LocalMem t); - - public abstract void setArg(int index, Buffer t); - - public abstract void setArg(int index, Image i); - - public abstract void setArg(int index, byte b); - - public abstract void setArg(int index, short s); - - public abstract void setArg(int index, int i); - - public abstract void setArg(int index, long l); - - public abstract void setArg(int index, float f); - - public abstract void setArg(int index, double d); - - public abstract void setArg(int index, Vector2f v); - - public abstract void setArg(int index, Vector4f v); - - public abstract void setArg(int index, Quaternion q); - - public abstract void setArg(int index, Matrix4f mat); - - public void setArg(int index, Matrix3f mat) { - TempVars vars = TempVars.get(); - try { - Matrix4f m = vars.tempMat4; - m.zero(); - for (int i = 0; i < 3; ++i) { - for (int j = 0; j < 3; ++j) { - m.set(i, j, mat.get(i, j)); - } - } - setArg(index, m); - } finally { - vars.release(); - } - } - - /** - * Raw version to set an argument. - * {@code size} bytes of the provided byte buffer are copied to the kernel - * argument. The size in bytes must match exactly the argument size - * as defined in the kernel code. - * Use this method to send custom structures to the kernel - * - * @param index the index of the argument - * @param buffer the raw buffer - * @param size the size in bytes - */ - public abstract void setArg(int index, ByteBuffer buffer, long size); - - /** - * Sets the kernel argument at the specified index.
    - * The argument must be a known type: - * {@code LocalMemPerElement, LocalMem, Image, Buffer, byte, short, int, - * long, float, double, Vector2f, Vector4f, Quaternion, Matrix3f, Matrix4f}. - *
    - * Note: Matrix3f and Matrix4f will be mapped to a {@code float16} (row major). - * - * @param index the index of the argument, from 0 to {@link #getArgCount()}-1 - * @param arg the argument - * @throws IllegalArgumentException if the argument type is not one of the listed ones - */ - public void setArg(int index, Object arg) { - if (arg instanceof Byte) { - setArg(index, (byte) arg); - } else if (arg instanceof Short) { - setArg(index, (short) arg); - } else if (arg instanceof Integer) { - setArg(index, (int) arg); - } else if (arg instanceof Long) { - setArg(index, (long) arg); - } else if (arg instanceof Float) { - setArg(index, (float) arg); - } else if (arg instanceof Double) { - setArg(index, (double) arg); - } else if (arg instanceof Vector2f) { - setArg(index, (Vector2f) arg); - } else if (arg instanceof Vector4f) { - setArg(index, (Vector4f) arg); - } else if (arg instanceof Quaternion) { - setArg(index, (Quaternion) arg); - } else if (arg instanceof Matrix3f) { - setArg(index, (Matrix3f) arg); - } else if (arg instanceof Matrix4f) { - setArg(index, (Matrix4f) arg); - } else if (arg instanceof LocalMemPerElement) { - setArg(index, (LocalMemPerElement) arg); - } else if (arg instanceof LocalMem) { - setArg(index, (LocalMem) arg); - } else if (arg instanceof Buffer) { - setArg(index, (Buffer) arg); - } else if (arg instanceof Image) { - setArg(index, (Image) arg); - } else { - throw new IllegalArgumentException("unknown kernel argument type: " + arg); - } - } - - private void setArgs(Object... args) { - for (int i = 0; i < args.length; ++i) { - setArg(i, args[i]); - } - } - - /** - * Launches the kernel with the current global work size, work group size - * and arguments. - * If the returned event object is not needed and would otherwise be - * released immediately, {@link #RunNoEvent(com.jme3.opencl.CommandQueue) } - * might bring a better performance. - * - * @param queue the command queue - * @return an event object indicating when the kernel is finished - * @see #setGlobalWorkSize(com.jme3.opencl.Kernel.WorkSize) - * @see #setWorkGroupSize(com.jme3.opencl.Kernel.WorkSize) - * @see #setArg(int, java.lang.Object) - */ - public abstract Event Run(CommandQueue queue); - - /** - * Launches the kernel with the current global work size, work group size - * and arguments without returning an event object. - * The generated event is directly released. Therefore, the performance - * is better, but there is no way to detect when the kernel execution - * has finished. For this purpose, use {@link #Run(com.jme3.opencl.CommandQueue) }. - * - * @param queue the command queue - * @see #setGlobalWorkSize(com.jme3.opencl.Kernel.WorkSize) - * @see #setWorkGroupSize(com.jme3.opencl.Kernel.WorkSize) - * @see #setArg(int, java.lang.Object) - */ - public void RunNoEvent(CommandQueue queue) { - //Default implementation, overwrite to not allocate the event object - Run(queue).release(); - } - - /** - * Sets the work sizes and arguments in one call and launches the kernel. - * The global work size is set to the specified size. The work group - * size is automatically determined by the driver. - * Each object in the argument array is sent to the kernel by - * {@link #setArg(int, java.lang.Object) }. - * - * @param queue the command queue - * @param globalWorkSize the global work size - * @param args the kernel arguments - * @return an event object indicating when the kernel is finished - * @see #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) - */ - public Event Run1(CommandQueue queue, WorkSize globalWorkSize, Object... args) { - setGlobalWorkSize(globalWorkSize); - setWorkGroupSizeToNull(); - setArgs(args); - return Run(queue); - } - - /** - * Sets the work sizes and arguments in one call and launches the kernel. - * The global work size is set to the specified size. The work group - * size is automatically determined by the driver. - * Each object in the argument array is sent to the kernel by - * {@link #setArg(int, java.lang.Object) }. - * The generated event is directly released. Therefore, the performance - * is better, but there is no way to detect when the kernel execution - * has finished. For this purpose, use - * {@link #Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. - * - * @param queue the command queue - * @param globalWorkSize the global work size - * @param args the kernel arguments - * @see #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) - */ - public void Run1NoEvent(CommandQueue queue, WorkSize globalWorkSize, Object... args) { - setGlobalWorkSize(globalWorkSize); - setWorkGroupSizeToNull(); - setArgs(args); - RunNoEvent(queue); - } - - /** - * Sets the work sizes and arguments in one call and launches the kernel. - * - * @param queue the command queue - * @param globalWorkSize the global work size - * @param workGroupSize the work group size - * @param args the kernel arguments - * @return an event object indicating when the kernel is finished - */ - public Event Run2(CommandQueue queue, WorkSize globalWorkSize, - WorkSize workGroupSize, Object... args) { - setGlobalWorkSize(globalWorkSize); - setWorkGroupSize(workGroupSize); - setArgs(args); - return Run(queue); - } - - /** - * Sets the work sizes and arguments in one call and launches the kernel. - * The generated event is directly released. Therefore, the performance - * is better, but there is no way to detect when the kernel execution - * has finished. For this purpose, use - * {@link #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. - * - * @param queue the command queue - * @param globalWorkSize the global work size - * @param workGroupSize the work group size - * @param args the kernel arguments - */ - public void Run2NoEvent(CommandQueue queue, WorkSize globalWorkSize, - WorkSize workGroupSize, Object... args) { - setGlobalWorkSize(globalWorkSize); - setWorkGroupSize(workGroupSize); - setArgs(args); - RunNoEvent(queue); - } - - @Override - public String toString() { - return "Kernel (" + getName() + ")"; - } - - /** - * A placeholder for kernel arguments representing local kernel memory. This - * defines the size of available shared memory of a {@code __shared} kernel - * argument - */ - public static final class LocalMem { - private int size; - - /** - * Creates a new LocalMem instance - * - * @param size the size of the available shared memory in bytes - */ - public LocalMem(int size) { - super(); - this.size = size; - } - - public int getSize() { - return size; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 79 * hash + this.size; - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final LocalMem other = (LocalMem) obj; - if (this.size != other.size) { - return false; - } - return true; - } - - @Override - public String toString() { - return "LocalMem (" + size + "B)"; - } - - } - - /** - * A placeholder for a kernel argument representing local kernel memory per thread. - * This effectively computes {@code SharedMemoryPerElement * WorkGroupSize} - * and uses this value as the size of shared memory available in the kernel. - * Therefore, an instance of this class must be set as an argument AFTER - * the work group size has been specified. This is - * ensured by {@link #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. - * This argument can't be used when no work group size was defined explicitly - * (e.g. by {@link #setWorkGroupSizeToNull()} or {@link #Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }). - */ - public static final class LocalMemPerElement { - private int size; - - /** - * Creates a new LocalMemPerElement instance - * - * @param size the number of bytes available for each thread within - * a work group - */ - public LocalMemPerElement(int size) { - super(); - this.size = size; - } - - public int getSize() { - return size; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 79 * hash + this.size; - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final LocalMemPerElement other = (LocalMemPerElement) obj; - if (this.size != other.size) { - return false; - } - return true; - } - - @Override - public String toString() { - return "LocalMemPerElement (" + size + "B)"; - } - - } - - /** - * The work size (global and local) for executing a kernel - * - * @author shaman - */ - public static final class WorkSize { - - private int dimension; - private long[] sizes; - - /** - * Creates a new work size object - * - * @param dimension the dimension (1,2,3) - * @param sizes the sizes in each dimension, the length must match the specified dimension - */ - public WorkSize(int dimension, long... sizes) { - super(); - set(dimension, sizes); - } - - /** - * Creates a work size of dimension 1 and extend 1,1,1 (only one thread). - */ - public WorkSize() { - this(1, 1, 1, 1); - } - - /** - * Creates a 1D work size of the specified extend - * - * @param size the size - */ - public WorkSize(long size) { - this(1, size, 1, 1); - } - - /** - * Creates a 2D work size of the specified extend - * - * @param width the width - * @param height the height - */ - public WorkSize(long width, long height) { - this(2, width, height, 1); - } - - /** - * Creates a 3D work size of the specified extend. - * - * @param width the width - * @param height the height - * @param depth the depth - */ - public WorkSize(long width, long height, long depth) { - this(3, width, height, depth); - } - - public int getDimension() { - return dimension; - } - - public long[] getSizes() { - return sizes; - } - - public void set(int dimension, long... sizes) { - if (sizes == null || sizes.length != 3) { - throw new IllegalArgumentException("sizes must be an array of length 3"); - } - if (dimension <= 0 || dimension > 3) { - throw new IllegalArgumentException("dimension must be between 1 and 3"); - } - this.dimension = dimension; - this.sizes = sizes; - } - - public void set(WorkSize ws) { - this.dimension = ws.dimension; - this.sizes = ws.sizes; - } - - @Override - public int hashCode() { - int hash = 5; - hash = 47 * hash + this.dimension; - hash = 47 * hash + Arrays.hashCode(this.sizes); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final WorkSize other = (WorkSize) obj; - if (this.dimension != other.dimension) { - return false; - } - if (!Arrays.equals(this.sizes, other.sizes)) { - return false; - } - return true; - } - - @Override - public String toString() { - StringBuilder str = new StringBuilder(); - str.append("WorkSize["); - for (int i = 0; i < dimension; ++i) { - if (i > 0) { - str.append(", "); - } - str.append(sizes[i]); - } - str.append(']'); - return str.toString(); - } - } -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/MappingAccess.java b/jme3-core/src/main/java/com/jme3/opencl/MappingAccess.java deleted file mode 100644 index bbf7d4df67..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/MappingAccess.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2009-2016 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -/** - * Specifies the access flags when mapping a {@link Buffer} or {@link Image} object. - * @see Buffer#map(com.jme3.opencl.CommandQueue, long, long, com.jme3.opencl.MappingAccess) - * @see Image#map(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) - * @author shaman - */ -public enum MappingAccess { - /** - * Only read access is allowed to the mapped memory. - */ - MAP_READ_ONLY, - /** - * Only write access is allowed to the mapped memory. - */ - MAP_WRITE_ONLY, - /** - * Both read and write access is allowed. - */ - MAP_READ_WRITE, - /** - * The old memory content is completely discarded and the buffer is filled - * completely with new data. This might be faster than {@link #MAP_WRITE_ONLY} - */ - MAP_WRITE_INVALIDATE -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObject.java b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObject.java deleted file mode 100644 index d030f0b6eb..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObject.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -/** - * Base interface of all native OpenCL objects. - * This interface provides the functionality to safely release the object. - * @author shaman - */ -public interface OpenCLObject { - - /** - * Releaser for an {@link OpenCLObject}. - * Implementations of this interface must not hold a reference to the - * {@code OpenCLObject} directly. - */ - public static interface ObjectReleaser { - /** - * Releases the native resources of the associated {@link OpenCLObject}. - * This method must be guarded against multiple calls: only the first - * call should release, the next ones must not throw an exception. - */ - void release(); - } - /** - * Returns the releaser object. Multiple calls should return the same object. - * The ObjectReleaser is used to release the OpenCLObject when it is garbage - * collected. Therefore, the returned object must not hold a reference to - * the OpenCLObject. - * @return the object releaser - */ - ObjectReleaser getReleaser(); - /** - * Releases this native object. - * - * Should delegate to {@code getReleaser().release()}. - */ - void release(); - /** - * Registers this object for automatic releasing on garbage collection. - * By default, OpenCLObjects are not registered in the - * {@link OpenCLObjectManager}, you have to release it manually - * by calling {@link #release() }. - * Without registering or releasing, a memory leak might occur. - *
    - * Returns {@code this} to allow calls like - * {@code Buffer buffer = clContext.createBuffer(1024).register();}. - * @return {@code this} - */ - OpenCLObject register(); -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java deleted file mode 100644 index bb5d95b6dd..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import java.lang.ref.PhantomReference; -import java.lang.ref.ReferenceQueue; -import java.util.HashSet; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author shaman - */ -public class OpenCLObjectManager { - private static final Logger LOG = Logger.getLogger(OpenCLObjectManager.class.getName()); - private static final Level LOG_LEVEL1 = Level.FINEST; - private static final Level LOG_LEVEL2 = Level.FINER; - - private static final OpenCLObjectManager INSTANCE = new OpenCLObjectManager(); - private OpenCLObjectManager() {} - - public static OpenCLObjectManager getInstance() { - return INSTANCE; - } - - private final ReferenceQueue refQueue = new ReferenceQueue<>(); - private final HashSet activeObjects = new HashSet<>(); - - private static class OpenCLObjectRef extends PhantomReference { - - final private OpenCLObject.ObjectReleaser releaser; - - public OpenCLObjectRef(ReferenceQueue refQueue, OpenCLObject obj){ - super(obj, refQueue); - releaser = obj.getReleaser(); - } - } - - public void registerObject(OpenCLObject obj) { - OpenCLObjectRef ref = new OpenCLObjectRef(refQueue, obj); - activeObjects.add(ref); - LOG.log(LOG_LEVEL1, "registered OpenCL object: {0}", obj); - } - - private void deleteObject(OpenCLObjectRef ref) { - LOG.log(LOG_LEVEL1, "deleting OpenCL object by: {0}", ref.releaser); - ref.releaser.release(); - ref.clear(); - activeObjects.remove(ref); - } - - public void deleteUnusedObjects() { - if (activeObjects.isEmpty()) { - LOG.log(LOG_LEVEL2, "no active natives"); - return; //nothing to do - } - - int removed = 0; - while (true) { - // Remove objects reclaimed by GC. - OpenCLObjectRef ref = (OpenCLObjectRef) refQueue.poll(); - if (ref == null) { - break; - } - deleteObject(ref); - removed++; - } - if (removed >= 1) { - LOG.log(LOG_LEVEL2, "{0} native objects were removed from native", removed); - } - } - - public void deleteAllObjects() { - for (OpenCLObjectRef ref : activeObjects) { - LOG.log(LOG_LEVEL1, "deleting OpenCL object by: {0}", ref.releaser); - ref.releaser.release(); - ref.clear(); - } - activeObjects.clear(); - } -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/Platform.java b/jme3-core/src/main/java/com/jme3/opencl/Platform.java deleted file mode 100644 index 10b3fd4663..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/Platform.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2009-2016 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import java.util.Collection; -import java.util.List; - -/** - * A wrapper for an OpenCL platform. A platform is the highest object in the - * object hierarchy, it creates the {@link Device}s which are then used to - * create the {@link Context}.
    - * This class is mostly used within {@link PlatformChooser}. - * - * @author shaman - */ -public interface Platform { - - /** - * @return the list of available devices for this platform - */ - List getDevices(); - - /** - * @return The profile string - */ - String getProfile(); - /** - * @return {@code true} if this platform implements the full profile - */ - boolean isFullProfile(); - /** - * @return {@code true} if this platform implements the embedded profile - */ - boolean isEmbeddedProfile(); - - /** - * @return the version string - */ - String getVersion(); - /** - * Extracts the major version from the version string - * @return the major version - */ - int getVersionMajor(); - /** - * Extracts the minor version from the version string - * @return the minor version - */ - int getVersionMinor(); - - /** - * @return the name of the platform - */ - String getName(); - /** - * @return the vendor of the platform - */ - String getVendor(); - /** - * Queries if this platform supports OpenGL interop at all. - * This value has also to be tested for every device. - * @return {@code true} if OpenGL interop is supported - */ - boolean hasOpenGLInterop(); - /** - * Queries if the specified extension is available. - * This value has to be tested also for every device. - * @param extension the extension string - * @return {@code true} if this extension is supported by the platform - * (however, not all devices might support it as well) - */ - boolean hasExtension(String extension); - /** - * @return All available extensions - */ - Collection getExtensions(); - -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/Program.java b/jme3-core/src/main/java/com/jme3/opencl/Program.java deleted file mode 100644 index ce900547c3..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/Program.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2009-2020 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import java.nio.ByteBuffer; - -/** - * A wrapper for an OpenCL program. A program is created from kernel source code, - * manages the build process and creates the kernels. - *

    - * Warning: Creating the same kernel more than one leads to undefined behaviour, - * this is especially important for {@link #createAllKernels() } - * - * @see Context#createProgramFromSourceCode(java.lang.String) - * @see #createKernel(java.lang.String) - * @author shaman - */ -public abstract class Program extends AbstractOpenCLObject { - - protected Program(ObjectReleaser releaser) { - super(releaser); - } - - @Override - public Program register() { - super.register(); - return this; - } - - /** - * Builds this program with the specified argument string on the specified - * devices. - * Please see the official OpenCL specification for a definition of - * all supported arguments. - * The list of devices specify on which device the compiled program - * can then be executed. It must be a subset of {@link Context#getDevices() }. - * If {@code null} is passed, the program is built on all available devices. - * - * @param args the compilation arguments - * @param devices a list of devices on which the program is build. - * @throws KernelCompilationException if the compilation fails - * @see #build() - */ - public abstract void build(String args, Device... devices) throws KernelCompilationException; - - /** - * Builds this program without additional arguments - * - * @throws KernelCompilationException if the compilation fails - */ - public void build() throws KernelCompilationException { - build("", (Device[]) null); - } - - /** - * Creates the kernel with the specified name. - * - * @param name the name of the kernel as defined in the source code - * @return the kernel object - * @throws OpenCLException if the kernel was not found or some other error - * occurred - */ - public abstract Kernel createKernel(String name); - - /** - * Creates all available kernels in this program. - * The names of the kernels can then be queried by {@link Kernel#getName() }. - * - * @return an array of all kernels - */ - public abstract Kernel[] createAllKernels(); - - /** - * Queries a compiled binary representation of this program for a particular - * device. This binary can then be used e.g. in the next application launch - * to create the program from the binaries and not from the sources. - * This saves time. - * - * @param device the device from which the binaries are taken - * @return the binaries - * @see Context#createProgramFromBinary(java.nio.ByteBuffer, com.jme3.opencl.Device) - */ - public abstract ByteBuffer getBinary(Device device); -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/ProgramCache.java b/jme3-core/src/main/java/com/jme3/opencl/ProgramCache.java deleted file mode 100644 index bc533acaf2..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/ProgramCache.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2009-2016 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.opencl; - -import com.jme3.system.JmeSystem; -import com.jme3.util.BufferUtils; -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Implements a simple cache system for program objects. - * The program objects are saved persistently with {@link #saveToCache(java.lang.String, com.jme3.opencl.Program) }. - * On the next run, the stored programs can then be loaded - * with {@link #loadFromCache(java.lang.String, java.lang.String) }. - *
    - * The programs are identified by a unique id. The following format is recommended: - * {@code id = .}. - * - * @author shaman - */ -public class ProgramCache { - private static final Logger LOG = Logger.getLogger(ProgramCache.class.getName()); - private static final String FILE_EXTENSION = ".clbin"; - - private final Context context; - private final Device device; - private final File tmpFolder; - - /** - * Creates a "disabled" program cache, no caching is done. - * {@link #loadFromCache(java.lang.String) } will always return {@code null} - * and {@link #saveToCache(java.lang.String, com.jme3.opencl.Program) } does - * nothing.
    - * Use this during development if you still modify your kernel code. - * (Otherwise, you don't see the changes because you are still use the - * cached version of your program) - */ - public ProgramCache() { - this.context = null; - this.device = null; - this.tmpFolder = null; - } - - /** - * Creates a new program cache associated with the specified context and - * devices. - * The cached programs are built against the specified device and also - * only the binaries linked to that device are stored. - * @param context the OpenCL context - * @param device the OpenCL device - */ - public ProgramCache(Context context, Device device) { - this.context = context; - this.device = device; - if (JmeSystem.isLowPermissions()) { - tmpFolder = null; - } else { - tmpFolder = JmeSystem.getStorageFolder(); - } - } - - protected String getCleanFileName(String id) { - //http://stackoverflow.com/a/35591188/4053176 - return id.replaceAll("[^a-zA-Z0-9.-]", "") + FILE_EXTENSION; - } - - /** - * Creates a new program cache using the first device from the specified - * context. - * @param context the context - * @see #ProgramCache(com.jme3.opencl.Context, com.jme3.opencl.Device) - */ - public ProgramCache(Context context) { - this(context, context.getDevices().get(0)); - } - - /** - * Loads the program from the cache and builds it against the current device. - * You can pass additional build arguments with the parameter {@code buildArgs}. - *

    - * The cached program is identified by the specified id. - * This id must be unique, otherwise collisions within the cache occur. - * Therefore, the following naming schema is recommended: - * {@code id = .}. - *

    - * If the program can't be loaded, built or any other exception happened, - * {@code null} is returned. - * - * @param id the unique identifier of this program - * @param buildArgs additional build arguments, can be {@code null} - * @return the loaded and built program, or {@code null} - * @see #saveToCache(java.lang.String, com.jme3.opencl.Program) - */ - public Program loadFromCache(String id, String buildArgs) { - if (tmpFolder == null) { - return null; //low permissions - } - //get file - File file = new File(tmpFolder, getCleanFileName(id)); - if (!file.exists()) { - if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, "Cache file {0} does not exist", file.getAbsolutePath()); - } - return null; - } - //load from file - ByteBuffer bb; - try { - byte[] bytes = Files.readAllBytes(file.toPath()); - bb = BufferUtils.createByteBuffer(bytes); - } catch (IOException ex) { - LOG.log(Level.FINE, "Unable to read cache file", ex); - return null; - } - //create program - Program program; - try { - program = context.createProgramFromBinary(bb, device); - } catch (OpenCLException ex) { - LOG.log(Level.FINE, "Unable to create program from binary", ex); - return null; - } - //build program - try { - program.build(buildArgs, device); - } catch (OpenCLException ex) { - LOG.log(Level.FINE, "Unable to build program", ex); - return null; - } - //done - return program; - } - - /** - * Calls {@link #loadFromCache(java.lang.String, java.lang.String) } - * with the additional build arguments set to {@code ""}. - * @param id a unique identifier of the program - * @return the loaded and built program or {@code null} if this - * program could not be loaded from the cache - * @see #loadFromCache(java.lang.String, java.lang.String) - */ - public Program loadFromCache(String id) { - return loadFromCache(id, ""); - } - - /** - * Saves the specified program in the cache. - * The parameter {@code id} denotes the name of the program. Under this id, - * the program is then loaded again by {@link #loadFromCache(java.lang.String, java.lang.String) }. - *
    - * The id must be unique, otherwise collisions within the cache occur. - * Therefore, the following naming schema is recommended: - * {@code id = .}. - * - * @param id the program id - * @param program the program to store in the cache - */ - public void saveToCache(String id, Program program) { - if (tmpFolder == null) { - return; //low permissions - } - //get file - File file = new File(tmpFolder, getCleanFileName(id)); - //get binaries - ByteBuffer bb; - try { - bb = program.getBinary(device); - } catch (UnsupportedOperationException | OpenCLException ex) { - LOG.log(Level.WARNING, "Unable to retrieve the program binaries", ex); - return; - } - byte[] bytes = new byte[bb.remaining()]; - bb.get(bytes); - //save - try { - Files.write(file.toPath(), bytes); - } catch (IOException ex) { - LOG.log(Level.WARNING, "Unable to save program binaries to the cache", ex); - } - } - - /** - * Clears the cache. - * All saved program binaries are deleted. - */ - public void clearCache() { - if (tmpFolder == null) { - return; //low permissions - } - for (File file : tmpFolder.listFiles()) { - if (file.isFile() && file.getName().endsWith(FILE_EXTENSION)) { - file.delete(); - } - } - } -} diff --git a/jme3-core/src/main/java/com/jme3/opencl/package-info.java b/jme3-core/src/main/java/com/jme3/opencl/package-info.java deleted file mode 100644 index 0a19e3a24a..0000000000 --- a/jme3-core/src/main/java/com/jme3/opencl/package-info.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * This package contains an API for using OpenCL together with jME3. - *

    - * Activation:
    - * OpenCL is deactivated by default. To activate it, set {@link com.jme3.system.AppSettings#setOpenCLSupport(boolean) } - * to {@code true}. - * If the current platform supports OpenCL, then the central {@link com.jme3.opencl.Context} - * can be fetched by {@link com.jme3.system.JmeContext#getOpenCLContext() } which is - * available in each application. If OpenCL is deactivated or not available, - * this method returns {@code null}. - * - *

    - * First steps:
    - * Once you have obtained your {@link com.jme3.opencl.Context} you start by - * creating a {@link com.jme3.opencl.CommandQueue} by calling - * {@link com.jme3.opencl.Context#createQueue() } or alternative versions. - * The command queue must be passed to every following method that execute - * some action involving the GPU. All actions are executed in the order in which they - * are added to the queue. - *
    - * Programs and Kernels: - * The main purpose of OpenCL is to execute code in parallel - * on the GPU. From the source code, a {@link com.jme3.opencl.Program} object - * is created by {@link com.jme3.opencl.Context#createProgramFromSourceCode(java.lang.String) }, - * {@link com.jme3.opencl.Context#createProgramFromSourceFilesWithInclude(com.jme3.asset.AssetManager, java.lang.String, java.util.List) } - * or alternative versions. - * Before using it, the source code must be build using {@link com.jme3.opencl.Program#build() }. - * Any compilation error is thrown here. Each program consists of multiple kernels. - * Each kernel represents one executable unit and is declared in the source code - * with the following syntax: {@code __kernel void KernelName(KernelArgs) {Code} }. - * On the programming side, a {@link com.jme3.opencl.Kernel} instance is obtained - * by calling {@link com.jme3.opencl.Program#createKernel(java.lang.String) }. - * To execute the kernel, the method - * {@link com.jme3.opencl.Kernel#Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...)} - * is provided. You first pass the command queue and the work size (i.e. the number of parallel executed threads) - * followed by the kernel arguments. - *
    - * Buffers and Images: - * OpenCL Kernels show their true power first when they operate on buffers and images. - * Buffers are simple one dimensional consecutive chunks of memory of arbitrary size. - * These {@link com.jme3.opencl.Buffer} instances are created by calling - * {@link com.jme3.opencl.Context#createBuffer(long)} with the size in bytes as - * the argument. A buffer on its own is typeless. In the kernel, you then specify - * the type of the buffer by argument declarations like {@code __global float* buffer}. - * Note that OpenCL does not check buffer boundaries. If you read or write outside - * the buffer, the behavior is completely undefined and may often result in - * a program cache later on. - * {@link com.jme3.opencl.Image} objects are structured one-, two-, or three-dimensional - * memory chunks of a fixed type. They are created by Context.createImage(). - * They need special functions in the kernel code to write to or read from images. - * Both buffer and image objects provide methods for copying between buffers and images, - * reading and writing to host code and directly mapping memory parts to the host code. - *
    - * Events: - * Most methods are provided in two variations: blocking calls or asynchronous - * calls (the later one have the suffix -Async, or all kernel calls). - * These async calls all return {@link com.jme3.opencl.Event} objects. - * These events can be used to check (non-blocking) if the action has completed, e.g. a memory copy - * is finished, or to block the execution until the action has finished. - *
    - * Some methods have the suffix {@code -NoEvent}. This means that these methods - * don't return an event object even if the OpenCL function would return an event. - * There's always an alternative version that does return an event. - * These methods exist to increase the performance: since all actions (like multiple kernel calls) - * that are sent to the same command queue are executed in order, there is no - * need for intermediate events. (These intermediate events would be released - * immediately). Therefore, the no-event alternatives increase the performance - * because no additional event object has to be allocated and less system calls - * are necessary. - * - *

    - * Interoperability between OpenCL and jME3:
    - * This Wrapper allows sharing jME3 Images and VertexBuffers with OpenCL.
    - * {@link com.jme3.scene.VertexBuffer} objects can be shared with OpenCL - * by calling {@link com.jme3.opencl.Context#bindVertexBuffer(com.jme3.scene.VertexBuffer, com.jme3.opencl.MemoryAccess) } - * resulting in a {@link com.jme3.opencl.Buffer} object. This buffer object - * can then be used as usual, allowing e.g. the dynamic modification of position buffers for particle systems.
    - * {@link com.jme3.texture.Image} and {@link com.jme3.texture.Texture} objects can be used in OpenCL with the method - * {@link com.jme3.opencl.Context#bindImage(com.jme3.texture.Texture, com.jme3.opencl.MemoryAccess) } - * or variations of this method. The same holds for {@link com.jme3.texture.FrameBuffer.RenderBuffer} objects - * using {@link com.jme3.opencl.Context#bindRenderBuffer(com.jme3.texture.FrameBuffer.RenderBuffer, com.jme3.opencl.MemoryAccess) }. - * These methods result in an OpenCL-Image. Usages are e.g. animated textures, - * terrain based on height maps, post-processing effects, and so forth. - *
    - * Important: Before shared objects can be used by any OpenCL function - * like kernel calls or read/write/copy methods, they must be acquired explicitly - * by {@link com.jme3.opencl.Buffer#acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } - * or {@link com.jme3.opencl.Image#acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) }. - * After the work is done, release the resource with - * {@link com.jme3.opencl.Buffer#releaseBufferForSharingAsync(com.jme3.opencl.CommandQueue) } - * or {@link com.jme3.opencl.Image#releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) }. - * This ensures the synchronization of OpenCL and OpenGL. - * - *

    - * Experts: choosing the right platform and devices
    - * OpenCL can run on different platforms and different devices. On some systems, - * like multi-GPU setups, this choice really matters. To specify which platform - * and which devices are used, a custom implementation of - * {@link com.jme3.opencl.PlatformChooser} can be used by calling - * {@link com.jme3.system.AppSettings#setOpenCLPlatformChooser(java.lang.Class) }. - * For more details, see the documentation of {@code PlatformChooser}. - * - *

    - * Exception handling:
    - * All OpenCL-wrapper classes in this package - * (this includes {@link com.jme3.opencl.Platform}, {@link com.jme3.opencl.Device}, - * {@link com.jme3.opencl.Context}, {@link com.jme3.opencl.CommandQueue}, - * {@link com.jme3.opencl.Buffer}, {@link com.jme3.opencl.Image}, - * {@link com.jme3.opencl.Program}, {@link com.jme3.opencl.Kernel} and - * {@link com.jme3.opencl.Event}) - * may throw the following exceptions in each method without being mentioned - * explicitly in the documentation: - *

      - *
    • {@code NullPointerException}: one of the arguments is {@code null} and - * {@code null} is not allowed
    • - *
    • {@code IllegalArgumentException}: the arguments don't follow the rules - * as specified in the documentation of the method, e.g. values are out of range - * or an array has the wrong size
    • - *
    • {@link com.jme3.opencl.OpenCLException}: some low-level exception was - * thrown. The exception always records the error code and error name and the - * OpenCL function call where the error was detected. Please check the official - * OpenCL specification for the meanings of these errors for that particular function.
    • - *
    - */ -package com.jme3.opencl; - -//TODO: add profiling to Kernel, CommandQueue diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index 5b111c811b..1183002a65 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -427,6 +427,11 @@ public enum Caps { */ DepthTexture, + /** + * Supports hardware depth texture comparison for shadow maps. + */ + TextureShadowCompare, + /** * Supports 32-bit index buffers. */ @@ -467,6 +472,12 @@ public enum Caps { * GPU support for binary shaders. */ BinaryShader, + + /** + * Supports GPU timer queries with full 64-bit elapsed-time results. + */ + GpuTimerQuery, + /** * Supporting working with UniformBufferObject. */ @@ -648,7 +659,7 @@ public static boolean supports(Collection caps, FrameBuffer fb) { return false; } - RenderBuffer depthBuf = fb.getDepthBuffer(); + RenderBuffer depthBuf = fb.getDepthTarget(); if (depthBuf != null) { Format depthFmt = depthBuf.getFormat(); if (!depthFmt.isDepthFormat()) { @@ -675,8 +686,8 @@ public static boolean supports(Collection caps, FrameBuffer fb) { } } } - for (int i = 0; i < fb.getNumColorBuffers(); i++) { - if (!supportsColorBuffer(caps, fb.getColorBuffer(i))) { + for (int i = 0; i < fb.getNumColorTargets(); i++) { + if (!supportsColorBuffer(caps, fb.getColorTarget(i))) { return false; } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java index ae51d09bdd..15a202a6a5 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java @@ -35,16 +35,21 @@ import com.jme3.math.ColorRGBA; import com.jme3.scene.VertexBuffer; import com.jme3.shader.Shader; +import com.jme3.shader.bufferobject.BufferObject; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import java.lang.ref.WeakReference; -import com.jme3.shader.bufferobject.BufferObject; /** * Represents the current state of the graphics library. This class is used * internally to reduce state changes. NOTE: This class is specific to OpenGL. */ public class RenderContext { + @SuppressWarnings("unchecked") + private static WeakReference[] newWeakReferenceArray(int size) { + return (WeakReference[]) new WeakReference[size]; + } + /** * Number of texture units that JME supports. */ @@ -264,8 +269,8 @@ public class RenderContext { * * @see Renderer#setTexture(int, com.jme3.texture.Texture) */ - public final WeakReference boundTextures[] - = new WeakReference[maxTextureUnits]; + public final WeakReference[] boundTextures + = newWeakReferenceArray(maxTextureUnits); /** @@ -274,7 +279,7 @@ public class RenderContext { * @see Renderer#setUniformBufferObject(int, com.jme3.shader.BufferObject) * @see Renderer#setShaderStorageBufferObject(int, com.jme3.shader.BufferObject) */ - public final WeakReference[] boundBO = new WeakReference[maxBufferObjectUnits]; + public final WeakReference[] boundBO = newWeakReferenceArray(maxBufferObjectUnits); /** * IDList for texture units. @@ -331,7 +336,7 @@ public class RenderContext { * Vertex attribs currently bound and enabled. If a slot is null, then * it is disabled. */ - public final WeakReference[] boundAttribs = new WeakReference[16]; + public final WeakReference[] boundAttribs = newWeakReferenceArray(16); /** * IDList for vertex attributes. diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java index a554bcfa47..06665d1bf2 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -31,13 +31,9 @@ */ package com.jme3.renderer; -import com.jme3.renderer.pipeline.ForwardPipeline; -import com.jme3.renderer.pipeline.DefaultPipelineContext; -import com.jme3.renderer.pipeline.RenderPipeline; -import com.jme3.renderer.pipeline.PipelineContext; -import com.jme3.light.DefaultLightFilter; -import com.jme3.light.LightFilter; -import com.jme3.light.LightList; +import com.jme3.light.DefaultLightFilter; +import com.jme3.light.LightFilter; +import com.jme3.light.LightList; import com.jme3.material.MatParamOverride; import com.jme3.material.Material; import com.jme3.material.MaterialDef; @@ -46,11 +42,15 @@ import com.jme3.material.TechniqueDef; import com.jme3.math.FastMath; import com.jme3.math.Matrix4f; -import com.jme3.post.SceneProcessor; -import com.jme3.profile.AppProfiler; -import com.jme3.profile.AppStep; -import com.jme3.profile.VpStep; -import com.jme3.renderer.queue.GeometryList; +import com.jme3.post.SceneProcessor; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.AppStep; +import com.jme3.profile.VpStep; +import com.jme3.renderer.pipeline.DefaultPipelineContext; +import com.jme3.renderer.pipeline.ForwardPipeline; +import com.jme3.renderer.pipeline.PipelineContext; +import com.jme3.renderer.pipeline.RenderPipeline; +import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.renderer.queue.RenderQueue.ShadowMode; @@ -110,10 +110,12 @@ public class RenderManager { private final ArrayList postViewPorts = new ArrayList<>(); private final HashMap, PipelineContext> contexts = new HashMap<>(); private final LinkedList usedContexts = new LinkedList<>(); - private final LinkedList> usedPipelines = new LinkedList<>(); - private RenderPipeline defaultPipeline = new ForwardPipeline(); - private Camera prevCam = null; - private Material forcedMaterial = null; + private final LinkedList> usedPipelines = new LinkedList<>(); + private RenderPipeline defaultPipeline = new ForwardPipeline(); + private Camera prevCam = null; + private int prevViewportWidth = -1; + private int prevViewportHeight = -1; + private Material forcedMaterial = null; private String forcedTechnique = null; private RenderState forcedRenderState = null; private final SafeArrayList forcedOverrides = new SafeArrayList<>(MatParamOverride.class); @@ -472,10 +474,11 @@ public ViewPort createPostView(String viewName, Camera cam) { return vp; } - private void notifyReshape(ViewPort vp, int w, int h) { - List processors = vp.getProcessors(); - for (SceneProcessor proc : processors) { - if (!proc.isInitialized()) { + private void notifyReshape(ViewPort vp, int w, int h) { + vp.setRenderTargetSize(w, h); + List processors = vp.getProcessors(); + for (SceneProcessor proc : processors) { + if (!proc.isInitialized()) { proc.initialize(this, vp); } else { proc.reshape(vp, w, h); @@ -502,33 +505,55 @@ private void notifyRescale(ViewPort vp, float x, float y) { * @param w the new width (in pixels) * @param h the new height (in pixels) */ - public void notifyReshape(int w, int h) { - for (ViewPort vp : preViewPorts) { - if (vp.getOutputFrameBuffer() == null) { - Camera cam = vp.getCamera(); - cam.resize(w, h, true); - } - notifyReshape(vp, w, h); - } - for (ViewPort vp : viewPorts) { - if (vp.getOutputFrameBuffer() == null) { - Camera cam = vp.getCamera(); - cam.resize(w, h, true); - } - notifyReshape(vp, w, h); - } - for (ViewPort vp : postViewPorts) { - if (vp.getOutputFrameBuffer() == null) { - Camera cam = vp.getCamera(); - cam.resize(w, h, true); - } - notifyReshape(vp, w, h); - } - } - - /** - * Internal use only. - * Updates the scale of all on-screen ViewPorts + public void notifyReshape(int w, int h) { + notifyReshape(w, h, w, h); + } + + /** + * Internal use only. + * Updates logical on-screen camera sizes while keeping the physical size of + * the default framebuffer available for viewport and post-processing work. + * + * @param logicalWidth the logical application width + * @param logicalHeight the logical application height + * @param framebufferWidth the physical default framebuffer width + * @param framebufferHeight the physical default framebuffer height + */ + public void notifyReshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) { + prevCam = null; + int surfaceWidth = Math.max(framebufferWidth, 1); + int surfaceHeight = Math.max(framebufferHeight, 1); + reshapeViewPorts(preViewPorts, logicalWidth, logicalHeight, surfaceWidth, surfaceHeight); + reshapeViewPorts(viewPorts, logicalWidth, logicalHeight, surfaceWidth, surfaceHeight); + reshapeViewPorts(postViewPorts, logicalWidth, logicalHeight, surfaceWidth, surfaceHeight); + } + + private void reshapeViewPorts(List viewPorts, int logicalWidth, int logicalHeight, + int surfaceWidth, int surfaceHeight) { + for (ViewPort vp : viewPorts) { + FrameBuffer frameBuffer = vp.getOutputFrameBuffer(); + if (frameBuffer == null) { + Camera cam = vp.getCamera(); + cam.resize(logicalWidth, logicalHeight, true); + notifyReshape(vp, surfaceWidth, surfaceHeight); + } else { + notifyReshape(vp, getFrameBufferWidth(frameBuffer, logicalWidth), + getFrameBufferHeight(frameBuffer, logicalHeight)); + } + } + } + + private int getFrameBufferWidth(FrameBuffer frameBuffer, int fallbackWidth) { + return frameBuffer.getWidth() > 0 ? frameBuffer.getWidth() : fallbackWidth; + } + + private int getFrameBufferHeight(FrameBuffer frameBuffer, int fallbackHeight) { + return frameBuffer.getHeight() > 0 ? frameBuffer.getHeight() : fallbackHeight; + } + + /** + * Internal use only. + * Updates the scale of all on-screen ViewPorts * * @param x the new horizontal scale * @param y the new vertical scale @@ -1237,11 +1262,11 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { prof.vpStep(VpStep.RenderBucket, vp, Bucket.Gui); } renderer.setDepthRange(0, 0); - setCamera(cam, true); - rq.renderQueue(Bucket.Gui, this, cam, flush); - setCamera(cam, false); - depthRangeChanged = true; - } + setCamera(cam, true, vp.getRenderTargetWidth(), vp.getRenderTargetHeight()); + rq.renderQueue(Bucket.Gui, this, cam, flush); + setCamera(cam, false, vp.getRenderTargetWidth(), vp.getRenderTargetHeight()); + depthRangeChanged = true; + } // restore range to default if (depthRangeChanged) { @@ -1272,20 +1297,23 @@ public void renderTranslucentQueue(ViewPort vp) { } } - private void setViewPort(Camera cam) { - // this will make sure to clearReservations viewport only if needed - if (cam != prevCam || cam.isViewportChanged()) { - int viewX = (int) (cam.getViewPortLeft() * cam.getWidth()); - int viewY = (int) (cam.getViewPortBottom() * cam.getHeight()); - int viewX2 = (int) (cam.getViewPortRight() * cam.getWidth()); - int viewY2 = (int) (cam.getViewPortTop() * cam.getHeight()); - int viewWidth = viewX2 - viewX; - int viewHeight = viewY2 - viewY; - uniformBindingManager.setViewPort(viewX, viewY, viewWidth, viewHeight); + private void setViewPort(Camera cam, int surfaceWidth, int surfaceHeight) { + // this will make sure to clearReservations viewport only if needed + if (cam != prevCam || cam.isViewportChanged() + || surfaceWidth != prevViewportWidth || surfaceHeight != prevViewportHeight) { + int viewX = (int) (cam.getViewPortLeft() * surfaceWidth); + int viewY = (int) (cam.getViewPortBottom() * surfaceHeight); + int viewX2 = (int) (cam.getViewPortRight() * surfaceWidth); + int viewY2 = (int) (cam.getViewPortTop() * surfaceHeight); + int viewWidth = viewX2 - viewX; + int viewHeight = viewY2 - viewY; + uniformBindingManager.setViewPort(viewX, viewY, viewWidth, viewHeight); renderer.setViewPort(viewX, viewY, viewWidth, viewHeight); - renderer.setClipRect(viewX, viewY, viewWidth, viewHeight); - cam.clearViewportChanged(); - prevCam = cam; + renderer.setClipRect(viewX, viewY, viewWidth, viewHeight); + cam.clearViewportChanged(); + prevCam = cam; + prevViewportWidth = surfaceWidth; + prevViewportHeight = surfaceHeight; // float translateX = viewWidth == viewX ? 0 : -(viewWidth + viewX) / (viewWidth - viewX); // float translateY = viewHeight == viewY ? 0 : -(viewHeight + viewY) / (viewHeight - viewY); @@ -1328,14 +1356,18 @@ private void setViewProjection(Camera cam, boolean ortho) { * @param ortho True if to use orthographic projection (for GUI rendering), * false if to use the camera's view and projection matrices. */ - public void setCamera(Camera cam, boolean ortho) { - // Tell the light filter which camera to use for filtering. - if (lightFilter != null) { - lightFilter.setCamera(cam); - } - setViewPort(cam); - setViewProjection(cam, ortho); - } + public void setCamera(Camera cam, boolean ortho) { + setCamera(cam, ortho, cam.getWidth(), cam.getHeight()); + } + + private void setCamera(Camera cam, boolean ortho, int targetWidth, int targetHeight) { + // Tell the light filter which camera to use for filtering. + if (lightFilter != null) { + lightFilter.setCamera(cam); + } + setViewPort(cam, targetWidth, targetHeight); + setViewProjection(cam, ortho); + } /** * Draws the viewport but without notifying {@link SceneProcessor scene @@ -1345,9 +1377,9 @@ public void setCamera(Camera cam, boolean ortho) { * * @see #renderViewPort(com.jme3.renderer.ViewPort, float) */ - public void renderViewPortRaw(ViewPort vp) { - setCamera(vp.getCamera(), false); - List scenes = vp.getScenes(); + public void renderViewPortRaw(ViewPort vp) { + setCamera(vp.getCamera(), false, vp.getRenderTargetWidth(), vp.getRenderTargetHeight()); + List scenes = vp.getScenes(); for (int i = scenes.size() - 1; i >= 0; i--) { renderScene(scenes.get(i), vp); } @@ -1360,10 +1392,10 @@ public void renderViewPortRaw(ViewPort vp) { * * @param vp The ViewPort to apply. */ - public void applyViewPort(ViewPort vp) { - renderer.setFrameBuffer(vp.getOutputFrameBuffer()); - setCamera(vp.getCamera(), false); - if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) { + public void applyViewPort(ViewPort vp) { + renderer.setFrameBuffer(vp.getOutputFrameBuffer()); + setCamera(vp.getCamera(), false, vp.getRenderTargetWidth(), vp.getRenderTargetHeight()); + if (vp.isClearDepth() || vp.isClearColor() || vp.isClearStencil()) { if (vp.isClearColor()) { renderer.setBackgroundColor(vp.getBackgroundColor()); } @@ -1382,19 +1414,22 @@ public void applyViewPort(ViewPort vp) { * @param vp View port to render * @param tpf Time per frame value */ - public void renderViewPort(ViewPort vp, float tpf) { - if (!vp.isEnabled()) { - return; - } - RenderPipeline pipeline = vp.getPipeline(); - if (pipeline == null) { - pipeline = defaultPipeline; - } - - PipelineContext context = pipeline.fetchPipelineContext(this); - if (context == null) { - throw new NullPointerException("Failed to fetch pipeline context."); - } + public void renderViewPort(ViewPort vp, float tpf) { + if (!vp.isEnabled()) { + return; + } + RenderPipeline pipeline = vp.getPipeline(); + if (pipeline == null) { + pipeline = defaultPipeline; + } + renderViewPort(vp, tpf, pipeline); + } + + private void renderViewPort(ViewPort vp, float tpf, RenderPipeline pipeline) { + T context = pipeline.fetchPipelineContext(this); + if (context == null) { + throw new NullPointerException("Failed to fetch pipeline context."); + } if (!context.startViewPortRender(this, vp)) { usedContexts.add(context); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java index f256405276..523f0ee7c4 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java +++ b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java @@ -31,9 +31,10 @@ */ package com.jme3.renderer; -import com.jme3.renderer.pipeline.RenderPipeline; import com.jme3.math.ColorRGBA; import com.jme3.post.SceneProcessor; +import com.jme3.renderer.pipeline.PipelineContext; +import com.jme3.renderer.pipeline.RenderPipeline; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; @@ -88,11 +89,13 @@ public class ViewPort { /** * Dedicated pipeline. */ - protected RenderPipeline pipeline; + protected RenderPipeline pipeline; /** * FrameBuffer for output. */ protected FrameBuffer out = null; + protected int renderTargetWidth; + protected int renderTargetHeight; /** * Color applied when the color buffer is cleared. @@ -309,6 +312,41 @@ public void setOutputFrameBuffer(FrameBuffer out) { this.out = out; } + void setRenderTargetSize(int width, int height) { + renderTargetWidth = Math.max(width, 1); + renderTargetHeight = Math.max(height, 1); + } + + /** + * Returns the width of this ViewPort's current render target. + * + * @return the width in pixels + */ + public int getRenderTargetWidth() { + if (out != null && out.getWidth() > 0) { + return out.getWidth(); + } + if (renderTargetWidth > 0) { + return renderTargetWidth; + } + return cam.getWidth(); + } + + /** + * Returns the height of this ViewPort's current render target. + * + * @return the height in pixels + */ + public int getRenderTargetHeight() { + if (out != null && out.getHeight() > 0) { + return out.getHeight(); + } + if (renderTargetHeight > 0) { + return renderTargetHeight; + } + return cam.getHeight(); + } + /** * Returns the camera which renders the attached scenes. * @@ -440,7 +478,7 @@ public boolean isEnabled() { * * @param pipeline pipeline, or null to use render manager's pipeline */ - public void setPipeline(RenderPipeline pipeline) { + public void setPipeline(RenderPipeline pipeline) { this.pipeline = pipeline; } @@ -449,7 +487,7 @@ public void setPipeline(RenderPipeline pipeline) { * * @return */ - public RenderPipeline getPipeline() { + public RenderPipeline getPipeline() { return pipeline; } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java index 7712c52a29..b55ed63376 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java @@ -890,6 +890,16 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo */ public String glGetProgramInfoLog(int program, int maxSize); + /** + * Returns whether this GL binding can execute elapsed-time timer queries + * and read their full 64-bit results. + * + * @return true if full GPU timer query support is exposed by the binding + */ + public default boolean supportsGpuTimerQuery() { + return true; + } + /** * Unsigned version. * diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java index 755e076cac..b70a5123f4 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java @@ -42,10 +42,14 @@ public interface GLES_30 extends GL { public static final int GL_RGB10_A2 = 0x8059; public static final int GL_UNSIGNED_INT_2_10_10_10_REV = 0x8368; - + public static final int GL_NUM_EXTENSIONS = 0x821D; + public void glBindVertexArray(int array); public void glDeleteVertexArrays(IntBuffer arrays); public void glGenVertexArrays(IntBuffer arrays); + + public String glGetString(final int name, final int index); + } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java index d7d2dd58c4..c16c9e4e2a 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java @@ -169,12 +169,17 @@ public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { format(formatToGL, Format.RGB565, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_SHORT_5_6_5, opengl || opengles3 || webgl, false, true); } - // Additional desktop-specific formats. + // Additional byte-order formats supported directly on desktop GL. if (opengl) { format(formatToGL, Format.BGR8, GL2.GL_RGB8, GL2.GL_BGR, GL.GL_UNSIGNED_BYTE, true, false, true); format(formatToGL, Format.ARGB8, GLExt.GL_RGBA8, GL2.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8, true, false, true); format(formatToGL, Format.BGRA8, GLExt.GL_RGBA8, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, true, false, true); format(formatToGL, Format.ABGR8, GLExt.GL_RGBA8, GL.GL_RGBA, GL2.GL_UNSIGNED_INT_8_8_8_8, true, false, true); + } else if (opengles3) { + formatSwiz(formatToGL, Format.BGR8, GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true, false, true); + formatSwiz(formatToGL, Format.ARGB8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true, false, true); + formatSwiz(formatToGL, Format.BGRA8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true, false, true); + formatSwiz(formatToGL, Format.ABGR8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true, false, true); } // sRGB formats @@ -193,6 +198,12 @@ public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { formatSrgb(formatToGL, Format.ABGR8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL2.GL_UNSIGNED_INT_8_8_8_8, true, false, true); formatSrgb(formatToGL, Format.ARGB8, GLExt.GL_SRGB8_ALPHA8_EXT, GL2.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8, true, false, true); formatSrgb(formatToGL, Format.BGRA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL2.GL_BGRA, GL.GL_UNSIGNED_BYTE, true, false, true); + } else if (opengles3) { + // Match GLES RGB8/sRGB8 renderability for the same internal format. + formatSrgbSwiz(formatToGL, Format.BGR8, GLExt.GL_SRGB8_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false, false, true); + formatSrgbSwiz(formatToGL, Format.ARGB8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true, false, true); + formatSrgbSwiz(formatToGL, Format.BGRA8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true, false, true); + formatSrgbSwiz(formatToGL, Format.ABGR8, GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true, false, true); } if (caps.contains(Caps.TextureCompressionS3TC)) { diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index b7d11f9ba6..82bf96a428 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -37,7 +37,6 @@ import com.jme3.material.RenderState.StencilOperation; import com.jme3.material.RenderState.TestFunction; import com.jme3.math.*; -import com.jme3.opencl.OpenCLObjectManager; import com.jme3.renderer.*; import com.jme3.scene.Mesh; import com.jme3.scene.Mesh.Mode; @@ -188,7 +187,15 @@ private HashSet loadExtensions() { gl3.glGetInteger(GL3.GL_NUM_EXTENSIONS, intBuf16); int extensionCount = intBuf16.get(0); for (int i = 0; i < extensionCount; i++) { - String extension = gl3.glGetString(GL.GL_EXTENSIONS, i); + String extension = gl3.glGetString(GL3.GL_EXTENSIONS, i); + extensionSet.add(extension); + } + } else if (caps.contains(Caps.OpenGLES30)) { + GLES_30 gles = (GLES_30) gl; + gles.glGetInteger(GLES_30.GL_NUM_EXTENSIONS, intBuf16); + int extensionCount = intBuf16.get(0); + for (int i = 0; i < extensionCount; i++) { + String extension = gles.glGetString(GLES_30.GL_EXTENSIONS, i); extensionSet.add(extension); } } else { @@ -446,6 +453,10 @@ private void loadCapabilitiesCommon() { caps.add(Caps.DepthTexture); } + if (gl2 != null || caps.contains(Caps.OpenGLES30)) { + caps.add(Caps.TextureShadowCompare); + } + if (caps.contains(Caps.OpenGL20) || caps.contains(Caps.OpenGLES30) || caps.contains(Caps.WebGL) || hasExtension("GL_OES_depth24")) { caps.add(Caps.Depth24); @@ -601,6 +612,7 @@ && hasExtension("GL_ARB_half_float_pixel")) limits.put(Limits.FrameBufferSamples, getInteger(GLExt.GL_MAX_SAMPLES_EXT)); } + if (hasExtension("GL_ARB_texture_multisample") || caps.contains(Caps.OpenGL32) || caps.contains(Caps.OpenGLES31)) { // GLES31 does not fully support it caps.add(Caps.TextureMultisample); @@ -672,6 +684,13 @@ && hasExtension("GL_ARB_half_float_pixel")) } } + if (gl.supportsGpuTimerQuery() + && (caps.contains(Caps.OpenGL33) + || hasExtension("GL_ARB_timer_query") + || (caps.contains(Caps.OpenGLES20) && hasExtension("GL_EXT_disjoint_timer_query")))) { + caps.add(Caps.GpuTimerQuery); + } + if (hasExtension("GL_OES_geometry_shader") || hasExtension("GL_EXT_geometry_shader")) { caps.add(Caps.GeometryShader); } @@ -901,7 +920,6 @@ public void resetGLObjects() { public void cleanup() { logger.log(Level.FINE, "Deleting objects and invalidating state"); objManager.deleteAllObjects(this); - OpenCLObjectManager.getInstance().deleteAllObjects(); statistics.clearMemory(); invalidateState(); } @@ -1386,7 +1404,6 @@ public void clearClipRect() { @Override public void postFrame() { objManager.deleteUnused(this); - OpenCLObjectManager.getInstance().deleteUnusedObjects(); gl.resetStats(); } @@ -1737,7 +1754,14 @@ public void updateShaderSourceData(ShaderSource source) { if (language.startsWith("GLSL")) { if (version > 100) { stringBuf.append("#version "); - stringBuf.append(language.substring(4)); + + if (version >= 150 && version < 300 && gles3) { + // upgrade to 300, since it's the minimum version for GLES3. + version = 300; + } + + stringBuf.append(version); + if (version >= 150) { if(gles3) { stringBuf.append(" es"); @@ -1746,12 +1770,15 @@ public void updateShaderSourceData(ShaderSource source) { stringBuf.append(" core"); } } + stringBuf.append("\n"); } else { - if (gles2 || gles3) { + if (gles3) { + // request GLSL ES (3.00) when compiling under GLES3. + stringBuf.append("#version 300 es\n"); + } else if (gles2) { // request GLSL ES (1.00) when compiling under GLES2. stringBuf.append("#version 100\n"); - } else { // version 100 does not exist in desktop GLSL. // put version 110 in that case to enable strict checking @@ -1956,6 +1983,7 @@ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { } @Override + @Deprecated public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { copyFrameBuffer(src, dst, true, copyDepth); } @@ -2226,7 +2254,7 @@ private void toggleFramebufferSrgb(FrameBuffer fb) { } public void updateFrameBuffer(FrameBuffer fb) { - if (fb.getNumColorBuffers() == 0 && fb.getDepthBuffer() == null) { + if (fb.getNumColorTargets() == 0 && fb.getDepthTarget() == null) { throw new IllegalArgumentException("The framebuffer: " + fb + "\nDoesn't have any color/depth buffers"); } @@ -2242,13 +2270,13 @@ public void updateFrameBuffer(FrameBuffer fb) { bindFrameBuffer(fb); - FrameBuffer.RenderBuffer depthBuf = fb.getDepthBuffer(); + FrameBuffer.RenderBuffer depthBuf = fb.getDepthTarget(); if (depthBuf != null) { updateFrameBufferAttachment(fb, depthBuf); } - for (int i = 0; i < fb.getNumColorBuffers(); i++) { - FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i); + for (int i = 0; i < fb.getNumColorTargets(); i++) { + FrameBuffer.RenderBuffer colorBuf = fb.getColorTarget(i); updateFrameBufferAttachment(fb, colorBuf); } @@ -2309,13 +2337,13 @@ public void setReadDrawBuffers(FrameBuffer fb) { if (fb != null) { - if (fb.getNumColorBuffers() == 0) { + if (fb.getNumColorTargets() == 0) { // make sure to select NONE as draw buf // no color buffer attached. gl2.glDrawBuffer(GL.GL_NONE); gl2.glReadBuffer(GL.GL_NONE); } else { - if (fb.getNumColorBuffers() > limits.get(Limits.FrameBufferAttachments)) { + if (fb.getNumColorTargets() > limits.get(Limits.FrameBufferAttachments)) { throw new RendererException("Framebuffer has more color " + "attachments than are supported" + " by the video hardware!"); @@ -2325,21 +2353,21 @@ public void setReadDrawBuffers(FrameBuffer fb) { throw new RendererException("Multiple render targets " + " are not supported by the video hardware"); } - if (fb.getNumColorBuffers() > limits.get(Limits.FrameBufferMrtAttachments)) { + if (fb.getNumColorTargets() > limits.get(Limits.FrameBufferMrtAttachments)) { throw new RendererException("Framebuffer has more" + " multi targets than are supported" + " by the video hardware!"); } intBuf16.clear(); - for (int i = 0; i < fb.getNumColorBuffers(); i++) { + for (int i = 0; i < fb.getNumColorTargets(); i++) { intBuf16.put(GLFbo.GL_COLOR_ATTACHMENT0_EXT + i); } intBuf16.flip(); glext.glDrawBuffers(intBuf16); } else { - RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); + RenderBuffer rb = fb.getColorTarget(fb.getTargetIndex()); // select this draw buffer gl2.glDrawBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); // select this read buffer @@ -2373,8 +2401,8 @@ public void setFrameBuffer(FrameBuffer fb) { if (boundFB != null && (boundFB.getMipMapsGenerationHint() != null ? boundFB.getMipMapsGenerationHint() : generateMipmapsForFramebuffers)) { - for (int i = 0; i < boundFB.getNumColorBuffers(); i++) { - RenderBuffer rb = boundFB.getColorBuffer(i); + for (int i = 0; i < boundFB.getNumColorTargets(); i++) { + RenderBuffer rb = boundFB.getColorTarget(i); Texture tex = rb.getTexture(); if (tex != null && tex.getMinFilter().usesMipMapLevels() && isMipmapGenerationSupported(tex.getImage().getFormat(), @@ -2429,7 +2457,7 @@ public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int glFormat, int dataType) { if (fb != null) { - RenderBuffer rb = fb.getColorBuffer(); + RenderBuffer rb = fb.getColorTarget(); if (rb == null) { throw new IllegalArgumentException("Specified framebuffer" + " does not have a colorbuffer"); @@ -2464,11 +2492,11 @@ public void deleteFrameBuffer(FrameBuffer fb) { context.boundFBO = 0; } - if (fb.getDepthBuffer() != null) { - deleteRenderBuffer(fb, fb.getDepthBuffer()); + if (fb.getDepthTarget() != null) { + deleteRenderBuffer(fb, fb.getDepthTarget()); } - if (fb.getColorBuffer() != null) { - deleteRenderBuffer(fb, fb.getColorBuffer()); + if (fb.getColorTarget() != null) { + deleteRenderBuffer(fb, fb.getColorTarget()); } intBuf1.put(0, fb.getId()); @@ -2660,7 +2688,7 @@ && isMipmapGenerationSupported(image.getFormat(), } ShadowCompareMode texCompareMode = tex.getShadowCompareMode(); - if ( (gl2 != null || caps.contains(Caps.OpenGLES30)) && curState.shadowCompareMode != texCompareMode) { + if (caps.contains(Caps.TextureShadowCompare) && curState.shadowCompareMode != texCompareMode) { bindTextureAndUnit(target, image, unit); if (texCompareMode != ShadowCompareMode.Off) { gl.glTexParameteri(target, GL2.GL_TEXTURE_COMPARE_MODE, GL2.GL_COMPARE_REF_TO_TEXTURE); @@ -3819,6 +3847,9 @@ public void setLinearizeSrgbImages(boolean linearize) { @Override public int[] generateProfilingTasks(int numTasks) { + if (!caps.contains(Caps.GpuTimerQuery)) { + throw new RendererException("GPU timer queries are not supported by the current renderer"); + } IntBuffer ids = BufferUtils.createIntBuffer(numTasks); gl.glGenQueries(numTasks, ids); return BufferUtils.getIntArray(ids); @@ -3826,21 +3857,33 @@ public int[] generateProfilingTasks(int numTasks) { @Override public void startProfiling(int taskId) { + if (!caps.contains(Caps.GpuTimerQuery)) { + throw new RendererException("GPU timer queries are not supported by the current renderer"); + } gl.glBeginQuery(GL.GL_TIME_ELAPSED, taskId); } @Override public void stopProfiling() { + if (!caps.contains(Caps.GpuTimerQuery)) { + throw new RendererException("GPU timer queries are not supported by the current renderer"); + } gl.glEndQuery(GL.GL_TIME_ELAPSED); } @Override public long getProfilingTime(int taskId) { + if (!caps.contains(Caps.GpuTimerQuery)) { + throw new RendererException("GPU timer queries are not supported by the current renderer"); + } return gl.glGetQueryObjectui64(taskId, GL.GL_QUERY_RESULT); } @Override public boolean isTaskResultAvailable(int taskId) { + if (!caps.contains(Caps.GpuTimerQuery)) { + throw new RendererException("GPU timer queries are not supported by the current renderer"); + } return gl.glGetQueryObjectiv(taskId, GL.GL_QUERY_RESULT_AVAILABLE) == 1; } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java index 5eafcf865a..7e74ceb3b3 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java @@ -128,6 +128,24 @@ private void setupTextureSwizzle(int target, Format format) { gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_GREEN); break; + case BGR8: + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_BLUE); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_GREEN); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_ONE); + break; + case ARGB8: + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_GREEN); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_BLUE); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_ALPHA); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_RED); + break; + case BGRA8: + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_BLUE); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_GREEN); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED); + gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_ALPHA); + break; case ABGR8: gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_ALPHA); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_BLUE); diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java index 2f02ab3e82..96850bd182 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java @@ -46,7 +46,7 @@ * * @author codex */ -public class ForwardPipeline implements RenderPipeline { +public class ForwardPipeline implements RenderPipeline { private boolean rendered = false; diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java index b104e5bd5d..bee0e4a56a 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java @@ -12,7 +12,7 @@ * * @author codex */ -public class NullPipeline implements RenderPipeline { +public class NullPipeline implements RenderPipeline { private boolean rendered = false; diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index 46ea295e73..7e7a4d82f2 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -685,22 +685,23 @@ with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing /** * Returns flat list of Spatials implementing the specified class AND * with name matching the specified pattern. - *

    + * * Note that we are matching the pattern, therefore the pattern * must match the entire pattern (i.e. it behaves as if it is sandwiched * between "^" and "$"). * You can set regex modes, like case insensitivity, by using the (?X) * or (?X:Y) constructs. - *

    - * By design, it is always safe to code loops like:

    +     *
    +     * 

    By design, it is always safe to code loops like:

    + *
          *     for (Spatial spatial : node.descendantMatches(AClass.class, "regex"))
          * 
    - *

    + * * "Descendants" does not include self, per the definition of the word. * To test for descendants AND self, you must do a * node.matches(aClass, aRegex) + * node.descendantMatches(aClass, aRegex). - *

    + * * * @param the type of Spatial returned * @param spatialSubclass Subclass which matching Spatials must implement. diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java index f8f450ab47..1b5e912a78 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java @@ -159,4 +159,4 @@ private T getMesh(String suffix) { return null; } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java index 6db686bed8..5c4ea5b11a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java @@ -55,7 +55,7 @@ * * @author Marcin Roguski (Kaelthas) */ -public class SkeletonInterBoneWire extends Mesh { +public class SkeletonInterBoneWire extends Mesh { private static final int POINT_AMOUNT = 10; /** The amount of connections between bones. */ private int connectionsAmount; diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java index c508533a06..6823d8f5fb 100644 --- a/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java @@ -310,7 +310,7 @@ public AccessHint getAccessHint() { /** * Set AccessHint to hint the renderer on how to access this data. * - * @param natureHint + * @param accessHint the access hint */ public void setAccessHint(AccessHint accessHint) { this.accessHint = accessHint; diff --git a/jme3-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java index 92354ff91e..e4ab5c2d38 100644 --- a/jme3-core/src/main/java/com/jme3/system/AppSettings.java +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -31,8 +31,6 @@ */ package com.jme3.system; -import com.jme3.opencl.DefaultPlatformChooser; -import com.jme3.opencl.PlatformChooser; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -64,6 +62,38 @@ public final class AppSettings extends HashMap { private static final AppSettings defaults = new AppSettings(false); + /** + * Do not request display scaling support on platforms where the backend can + * opt out. + */ + public static final float DISPLAY_SCALE_DISABLED = 0f; + + /** + * Request the native-density framebuffer and expose physical framebuffer + * pixels as the application coordinate system. + */ + public static final float DISPLAY_SCALE_NATIVE_PIXELS = -1f; + + /** + * Request the native-density framebuffer while keeping cameras, GUI, + * picking, and input in DPI-aware logical coordinates. + */ + public static final float DISPLAY_SCALE_DPI_AWARE = 1f; + + /** + * Use borderless fullscreen desktop mode when fullscreen is enabled. + * + * @see #setFullscreenMode(java.lang.String) + */ + public static final String FULLSCREEN_MODE_BORDERLESS_WINDOW = "BorderlessWindow"; + + /** + * Use an exclusive fullscreen display mode when fullscreen is enabled. + * + * @see #setFullscreenMode(java.lang.String) + */ + public static final String FULLSCREEN_MODE_EXCLUSIVE_FULLSCREEN = "ExclusiveFullscreen"; + /** * Use LWJGL as the display system and force using the OpenGL2.0 renderer. *

    @@ -72,6 +102,7 @@ public final class AppSettings extends HashMap { * * @see AppSettings#setRenderer(java.lang.String) */ + @Deprecated public static final String LWJGL_OPENGL2 = "LWJGL-OpenGL2"; /** @@ -101,6 +132,7 @@ public final class AppSettings extends HashMap { * * @see AppSettings#setRenderer(java.lang.String) */ + @Deprecated public static final String LWJGL_OPENGL30 = "LWJGL-OpenGL30"; /** @@ -114,6 +146,7 @@ public final class AppSettings extends HashMap { * * @see AppSettings#setRenderer(java.lang.String) */ + @Deprecated public static final String LWJGL_OPENGL31 = "LWJGL-OpenGL31"; /** @@ -216,17 +249,65 @@ public final class AppSettings extends HashMap { * Use the LWJGL OpenAL based renderer for audio capabilities. * * @see AppSettings#setAudioRenderer(java.lang.String) + * @deprecated Use {@link #OPENAL} instead. */ + @Deprecated public static final String LWJGL_OPENAL = "LWJGL"; + public static final String ANGLE_GLES3 = "ANGLE_GLES3"; + + /** + * Use the default OpenAL renderer for the current platform. + * + * @see AppSettings#setAudioRenderer(java.lang.String) + */ + public static final String OPENAL = "OPENAL"; + + /** + * Disable the on-screen virtual gamepad entirely. + * + * @see #setVirtualJoystick(String) + */ + public static final String VIRTUAL_JOYSTICK_DISABLED = "VirtualJoystickDisabled"; + + /** + * Always display the full virtual gamepad, even on desktop and even when a + * hardware gamepad is detected. + * + * @see #setVirtualJoystick(String) + */ + public static final String VIRTUAL_JOYSTICK_ENABLED = "VirtualJoystickEnabled"; + + /** + * Display the full virtual gamepad automatically on mobile when no + * hardware gamepad is detected. + * + * @see #setVirtualJoystick(String) + */ + public static final String VIRTUAL_JOYSTICK_AUTO = "VirtualJoystickAuto"; + + /** + * Use the fixed Xbox-like virtual joystick layout. + * + * @see #setVirtualJoystickDefaultLayout(String) + */ + public static final String VIRTUAL_JOYSTICK_LAYOUT_XBOX = "Xbox"; + + /** + * Use a virtual joystick layout that only displays bound controls. + * + * @see #setVirtualJoystickDefaultLayout(String) + */ + public static final String VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC = "Dynamic"; + + /** * Use the Android MediaPlayer / SoundPool based renderer for Android audio capabilities. *

    * NOTE: Supports Android 2.2+ platforms. * * @see AppSettings#setAudioRenderer(java.lang.String) - * @deprecated This audio renderer has too many limitations. - * use {@link #ANDROID_OPENAL_SOFT} instead. + * @deprecated This audio renderer has too many limitations. Use {@link #OPENAL} instead. */ @Deprecated public static final String ANDROID_MEDIAPLAYER = "MediaPlayer"; @@ -234,11 +315,12 @@ public final class AppSettings extends HashMap { /** * Use the OpenAL Soft based renderer for Android audio capabilities. *

    - * This is the current default for Android platforms. * NOTE: Only to be used on Android 2.3+ platforms due to using OpenSL. * * @see AppSettings#setAudioRenderer(java.lang.String) + * @deprecated Use {@link #OPENAL} instead. */ + @Deprecated public static final String ANDROID_OPENAL_SOFT = "OpenAL_SOFT"; /** @@ -247,7 +329,9 @@ public final class AppSettings extends HashMap { * N.B: This backend is EXPERIMENTAL * * @see AppSettings#setRenderer(java.lang.String) + * @deprecated Use LWJGL */ + @Deprecated public static final String JOGL_OPENGL_FORWARD_COMPATIBLE = "JOGL_OPENGL_FORWARD_COMPATIBLE"; /** @@ -256,7 +340,9 @@ public final class AppSettings extends HashMap { * N.B: This backend is EXPERIMENTAL * * @see AppSettings#setRenderer(java.lang.String) + * @deprecated Use LWJGL */ + @Deprecated public static final String JOGL_OPENGL_BACKWARD_COMPATIBLE = "JOGL_OPENGL_BACKWARD_COMPATIBLE"; /** @@ -265,51 +351,54 @@ public final class AppSettings extends HashMap { * N.B: This backend is EXPERIMENTAL * * @see AppSettings#setAudioRenderer(java.lang.String) + * @deprecated Use {@link #OPENAL} instead. */ + @Deprecated public static final String JOAL = "JOAL"; /** - * Map gamepads to Xbox-like layout. + * Map joysticks to Xbox-like layout. */ public static final String JOYSTICKS_XBOX_MAPPER = "JOYSTICKS_XBOX_MAPPER"; /** - * Map gamepads to an Xbox-like layout, with fallback to raw if the gamepad is not recognized. + * Map joysticks to an Xbox-like layout, with fallback to raw if the joystick is not recognized. */ public static final String JOYSTICKS_XBOX_WITH_FALLBACK_MAPPER = "JOYSTICKS_XBOX_WITH_FALLBACK_MAPPER"; /** - * Map gamepads to an Xbox-like layout using the legacy jME input + * Map joysticks to an Xbox-like layout using the legacy jME input */ public static final String JOYSTICKS_XBOX_LEGACY_MAPPER = "JOYSTICKS_XBOX_LEGACY_MAPPER"; /** - * Map gamepads using the legacy jME mapper and input. + * Map joysticks using the legacy jME mapper and input. */ public static final String JOYSTICKS_LEGACY_MAPPER = "JOYSTICKS_LEGACY_MAPPER"; /** - * Don't map gamepads, use raw events instead (ie. bring your own mapper) + * Don't map joysticks, use raw events instead (ie. bring your own mapper) */ public static final String JOYSTICKS_RAW_MAPPER = "JOYSTICKS_RAW_MAPPER"; static { defaults.put("Display", 0); defaults.put("CenterWindow", true); - defaults.put("Width", 640); - defaults.put("Height", 480); + defaults.put("Width", 1440); + defaults.put("Height", 900); defaults.put("WindowWidth", Integer.MIN_VALUE); defaults.put("WindowHeight", Integer.MIN_VALUE); defaults.put("BitsPerPixel", 24); - defaults.put("Frequency", 60); + defaults.put("Frequency", 0); defaults.put("DepthBits", 24); defaults.put("StencilBits", 0); defaults.put("Samples", 0); defaults.put("Fullscreen", false); + defaults.put("FullscreenMode", FULLSCREEN_MODE_BORDERLESS_WINDOW); defaults.put("Title", JmeVersion.FULL_NAME); - defaults.put("Renderer", LWJGL_OPENGL32); - defaults.put("AudioRenderer", LWJGL_OPENAL); - defaults.put("DisableJoysticks", true); + defaults.put("Renderer", ANGLE_GLES3); + defaults.put("AudioRenderer", OPENAL); + defaults.put("DisableJoysticks", false); defaults.put("UseInput", true); defaults.put("VSync", true); defaults.put("FrameRate", -1); @@ -317,10 +406,8 @@ public final class AppSettings extends HashMap { defaults.put("MinHeight", 0); defaults.put("MinWidth", 0); defaults.put("GammaCorrection", true); - defaults.put("Resizable", false); + defaults.put("Resizable", true); defaults.put("SwapBuffers", true); - defaults.put("OpenCL", false); - defaults.put("OpenCLPlatformChooser", DefaultPlatformChooser.class.getName()); defaults.put("UseRetinaFrameBuffer", false); defaults.put("WindowYPosition", 0); defaults.put("WindowXPosition", 0); @@ -329,6 +416,10 @@ public final class AppSettings extends HashMap { defaults.put("JoysticksTriggerToButtonThreshold", 0.5f); defaults.put("JoysticksAxisJitterThreshold", 0.0001f); defaults.put("SDLGameControllerDBResourcePath", ""); + defaults.put("OnDeviceJoystickRumble", false); + defaults.put("UseAndroidSensorJoystick", false); + defaults.put("VirtualJoystick", VIRTUAL_JOYSTICK_AUTO); + defaults.put("VirtualJoystickDefaultLayout", VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC); // defaults.put("Icons", null); } @@ -774,13 +865,78 @@ public void setUseInput(boolean use) { /** * @param use If true, the application will initialize and use joystick - * input. Set to false if no joystick input is desired. - * (Default: false) + * input, including hardware gamepads when available. Set to false if no + * joystick input is desired. + * (Default: true) */ public void setUseJoysticks(boolean use) { putBoolean("DisableJoysticks", !use); } + /** + * @param enabled If true, joystick rumble requests may be redirected to + * the device rumble motor on supported platforms. + * (Default: false) + */ + public void setOnDeviceJoystickRumble(boolean enabled) { + putBoolean("OnDeviceJoystickRumble", enabled); + } + + /** + * @param use If true, Android exposes device orientation sensors as a + * joystick. This is disabled by default because the sensor joystick reports + * movement from device rotation and can conflict with gamepad or virtual + * joystick mappings. + * (Default: false) + */ + public void setUseAndroidSensorJoystick(boolean use) { + putBoolean("UseAndroidSensorJoystick", use); + } + + /** + * Sets the on-screen virtual joystick mode. + *

    + * The default mode is {@link #VIRTUAL_JOYSTICK_AUTO}, which displays the + * virtual joystick on mobile only when joystick mappings exist and no + * hardware gamepad is connected. + *

      + *
    • {@link #VIRTUAL_JOYSTICK_DISABLED}: disable the virtual gamepad + * entirely.
    • + *
    • {@link #VIRTUAL_JOYSTICK_ENABLED}: always display the virtual + * joystick, even on desktop and even when a hardware gamepad is detected.
    • + *
    • {@link #VIRTUAL_JOYSTICK_AUTO}: display the full virtual gamepad + * automatically on mobile when joystick mappings exist and no hardware + * gamepad is detected.
    • + *
    + * + * @param mode one of {@link #VIRTUAL_JOYSTICK_DISABLED}, + * {@link #VIRTUAL_JOYSTICK_ENABLED}, or {@link #VIRTUAL_JOYSTICK_AUTO} + */ + public void setVirtualJoystick(String mode) { + if (!VIRTUAL_JOYSTICK_DISABLED.equals(mode) + && !VIRTUAL_JOYSTICK_ENABLED.equals(mode) + && !VIRTUAL_JOYSTICK_AUTO.equals(mode)) { + throw new IllegalArgumentException("Unsupported virtual joystick mode: " + mode); + } + putString("VirtualJoystick", mode); + } + + /** + * Sets the default virtual joystick layout used by supporting backends. + * + * @param layout one of {@link #VIRTUAL_JOYSTICK_LAYOUT_XBOX} or + * {@link #VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC} + */ + public void setVirtualJoystickDefaultLayout(String layout) { + if (VIRTUAL_JOYSTICK_LAYOUT_XBOX.equalsIgnoreCase(layout)) { + putString("VirtualJoystickDefaultLayout", VIRTUAL_JOYSTICK_LAYOUT_XBOX); + } else if (VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC.equalsIgnoreCase(layout)) { + putString("VirtualJoystickDefaultLayout", VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC); + } else { + throw new IllegalArgumentException("Unsupported virtual joystick layout: " + layout); + } + } + /** * Set the graphics renderer to use, one of:
    *
      @@ -813,12 +969,13 @@ public void setCustomRenderer(Class clazz) { /** * Set the audio renderer to use. One of:
      *
        - *
      • AppSettings.LWJGL_OPENAL - Default for LWJGL
      • + *
      • AppSettings.OPENAL - Default OpenAL renderer for the current platform
      • + *
      • AppSettings.LWJGL_OPENAL - Deprecated LWJGL OpenAL renderer identifier
      • *
      • AppSettings.JOAL
      • *
      • null - Disable audio
      • *
      * @param audioRenderer - * (Default: LWJGL) + * (Default: AppSettings.OPENAL) */ public void setAudioRenderer(String audioRenderer) { putString("AudioRenderer", audioRenderer); @@ -988,6 +1145,29 @@ public void setFullscreen(boolean value) { putBoolean("Fullscreen", value); } + /** + * Sets how supporting backends enter fullscreen when + * {@link #setFullscreen(boolean)} is enabled. + *
        + *
      • {@link #FULLSCREEN_MODE_BORDERLESS_WINDOW}: borderless fullscreen + * desktop mode.
      • + *
      • {@link #FULLSCREEN_MODE_EXCLUSIVE_FULLSCREEN}: exclusive fullscreen + * using the closest display mode to the configured resolution and refresh + * rate.
      • + *
      + * Some backends may ignore this setting. + * (Default: {@link #FULLSCREEN_MODE_BORDERLESS_WINDOW}) + * + * @param mode fullscreen mode + */ + public void setFullscreenMode(String mode) { + if (!FULLSCREEN_MODE_BORDERLESS_WINDOW.equals(mode) + && !FULLSCREEN_MODE_EXCLUSIVE_FULLSCREEN.equals(mode)) { + throw new IllegalArgumentException("Unsupported fullscreen mode: " + mode); + } + putString("FullscreenMode", mode); + } + /** * Enable or disable vertical synchronization. If enabled, rendering will be * synchronized with the display's refresh interval. @@ -1240,6 +1420,21 @@ public boolean isFullscreen() { return getBoolean("Fullscreen"); } + /** + * Gets how supporting backends enter fullscreen. + * + * @return one of {@link #FULLSCREEN_MODE_BORDERLESS_WINDOW} or + * {@link #FULLSCREEN_MODE_EXCLUSIVE_FULLSCREEN} + * @see #setFullscreenMode(java.lang.String) + */ + public String getFullscreenMode() { + String mode = getString("FullscreenMode", FULLSCREEN_MODE_BORDERLESS_WINDOW); + if (!FULLSCREEN_MODE_EXCLUSIVE_FULLSCREEN.equals(mode)) { + return FULLSCREEN_MODE_BORDERLESS_WINDOW; + } + return mode; + } + /** * Get the use joysticks state * @@ -1250,10 +1445,93 @@ public boolean useJoysticks() { return !getBoolean("DisableJoysticks"); } + /** + * Get whether joystick rumble may be redirected to device rumble. + * + * @return true to redirect joystick rumble to device rumble when supported + * @see #setOnDeviceJoystickRumble(boolean) + */ + public boolean isOnDeviceJoystickRumble() { + return getBoolean("OnDeviceJoystickRumble"); + } + + /** + * Get the Android sensor joystick state. + * + * @return true to expose Android device orientation sensors as a joystick + * @see #setUseAndroidSensorJoystick(boolean) + */ + public boolean useAndroidSensorJoystick() { + return getBoolean("UseAndroidSensorJoystick"); + } + + /** + * Get whether supporting backends should expose an on-screen virtual joystick. + * + * @return true to expose the on-screen virtual joystick + * @see #setVirtualJoystick(String) + */ + public boolean isVirtualJoystick() { + return !VIRTUAL_JOYSTICK_DISABLED.equals(getVirtualJoystickMode()); + } + + /** + * Get whether supporting backends should expose an on-screen virtual joystick. + * + * @return true to expose the on-screen virtual joystick + * @see #setVirtualJoystick(String) + */ + public boolean isVirtualJoystickEnabled() { + return isVirtualJoystick(); + } + + /** + * Gets the on-screen virtual joystick mode. + * + * @return one of {@link #VIRTUAL_JOYSTICK_DISABLED}, + * {@link #VIRTUAL_JOYSTICK_ENABLED}, or {@link #VIRTUAL_JOYSTICK_AUTO} + */ + public String getVirtualJoystickMode() { + Object value = get("VirtualJoystick"); + if (value instanceof Boolean) { + return (Boolean) value ? VIRTUAL_JOYSTICK_ENABLED : VIRTUAL_JOYSTICK_DISABLED; + } + if (value instanceof String) { + String mode = (String) value; + if (VIRTUAL_JOYSTICK_DISABLED.equalsIgnoreCase(mode)) { + return VIRTUAL_JOYSTICK_DISABLED; + } else if (VIRTUAL_JOYSTICK_ENABLED.equalsIgnoreCase(mode)) { + return VIRTUAL_JOYSTICK_ENABLED; + } else if (VIRTUAL_JOYSTICK_AUTO.equalsIgnoreCase(mode)) { + return VIRTUAL_JOYSTICK_AUTO; + } + } + return VIRTUAL_JOYSTICK_AUTO; + } + + /** + * Gets the default virtual joystick layout. + * + * @return one of {@link #VIRTUAL_JOYSTICK_LAYOUT_XBOX} or + * {@link #VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC} + */ + public String getVirtualJoystickDefaultLayout() { + Object value = get("VirtualJoystickDefaultLayout"); + if (value instanceof String) { + String layout = (String) value; + if (VIRTUAL_JOYSTICK_LAYOUT_XBOX.equalsIgnoreCase(layout)) { + return VIRTUAL_JOYSTICK_LAYOUT_XBOX; + } else if (VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC.equalsIgnoreCase(layout)) { + return VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC; + } + } + return VIRTUAL_JOYSTICK_LAYOUT_DYNAMIC; + } + /** * Get the audio renderer * - * @return the audio renderer's name, for example "LWJGL" + * @return the audio renderer's name, for example "OPENAL" * @see #setAudioRenderer(java.lang.String) */ public String getAudioRenderer() { @@ -1353,32 +1631,45 @@ public boolean isSwapBuffers() { } /** - * True to enable the creation of an OpenCL context. + * OpenCL support has been removed from the engine. * - * @param support whether to create the context or not + * @param support ignored + * @deprecated OpenCL support has been removed. */ + @Deprecated public void setOpenCLSupport(boolean support) { - putBoolean("OpenCL", support); } + /** + * OpenCL support has been removed from the engine. + * + * @return false + * @deprecated OpenCL support has been removed. + */ + @Deprecated public boolean isOpenCLSupport() { - return getBoolean("OpenCL"); + return false; } /** - * Sets a custom platform chooser. This chooser specifies which platform and - * which devices are used for the OpenCL context. - *

      - * Default: an implementation defined one. + * OpenCL support has been removed from the engine. * - * @param chooser the class of the chooser, must have a default constructor + * @param chooser ignored + * @deprecated OpenCL support has been removed. */ - public void setOpenCLPlatformChooser(Class chooser) { - putString("OpenCLPlatformChooser", chooser.getName()); + @Deprecated + public void setOpenCLPlatformChooser(Class chooser) { } + /** + * OpenCL support has been removed from the engine. + * + * @return null + * @deprecated OpenCL support has been removed. + */ + @Deprecated public String getOpenCLPlatformChooser() { - return getString("OpenCLPlatformChooser"); + return null; } /** @@ -1461,20 +1752,67 @@ public void setGraphicsTrace(boolean trace) { putBoolean("GraphicsTrace", trace); } + /** + * Returns the active display scale mode. + *

      + * If {@link #setDisplayScaleMode(float)} was not called, this method + * returns {@link #DISPLAY_SCALE_DISABLED}. + *

      + * Values greater than {@link #DISPLAY_SCALE_DPI_AWARE} mean emulated display + * scaling. For example, {@code 1.5f} keeps DPI-aware application coordinates + * while rendering on-screen content to an intermediate framebuffer sized + * {@code physicalFramebufferSize * 1.5}. + * + * @return the active display scale mode + */ + public float getDisplayScaleMode() { + Object mode = get("DisplayScaleMode"); + if (mode instanceof Float) { + return DisplayScaleUtils.normalizeDisplayScaleMode((Float) mode); + } + return DISPLAY_SCALE_DISABLED; + } + + /** + * Sets the display scale mode. New applications should use this API + * instead of {@link #setUseRetinaFrameBuffer(boolean)}. + *

      + * Use {@link #DISPLAY_SCALE_DISABLED}, + * {@link #DISPLAY_SCALE_NATIVE_PIXELS}, or + * {@link #DISPLAY_SCALE_DPI_AWARE} for built-in modes. Values below + * {@code 1.0}, except {@link #DISPLAY_SCALE_NATIVE_PIXELS}, are normalized + * to {@link #DISPLAY_SCALE_DISABLED}. Values above + * {@link #DISPLAY_SCALE_DPI_AWARE} enable emulated display scaling above the + * physical framebuffer size. + * + * @param mode the desired mode + */ + public void setDisplayScaleMode(float mode) { + if (!Float.isFinite(mode)) { + throw new IllegalArgumentException("DisplayScaleMode must be finite."); + } + putFloat("DisplayScaleMode", DisplayScaleUtils.normalizeDisplayScaleMode(mode)); + } + /** * Determine whether to use full resolution framebuffers on Retina displays. * * @return whether to use full resolution framebuffers on Retina displays. + * @deprecated use {@link #getDisplayScaleMode()} instead. */ + @Deprecated public boolean isUseRetinaFrameBuffer() { return getBoolean("UseRetinaFrameBuffer"); } /** - * Specifies whether to use full resolution framebuffers on Retina displays. This is ignored on other platforms. + * Specifies whether to use full resolution framebuffers on Retina/high-DPI + * displays where the backend supports requesting them. * * @param useRetinaFrameBuffer whether to use full resolution framebuffers on Retina displays. + * @deprecated use {@link #setDisplayScaleMode(float)} instead. */ + @Deprecated public void setUseRetinaFrameBuffer(boolean useRetinaFrameBuffer) { putBoolean("UseRetinaFrameBuffer", useRetinaFrameBuffer); } @@ -1722,4 +2060,3 @@ public String getSDLGameControllerDBResourcePath() { return getString("SDLGameControllerDBResourcePath"); } } - diff --git a/jme3-core/src/main/java/com/jme3/system/DisplayScaleUtils.java b/jme3-core/src/main/java/com/jme3/system/DisplayScaleUtils.java new file mode 100644 index 0000000000..2fb214f809 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/DisplayScaleUtils.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +/** + * Shared calculations for the split logical/physical display scale model. + */ +public final class DisplayScaleUtils { + + private DisplayScaleUtils() { + } + + /** + * Resolves the logical application size for a window-backed context. + * + * @param mode the active display scale mode + * @param windowWidth the platform window coordinate width + * @param windowHeight the platform window coordinate height + * @param framebufferWidth the physical framebuffer width + * @param framebufferHeight the physical framebuffer height + * @param displayScaleX the platform content/display scale on X + * @param displayScaleY the platform content/display scale on Y + * @return an array containing logical width and logical height + */ + public static int[] resolveLogicalSize(float mode, int windowWidth, int windowHeight, + int framebufferWidth, int framebufferHeight, float displayScaleX, float displayScaleY) { + if (!isDpiAwareMode(mode)) { + return new int[] { Math.max(framebufferWidth, 1), Math.max(framebufferHeight, 1) }; + } + + int safeWindowWidth = Math.max(windowWidth, 1); + int safeWindowHeight = Math.max(windowHeight, 1); + int safeFramebufferWidth = Math.max(framebufferWidth, 1); + int safeFramebufferHeight = Math.max(framebufferHeight, 1); + float scaleX = sanitizeScale(displayScaleX); + float scaleY = sanitizeScale(displayScaleY); + + int scaledWidth = Math.max(Math.round(safeFramebufferWidth / scaleX), 1); + int scaledHeight = Math.max(Math.round(safeFramebufferHeight / scaleY), 1); + + if (approximatelyEqual(scaledWidth, safeWindowWidth) && approximatelyEqual(scaledHeight, safeWindowHeight)) { + return new int[] { safeWindowWidth, safeWindowHeight }; + } + + return new int[] { scaledWidth, scaledHeight }; + } + + public static float sanitizeScale(float value) { + return Float.isFinite(value) && value > 0f ? value : 1f; + } + + public static float normalizeDisplayScaleMode(float mode) { + if (!Float.isFinite(mode)) { + return AppSettings.DISPLAY_SCALE_DISABLED; + } + if (mode == AppSettings.DISPLAY_SCALE_NATIVE_PIXELS) { + return AppSettings.DISPLAY_SCALE_NATIVE_PIXELS; + } + if (mode < AppSettings.DISPLAY_SCALE_DPI_AWARE) { + return AppSettings.DISPLAY_SCALE_DISABLED; + } + return mode; + } + + public static boolean isNativePixelsMode(float mode) { + return mode == AppSettings.DISPLAY_SCALE_NATIVE_PIXELS; + } + + public static boolean isDpiAwareMode(float mode) { + return mode >= AppSettings.DISPLAY_SCALE_DPI_AWARE; + } + + public static boolean isEmulatedScaleMode(float mode) { + return mode > AppSettings.DISPLAY_SCALE_DPI_AWARE; + } + + public static boolean isDisabledMode(float mode) { + return !isNativePixelsMode(mode) && !isDpiAwareMode(mode); + } + + public static boolean requestsHighDensityFramebuffer(float mode) { + return isNativePixelsMode(mode) || isDpiAwareMode(mode); + } + + /** + * Converts a native window-coordinate X value to jME input coordinates. + * + * @param nativeX the platform coordinate + * @param targetWidth the target jME coordinate width + * @param nativeWidth the platform coordinate width + * @return the jME X coordinate + */ + public static int toInputX(float nativeX, int targetWidth, int nativeWidth) { + return Math.round(nativeX * Math.max(targetWidth, 1) / Math.max(nativeWidth, 1)); + } + + /** + * Converts a native top-origin Y value to jME bottom-origin input + * coordinates. + * + * @param nativeY the platform coordinate + * @param targetHeight the target jME coordinate height + * @param nativeHeight the platform coordinate height + * @return the jME Y coordinate + */ + public static int toInputY(float nativeY, int targetHeight, int nativeHeight) { + int safeTargetHeight = Math.max(targetHeight, 1); + return Math.round(safeTargetHeight - (nativeY * safeTargetHeight / Math.max(nativeHeight, 1))); + } + + private static boolean approximatelyEqual(int a, int b) { + return Math.abs(a - b) <= 1; + } +} diff --git a/jme3-core/src/main/java/com/jme3/system/JmeContext.java b/jme3-core/src/main/java/com/jme3/system/JmeContext.java index c2ffe912ef..478e8385e2 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeContext.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeContext.java @@ -118,11 +118,6 @@ public enum Type { */ public Renderer getRenderer(); - /** - * @return The OpenCL context if available. - */ - public com.jme3.opencl.Context getOpenCLContext(); - /** * @return Mouse input implementation. May be null if not available. */ diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java index 9def80e73a..675dab19dc 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java @@ -31,9 +31,9 @@ */ package com.jme3.system; -import com.jme3.asset.AssetManager; -import com.jme3.audio.AudioRenderer; -import com.jme3.input.SoftTextDialogInput; +import com.jme3.asset.AssetManager; +import com.jme3.audio.AudioRenderer; +import com.jme3.input.SoftTextDialogInput; import java.io.File; import java.io.IOException; @@ -48,11 +48,11 @@ import java.util.logging.Logger; /** - * Utility class to access platform-dependant features. - */ -public class JmeSystem { - - private static final Logger logger = Logger.getLogger(JmeSystem.class.getName()); + * Utility class to access platform-dependant features. + */ +public class JmeSystem { + + private static final Logger logger = Logger.getLogger(JmeSystem.class.getName()); public enum StorageFolderType { Internal, @@ -67,10 +67,10 @@ public enum StorageFolderType { private JmeSystem() { } - public static void setSystemDelegate(JmeSystemDelegate systemDelegate) { - JmeSystem.systemDelegate = systemDelegate; - } - + public static void setSystemDelegate(JmeSystemDelegate systemDelegate) { + JmeSystem.systemDelegate = systemDelegate; + } + public static synchronized File getStorageFolder() { return getStorageFolder(StorageFolderType.External); } @@ -120,14 +120,34 @@ public static void setSoftTextDialogInput(SoftTextDialogInput input) { * * @param show If true, the keyboard is displayed, if false, the screen is hidden. */ - public static void showSoftKeyboard(boolean show) { - checkDelegate(); - systemDelegate.showSoftKeyboard(show); - } - - public static SoftTextDialogInput getSoftTextDialogInput() { - checkDelegate(); - return systemDelegate.getSoftTextDialogInput(); + public static void showSoftKeyboard(boolean show) { + checkDelegate(); + systemDelegate.showSoftKeyboard(show); + } + + public static boolean isDeviceRumbleSupported() { + checkDelegate(); + return systemDelegate.isDeviceRumbleSupported(); + } + + public static void rumble(float amount) { + checkDelegate(); + systemDelegate.rumble(amount); + } + + public static void rumble(float amountHigh, float amountLow, float duration) { + checkDelegate(); + systemDelegate.rumble(amountHigh, amountLow, duration); + } + + public static void stopRumble() { + checkDelegate(); + systemDelegate.stopRumble(); + } + + public static SoftTextDialogInput getSoftTextDialogInput() { + checkDelegate(); + return systemDelegate.getSoftTextDialogInput(); } /** diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java index f95745011a..f77872412f 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -34,6 +34,7 @@ import com.jme3.asset.AssetManager; import com.jme3.asset.DesktopAssetManager; import com.jme3.audio.AudioRenderer; +import com.jme3.input.HapticDevice; import com.jme3.input.SoftTextDialogInput; import com.jme3.util.res.Resources; @@ -55,7 +56,7 @@ * * @author Kirill Vainer, normenhansen */ -public abstract class JmeSystemDelegate { +public abstract class JmeSystemDelegate implements HapticDevice { protected final Logger logger = Logger.getLogger(JmeSystem.class.getName()); protected boolean initialized = false; @@ -154,10 +155,18 @@ public SoftTextDialogInput getSoftTextDialogInput() { return softTextDialogInput; } - public final AssetManager newAssetManager(URL configFile) { - return new DesktopAssetManager(configFile); + public boolean isDeviceRumbleSupported() { + return false; } + @Override + public void rumble(float amountHigh, float amountLow, float duration) { + } + + public final AssetManager newAssetManager(URL configFile) { + return new DesktopAssetManager(configFile); + } + public final AssetManager newAssetManager() { return new DesktopAssetManager(null); } @@ -215,27 +224,27 @@ public boolean showSettingsDialog(AppSettings settings, boolean loadFromRegistry } - private boolean is64Bit(String arch) { - switch (arch) { - case "amd64": - case "x86_64": - case "aarch64": - case "arm64": - case "ppc64": - case "universal": - return true; - case "x86": - case "i386": - case "i686": - case "aarch32": - case "arm": - case "armv7": - case "armv7l": - return false; - default: - throw new UnsupportedOperationException("Unsupported architecture: " + arch); - } - } + private boolean is64Bit(String arch) { + switch (arch) { + case "amd64": + case "x86_64": + case "aarch64": + case "arm64": + case "ppc64": + case "universal": + return true; + case "x86": + case "i386": + case "i686": + case "aarch32": + case "arm": + case "armv7": + case "armv7l": + return false; + default: + throw new UnsupportedOperationException("Unsupported architecture: " + arch); + } + } private boolean isArmArchitecture(String arch) { return arch.startsWith("arm") || arch.startsWith("aarch"); diff --git a/jme3-core/src/main/java/com/jme3/system/NullContext.java b/jme3-core/src/main/java/com/jme3/system/NullContext.java index 2f2518f8c3..d91d9de162 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullContext.java +++ b/jme3-core/src/main/java/com/jme3/system/NullContext.java @@ -37,7 +37,6 @@ import com.jme3.input.TouchInput; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; -import com.jme3.opencl.Context; import com.jme3.renderer.Renderer; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; @@ -257,11 +256,6 @@ public boolean isRenderable() { // RenderManager won't render anything. } - @Override - public Context getOpenCLContext() { - return null; - } - /** * Returns the height of the framebuffer. * diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java index f220c50b98..162f347206 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java +++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java @@ -145,6 +145,7 @@ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { } @Override + @Deprecated public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { } diff --git a/jme3-core/src/main/java/com/jme3/system/Platform.java b/jme3-core/src/main/java/com/jme3/system/Platform.java index 2ccd763c8f..c22dd67e37 100644 --- a/jme3-core/src/main/java/com/jme3/system/Platform.java +++ b/jme3-core/src/main/java/com/jme3/system/Platform.java @@ -34,62 +34,159 @@ /** * Enumerate known operating system/architecture pairs. */ -public enum Platform { - - /** - * Microsoft Windows 64-bit AMD/Intel - */ - Windows64(Os.Windows, true), - - /** - * Microsoft Windows 64-bit ARM - */ - Windows_ARM64(Os.Windows, true), - - /** - * Linux 64-bit Intel - */ - Linux64(Os.Linux, true), - - /** - * Linux 64-bit ARM - */ - Linux_ARM64(Os.Linux, true), - - /** - * Apple Mac OS X 64-bit Intel - */ - MacOSX64(Os.MacOS, true), - - /** - * Apple Mac OS X 64-bit ARM - */ - MacOSX_ARM64(Os.MacOS, true), - - /** - * Android ARM8 - */ - Android_ARM8(Os.Android, true), +public enum Platform { + + /** + * Microsoft Windows 32-bit AMD/Intel + * + * @deprecated 32-bit Windows is no longer supported. + */ + @Deprecated + Windows32(Os.Windows), + + /** + * Microsoft Windows 64-bit AMD/Intel + */ + Windows64(Os.Windows, true), + + /** + * Microsoft Windows 32-bit ARM + * + * @deprecated 32-bit Windows is no longer supported. + */ + @Deprecated + Windows_ARM32(Os.Windows), + + /** + * Microsoft Windows 64-bit ARM + */ + Windows_ARM64(Os.Windows, true), + + /** + * Linux 32-bit Intel + * + * @deprecated 32-bit Linux is no longer supported. + */ + @Deprecated + Linux32(Os.Linux), + + /** + * Linux 64-bit Intel + */ + Linux64(Os.Linux, true), + + /** + * Linux 32-bit ARM + * + * @deprecated 32-bit Linux is no longer supported. + */ + @Deprecated + Linux_ARM32(Os.Linux), + + /** + * Linux 64-bit ARM + */ + Linux_ARM64(Os.Linux, true), + + /** + * Apple Mac OS X 32-bit Intel + * + * @deprecated 32-bit macOS is no longer supported. + */ + @Deprecated + MacOSX32(Os.MacOS), + + /** + * Apple Mac OS X 64-bit Intel + */ + MacOSX64(Os.MacOS, true), /** - * Android x86_64 - */ - Android_X86_64(Os.Android, true), - + * Apple Mac OS X 64-bit ARM + */ + MacOSX_ARM64(Os.MacOS, true), + /** - * iOS on ARM + * Apple Mac OS X 32 bit PowerPC + * + * @deprecated PowerPC macOS is no longer supported. */ - iOS_ARM(Os.iOS, true), + @Deprecated + MacOSX_PPC32(Os.MacOS), + + /** + * Apple Mac OS X 64 bit PowerPC + * + * @deprecated PowerPC macOS is no longer supported. + */ + @Deprecated + MacOSX_PPC64(Os.MacOS, true), + + /** + * Android ARM5 + * + * @deprecated 32-bit Android is no longer supported. + */ + @Deprecated + Android_ARM5(Os.Android), + + /** + * Android ARM6 + * + * @deprecated 32-bit Android is no longer supported. + */ + @Deprecated + Android_ARM6(Os.Android), + + /** + * Android ARM7 + * + * @deprecated 32-bit Android is no longer supported. + */ + @Deprecated + Android_ARM7(Os.Android), + + /** + * Android ARM8 + */ + Android_ARM8(Os.Android, true), + + /** + * Android x86 + * + * @deprecated 32-bit Android is no longer supported. + */ + @Deprecated + Android_X86(Os.Android), + + /** + * Android x86_64 + */ + Android_X86_64(Os.Android, true), /** * iOS on x86_64 (simulator) */ iOS_X86(Os.iOS, true), - /** - * Generic web platform on unknown architecture - */ - Web(Os.Web, true) // assume always 64-bit, it shouldn't matter for web - ; + + /** + * iOS on ARM + */ + iOS_ARM(Os.iOS, true), + + /** + * Android running on unknown platform (could be x86 or mips for example). + * + * @deprecated Android platforms with unknown architectures are no longer supported. + */ + @Deprecated + Android_Other(Os.Android), + + /** + * Generic web platform on unknown architecture + */ + Web(Os.Web, true) // assume always 64-bit, it shouldn't matter for web + ; /** @@ -124,6 +221,7 @@ public enum Os { private final boolean is64bit; private final Os os; + private static final boolean NATIVE_IMAGE_RUNTIME = detectNativeImageRuntime(); /** * Test for a 64-bit address space. @@ -143,6 +241,19 @@ public Os getOs() { return os; } + /** + * Test whether this process is running as a GraalVM native-image executable. + * + * @return true if running inside a native-image runtime, otherwise false + */ + public boolean isGraalVMNativeImage() { + return NATIVE_IMAGE_RUNTIME; + } + + private static boolean detectNativeImageRuntime() { + return System.getProperty("org.graalvm.nativeimage.imagecode") != null; + } + private Platform(Os os, boolean is64bit) { this.os = os; this.is64bit = is64bit; diff --git a/jme3-core/src/main/java/com/jme3/system/SystemListener.java b/jme3-core/src/main/java/com/jme3/system/SystemListener.java index f6145c5783..a1c32b0e1c 100644 --- a/jme3-core/src/main/java/com/jme3/system/SystemListener.java +++ b/jme3-core/src/main/java/com/jme3/system/SystemListener.java @@ -46,11 +46,24 @@ public interface SystemListener { /** * Called to notify the application that the resolution has changed. - * @param width the new width of the display (in pixels, ≥0) - * @param height the new height of the display (in pixels, ≥0) + * @param width the new logical width of the display (≥0) + * @param height the new logical height of the display (≥0) */ public void reshape(int width, int height); + /** + * Called to notify the application that logical application size and + * physical framebuffer size changed independently. + * + * @param logicalWidth the width used by cameras, GUI, picking, and input + * @param logicalHeight the height used by cameras, GUI, picking, and input + * @param framebufferWidth the physical framebuffer width in pixels + * @param framebufferHeight the physical framebuffer height in pixels + */ + public default void reshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) { + reshape(logicalWidth, logicalHeight); + } + /** * Called to notify the application that the scale has changed. * @param x the new horizontal scale of the display diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture2D.java b/jme3-core/src/main/java/com/jme3/texture/Texture2D.java index ab633aed49..7bdc55bec8 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Texture2D.java +++ b/jme3-core/src/main/java/com/jme3/texture/Texture2D.java @@ -97,6 +97,7 @@ public Texture2D(int width, int height, int numSamples, Image.Format format){ } @Override + @Deprecated public Texture createSimpleClone() { Texture2D clone = new Texture2D(); createSimpleClone(clone); @@ -104,6 +105,7 @@ public Texture createSimpleClone() { } @Override + @Deprecated public Texture createSimpleClone(Texture rVal) { rVal.setWrap(WrapAxis.S, wrapS); rVal.setWrap(WrapAxis.T, wrapT); diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture3D.java b/jme3-core/src/main/java/com/jme3/texture/Texture3D.java index bb12a858dd..6052ea35ff 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Texture3D.java +++ b/jme3-core/src/main/java/com/jme3/texture/Texture3D.java @@ -100,6 +100,7 @@ public Texture3D(int width, int height, int depth, int numSamples, Image.Format } @Override + @Deprecated public Texture createSimpleClone() { Texture3D clone = new Texture3D(); createSimpleClone(clone); @@ -107,6 +108,7 @@ public Texture createSimpleClone() { } @Override + @Deprecated public Texture createSimpleClone(Texture rVal) { rVal.setWrap(WrapAxis.S, wrapS); rVal.setWrap(WrapAxis.T, wrapT); @@ -226,4 +228,4 @@ public void read(JmeImporter importer) throws IOException { wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp); } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureArray.java b/jme3-core/src/main/java/com/jme3/texture/TextureArray.java index e839399c33..644e094f70 100644 --- a/jme3-core/src/main/java/com/jme3/texture/TextureArray.java +++ b/jme3-core/src/main/java/com/jme3/texture/TextureArray.java @@ -99,6 +99,7 @@ public TextureArray(List images) { } @Override + @Deprecated public Texture createSimpleClone() { TextureArray clone = new TextureArray(); createSimpleClone(clone); @@ -106,6 +107,7 @@ public Texture createSimpleClone() { } @Override + @Deprecated public Texture createSimpleClone(Texture rVal) { rVal.setWrap(WrapAxis.S, wrapS); rVal.setWrap(WrapAxis.T, wrapT); diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java b/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java index 40e6c037c3..e867e49d5e 100644 --- a/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java +++ b/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java @@ -94,11 +94,13 @@ private static Image createEmptyLayeredImage(int width, int height, } @Override + @Deprecated public Texture createSimpleClone() { return createSimpleClone(new TextureCubeMap()); } @Override + @Deprecated public Texture createSimpleClone(Texture rVal) { rVal.setWrap(WrapAxis.S, wrapS); rVal.setWrap(WrapAxis.T, wrapT); diff --git a/jme3-core/src/main/java/com/jme3/util/IntMap.java b/jme3-core/src/main/java/com/jme3/util/IntMap.java index 198b380dd8..49e38fd41e 100644 --- a/jme3-core/src/main/java/com/jme3/util/IntMap.java +++ b/jme3-core/src/main/java/com/jme3/util/IntMap.java @@ -47,7 +47,7 @@ */ public final class IntMap implements Iterable>, Cloneable, JmeCloneable { - private Entry[] table; + private Entry[] table; private final float loadFactor; private int size, mask, capacity, threshold; @@ -75,7 +75,7 @@ public IntMap(int initialCapacity, float loadFactor) { } this.loadFactor = loadFactor; this.threshold = (int) (capacity * loadFactor); - this.table = new Entry[capacity]; + this.table = newTable(capacity); this.mask = capacity - 1; } @@ -84,7 +84,7 @@ public IntMap(int initialCapacity, float loadFactor) { public IntMap clone(){ try { IntMap clone = (IntMap) super.clone(); - Entry[] newTable = new Entry[table.length]; + Entry[] newTable = newTable(table.length); for (int i = table.length - 1; i >= 0; i--){ if (table[i] != null) newTable[i] = table[i].clone(); @@ -117,9 +117,9 @@ public void cloneFields(Cloner cloner, Object original) { } public boolean containsValue(Object value) { - Entry[] table = this.table; + Entry[] table = this.table; for (int i = table.length; i-- > 0;){ - for (Entry e = table[i]; e != null; e = e.next){ + for (Entry e = table[i]; e != null; e = e.next){ if (e.value.equals(value)){ return true; } @@ -130,7 +130,7 @@ public boolean containsValue(Object value) { public boolean containsKey(int key) { int index = ((int) key) & mask; - for (Entry e = table[index]; e != null; e = e.next){ + for (Entry e = table[index]; e != null; e = e.next){ if (e.key == key){ return true; } @@ -138,42 +138,40 @@ public boolean containsKey(int key) { return false; } - @SuppressWarnings("unchecked") public T get(int key) { int index = key & mask; - for (Entry e = table[index]; e != null; e = e.next){ + for (Entry e = table[index]; e != null; e = e.next){ if (e.key == key){ - return (T) e.value; + return e.value; } } return null; } - @SuppressWarnings("unchecked") public T put(int key, T value) { int index = key & mask; // Check if key already exists. - for (Entry e = table[index]; e != null; e = e.next){ + for (Entry e = table[index]; e != null; e = e.next){ if (e.key != key){ continue; } - Object oldValue = e.value; + T oldValue = e.value; e.value = value; - return (T) oldValue; + return oldValue; } - table[index] = new Entry(key, value, table[index]); + table[index] = new Entry<>(key, value, table[index]); if (size++ >= threshold){ // Rehash. int newCapacity = 2 * capacity; - Entry[] newTable = new Entry[newCapacity]; - Entry[] src = table; + Entry[] newTable = newTable(newCapacity); + Entry[] src = table; int bucketMask = newCapacity - 1; for (int j = 0; j < src.length; j++){ - Entry e = src[j]; + Entry e = src[j]; if (e != null){ src[j] = null; do{ - Entry next = e.next; + Entry next = e.next; index = e.key & bucketMask; e.next = newTable[index]; newTable[index] = e; @@ -189,13 +187,12 @@ public T put(int key, T value) { return null; } - @SuppressWarnings("unchecked") public T remove(int key) { int index = key & mask; - Entry prev = table[index]; - Entry e = prev; + Entry prev = table[index]; + Entry e = prev; while (e != null){ - Entry next = e.next; + Entry next = e.next; if (e.key == key){ size--; if (prev == e){ @@ -203,7 +200,7 @@ public T remove(int key) { }else{ prev.next = next; } - return (T) e.value; + return e.value; } prev = e; e = next; @@ -216,7 +213,7 @@ public int size() { } public void clear() { - Entry[] table = this.table; + Entry[] table = this.table; for (int index = table.length; --index >= 0;) { table[index] = null; } @@ -235,7 +232,7 @@ final class IntMapIterator implements Iterator> { /** * Current entry. */ - private Entry cur; + private Entry cur; /** * Entry in the table @@ -262,13 +259,12 @@ public boolean hasNext() { } @Override - @SuppressWarnings("unchecked") - public Entry next() { + public Entry next() { if (el >= size) throw new NoSuchElementException("No more elements!"); if (cur != null) { - Entry e = cur; + Entry e = cur; cur = cur.next; el++; return e; @@ -286,7 +282,7 @@ public Entry next() { cur = table[++idx]; } while (cur == null); - Entry e = cur; + Entry e = cur; cur = cur.next; el ++; @@ -298,13 +294,18 @@ public void remove() { } } + @SuppressWarnings("unchecked") + private static Entry[] newTable(int size) { + return (Entry[]) new Entry[size]; + } + public static final class Entry implements Cloneable, JmeCloneable { final int key; T value; - Entry next; + Entry next; - Entry(int k, T v, Entry n) { + Entry(int k, T v, Entry n) { key = k; value = v; next = n; diff --git a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java index 62d4c6c5de..7f0a81d94a 100644 --- a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java +++ b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java @@ -377,16 +377,23 @@ public void init() { file = new File(url.getFile()); fileLastM = file.lastModified(); - } catch (NoSuchFieldException - | SecurityException + } catch (NoSuchFieldException ex) { + Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.FINE, + "Material hot reload disabled for {0}; asset URL is not reflectively available.", + fileName); + } catch (SecurityException | IllegalArgumentException | IllegalAccessException ex) { - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); + Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.FINE, + "Material hot reload disabled for " + fileName, ex); } } } public boolean shouldFire() { + if (file == null || fileLastM == null) { + return false; + } if (file.lastModified() != fileLastM) { fileLastM = file.lastModified(); return true; diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObject.java b/jme3-core/src/main/java/com/jme3/util/NativeObject.java index 44ec3ad947..c1c49e2c48 100644 --- a/jme3-core/src/main/java/com/jme3/util/NativeObject.java +++ b/jme3-core/src/main/java/com/jme3/util/NativeObject.java @@ -167,6 +167,7 @@ protected NativeObject clone() { obj.objectManager = null; obj.id = INVALID_ID; obj.updateNeeded = true; + obj.weakRef = null; return obj; } catch (CloneNotSupportedException ex) { throw new AssertionError(); @@ -244,6 +245,7 @@ public void dispose() { * @param the type * @return a weak reference (possibly a pre-existing one) */ + @SuppressWarnings("unchecked") public WeakReference getWeakRef() { if (weakRef == null) { weakRef = new WeakReference<>(this); diff --git a/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java b/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java index b71ad69910..a1ba2eaedd 100644 --- a/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java +++ b/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java @@ -41,7 +41,9 @@ /** * This class contains the reflection based way to remove DirectByteBuffers in * java, allocation is done via ByteBuffer.allocateDirect + * @deprecated This class relies on internal APIs that are not accessible in Java 9+, and is not thread-safe for allocation bookkeeping. Use {@code com.jme3.util.SaferBufferAllocator} from the {@code jme3-saferallocator} module instead. */ +@Deprecated public final class ReflectionAllocator implements BufferAllocator { private static Method cleanerMethod = null; private static Method cleanMethod = null; diff --git a/jme3-core/src/main/java/com/jme3/util/res/ResourceLoader.java b/jme3-core/src/main/java/com/jme3/util/res/ResourceLoader.java index ef036e4400..0c31beab11 100644 --- a/jme3-core/src/main/java/com/jme3/util/res/ResourceLoader.java +++ b/jme3-core/src/main/java/com/jme3/util/res/ResourceLoader.java @@ -68,7 +68,7 @@ public interface ResourceLoader { * * @param path * The resource name - * @return An enumeration of {@link java.net.URL URL} objects for + * @return An enumeration of {@link java.net.URL URL} objects for * the resource. If no resources could be found, the enumeration * will be empty. * diff --git a/jme3-core/src/main/java/com/jme3/util/res/Resources.java b/jme3-core/src/main/java/com/jme3/util/res/Resources.java index 75c882c396..2b3ac54b3c 100644 --- a/jme3-core/src/main/java/com/jme3/util/res/Resources.java +++ b/jme3-core/src/main/java/com/jme3/util/res/Resources.java @@ -166,7 +166,7 @@ public static InputStream getResourceAsStream(String path, Class parent) { * The resource name * * - * @return An enumeration of {@link java.net.URL URL} objects for + * @return An enumeration of {@link java.net.URL URL} objects for * the resource. If no resources could be found, the enumeration * will be empty. * diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java b/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java index 4529239865..1a176f64b0 100644 --- a/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java @@ -58,7 +58,6 @@ public class StructStd140BufferObject extends BufferObject { /** * Create an empty Struct buffer * - * @param str */ public StructStd140BufferObject() { } @@ -120,7 +119,7 @@ public void read(JmeImporter im) throws IOException { try { String rootClass = ic.readString("rootClass", null); if (rootClass == null) throw new Exception("rootClass is undefined"); - Class rootStructClass = (Class) Class.forName(rootClass); + Class rootStructClass = Class.forName(rootClass).asSubclass(Struct.class); Struct rootStruct = rootStructClass.newInstance(); loadLayout(rootStruct); } catch (Exception e) { diff --git a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag index 220c6ad4ba..aa896e0536 100644 --- a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag +++ b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag @@ -15,11 +15,10 @@ in vec3 LocalPos; uniform samplerCube m_Texture; -#ifdef SH_COEF - uniform sampler2D m_ShCoef; +#ifdef FAST_SPHERICAL_HARMONICS + uniform int m_SampleCount; #endif uniform vec2 m_Resolution; -uniform int m_FaceId; const float sqrtPi = sqrt(PI); const float sqrt3Pi = sqrt(3. / PI); @@ -30,7 +29,6 @@ const float sqrt15Pi = sqrt(15. / PI); uniform float m_RemapMaxValue; #endif - vec3 getVectorFromCubemapFaceTexCoord(float x, float y, float mapSize, int face) { float u; float v; @@ -145,6 +143,37 @@ vec3 pixelFaceToV(int faceId, float pixelX, float pixelY, float cubeMapSize) { return normalize(direction); } +#ifdef FAST_SPHERICAL_HARMONICS +void sphHammersleyKernel(int coefficientIndex, out vec3 shCoef, out float weightAccum) { + vec3 texelVect = vec3(0.0); + float shDir = 0.0; + vec4 color = vec4(0.0); + + shCoef = vec3(0.0); + weightAccum = 0.0; + + for(int sampleIndex = 0; sampleIndex < m_SampleCount; sampleIndex++) { + vec4 xi = Hammersley(uint(sampleIndex), uint(m_SampleCount)); + float z = 1.0 - 2.0 * xi.x; + float r = sqrt(max(0.0, 1.0 - z * z)); + float phi = 2.0 * PI * xi.y; + texelVect = vec3(r * cos(phi), z, r * sin(phi)); + evalShBasis(texelVect, coefficientIndex, shDir); + color = texture(m_Texture, texelVect); + shCoef.x = (shCoef.x + color.r * shDir); + shCoef.y = (shCoef.y + color.g * shDir); + shCoef.z = (shCoef.z + color.b * shDir); + weightAccum += 1.0; + } + + #ifdef REMAP_MAX_VALUE + float sampleWeight = 4.0 * PI / float(m_SampleCount); + shCoef.xyz = shCoef.xyz * sampleWeight; + weightAccum = weightAccum * sampleWeight; + #endif +} +#endif + void sphKernel() { int width = int(m_Resolution.x); int height = int(m_Resolution.y); @@ -155,28 +184,26 @@ void sphKernel() { int i=int(gl_FragCoord.x); - #ifdef SH_COEF - vec4 r=texelFetch(m_ShCoef, ivec2(i, 0), 0); - vec3 shCoef=r.rgb; - float weightAccum = r.a; - #else - vec3 shCoef=vec3(0.0); - float weightAccum = 0.0; - #endif + vec3 shCoef=vec3(0.0); + float weightAccum = 0.0; - for(int y = 0; y < height; y++) { - for(int x = 0; x < width; x++) { - weight = getSolidAngleAndVector(float(x), float(y), float(width), m_FaceId, texelVect); - evalShBasis(texelVect, i, shDir); - color = texture(m_Texture, texelVect); - shCoef.x = (shCoef.x + color.r * shDir * weight); - shCoef.y = (shCoef.y + color.g * shDir * weight); - shCoef.z = (shCoef.z + color.b * shDir * weight); - weightAccum += weight; + #ifdef FAST_SPHERICAL_HARMONICS + sphHammersleyKernel(i, shCoef, weightAccum); + #else + for(int faceId = 0; faceId < 6; faceId++) { + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + weight = getSolidAngleAndVector(float(x), float(y), float(width), faceId, texelVect); + evalShBasis(texelVect, i, shDir); + color = texture(m_Texture, texelVect); + shCoef.x = (shCoef.x + color.r * shDir * weight); + shCoef.y = (shCoef.y + color.g * shDir * weight); + shCoef.z = (shCoef.z + color.b * shDir * weight); + weightAccum += weight; + } + } } - } - - + #endif #ifdef REMAP_MAX_VALUE shCoef.xyz=shCoef.xyz*m_RemapMaxValue; diff --git a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md index eaafd2e108..a350cf01d8 100644 --- a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md +++ b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md @@ -3,9 +3,9 @@ MaterialDef IBLSphH { MaterialParameters { Int BoundDrawBuffer TextureCubeMap Texture -LINEAR - Int FaceId : 0 - Texture2D ShCoef -LINEAR Vector2 Resolution + Int SampleCount + Boolean UseFastSphericalHarmonics Float RemapMaxValue } @@ -26,8 +26,8 @@ MaterialDef IBLSphH { Defines { BOUND_DRAW_BUFFER: BoundDrawBuffer + FAST_SPHERICAL_HARMONICS: UseFastSphericalHarmonics REMAP_MAX_VALUE: RemapMaxValue - SH_COEF: ShCoef } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blit/Blit.j3md b/jme3-core/src/main/resources/Common/MatDefs/Blit/Blit.j3md index 2197032e08..27fd9739be 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Blit/Blit.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Blit/Blit.j3md @@ -8,8 +8,8 @@ MaterialDef Blit { } Technique { - VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Blit/Blit.vert - FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Blit/Blit.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Blit/Blit.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Blit/Blit.frag WorldParameters { } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert index b46f4eab59..4600c577b8 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -10,7 +10,7 @@ // fog - jayfella #ifdef USE_FOG -varying float fog_distance; +varying float fogDistance; uniform vec3 g_CameraPosition; #endif @@ -201,6 +201,6 @@ void main(){ #endif #ifdef USE_FOG - fog_distance = distance(g_CameraPosition, (TransformWorld(modelSpacePos)).xyz); + fogDistance = distance(g_CameraPosition, (TransformWorld(modelSpacePos)).xyz); #endif } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag index 034bb51d3b..637fc60117 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag @@ -140,11 +140,10 @@ void main() { for (int i = 0; i < m_NumSamples; i++){ color += main_multiSample(i); } - gl_FragColor = color / m_NumSamples; + gl_FragColor = color / float(m_NumSamples); #else gl_FragColor = main_multiSample(0); #endif } - diff --git a/jme3-core/src/main/resources/Common/OpenCL/Matrix3f.clh b/jme3-core/src/main/resources/Common/OpenCL/Matrix3f.clh deleted file mode 100644 index 3888a8e741..0000000000 --- a/jme3-core/src/main/resources/Common/OpenCL/Matrix3f.clh +++ /dev/null @@ -1,224 +0,0 @@ - -#ifndef MATRIX3_H -#define MATRIX3_H - -//Simple matrix library. -//A 3x3 matrix is represented as a float16 in row major order - -typedef float16 mat3; - -//All matrix functions are prefixed with mat3 or mat4 - -//Returns the zero matrix -inline mat3 mat3Zero() { - return (float16)(0); -} - -//Returns the identity matrix -inline mat3 mat3Identity() { - return (float16) - (1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); -} - -inline mat3 mat3FromRows(float3 row1, float3 row2, float3 row3) { - return (float16) - (row1.x, row1.y, row1.z, 0, - row2.x, row2.y, row2.z, 0, - row3.x, row3.y, row3.z, 0, - 0, 0, 0, 1); -} - -inline mat3 mat3FromColumns(float3 col1, float3 col2, float3 col3) { - return (float16) - (col1.x, col2.x, col3.x, 0, - col1.y, col2.y, col3.y, 0, - col1.z, col2.z, col3.z, 0, - 0, 0, 0, 1); -} - -inline mat3 mat3FromDiagonal(float3 diag) { - return (float16) - (diag.x, 0, 0, 0, - 0, diag.y, 0, 0, - 0, 0, diag.z, 0, - 0, 0, 0, 1); -} - -//Returns the i-th row (0-based) -inline float3 mat3GetRow(mat3 mat, int i) { - if (i==0) return mat.s012; - else if (i==1) return mat.s456; - else return mat.s89a; -} - -//Sets the i-th row (0-based) -inline mat3 mat3SetRow(mat3 mat, int i, float3 row) { - if (i==0) mat.s012 = row; - else if (i==1) mat.s456 = row; - else mat.s89a = row; - return mat; -} - -//Returns the i-th column (0-based) -inline float3 mat3GetColumn(mat3 mat, int i) { - if (i==0) return mat.s048; - else if (i==1) return mat.s159; - else return mat.s26a; -} - -//Sets the i-th column (0-based) -inline mat3 mat3SetColumn(mat3 mat, int i, float3 col) { - if (i==0) mat.s048 = col; - else if (i==1) mat.s159 = col; - else mat.s26a = col; - return mat; -} - -//Returns the diagonal -inline float3 mat3GetDiagonal(mat3 mat) { - return mat.s05a; -} - -//Sets the diagonal -inline mat3 mat3SetDiagonal(mat3 mat, float3 diag) { - mat.s05a = diag; - return mat; -} - -mat3 mat3FromAngleNormalAxis(float angle, float3 axis) { - float fCos = cos(angle); - float fSin = sin(angle); - float fOneMinusCos = 1.0f - fCos; - float fX2 = axis.x * axis.x; - float fY2 = axis.y * axis.y; - float fZ2 = axis.z * axis.z; - float fXYM = axis.x * axis.y * fOneMinusCos; - float fXZM = axis.x * axis.z * fOneMinusCos; - float fYZM = axis.y * axis.z * fOneMinusCos; - float fXSin = axis.x * fSin; - float fYSin = axis.y * fSin; - float fZSin = axis.z * fSin; - - return (float16) ( - fX2 * fOneMinusCos + fCos, - fXYM - fZSin, - fXZM + fYSin, - 0, - fXYM + fZSin, - fY2 * fOneMinusCos + fCos, - fYZM - fXSin, - 0, - fXZM - fYSin, - fYZM + fXSin, - fZ2 * fOneMinusCos + fCos, - 0, - 0, 0, 0, 1 - ); -} - -mat3 mat3FromAngleAxis(float angle, float3 axis) { - return mat3FromAngleNormalAxis(angle, normalize(axis)); -} - -//Multiplies the two matrices A and B -inline mat3 mat3Mult(mat3 A, mat3 B) { - return (float16) ( - dot(A.s012, B.s048), - dot(A.s012, B.s159), - dot(A.s012, B.s26a), - 0, - dot(A.s456, B.s048), - dot(A.s456, B.s159), - dot(A.s456, B.s26a), - 0, - dot(A.s89a, B.s048), - dot(A.s89a, B.s159), - dot(A.s89a, B.s26a), - 0, - 0, 0, 0, 1 - ); -} - -//Computes Av (right multiply of a vector to a matrix) -inline float3 mat3VMult(mat3 A, float3 v) { - return (float3) ( - dot(A.s012, v), - dot(A.s456, v), - dot(A.s89a, v)); -} - -//Computes vA (left multiply of a vector to a matrix) -inline float3 mat3VMult2(float3 v, mat3 A) { - return (float3) ( - dot(v, A.s048), - dot(v, A.s159), - dot(v, A.s26a)); -} - -//Scales this matrix by a constant -inline mat3 mat3Scale(mat3 mat, float s) { - return s*mat; -} - -//Transposes this matrix -inline mat3 mat3Transpose(mat3 mat) { - return mat.s048c159d26ae37bf; //magic -} - -//Computes the determinant -inline float mat3Determinant(mat3 mat) { - float fCo00 = mat.s5 * mat.sa - mat.s6 * mat.s9; - float fCo10 = mat.s6 * mat.s8 - mat.s4 * mat.sa; - float fCo20 = mat.s4 * mat.s9 - mat.s5 * mat.s8; - float fDet = mat.s0 * fCo00 + mat.s1 * fCo10 + mat.s2 * fCo20; - return fDet; -} - -//Creates the adjoint -inline mat3 mat3Adjoint(mat3 mat) { - return (float16) ( - mat.s5 * mat.sa - mat.s6 * mat.s9, - mat.s2 * mat.s9 - mat.s1 * mat.sa, - mat.s1 * mat.s6 - mat.s2 * mat.s5, - 0, - mat.s6 * mat.s8 - mat.s4 * mat.sa, - mat.s0 * mat.sa - mat.s2 * mat.s8, - mat.s2 * mat.s4 - mat.s0 * mat.s6, - 0, - mat.s4 * mat.s9 - mat.s5 * mat.s8, - mat.s1 * mat.s8 - mat.s0 * mat.s9, - mat.s0 * mat.s5 - mat.s1 * mat.s4, - 0, - 0, 0, 0, 1 - ); -} - -//Inverts this matrix -inline mat3 mat3Invert(mat3 mat) { - float det = mat3Determinant(mat); - if (fabs(det) <= 1.1920928955078125E-7f) return mat3Zero(); - mat3 m = mat3Adjoint(mat); - return m / det; -} - -//Computes A+B -inline mat3 mat3Add(mat3 A, mat3 B) { - return A + B; -} - -inline bool mat3Equals(mat3 A, mat3 B, float epsilon) { - return fabs(A.s0 - B.s0)> (48 - bits)); -} - -/** - * Retrieves the next random integer value. - * The buffer used as seed must be read-write. - * Usage: - * - * __kernel void TestRandom(__global ulong* seeds) { - * // ... - * int i = randInt(seeds + get_global_id(0)); - * // --- - * } - * - */ -inline int randInt(__global ulong* seed) { - return randNext(32, seed); -} - -/** - * Retrieves the next random integer value between 0 (inclusive) and n (exclusive). - * The buffer used as seed must be read-write. - * Usage: - * - * __kernel void TestRandom(__global ulong* seeds) { - * // ... - * int i = randIntN(n, seeds + get_global_id(0)); - * // --- - * } - * - */ -inline int randIntN(int n, __global ulong* seed) { - if (n <= 0) - return 0; - - if ((n & -n) == n) // i.e., n is a power of 2 - return (int)((n * (long)randNext(31, seed)) >> 31); - - int bits, val; - do { - bits = randNext(31, seed); - val = bits % n; - } while (bits - val + (n-1) < 0); - return val; -} - -/** - * Retrieves the next random long value. - * The buffer used as seed must be read-write. - * Usage: - * - * __kernel void TestRandom(__global ulong* seeds) { - * // ... - * long l = randLong(seeds + get_global_id(0)); - * // --- - * } - * - */ -inline long randLong(__global ulong* seed) { - // it's okay that the bottom word remains signed. - return ((long)(randNext(32, seed)) << 32) + randNext(32, seed); -} - -/** - * Retrieves the next random boolean value. - * The buffer used as seed must be read-write. - * Usage: - * - * __kernel void TestRandom(__global ulong* seeds) { - * // ... - * bool b = randBool(seeds + get_global_id(0)); - * // --- - * } - * - */ -inline bool randBool(__global ulong* seed) { - return randNext(1, seed) != 0; -} - -#ifdef RANDOM_DOUBLES -/** - * Retrieves the next random double value. - * The buffer used as seed must be read-write. - * To use this function, the preprocessor define RANDOM_DOUBLES must be set. - * Usage: - * - * __kernel void TestRandom(__global ulong* seeds) { - * // ... - * double d = randDouble(seeds + get_global_id(0)); - * // --- - * } - * - */ -inline double randDouble(__global ulong* seed) { - return (((long)(randNext(26, seed)) << 27) + randNext(27, seed)) - / (double)(1L << 53); -} -#endif - -/** - * Retrieves the next random float value. - * The buffer used as seed must be read-write. - * Usage: - * - * __kernel void TestRandom(__global ulong* seeds) { - * // ... - * float f = randFloat(seeds + get_global_id(0)); - * // --- - * } - * - */ -inline float randFloat(__global ulong* seed) -{ - return randNext(24, seed) / ((float)(1 << 24)); -} - -/** - * Retrieves the next random float values with a gaussian distribution of mean 0 - * and derivation 1. - * The buffer used as seed must be read-write. - * Usage: - * - * __kernel void TestRandom(__global ulong* seeds) { - * // ... - * float2 f2 = randGaussianf(seeds + get_global_id(0)); - * // --- - * } - * - */ -inline float2 randGaussianf(__global ulong* seed) { - float v1, v2, s; - do { - v1 = 2 * randFloat(seed) - 1; // between -1 and 1 - v2 = 2 * randFloat(seed) - 1; // between -1 and 1 - s = v1 * v1 + v2 * v2; - } while (s >= 1 || s == 0); - float multiplier = sqrt(-2 * log(s)/s); - return (float2) (v1 * multiplier, v2 * multiplier); -} - -#ifdef RANDOM_DOUBLES -/** - * Retrieves the next random double values with a gaussian distribution of mean 0 - * and derivation 1. - * The buffer used as seed must be read-write. - * To use this function, the preprocessor define RANDOM_DOUBLES must be set. - * Usage: - * - * __kernel void TestRandom(__global ulong* seeds) { - * // ... - * double2 f2 = randGaussian(seeds + get_global_id(0)); - * // --- - * } - * - */ -inline double2 randGaussian(__global ulong* seed) { - double v1, v2, s; - do { - v1 = 2 * randDouble(seed) - 1; // between -1 and 1 - v2 = 2 * randDouble(seed) - 1; // between -1 and 1 - s = v1 * v1 + v2 * v2; - } while (s >= 1 || s == 0); - double multiplier = sqrt(-2 * log(s)/s); - return (double2) (v1 * multiplier, v2 * multiplier); -} -#endif \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib index 558ede0d4a..ce125b5544 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib @@ -1,4 +1,5 @@ #extension GL_ARB_texture_multisample : enable +#extension GL_EXT_multisampled_render_to_texture : enable uniform int m_NumSamples; uniform int m_NumSamplesDepth; @@ -16,7 +17,7 @@ uniform int m_NumSamplesDepth; #endif // NOTE: Only define multisample functions if multisample is available -#if defined(GL_ARB_texture_multisample) || (defined GL_ES && __VERSION__>=310) +#if defined(GL_ARB_texture_multisample) || (defined GL_ES && (__VERSION__>=310 || defined(GL_EXT_multisampled_render_to_texture))) vec4 textureFetch(in sampler2DMS tex,in vec2 texC, in int numSamples){ ivec2 iTexC = ivec2(texC * vec2(textureSize(tex))); vec4 color = vec4(0.0); diff --git a/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib index 7593120476..ddd42c08cd 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib @@ -214,17 +214,24 @@ #if defined(ENABLE_PBRLightingUtils_computeDirectLightContribution) || defined(ENABLE_PBRLightingUtils_newLight) - Light PBRLightingUtils_newLight(vec4 color, vec3 position, float type, float invRadius, float spotAngleCos, vec3 spotDirection){ - Light l; - l.color = color; - l.position = position; - l.type = type; - l.invRadius = invRadius; - l.spotAngleCos = spotAngleCos; - l.spotDirection = spotDirection; - l.ready = false; - return l; - } + Light PBRLightingUtils_newLight(vec4 color, vec3 position, float type, float invRadius, float spotAngleCos, vec3 spotDirection){ + Light l; + l.color = color; + l.position = position; + l.type = type; + l.invRadius = invRadius; + l.spotAngleCos = spotAngleCos; + l.spotDirection = spotDirection; + l.NdotL = 0.0; + l.NdotH = 0.0; + l.LdotH = 0.0; + l.HdotV = 0.0; + l.vector = vec3(0.0); + l.dir = vec3(0.0); + l.fallOff = 0.0; + l.ready = false; + return l; + } #endif @@ -282,23 +289,33 @@ PBRSurface surface; //creates a new PBRSurface - surface.position = wPosition; - surface.viewDir = wViewDir; - surface.geometryNormal = normalize(wNormal); - - //set default values - surface.hasTangents = false; - surface.hasBasicLightMap = false; - surface.albedo = vec3(1.0); - surface.normal = surface.geometryNormal; - surface.emission = vec3(0.0); + surface.position = wPosition; + surface.viewDir = wViewDir; + surface.geometryNormal = normalize(wNormal); + + //set default values + surface.frontFacing = true; + surface.depth = 0.0; + surface.tbnMat = mat3(1.0); + surface.hasTangents = false; + surface.hasBasicLightMap = false; + surface.albedo = vec3(1.0); + surface.normal = surface.geometryNormal; + surface.emission = vec3(0.0); surface.ao = vec3(1.0); surface.lightMapColor = vec3(0.0); - surface.alpha = 1.0; - surface.roughness = 1.0; - surface.metallic = 0.0; - surface.alpha = 1.0; - surface.exposure = 1.0; + surface.alpha = 1.0; + surface.roughness = 1.0; + surface.metallic = 0.0; + surface.exposure = 1.0; + surface.diffuseColor = vec3(1.0); + surface.specularColor = vec3(0.04); + surface.fZero = vec3(0.04); + surface.NdotV = 1.0; + surface.bakedLightContribution = vec3(0.0); + surface.directLightContribution = vec3(0.0); + surface.envLightContribution = vec3(0.0); + surface.brightestNonGlobalLightStrength = 0.0; return surface; } @@ -495,7 +512,11 @@ void PBRLightingUtils_calculatePreLightingValues(inout PBRSurface surface){ - #ifdef SPECGLOSSPIPELINE + surface.frontFacing = gl_FrontFacing; + surface.depth = gl_FragCoord.z; + surface.NdotV = clamp(abs(dot(surface.normal, surface.viewDir)), 0.001, 1.0); + + #ifdef SPECGLOSSPIPELINE float maxSpecular = max(max(surface.specularColor.r, surface.specularColor.g), surface.specularColor.b); surface.diffuseColor = surface.albedo * (1.0 - clamp(maxSpecular, 0.0, 1.0)); surface.fZero = surface.specularColor.xyz; diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/button_circle.png b/jme3-core/src/main/resources/Common/VirtualJoystick/button_circle.png new file mode 100644 index 0000000000..75d8fefcce Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/button_circle.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/button_circle_wide.png b/jme3-core/src/main/resources/Common/VirtualJoystick/button_circle_wide.png new file mode 100644 index 0000000000..92dedaa905 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/button_circle_wide.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/credits.txt b/jme3-core/src/main/resources/Common/VirtualJoystick/credits.txt new file mode 100644 index 0000000000..f55ed57810 --- /dev/null +++ b/jme3-core/src/main/resources/Common/VirtualJoystick/credits.txt @@ -0,0 +1 @@ +https://kenney.nl/assets/mobile-controls \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_east.png b/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_east.png new file mode 100644 index 0000000000..d0519ecc69 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_east.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_north.png b/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_north.png new file mode 100644 index 0000000000..885aa2aa55 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_north.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_south.png b/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_south.png new file mode 100644 index 0000000000..5ff17b32e0 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_south.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_west.png b/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_west.png new file mode 100644 index 0000000000..920a0ddb33 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/dpad_element_west.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_back.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_back.png new file mode 100644 index 0000000000..706eb8000f Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_back.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_a.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_a.png new file mode 100644 index 0000000000..901a44a4f3 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_a.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_b.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_b.png new file mode 100644 index 0000000000..e134dac4cf Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_b.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_x.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_x.png new file mode 100644 index 0000000000..ee5c55193b Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_x.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_y.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_y.png new file mode 100644 index 0000000000..a6d3ab4609 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_button_y.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_dpad.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_dpad.png new file mode 100644 index 0000000000..a95cdb7eec Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_dpad.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_joystick.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_joystick.png new file mode 100644 index 0000000000..404345fb89 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_joystick.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_menu.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_menu.png new file mode 100644 index 0000000000..56c4193c11 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_menu.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/icon_star.png b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_star.png new file mode 100644 index 0000000000..ab2f6ad86a Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/icon_star.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/joystick_circle_nub_a.png b/jme3-core/src/main/resources/Common/VirtualJoystick/joystick_circle_nub_a.png new file mode 100644 index 0000000000..e2cfc1b874 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/joystick_circle_nub_a.png differ diff --git a/jme3-core/src/main/resources/Common/VirtualJoystick/joystick_circle_pad_a.png b/jme3-core/src/main/resources/Common/VirtualJoystick/joystick_circle_pad_a.png new file mode 100644 index 0000000000..74e2c93ca8 Binary files /dev/null and b/jme3-core/src/main/resources/Common/VirtualJoystick/joystick_circle_pad_a.png differ diff --git a/jme3-core/src/main/resources/Interface/Fonts/Console.j3o b/jme3-core/src/main/resources/Interface/Fonts/Console.j3o new file mode 100644 index 0000000000..94a18c724f Binary files /dev/null and b/jme3-core/src/main/resources/Interface/Fonts/Console.j3o differ diff --git a/jme3-core/src/main/resources/Interface/Fonts/Default.j3o b/jme3-core/src/main/resources/Interface/Fonts/Default.j3o new file mode 100644 index 0000000000..3b62130002 Binary files /dev/null and b/jme3-core/src/main/resources/Interface/Fonts/Default.j3o differ diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java index bc8c7e825c..a620f045a7 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java @@ -913,13 +913,11 @@ protected void write(FloatBuffer value) throws IOException { write(NULL_OBJECT); return; } - value.rewind(); int length = value.limit(); write(length); for (int x = 0; x < length; x++) { - writeForBuffer(value.get()); + writeForBuffer(value.get(x)); } - value.rewind(); } // int buffer @@ -929,14 +927,12 @@ protected void write(IntBuffer value) throws IOException { write(NULL_OBJECT); return; } - value.rewind(); int length = value.limit(); write(length); for (int x = 0; x < length; x++) { - writeForBuffer(value.get()); + writeForBuffer(value.get(x)); } - value.rewind(); } // byte buffer @@ -946,13 +942,11 @@ protected void write(ByteBuffer value) throws IOException { write(NULL_OBJECT); return; } - value.rewind(); int length = value.limit(); write(length); for (int x = 0; x < length; x++) { - writeForBuffer(value.get()); + writeForBuffer(value.get(x)); } - value.rewind(); } // short buffer @@ -962,13 +956,11 @@ protected void write(ShortBuffer value) throws IOException { write(NULL_OBJECT); return; } - value.rewind(); int length = value.limit(); write(length); for (int x = 0; x < length; x++) { - writeForBuffer(value.get()); + writeForBuffer(value.get(x)); } - value.rewind(); } @Override @@ -981,4 +973,4 @@ public void write(Enum value, String name, Enum defVal) throws IOException { write(value.name(), name, null); } } -} \ No newline at end of file +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java index 0e1bf901b2..819f6fb883 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java @@ -47,7 +47,7 @@ import java.util.logging.Logger; /** - * @deprecated use {@link StbImageLoader} instead, which supports HDR images and more formats. This loader is + * @deprecated use {@code StbImageLoader} instead, which supports HDR images and more formats. This loader is * kept for backward compatibility but may be removed in future versions. */ @Deprecated diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java index 66211d1e7c..2164c55a5a 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java @@ -53,7 +53,7 @@ * @author Joshua Slack - cleaned, commented, added ability to read 16bit true color and color-mapped TGAs. * @author Kirill Vainer - ported to jME3 * @version $Id: TGALoader.java 4131 2009-03-19 20:15:28Z blaine.dev $ - * @deprecated use {@link StbImageLoader} instead + * @deprecated use {@code StbImageLoader} instead */ @Deprecated public final class TGALoader implements AssetLoader { @@ -73,6 +73,14 @@ public final class TGALoader implements AssetLoader { // 11 - run-length encoded, black and white image public static final int TYPE_BLACKANDWHITE_RLE = 11; + private static void convertBGRtoRGB(byte[] data, int dl){ + for (int i = 0; i < data.length; i += dl) { + byte tmp = data[i]; + data[i] = data[i + 2]; + data[i + 2] = tmp; + } + } + @Override public Object load(AssetInfo info) throws IOException { if (!(info.getKey() instanceof TextureKey)) { @@ -262,7 +270,8 @@ public static Image load(InputStream in, boolean flip) throws IOException { // rawData[rawDataIndex++] = blue; // } } - format = Format.BGR8; + convertBGRtoRGB(rawData, dl); + format = Format.RGB8; } else if (pixelDepth == 32) { for (int i = 0; i <= (height - 1); i++) { if (!flip) { diff --git a/jme3-core/src/test/java/com/jme3/asset/DefaultFontJ3oTest.java b/jme3-core/src/test/java/com/jme3/asset/DefaultFontJ3oTest.java new file mode 100644 index 0000000000..a58e60df97 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/asset/DefaultFontJ3oTest.java @@ -0,0 +1,47 @@ +package com.jme3.asset; + +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.export.binary.BinaryLoader; +import com.jme3.font.BitmapFont; +import com.jme3.material.plugins.J3MLoader; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * Verifies that Default.j3o (with embedded image data) can be loaded using only + * the loaders available in jme3-core, without depending on the PNG loader from + * jme3-plugins. This is needed for Android/iOS which don't depend on jme3-plugins. + */ +public class DefaultFontJ3oTest { + + private static DesktopAssetManager createCoreOnlyAssetManager() { + DesktopAssetManager assetManager = new DesktopAssetManager(false); + assetManager.registerLocator("/", ClasspathLocator.class); + assetManager.registerLoader(BinaryLoader.class, "j3o", "j3f"); + assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); + return assetManager; + } + + private static void assertFontLoaded(DesktopAssetManager assetManager, String path) { + BitmapFont font = assetManager.loadFont(path); + assertNotNull(font, path + " should load without a PNG loader"); + assertNotNull(font.getPage(0), path + " should have at least one material page"); + assertNotNull(font.getPage(0).getTextureParam("ColorMap"), + path + " material page should have a ColorMap texture"); + assertNotNull(font.getPage(0).getTextureParam("ColorMap").getTextureValue().getImage(), + path + " texture should have embedded image data (no key needed)"); + } + + @Test + public void testDefaultFontJ3oLoadsWithoutPngLoader() { + // This should succeed: Default.j3o has embedded image data (no PNG loader needed) + assertFontLoaded(createCoreOnlyAssetManager(), "Interface/Fonts/Default.j3o"); + } + + @Test + public void testConsoleFontJ3oLoadsWithoutPngLoader() { + // This should succeed: Console.j3o has embedded image data (no PNG loader needed) + assertFontLoaded(createCoreOnlyAssetManager(), "Interface/Fonts/Console.j3o"); + } +} diff --git a/jme3-core/src/test/java/com/jme3/export/binary/BinaryOutputCapsuleBufferPositionTest.java b/jme3-core/src/test/java/com/jme3/export/binary/BinaryOutputCapsuleBufferPositionTest.java new file mode 100644 index 0000000000..6667086000 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/export/binary/BinaryOutputCapsuleBufferPositionTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export.binary; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BinaryOutputCapsuleBufferPositionTest { + + @Test + public void savePreservesNioBufferPositions() throws IOException { + BufferPositionSavable savable = new BufferPositionSavable(); + savable.byteBuffer = byteBuffer(1, 2, 3, 4); + savable.floatBuffer = floatBuffer(1f, 2f, 3f, 4f); + savable.intBuffer = intBuffer(1, 2, 3, 4); + savable.shortBuffer = shortBuffer((short) 1, (short) 2, (short) 3, (short) 4); + + savable.byteBuffer.position(2); + savable.floatBuffer.position(2); + savable.intBuffer.position(2); + savable.shortBuffer.position(2); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + BinaryExporter.getInstance().save(savable, output); + + assertEquals(2, savable.byteBuffer.position()); + assertEquals(2, savable.floatBuffer.position()); + assertEquals(2, savable.intBuffer.position()); + assertEquals(2, savable.shortBuffer.position()); + + BufferPositionSavable copy = (BufferPositionSavable) BinaryImporter.getInstance() + .load(new ByteArrayInputStream(output.toByteArray())); + + assertEquals(4, copy.byteBuffer.limit()); + assertEquals(1, copy.byteBuffer.get(0)); + assertEquals(4, copy.byteBuffer.get(3)); + assertEquals(4, copy.floatBuffer.limit()); + assertEquals(1f, copy.floatBuffer.get(0)); + assertEquals(4f, copy.floatBuffer.get(3)); + assertEquals(4, copy.intBuffer.limit()); + assertEquals(1, copy.intBuffer.get(0)); + assertEquals(4, copy.intBuffer.get(3)); + assertEquals(4, copy.shortBuffer.limit()); + assertEquals((short) 1, copy.shortBuffer.get(0)); + assertEquals((short) 4, copy.shortBuffer.get(3)); + } + + private static ByteBuffer byteBuffer(int... values) { + ByteBuffer buffer = ByteBuffer.allocate(values.length); + for (int value : values) { + buffer.put((byte) value); + } + buffer.rewind(); + return buffer; + } + + private static FloatBuffer floatBuffer(float... values) { + FloatBuffer buffer = FloatBuffer.allocate(values.length); + buffer.put(values); + buffer.rewind(); + return buffer; + } + + private static IntBuffer intBuffer(int... values) { + IntBuffer buffer = IntBuffer.allocate(values.length); + buffer.put(values); + buffer.rewind(); + return buffer; + } + + private static ShortBuffer shortBuffer(short... values) { + ShortBuffer buffer = ShortBuffer.allocate(values.length); + buffer.put(values); + buffer.rewind(); + return buffer; + } + + public static class BufferPositionSavable implements Savable { + + ByteBuffer byteBuffer; + FloatBuffer floatBuffer; + IntBuffer intBuffer; + ShortBuffer shortBuffer; + + @Override + public void write(JmeExporter exporter) throws IOException { + OutputCapsule capsule = exporter.getCapsule(this); + capsule.write(byteBuffer, "byteBuffer", null); + capsule.write(floatBuffer, "floatBuffer", null); + capsule.write(intBuffer, "intBuffer", null); + capsule.write(shortBuffer, "shortBuffer", null); + } + + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); + byteBuffer = capsule.readByteBuffer("byteBuffer", null); + floatBuffer = capsule.readFloatBuffer("floatBuffer", null); + intBuffer = capsule.readIntBuffer("intBuffer", null); + shortBuffer = capsule.readShortBuffer("shortBuffer", null); + } + } +} diff --git a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java index c1c330d226..076feb199c 100644 --- a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java +++ b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java @@ -21,8 +21,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author Daniel Johansson diff --git a/jme3-core/src/test/java/com/jme3/renderer/opengl/GLImageFormatsTest.java b/jme3-core/src/test/java/com/jme3/renderer/opengl/GLImageFormatsTest.java index 3338e6d4b4..e6c7caf1ac 100644 --- a/jme3-core/src/test/java/com/jme3/renderer/opengl/GLImageFormatsTest.java +++ b/jme3-core/src/test/java/com/jme3/renderer/opengl/GLImageFormatsTest.java @@ -39,6 +39,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public class GLImageFormatsTest { @@ -56,20 +57,29 @@ public void testGles3UsesCoreHalfFloatType() { } @Test - public void testGles3DoesNotExposeDesktopByteOrderFormats() { + public void testGles3ExposesByteOrderFormatsViaSwizzle() { EnumSet caps = EnumSet.of(Caps.OpenGLES20, Caps.OpenGLES30, Caps.CoreProfile, Caps.Srgb); GLImageFormat[][] formats = GLImageFormats.getFormatsForCaps(caps); - assertNull(formats[0][Image.Format.BGR8.ordinal()]); - assertNull(formats[0][Image.Format.ABGR8.ordinal()]); - assertNull(formats[0][Image.Format.ARGB8.ordinal()]); - assertNull(formats[0][Image.Format.BGRA8.ordinal()]); - assertNull(formats[1][Image.Format.BGR8.ordinal()]); - assertNull(formats[1][Image.Format.ABGR8.ordinal()]); - assertNull(formats[1][Image.Format.ARGB8.ordinal()]); - assertNull(formats[1][Image.Format.BGRA8.ordinal()]); + assertGles3SwizzledFormat(formats[0][Image.Format.BGR8.ordinal()], + GL2.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true); + assertGles3SwizzledFormat(formats[0][Image.Format.ARGB8.ordinal()], + GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); + assertGles3SwizzledFormat(formats[0][Image.Format.BGRA8.ordinal()], + GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); + assertGles3SwizzledFormat(formats[0][Image.Format.ABGR8.ordinal()], + GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); + + assertGles3SwizzledFormat(formats[1][Image.Format.BGR8.ordinal()], + GLExt.GL_SRGB8_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false); + assertGles3SwizzledFormat(formats[1][Image.Format.ARGB8.ordinal()], + GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); + assertGles3SwizzledFormat(formats[1][Image.Format.BGRA8.ordinal()], + GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); + assertGles3SwizzledFormat(formats[1][Image.Format.ABGR8.ordinal()], + GLExt.GL_SRGB8_ALPHA8_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); } @Test @@ -130,4 +140,14 @@ public void testDepthFormatsFollowExplicitCaps() { assertNotNull(formats[0][Image.Format.Depth32.ordinal()]); } + + private static void assertGles3SwizzledFormat(GLImageFormat format, int internalFormat, + int glFormat, int dataType, boolean colorRenderable) { + assertNotNull(format); + assertEquals(internalFormat, format.internalFormat); + assertEquals(glFormat, format.format); + assertEquals(dataType, format.dataType); + assertEquals(colorRenderable, format.colorRenderable); + assertTrue(format.swizzleRequired); + } } diff --git a/jme3-core/src/test/java/com/jme3/system/DisplayScaleUtilsTest.java b/jme3-core/src/test/java/com/jme3/system/DisplayScaleUtilsTest.java new file mode 100644 index 0000000000..507a207784 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/system/DisplayScaleUtilsTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class DisplayScaleUtilsTest { + + @Test + void logicalModeKeepsWindowCoordinatesWhenTheyMatchDisplayScale() { + int[] size = DisplayScaleUtils.resolveLogicalSize( + AppSettings.DISPLAY_SCALE_DPI_AWARE, 1280, 720, 2560, 1440, 2f, 2f); + + assertArrayEquals(new int[] {1280, 720}, size); + } + + @Test + void nativePixelsUsesFramebufferSizeAsLogicalSize() { + int[] size = DisplayScaleUtils.resolveLogicalSize( + AppSettings.DISPLAY_SCALE_NATIVE_PIXELS, 1280, 720, 2560, 1440, 2f, 2f); + + assertArrayEquals(new int[] {2560, 1440}, size); + } + + @Test + void disabledUsesFramebufferSizeAsLogicalSize() { + int[] size = DisplayScaleUtils.resolveLogicalSize( + AppSettings.DISPLAY_SCALE_DISABLED, 1280, 720, 2560, 1440, 2f, 2f); + + assertArrayEquals(new int[] {2560, 1440}, size); + } + + @Test + void inputConversionCanTargetLogicalOrPhysicalCoordinates() { + assertEquals(640, DisplayScaleUtils.toInputX(640, 1280, 1280)); + assertEquals(360, DisplayScaleUtils.toInputY(360, 720, 720)); + assertEquals(1280, DisplayScaleUtils.toInputX(640, 2560, 1280)); + assertEquals(720, DisplayScaleUtils.toInputY(360, 1440, 720)); + } +} diff --git a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java index 648f85f070..ff7c5fbfd6 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java @@ -88,8 +88,8 @@ * LodGenerator lODGenerator = new LodGenerator(geometry); * lODGenerator.bakeLods(reductionMethod,reductionValue); *

    reductionMethod type is VertexReductionMethod described here - * {@link TriangleReductionMethod} reduction value depends on the - * reductionMethod

    + * {@link TriangleReductionMethod} reduction value depends on the + * reductionMethod. * * * @author Nehon diff --git a/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java deleted file mode 100644 index eddf09b333..0000000000 --- a/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.app; - -import com.jme3.system.AppSettings; -import com.jme3.system.JmeCanvasContext; -import com.jme3.system.JmeSystem; -import com.jme3.util.res.Resources; - -import java.applet.Applet; -import java.awt.Canvas; -import java.awt.Graphics; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; - -/** - * @author Kirill Vainer - */ -public class AppletHarness extends Applet { - - public static final HashMap appToApplet - = new HashMap(); - - protected JmeCanvasContext context; - protected Canvas canvas; - protected LegacyApplication app; - - protected String appClass; - protected URL appCfg = null; - protected URL assetCfg = null; - - public static Applet getApplet(Application app){ - return appToApplet.get(app); - } - - @SuppressWarnings("unchecked") - private void createCanvas(){ - AppSettings settings = new AppSettings(true); - - // load app cfg - if (appCfg != null){ - InputStream in = null; - try { - in = appCfg.openStream(); - settings.load(in); - in.close(); - } catch (IOException ex){ - // Called before application has been created .... - // Display error message through AWT - JOptionPane.showMessageDialog(this, "An error has occurred while " - + "loading applet configuration" - + ex.getMessage(), - "jME3 Applet", - JOptionPane.ERROR_MESSAGE); - ex.printStackTrace(); - } finally { - if (in != null) - try { - in.close(); - } catch (IOException ex) { - } - } - } - - if (assetCfg != null){ - settings.putString("AssetConfigURL", assetCfg.toString()); - } - - settings.setWidth(getWidth()); - settings.setHeight(getHeight()); - - JmeSystem.setLowPermissions(true); - - try{ - Class clazz = Class.forName(appClass); - app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); - } catch (ClassNotFoundException - | InstantiationException - | IllegalAccessException - | NoSuchMethodException - | IllegalArgumentException - | InvocationTargetException ex) { - ex.printStackTrace(); - } - - appToApplet.put(app, this); - app.setSettings(settings); - app.createCanvas(); - - context = (JmeCanvasContext) app.getContext(); - canvas = context.getCanvas(); - canvas.setSize(getWidth(), getHeight()); - - add(canvas); - app.startCanvas(); - } - - @Override - public final void update(Graphics g) { - canvas.setSize(getWidth(), getHeight()); - } - - @Override - public void init(){ - appClass = getParameter("AppClass"); - if (appClass == null) - throw new RuntimeException("The required parameter AppClass isn't specified!"); - - try { - appCfg = new URL(getParameter("AppSettingsURL")); - } catch (MalformedURLException ex) { - System.out.println(ex.getMessage()); - appCfg = null; - } - - try { - assetCfg = new URL(getParameter("AssetConfigURL")); - } catch (MalformedURLException ex){ - System.out.println(ex.getMessage()); - assetCfg = Resources.getResource("/com/jme3/asset/Desktop.cfg",this.getClass()); - } - - createCanvas(); - System.out.println("applet:init"); - } - - @Override - public void start(){ - context.setAutoFlushFrames(true); - System.out.println("applet:start"); - } - - @Override - public void stop(){ - context.setAutoFlushFrames(false); - System.out.println("applet:stop"); - } - - @Override - public void destroy(){ - System.out.println("applet:destroyStart"); - SwingUtilities.invokeLater(new Runnable(){ - @Override - public void run(){ - removeAll(); - System.out.println("applet:destroyRemoved"); - } - }); - app.stop(true); - System.out.println("applet:destroyDone"); - - appToApplet.remove(app); - } - -} diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java b/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java index bfb0e85e31..93c03e13d5 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java @@ -186,8 +186,8 @@ public AWTComponentRenderer(Component destination, TransferMode transferMode, Fr this.frameBuffer = frameBuffer; } else { this.frameBuffer = new FrameBuffer(width, height, 1); - this.frameBuffer.setDepthBuffer(Image.Format.Depth); - this.frameBuffer.setColorBuffer(Image.Format.RGBA8); + this.frameBuffer.setDepthTarget(FrameBuffer.FrameBufferTarget.newTarget(Image.Format.Depth)); + this.frameBuffer.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(Image.Format.RGBA8)); this.frameBuffer.setSrgb(true); } @@ -388,4 +388,4 @@ public void dispose() { frameState.compareAndSet(DISPOSING_STATE, DISPOSED_STATE); imageState.compareAndSet(DISPOSING_STATE, DISPOSED_STATE); } -} \ No newline at end of file +} diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java index d1911e3482..e14ee9b9f9 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTContext.java @@ -35,7 +35,6 @@ import com.jme3.input.AWTMouseInput; import com.jme3.input.JoyInput; import com.jme3.input.TouchInput; -import com.jme3.opencl.Context; import com.jme3.renderer.Renderer; /** @@ -168,11 +167,6 @@ public Renderer getRenderer() { return backgroundContext.getRenderer(); } - @Override - public Context getOpenCLContext() { - return null; - } - @Override public AWTMouseInput getMouseInput() { return mouseInput; diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java index 111e856973..541dad9e3d 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java @@ -620,8 +620,8 @@ protected void reshapeCurrentViewPort(int width, int height) { if (found) { FrameBuffer frameBuffer = new FrameBuffer(width, height, 1); - frameBuffer.setDepthBuffer(Image.Format.Depth); - frameBuffer.setColorBuffer(Image.Format.RGBA8); + frameBuffer.setDepthTarget(FrameBuffer.FrameBufferTarget.newTarget(Image.Format.Depth)); + frameBuffer.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(Image.Format.RGBA8)); frameBuffer.setSrgb(true); viewPort.setOutputFrameBuffer(frameBuffer); diff --git a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java index 81c197a3c5..a8002c075c 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java +++ b/jme3-desktop/src/main/java/com/jme3/system/JmeDesktopSystem.java @@ -216,7 +216,7 @@ public JmeContext newContext(AppSettings settings, Type contextType) { || contextType == JmeContext.Type.Headless) { ctx = new NullContext(); ctx.setSettings(settings); - } else if (settings.getRenderer().startsWith("LWJGL")) { + } else if (settings.getRenderer().startsWith("LWJGL") || settings.getRenderer().startsWith("ANGLE")) { ctx = newContextLwjgl(settings, contextType); ctx.setSettings(settings); } else if (settings.getRenderer().startsWith("JOGL")) { @@ -257,18 +257,22 @@ public AudioRenderer newAudioRenderer(AppSettings settings) { AL al; ALC alc; EFX efx; - if (settings.getAudioRenderer().startsWith("LWJGL")) { + String audioRenderer = settings.getAudioRenderer(); + if (audioRenderer == null) { + return null; + } + if (audioRenderer.startsWith("LWJGL") || AppSettings.OPENAL.equals(audioRenderer)) { al = newObject("com.jme3.audio.lwjgl.LwjglAL"); alc = newObject("com.jme3.audio.lwjgl.LwjglALC"); efx = newObject("com.jme3.audio.lwjgl.LwjglEFX"); - } else if (settings.getAudioRenderer().startsWith("JOAL")) { + } else if (audioRenderer.startsWith("JOAL")) { al = newObject("com.jme3.audio.joal.JoalAL"); alc = newObject("com.jme3.audio.joal.JoalALC"); efx = newObject("com.jme3.audio.joal.JoalEFX"); } else { throw new UnsupportedOperationException( "Unrecognizable audio renderer specified: " - + settings.getAudioRenderer()); + + audioRenderer); } if (al == null || alc == null || efx == null) { diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java index e6be5861a6..16d75fddb7 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java @@ -44,6 +44,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.system.NativeLibraries.LibraryInfo; import com.jme3.util.res.Resources; /** @@ -69,13 +70,25 @@ * * @author Kirill Vainer */ -public final class NativeLibraryLoader { - - private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName()); - private static File extractionFolderOverride = null; - private static File extractionFolder = null; - - private static final HashMap nativeLibraryMap = new HashMap<>(); +public final class NativeLibraryLoader { + /** + * System property containing the filesystem directory where native libraries + * should be extracted to or loaded from. + */ + public static final String CUSTOM_EXTRACTION_FOLDER_PROPERTY = "com.jme3.NativeLibraryExtractionFolder"; + + /** + * System property controlling whether native libraries should be extracted + * from the classpath before loading. Defaults to {@code true}. + */ + public static final String EXTRACT_NATIVE_LIBRARIES_PROPERTY = "com.jme3.ExtractNativeLibraries"; + + private static final Logger logger = Logger.getLogger(NativeLibraryLoader.class.getName()); + private static File extractionFolderOverride = null; + private static File extractionFolder = null; + private static Boolean extractNativeLibrariesOverride = null; + + private static final HashMap nativeLibraryMap = new HashMap<>(); static { NativeLibraries.registerDefaultLibraries(); @@ -91,6 +104,16 @@ public static void registerNativeLibrary(NativeLibrary library) { nativeLibraryMap.put(library.getKey(), library); } + /** + * Register a new native library. + * + * This simply registers a known library, the actual extraction and loading is performed by calling + * {@link #loadNativeLibrary(java.lang.String, boolean) }. + */ + public static void registerNativeLibrary(LibraryInfo library) { + library.getNativeVariants().forEach(NativeLibraryLoader::registerNativeLibrary); + } + /** * Register a new native library. * @@ -163,18 +186,75 @@ public static boolean isUsingNativeBullet() { * * @param path Path where to extract native libraries. */ - public static void setCustomExtractionFolder(String path) { - extractionFolderOverride = new File(path).getAbsoluteFile(); - } + public static void setCustomExtractionFolder(String path) { + extractionFolderOverride = path == null ? null : new File(path).getAbsoluteFile(); + } + + /** + * Returns the configured custom extraction folder. + * + * @return the programmatic override if set, otherwise the + * {@link #CUSTOM_EXTRACTION_FOLDER_PROPERTY} system property + */ + public static File getCustomExtractionFolder() { + if (extractionFolderOverride != null) { + return extractionFolderOverride; + } + + String extractionFolderProperty = System.getProperty(CUSTOM_EXTRACTION_FOLDER_PROPERTY); + if (extractionFolderProperty != null && !extractionFolderProperty.trim().isEmpty()) { + return new File(extractionFolderProperty).getAbsoluteFile(); + } + + return null; + } + + /** + * Specify whether native libraries should be extracted from the classpath + * before loading. Set to {@code true} to preserve the default behavior. + * + * @param extractNativeLibraries true to extract classpath natives, false to + * load existing files from the extraction folder + */ + public static void setExtractNativeLibraries(boolean extractNativeLibraries) { + extractNativeLibrariesOverride = extractNativeLibraries; + } + + /** + * Clears the programmatic extraction flag override. + */ + public static void clearExtractNativeLibrariesOverride() { + extractNativeLibrariesOverride = null; + } + + /** + * Returns whether native libraries should be extracted before loading. + * + * @return the programmatic override if set, otherwise the + * {@link #EXTRACT_NATIVE_LIBRARIES_PROPERTY} system property, + * defaulting to true + */ + public static boolean isExtractNativeLibraries() { + if (extractNativeLibrariesOverride != null) { + return extractNativeLibrariesOverride; + } + + String extractNativeLibrariesProperty = System.getProperty(EXTRACT_NATIVE_LIBRARIES_PROPERTY); + return extractNativeLibrariesProperty == null + || extractNativeLibrariesProperty.trim().isEmpty() + || Boolean.parseBoolean(extractNativeLibrariesProperty); + } /** * Returns the folder where native libraries will be extracted. * This is automatically determined at run-time based on the - * following criteria:
    - *

      - *
    • If a {@link #setCustomExtractionFolder(java.lang.String) custom - * extraction folder} has been specified, it is returned. - *
    • If the user can write to "java.io.tmpdir" folder, then it + * following criteria:
      + *
        + *
      • If a {@link #setCustomExtractionFolder(java.lang.String) custom + * extraction folder} has been specified, it is returned.
      • + *
      • If the {@link #CUSTOM_EXTRACTION_FOLDER_PROPERTY} system property + * has been specified, it is returned.
      • + *
      • If the user can write to "java.io.tmpdir" folder, then it * is used.
      • *
      • Otherwise, the {@link JmeSystem#getStorageFolder() storage folder} * is used, to prevent collisions, a special subfolder is used @@ -185,10 +265,11 @@ public static void setCustomExtractionFolder(String path) { * * @return Path where natives will be extracted to. */ - public static File getExtractionFolder() { - if (extractionFolderOverride != null) { - return extractionFolderOverride; - } + public static File getExtractionFolder() { + File customExtractionFolder = getCustomExtractionFolder(); + if (customExtractionFolder != null) { + return customExtractionFolder; + } if (extractionFolder == null) { File userTempDir = new File(System.getProperty("java.io.tmpdir")); if (!userTempDir.canWrite()) { @@ -429,11 +510,15 @@ public static void extractNativeLibrary(Platform platform, String name, File tar /** * First extracts the native library and then loads it. * - * @param name The name of the library to load. - * @param isRequired If true and the library fails to load, throw exception. If - * false, do nothing if it fails to load. + * @param name + * The name of the library to load. + * @param isRequired + * If true and the library fails to load, throw exception. If false, do nothing if it fails to + * load. + * + * @return The absolute path of the loaded library. */ - public static void loadNativeLibrary(String name, boolean isRequired) { + public static String loadNativeLibrary(String name, boolean isRequired) { if (JmeSystem.isLowPermissions()) { throw new UnsupportedOperationException("JVM is running under " + "reduced permissions. Cannot load native libraries."); @@ -454,7 +539,7 @@ public static void loadNativeLibrary(String name, boolean isRequired) { " is not available for your OS: {1}", new Object[]{name, platform}); } - return; + return null; } } @@ -462,80 +547,98 @@ public static void loadNativeLibrary(String name, boolean isRequired) { if (pathInJar == null) { // This platform does not require the native library to be loaded. - return; - } - - URL url = Resources.getResource(pathInJar); - - if (url == null) { - if (isRequired) { - throw new UnsatisfiedLinkError( - "The required native library '" + library.getName() + "'" - + " was not found in the classpath via '" + pathInJar); - } else if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "The optional native library ''{0}''" + - " was not found in the classpath via ''{1}''.", - new Object[]{library.getName(), pathInJar}); - } - return; - } - - // The library has been found and is ready to be extracted. - // Determine what filename it should be extracted as. - String loadedAsFileName; - if (library.getExtractedAsName() != null) { - loadedAsFileName = library.getExtractedAsName(); - } else { - // Just use the original filename as it is in the JAR. - loadedAsFileName = Paths.get(pathInJar).getFileName().toString(); - } - - File extractionDirectory = getExtractionFolder(); - URLConnection conn; - - try { - conn = url.openConnection(); - } catch (IOException ex) { - // Maybe put more detail here? Not sure. - throw new UncheckedIOException("Failed to open file: '" + url + - "'. Error: " + ex, ex); + return null; } - - File targetFile = new File(extractionDirectory, loadedAsFileName); - try (InputStream in = conn.getInputStream()) { - if (isExtractingRequired(conn, targetFile)) { - Files.copy(in, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - - // NOTE: On OSes that support "Date Created" property, - // this will cause the last modified date to be lower than - // date created which makes no sense - targetFile.setLastModified(conn.getLastModified()); - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Extracted native library from ''{0}'' into ''{1}''. ", - new Object[]{url, targetFile}); - } - } else { - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", - loadedAsFileName); - } - } - - library.getLoadFunction().accept(targetFile.getAbsolutePath()); - - if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE, "Loaded native library {0}.", library.getName()); - } - } catch (IOException ex) { - /*if (ex.getMessage().contains("used by another process")) { - return; - }*/ - - throw new UncheckedIOException("Failed to extract native library to: " - + targetFile, ex); - } - } + String loadedAsFileName = getLoadedAsFileName(library, pathInJar); + File extractionDirectory = getExtractionFolder(); + File targetFile = new File(extractionDirectory, loadedAsFileName); + + if (isExtractNativeLibraries()) { + URL url = Resources.getResource(pathInJar); + + if (url == null) { + if (isRequired) { + throw new UnsatisfiedLinkError( + "The required native library '" + library.getName() + "'" + + " was not found in the classpath via '" + pathInJar); + } else if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "The optional native library ''{0}''" + + " was not found in the classpath via ''{1}''.", + new Object[]{library.getName(), pathInJar}); + } + return null; + } + + // The library has been found and is ready to be extracted. + URLConnection conn; + + try { + conn = url.openConnection(); + } catch (IOException ex) { + // Maybe put more detail here? Not sure. + throw new UncheckedIOException("Failed to open file: '" + url + + "'. Error: " + ex, ex); + } + + try (InputStream in = conn.getInputStream()) { + if (isExtractingRequired(conn, targetFile)) { + Files.copy(in, targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + + // NOTE: On OSes that support "Date Created" property, + // this will cause the last modified date to be lower than + // date created which makes no sense + targetFile.setLastModified(conn.getLastModified()); + + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Extracted native library from ''{0}'' into ''{1}''. ", + new Object[]{url, targetFile}); + } + } else { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Not copying library {0}. Latest already extracted.", + loadedAsFileName); + } + } + } catch (IOException ex) { + /*if (ex.getMessage().contains("used by another process")) { + return; + }*/ + + throw new UncheckedIOException("Failed to extract native library to: " + + targetFile, ex); + } + } else if (!targetFile.isFile()) { + if (isRequired) { + throw new UnsatisfiedLinkError( + "The required native library '" + library.getName() + "'" + + " was not found at '" + targetFile + + "' and native library extraction is disabled"); + } else if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "The optional native library ''{0}''" + + " was not found at ''{1}'' and native library extraction is disabled.", + new Object[]{library.getName(), targetFile}); + } + return null; + } + + library.getLoadFunction().accept(targetFile.getAbsolutePath()); + + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Loaded native library {0}.", library.getName()); + } + + return targetFile.getAbsolutePath(); + } + + private static String getLoadedAsFileName(NativeLibrary library, String pathInJar) { + if (library.getExtractedAsName() != null) { + return library.getExtractedAsName(); + } + + // Just use the original filename as it is in the JAR. + return Paths.get(pathInJar).getFileName().toString(); + } /** * Checks if library extraction is required by comparing source and target diff --git a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java index a25f25379e..cc512c2198 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanelsContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2023 jMonkeyEngine + * Copyright (c) 2009-2026 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ import com.jme3.input.TouchInput; import com.jme3.input.awt.AwtKeyInput; import com.jme3.input.awt.AwtMouseInput; -import com.jme3.opencl.Context; import com.jme3.renderer.Renderer; import com.jme3.system.*; import java.util.ArrayList; @@ -182,11 +181,6 @@ public boolean isRenderable() { return actualContext != null && actualContext.isRenderable(); } - @Override - public Context getOpenCLContext() { - return actualContext.getOpenCLContext(); - } - public AwtPanelsContext() {} public AwtPanel createPanel(PaintMode paintMode) { @@ -245,7 +239,7 @@ private void destroyInThread() { @Override public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); - this.settings.setRenderer(AppSettings.LWJGL_OPENGL2); + this.settings.setRenderer(AppSettings.LWJGL_OPENGL32); if (actualContext != null) { actualContext.setSettings(settings); } diff --git a/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java b/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java index 49e318b59e..7a1493d90c 100644 --- a/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java @@ -107,6 +107,27 @@ private void flipImage(short[] img, int width, int height, int bpp){ } } + private static void convertBGRtoRGB(byte[] data){ + for (int i = 0; i < data.length; i += 3) { + byte tmp = data[i]; + data[i] = data[i + 2]; + data[i + 2] = tmp; + } + } + + private static void convertABGRtoRGBA(byte[] data){ + for (int i = 0; i < data.length; i += 4) { + byte a = data[i]; + byte b = data[i + 1]; + byte g = data[i + 2]; + byte r = data[i + 3]; + data[i] = r; + data[i + 1] = g; + data[i + 2] = b; + data[i + 3] = a; + } + } + public Image load(BufferedImage img, boolean flipY){ int width = img.getWidth(); int height = img.getHeight(); @@ -116,18 +137,22 @@ public Image load(BufferedImage img, boolean flipY){ byte[] dataBuf1 = (byte[]) extractImageData(img); if (flipY) flipImage(dataBuf1, width, height, 32); + + convertABGRtoRGBA(dataBuf1); ByteBuffer data1 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*4); data1.put(dataBuf1); - return new Image(Format.ABGR8, width, height, data1, null, com.jme3.texture.image.ColorSpace.sRGB); + return new Image(Format.RGBA8, width, height, data1, null, com.jme3.texture.image.ColorSpace.sRGB); case BufferedImage.TYPE_3BYTE_BGR: // most common in JPEG images byte[] dataBuf2 = (byte[]) extractImageData(img); if (flipY) flipImage(dataBuf2, width, height, 24); + + convertBGRtoRGB(dataBuf2); ByteBuffer data2 = BufferUtils.createByteBuffer(img.getWidth()*img.getHeight()*3); data2.put(dataBuf2); - return new Image(Format.BGR8, width, height, data2, null, com.jme3.texture.image.ColorSpace.sRGB); + return new Image(Format.RGB8, width, height, data2, null, com.jme3.texture.image.ColorSpace.sRGB); case BufferedImage.TYPE_BYTE_GRAY: // grayscale fonts byte[] dataBuf3 = (byte[]) extractImageData(img); if (flipY) diff --git a/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java b/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java index 82f03a410d..507498e2dc 100644 --- a/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java +++ b/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java @@ -183,6 +183,16 @@ public DecodeParams(int bpp, int rm, int rs, int im, int is){ private ImageToAwt() { } + private static Format toGLESFormat(Format format){ + if (format == Format.BGR8) { + return Format.RGB8; + } + if (format == Format.BGRA8 || format == Format.ABGR8 || format == Format.ARGB8) { + return Format.RGBA8; + } + return format; + } + private static int Ix(int x, int y, int w){ return y * w + x; } @@ -214,6 +224,8 @@ private static void writePixel(ByteBuffer buf, int idx, int pixel, int bpp){ * @param buf the output buffer (not null, modified) */ public static void convert(BufferedImage image, Format format, ByteBuffer buf) { + format = toGLESFormat(format); + DecodeParams p = params.get(format); if (p == null) throw new UnsupportedOperationException("Image format " + format + " is not supported"); diff --git a/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog.properties b/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog.properties index 416fd85d2e..ce0434f275 100644 --- a/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog.properties +++ b/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog.properties @@ -11,6 +11,7 @@ label.resolutions=Screen Resolution label.colordepth=Color Depth label.refresh=Refresh Rate label.antialias=Anti-Aliasing +label.renderer=Renderer antialias.disabled=Disabled refresh.na=n/a diff --git a/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog_zh_CN.properties b/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog_zh_CN.properties index bead05a3c0..26dcb95ca1 100644 --- a/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog_zh_CN.properties +++ b/jme3-desktop/src/main/resources/com/jme3/app/SettingsDialog_zh_CN.properties @@ -9,10 +9,11 @@ checkbox.gamma=\u4F3D\u9A6C\u6821\u6B63 label.resolutions=\u5206\u8FA8\u7387 label.colordepth=\u8272\u5F69\u6DF1\u5EA6 -label.refresh=\u5237\u65B0\u7387 -label.antialias=\u6297\u952F\u9F7F - -antialias.disabled=\u7981\u7528 -refresh.na=N/A +label.refresh=\u5237\u65B0\u7387 +label.antialias=\u6297\u952F\u9F7F +label.renderer=\u6E32\u67D3\u5668 + +antialias.disabled=\u7981\u7528 +refresh.na=N/A error.unsupportedmode=\u60A8\u7684\u663E\u793A\u5668\u4E0D\u652F\u6301\u6240\u9009\u5206\u8FA8\u7387\u3002\n\u4E0D\u652F\u6301\u8BE5\u5206\u8FA8\u7387\u548C\u8272\u6DF1\u7684\u7EC4\u5408\u3002 diff --git a/jme3-desktop/src/test/java/com/jme3/system/NativeLibraryLoaderTest.java b/jme3-desktop/src/test/java/com/jme3/system/NativeLibraryLoaderTest.java new file mode 100644 index 0000000000..8a5d0a39c4 --- /dev/null +++ b/jme3-desktop/src/test/java/com/jme3/system/NativeLibraryLoaderTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class NativeLibraryLoaderTest { + + @TempDir + private Path tempDir; + + private String previousExtractionFolder; + private String previousExtractNativeLibraries; + + @BeforeEach + void rememberNativeLibrarySettings() { + previousExtractionFolder = System.getProperty(NativeLibraryLoader.CUSTOM_EXTRACTION_FOLDER_PROPERTY); + previousExtractNativeLibraries = System.getProperty(NativeLibraryLoader.EXTRACT_NATIVE_LIBRARIES_PROPERTY); + NativeLibraryLoader.setCustomExtractionFolder(null); + NativeLibraryLoader.clearExtractNativeLibrariesOverride(); + } + + @AfterEach + void restoreNativeLibrarySettings() { + NativeLibraryLoader.setCustomExtractionFolder(null); + NativeLibraryLoader.clearExtractNativeLibrariesOverride(); + if (previousExtractionFolder == null) { + System.clearProperty(NativeLibraryLoader.CUSTOM_EXTRACTION_FOLDER_PROPERTY); + } else { + System.setProperty(NativeLibraryLoader.CUSTOM_EXTRACTION_FOLDER_PROPERTY, previousExtractionFolder); + } + if (previousExtractNativeLibraries == null) { + System.clearProperty(NativeLibraryLoader.EXTRACT_NATIVE_LIBRARIES_PROPERTY); + } else { + System.setProperty(NativeLibraryLoader.EXTRACT_NATIVE_LIBRARIES_PROPERTY, previousExtractNativeLibraries); + } + } + + @Test + void loadNativeLibraryUsesCustomExtractionFolderWhenExtractionIsDisabled() throws Exception { + Path nativeLibraryFile = tempDir.resolve("libcustom-native-test.so"); + Files.createFile(nativeLibraryFile); + AtomicReference loadedPath = new AtomicReference<>(); + String libraryName = "customExtractionFolderTest" + System.nanoTime(); + + NativeLibraryLoader.registerNativeLibrary(new NativeLibrary( + libraryName, + JmeSystem.getPlatform(), + "native/missing/libcustom-native-test.so", + "libcustom-native-test.so", + loadedPath::set)); + NativeLibraryLoader.setCustomExtractionFolder(tempDir.toString()); + NativeLibraryLoader.setExtractNativeLibraries(false); + + String result = NativeLibraryLoader.loadNativeLibrary(libraryName, true); + + assertEquals(nativeLibraryFile.toAbsolutePath().toString(), result); + assertEquals(result, loadedPath.get()); + } + + @Test + void loadNativeLibraryUsesExtractionFolderSystemProperties() throws Exception { + Path nativeLibraryFile = tempDir.resolve("libproperty-native-test.so"); + Files.createFile(nativeLibraryFile); + AtomicReference loadedPath = new AtomicReference<>(); + String libraryName = "propertyExtractionFolderTest" + System.nanoTime(); + + NativeLibraryLoader.registerNativeLibrary(new NativeLibrary( + libraryName, + JmeSystem.getPlatform(), + "native/missing/libproperty-native-test.so", + "libproperty-native-test.so", + loadedPath::set)); + System.setProperty(NativeLibraryLoader.CUSTOM_EXTRACTION_FOLDER_PROPERTY, tempDir.toString()); + System.setProperty(NativeLibraryLoader.EXTRACT_NATIVE_LIBRARIES_PROPERTY, "false"); + + String result = NativeLibraryLoader.loadNativeLibrary(libraryName, true); + + assertEquals(nativeLibraryFile.toAbsolutePath().toString(), result); + assertEquals(result, loadedPath.get()); + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag index 2803a95d87..4e5407f8cd 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag @@ -20,17 +20,17 @@ void main() { // - l - m - // g - h - i // === ('e' is the current texel) === - vec3 a = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y + 2*y)).rgb; - vec3 b = getColor(m_Texture, vec2(texCoord.x, texCoord.y + 2*y)).rgb; - vec3 c = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y + 2*y)).rgb; + vec3 a = getColor(m_Texture, vec2(texCoord.x - 2.0*x, texCoord.y + 2.0*y)).rgb; + vec3 b = getColor(m_Texture, vec2(texCoord.x, texCoord.y + 2.0*y)).rgb; + vec3 c = getColor(m_Texture, vec2(texCoord.x + 2.0*x, texCoord.y + 2.0*y)).rgb; - vec3 d = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y)).rgb; + vec3 d = getColor(m_Texture, vec2(texCoord.x - 2.0*x, texCoord.y)).rgb; vec3 e = getColor(m_Texture, vec2(texCoord.x, texCoord.y)).rgb; - vec3 f = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y)).rgb; + vec3 f = getColor(m_Texture, vec2(texCoord.x + 2.0*x, texCoord.y)).rgb; - vec3 g = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y - 2*y)).rgb; - vec3 h = getColor(m_Texture, vec2(texCoord.x, texCoord.y - 2*y)).rgb; - vec3 i = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y - 2*y)).rgb; + vec3 g = getColor(m_Texture, vec2(texCoord.x - 2.0*x, texCoord.y - 2.0*y)).rgb; + vec3 h = getColor(m_Texture, vec2(texCoord.x, texCoord.y - 2.0*y)).rgb; + vec3 i = getColor(m_Texture, vec2(texCoord.x + 2.0*x, texCoord.y - 2.0*y)).rgb; vec3 j = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y + y)).rgb; vec3 k = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y + y)).rgb; diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag index a39c4b11c6..18322139aa 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag @@ -1,5 +1,5 @@ -#extension GL_ARB_texture_multisample : enable #import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" #import "Common/ShaderLib/Hdr.glsllib" @@ -23,7 +23,7 @@ vec4 applyToneMap() { hdrColor += texelFetch(m_Texture, iTexC, i); } hdrColor /= float(NUM_SAMPLES); - vec3 ldrColor = vec4(applyCurve(hdrColor.rgb), hdrColor.a); + vec4 ldrColor = vec4(applyCurve(hdrColor.rgb), hdrColor.a); return ldrColor; } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Posterization.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Posterization.frag index 5635d155e5..27b3b4bb85 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/Posterization.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Posterization.frag @@ -13,10 +13,10 @@ void main() { vec4 texVal = vec4(color); texVal = pow(texVal, vec4(m_Gamma)); - texVal = texVal * vec4(m_NumColors); - texVal = floor(texVal); - texVal = texVal / vec4(m_NumColors); + texVal = texVal * vec4(float(m_NumColors)); + texVal = floor(texVal); + texVal = texVal / vec4(float(m_NumColors)); texVal = pow(texVal, vec4(1.0/m_Gamma)); gl_FragColor = mix(color, texVal, m_Strength); -} \ No newline at end of file +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.frag index 673be8c025..731b7ff024 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.frag @@ -1,5 +1,5 @@ -#extension GL_ARB_texture_multisample : enable #import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" vec3 FilmicCurve(in vec3 x) { const float A = 0.22; diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index 57d4ac9c0b..2017ffb303 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -1,7 +1,100 @@ +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.jvm.toolchain.JvmVendorSpec + +import groovy.json.JsonOutput +import java.nio.file.FileSystems +import java.nio.file.Files + +plugins { + id 'org.graalvm.buildtools.native' version '1.1.0' +} + ext.mainClassName = 'jme3test.TestChooser' +ext.nativeMainClassName = 'jme3test.TestChooserCli' +ext.jmeNativeImageAdditionalResourceGlobs = [] + +def generatedTestChooserResourcesDir = layout.buildDirectory.dir('generated/testchooser/resources') +def testChooserClassListFile = generatedTestChooserResourcesDir.map { it.file('jme3test/test-classes.txt') } +def testChooserReflectionConfigFile = layout.buildDirectory.file('generated/testchooser/reflect-config.json') +def nativeImageBuildOutputJsonFile = layout.buildDirectory.file('reports/native-image/jme3-testchooser-build-output.json') +def testChooserReachabilityMetadataFile = generatedTestChooserResourcesDir.map { + it.file('META-INF/native-image/org.jmonkeyengine/jme3-examples-testchooser/reachability-metadata.json') +} + +tasks.register('generateTestChooserClassList') { + group = 'build' + description = 'Generates a resource list of jme3test classes for TestChooserCli fallback discovery.' + dependsOn 'compileJava' + inputs.files(sourceSets.main.output.classesDirs) + outputs.files(testChooserClassListFile, testChooserReachabilityMetadataFile, testChooserReflectionConfigFile) + + doLast { + Set allJme3TestClassNames = new TreeSet() + Set launcherClassNames = new TreeSet() + sourceSets.main.output.classesDirs.files.findAll { it.exists() }.each { classesDir -> + fileTree(classesDir).matching { + include 'jme3test/**/*.class' + exclude '**/*$*' + exclude '**/module-info.class' + exclude '**/package-info.class' + exclude '**/TestChooser.class' + exclude '**/TestChooserCli.class' + }.files.each { classFile -> + String relativePath = classesDir.toPath().relativize(classFile.toPath()).toString().replace(File.separatorChar, (char) '/') + if (relativePath.endsWith('.class')) { + String className = relativePath.substring(0, relativePath.length() - '.class'.length()).replace('/', '.') + allJme3TestClassNames.add(className) + if (relativePath.contains('Test')) { + launcherClassNames.add(className) + } + } + } + } + + File classListFile = testChooserClassListFile.get().asFile + classListFile.parentFile.mkdirs() + classListFile.text = launcherClassNames.isEmpty() + ? '' + : launcherClassNames.join(System.lineSeparator()) + System.lineSeparator() + + List> reflectionEntries = allJme3TestClassNames.collect { className -> + [ + type : className, + allDeclaredConstructors: true, + allDeclaredMethods : true, + allPublicMethods : true + ] + } + Map metadata = [ + reflection: reflectionEntries, + resources : [] + ] + + File reachabilityFile = testChooserReachabilityMetadataFile.get().asFile + reachabilityFile.parentFile.mkdirs() + reachabilityFile.text = JsonOutput.prettyPrint(JsonOutput.toJson(metadata)) + System.lineSeparator() + + List> reflectionConfigEntries = allJme3TestClassNames.collect { className -> + [ + name : className, + allDeclaredConstructors: true, + allDeclaredMethods : true, + allPublicMethods : true + ] + } + File reflectionConfigFile = testChooserReflectionConfigFile.get().asFile + reflectionConfigFile.parentFile.mkdirs() + reflectionConfigFile.text = JsonOutput.prettyPrint(JsonOutput.toJson(reflectionConfigEntries)) + System.lineSeparator() + } +} + +sourceSets.main.resources.srcDir(generatedTestChooserResourcesDir) + +tasks.named('processResources') { + dependsOn 'generateTestChooserClassList' +} def androidProject = project(':jme3-android') -def androidNativeProject = project(':jme3-android-native') task run(dependsOn: 'build', type:JavaExec) { mainClass = mainClassName @@ -17,8 +110,11 @@ task run(dependsOn: 'build', type:JavaExec) { } -task runExamples(dependsOn: 'build', type: JavaExec) { +task runExamples(dependsOn: 'classes', type: JavaExec) { classpath = sourceSets.main.runtimeClasspath + if (System.getProperty('os.name').toLowerCase().contains('mac')) { + jvmArgs '-XstartOnFirstThread' + } if (System.properties['java.util.logging.config.file'] != null) { systemProperty "java.util.logging.config.file", System.properties['java.util.logging.config.file'] @@ -178,6 +274,84 @@ task runExamplesWithProton(dependsOn: ['build', 'downloadProtonRuntime', 'downlo } } +def graalLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + vendor = JvmVendorSpec.GRAAL_VM + nativeImageCapable = true +} + +graalvmNative { + testSupport = false + binaries { + named('main') { + imageName = 'jme3-testchooser' + mainClass = nativeMainClassName + sharedLibrary = false + javaLauncher = graalLauncher + project.ext.jmeApplyDefaultNativeImageResourceSettings(delegate) + buildArgs.add("-H:ReflectionConfigurationFiles=${testChooserReflectionConfigFile.get().asFile.absolutePath}") + buildArgs.add('-H:+BuildOutputBreakdowns') + buildArgs.add("-H:BuildOutputJSONFile=${nativeImageBuildOutputJsonFile.get().asFile.absolutePath}") + } + } +} + +tasks.named('nativeCompile') { + dependsOn 'processResources' + + doFirst { + // Gradle's Copy task cannot preserve symlinks in some provisioned toolchains. + // Recreate likely broken links in the GraalVM toolchain before native-image runs. + if (!FileSystems.getDefault().supportedFileAttributeViews().contains('posix')) { + logger.info("Skipping GraalVM toolchain symlink fixup on non-POSIX file system.") + return + } + + File graalvmHomeDir = System.getenv('GRAALVM_HOME') ? file(System.getenv('GRAALVM_HOME')) : null + def nativeImageLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(21) + vendor = JvmVendorSpec.GRAAL_VM + nativeImageCapable = true + } + + if (delegate.hasProperty('options') && delegate.options.hasProperty('javaLauncher')) { + delegate.options.javaLauncher.set(nativeImageLauncher) + } + + File toolchainDir = graalvmHomeDir + if (toolchainDir == null) { + File executableParent = nativeImageLauncher.get().executablePath.asFile.parentFile + toolchainDir = executableParent.name == 'bin' ? executableParent.parentFile : executableParent + } + + def toolchainFiles = project.fileTree(toolchainDir).files.findAll { it.isFile() } + def emptyFiles = toolchainFiles.findAll { it.length() == 0L } + Set emptyFileSet = emptyFiles as Set + + toolchainFiles.groupBy { it.name }.each { ignoredName, sameNamedFiles -> + List nonEmptyCandidates = sameNamedFiles.findAll { it.length() > 0L } + List emptyCandidates = sameNamedFiles.findAll { emptyFileSet.contains(it) } + if (!nonEmptyCandidates.isEmpty() && !emptyCandidates.isEmpty()) { + File target = nonEmptyCandidates.first() + emptyCandidates.each { File link -> + if (link != target) { + logger.quiet("Fixing up '${link}' to link to '${target}'.") + if (link.delete()) { + try { + Files.createSymbolicLink(link.toPath(), target.toPath()) + } catch (Exception ex) { + logger.warn("Unable to create symlink '${link}' to '${target}'.", ex) + } + } else { + logger.warn("Unable to delete '${link}'.") + } + } + } + } + } + } +} + dependencies { implementation project(':jme3-core') implementation project(':jme3-desktop') @@ -194,7 +368,7 @@ dependencies { implementation project(':jme3-plugins-json-gson') implementation project(':jme3-terrain') implementation project(':jme3-awt-dialogs') - runtimeOnly project(':jme3-testdata') + implementation project(':jme3-testdata') runtimeOnly libs.nifty.examples // for the "all/intro.xml" example GUI } @@ -219,7 +393,7 @@ jar.doFirst{ } } -task dist (dependsOn: ['build', ':jme3-android:jar', ':jme3-android-native:jar']) { +task dist (dependsOn: ['build', ':jme3-android:jar']) { doLast { // Copy all dependencies to ../dist/lib, remove versions from jar files configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact -> @@ -245,10 +419,17 @@ task dist (dependsOn: ['build', ':jme3-android:jar', ':jme3-android-native:jar'] into '../dist/opt/android' rename { androidProject.name + ".jar" } } + def androidNativeDependency = libs.jme3.android.natives.get() + def androidNativeArtifact = androidProject.configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.find { + it.moduleVersion.id.group == androidNativeDependency.module.group && it.name == androidNativeDependency.module.name + } + if (androidNativeArtifact == null) { + throw new GradleException("Unable to resolve ${androidNativeDependency.module} for the distribution") + } copy { - from androidNativeProject.tasks.named('jar').flatMap { it.archiveFile } + from androidNativeArtifact.file into '../dist/opt/android' - rename { androidNativeProject.name + ".jar" } + rename { androidNativeArtifact.name + ".${androidNativeArtifact.extension}" } } } } diff --git a/jme3-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java index 3799bd5832..f0b09a2a29 100644 --- a/jme3-examples/src/main/java/jme3test/TestChooser.java +++ b/jme3-examples/src/main/java/jme3test/TestChooser.java @@ -35,6 +35,7 @@ import com.jme3.app.LegacyApplication; import com.jme3.app.SimpleApplication; import com.jme3.system.JmeContext; +import com.jme3.system.JmeSystem; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; @@ -497,6 +498,11 @@ private void center() { * command line parameters */ public static void main(final String[] args) { + if (JmeSystem.getPlatform().isGraalVMNativeImage()) { + TestChooserCli.main(args); + return; + } + try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) {} diff --git a/jme3-examples/src/main/java/jme3test/TestChooserCli.java b/jme3-examples/src/main/java/jme3test/TestChooserCli.java new file mode 100644 index 0000000000..c876d896ce --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/TestChooserCli.java @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test; + +import com.jme3.app.LegacyApplication; +import com.jme3.app.SimpleApplication; +import com.jme3.system.JmeContext; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Command-line test chooser for running example classes without any AWT/Swing usage. + */ +public class TestChooserCli { + + private static final Logger logger = Logger.getLogger(TestChooserCli.class.getName()); + private static final String CLASS_LIST_RESOURCE = "/jme3test/test-classes.txt"; + private static final long WAIT_INTERVAL_MILLIS = Math.max(10L, + Long.getLong("jme3test.cli.waitIntervalMillis", 100L)); + private static final long START_TIMEOUT_MILLIS = Math.max(WAIT_INTERVAL_MILLIS, + Long.getLong("jme3test.cli.startTimeoutMillis", 30000L)); + private static final long RUN_TIMEOUT_MILLIS = Long.getLong("jme3test.cli.runTimeoutMillis", + 30L * 60L * 1000L); + + public static void main(String[] args) { + new TestChooserCli().start(args); + } + + private void start(String[] args) { + if (args.length > 0) { + launchFromArgument(args); + return; + } + + List fallbackClassNames = loadClassNamesFromResource(); + Set> discovered = new LinkedHashSet>(); + addDisplayedClasses(discovered); + + List> sorted = new ArrayList>(discovered); + Collections.sort(sorted, (a, b) -> a.getName().compareTo(b.getName())); + + if (sorted.isEmpty()) { + if (!fallbackClassNames.isEmpty()) { + printClassNameMenu(fallbackClassNames); + String selectedClassName = chooseClassName(fallbackClassNames); + if (selectedClassName != null) { + launchFromArgument(new String[]{selectedClassName}); + } + return; + } + logger.warning("No test classes discovered. Pass a class name explicitly, for example: jme3test.light.TestManyLights"); + return; + } + + printMenu(sorted); + Class target = chooseClass(sorted); + if (target != null) { + launchClass(target, new String[0]); + } + } + + private void launchFromArgument(String[] args) { + String requested = args[0]; + String[] appArgs = Arrays.copyOfRange(args, 1, args.length); + + for (String listedClassName : loadClassNamesFromResource()) { + if (listedClassName.equals(requested) || simpleClassName(listedClassName).equals(requested)) { + Class listedClass = loadFromClassName(listedClassName); + if (listedClass != null) { + launchClass(listedClass, appArgs); + return; + } + } + } + + Class target; + try { + target = Class.forName(requested); + } catch (ClassNotFoundException e) { + logger.log(Level.SEVERE, "Cannot find test class: " + requested, e); + return; + } + + launchClass(target, appArgs); + } + + private Class chooseClass(List> classes) { + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + System.out.print("Choose test index or class name (empty to exit): "); + try { + String input = reader.readLine(); + if (input == null || input.trim().isEmpty()) { + return null; + } + input = input.trim(); + try { + int index = Integer.parseInt(input); + if (index >= 1 && index <= classes.size()) { + return classes.get(index - 1); + } + System.err.println("Invalid index: " + index); + return null; + } catch (NumberFormatException ignored) { + for (Class c : classes) { + if (c.getName().equals(input) || c.getSimpleName().equals(input)) { + return c; + } + } + try { + return Class.forName(input); + } catch (ClassNotFoundException e) { + logger.log(Level.SEVERE, "Cannot find class: " + input, e); + return null; + } + } + } catch (IOException e) { + logger.log(Level.SEVERE, "Failed to read input", e); + return null; + } + } + + private void printMenu(List> classes) { + System.out.println("Available jME tests:"); + for (int i = 0; i < classes.size(); i++) { + System.out.println(String.format("%3d. %s", i + 1, classes.get(i).getName())); + } + } + + private void printClassNameMenu(List classNames) { + System.out.println("Available jME tests (from resource list):"); + for (int i = 0; i < classNames.size(); i++) { + System.out.println(String.format("%3d. %s", i + 1, classNames.get(i))); + } + } + + private String chooseClassName(List classNames) { + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + System.out.print("Choose test index or class name (empty to exit): "); + try { + String input = reader.readLine(); + if (input == null || input.trim().isEmpty()) { + return null; + } + input = input.trim(); + try { + int index = Integer.parseInt(input); + if (index >= 1 && index <= classNames.size()) { + return classNames.get(index - 1); + } + System.err.println("Invalid index: " + index); + return null; + } catch (NumberFormatException ignored) { + for (String className : classNames) { + if (className.equals(input) || simpleClassName(className).equals(input)) { + return className; + } + } + return input; + } + } catch (IOException e) { + logger.log(Level.SEVERE, "Failed to read input", e); + return null; + } + } + + private void launchClass(Class clazz, String[] appArgs) { + try { + if (LegacyApplication.class.isAssignableFrom(clazz)) { + LegacyApplication app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + if (app instanceof SimpleApplication) { + ((SimpleApplication) app).setShowSettings(false); + } + app.start(); + + JmeContext context = waitForContext(app, clazz.getName()); + waitForContextCreated(app, context, clazz.getName()); + waitForContextDestroyed(app, context, clazz.getName()); + } else { + Method mainMethod = clazz.getMethod("main", String[].class); + mainMethod.invoke(null, new Object[]{appArgs}); + } + } catch (IllegalAccessException e) { + logger.log(Level.SEVERE, "Cannot access constructor: " + clazz.getName(), e); + } catch (IllegalArgumentException e) { + logger.log(Level.SEVERE, "main() had illegal argument: " + clazz.getName(), e); + } catch (InvocationTargetException e) { + logger.log(Level.SEVERE, "main() method had exception: " + clazz.getName(), e); + } catch (InstantiationException e) { + logger.log(Level.SEVERE, "Failed to create app: " + clazz.getName(), e); + } catch (NoSuchMethodException e) { + logger.log(Level.SEVERE, "Test class does not have required method: " + clazz.getName(), e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.log(Level.SEVERE, "Interrupted while waiting for app context", e); + } catch (Exception e) { + logger.log(Level.SEVERE, "Cannot start test: " + clazz.getName(), e); + } + } + + private JmeContext waitForContext(LegacyApplication app, String className) throws InterruptedException { + long deadline = System.currentTimeMillis() + START_TIMEOUT_MILLIS; + JmeContext context = app.getContext(); + while (context == null) { + if (System.currentTimeMillis() >= deadline) { + requestStop(app); + throw new IllegalStateException("Timed out waiting for application context: " + className); + } + Thread.sleep(WAIT_INTERVAL_MILLIS); + context = app.getContext(); + } + return context; + } + + private void waitForContextCreated(LegacyApplication app, JmeContext context, String className) + throws InterruptedException { + long deadline = System.currentTimeMillis() + START_TIMEOUT_MILLIS; + while (!context.isCreated()) { + if (System.currentTimeMillis() >= deadline) { + requestStop(app); + throw new IllegalStateException("Timed out waiting for context creation: " + className); + } + Thread.sleep(WAIT_INTERVAL_MILLIS); + } + } + + private void waitForContextDestroyed(LegacyApplication app, JmeContext context, String className) + throws InterruptedException { + long deadline = RUN_TIMEOUT_MILLIS > 0L ? System.currentTimeMillis() + RUN_TIMEOUT_MILLIS : Long.MAX_VALUE; + while (context.isCreated()) { + if (System.currentTimeMillis() >= deadline) { + requestStop(app); + throw new IllegalStateException("Timed out waiting for application to exit: " + className); + } + Thread.sleep(WAIT_INTERVAL_MILLIS); + } + } + + private void requestStop(LegacyApplication app) { + JmeContext context = app.getContext(); + if (context == null) { + return; + } + try { + app.stop(false); + } catch (RuntimeException e) { + logger.log(Level.WARNING, "Failed to stop timed-out application", e); + } + } + + private void find(String packageName, boolean recursive, Set> classes) { + String name = packageName; + if (!name.startsWith("/")) { + name = "/" + name; + } + name = name.replace('.', '/'); + + packageName = packageName + "."; + URI uri; + FileSystem fileSystem = null; + boolean closeFileSystem = false; + try { + URL packageUrl = this.getClass().getResource(name); + if (packageUrl == null) { + return; + } + uri = packageUrl.toURI(); + } catch (URISyntaxException e) { + throw new RuntimeException("Failed to load demo classes.", e); + } + + if ("jar".equalsIgnoreCase(uri.getScheme())) { + try { + fileSystem = FileSystems.getFileSystem(uri); + } catch (FileSystemNotFoundException e) { + try { + fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap()); + closeFileSystem = true; + } catch (IOException ex) { + throw new RuntimeException("Failed to load demo classes from JAR.", ex); + } + } + } + + try { + Path directory = Paths.get(uri); + addAllFilesInDirectory(directory, classes, packageName, recursive); + } catch (Exception e) { + logger.log(Level.SEVERE, "Failed to find classes", e); + } finally { + if (fileSystem != null && closeFileSystem) { + try { + fileSystem.close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close JAR.", e); + } + } + } + } + + private void addAllFilesInDirectory( + final Path directory, + final Set> allClasses, + final String packageName, + final boolean recursive) { + + try (DirectoryStream stream = Files.newDirectoryStream(directory, getFileFilter())) { + for (Path file : stream) { + if (Files.isDirectory(file)) { + if (recursive) { + String dirName = String.valueOf(file.getFileName()); + if (dirName.endsWith("/")) { + dirName = dirName.substring(0, dirName.length() - 1); + } + addAllFilesInDirectory(file, allClasses, packageName + dirName + ".", true); + } + } else { + Class result = load(packageName + file.getFileName()); + if (result != null && !allClasses.contains(result)) { + allClasses.add(result); + } + } + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Could not search the folder", ex); + } + } + + private static DirectoryStream.Filter getFileFilter() { + return new DirectoryStream.Filter() { + @Override + public boolean accept(Path entry) throws IOException { + String fileName = entry.getFileName().toString(); + return ((fileName.endsWith(".class") && fileName.contains("Test") && !fileName.contains("$")) + || (!fileName.startsWith(".") && Files.isDirectory(entry))); + } + }; + } + + private Class load(String name) { + String classname = name.substring(0, name.length() - ".class".length()); + if (classname.startsWith("/")) { + classname = classname.substring(1); + } + classname = classname.replace('/', '.'); + + return loadFromClassName(classname); + } + + private Class loadFromClassName(String classname) { + + if (classname.equals(TestChooser.class.getName()) || classname.equals(TestChooserCli.class.getName())) { + return null; + } + + try { + final Class cls = Class.forName(classname, false, TestChooserCli.class.getClassLoader()); + cls.getMethod("main", String[].class); + return cls; + } catch (NoClassDefFoundError e) { + return null; + } catch (ClassNotFoundException e) { + return null; + } catch (NoSuchMethodException e) { + return null; + } catch (UnsupportedClassVersionError e) { + return null; + } + } + + private List loadClassNamesFromResource() { + InputStream stream = TestChooserCli.class.getResourceAsStream(CLASS_LIST_RESOURCE); + if (stream == null) { + return Collections.emptyList(); + } + + List classNames = new ArrayList(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { + String line; + while ((line = reader.readLine()) != null) { + String className = line.trim(); + if (!className.isEmpty()) { + classNames.add(className); + } + } + } catch (IOException e) { + logger.log(Level.WARNING, "Failed reading test class list resource: " + CLASS_LIST_RESOURCE, e); + } + return classNames; + } + + private String simpleClassName(String className) { + int lastDot = className.lastIndexOf('.'); + if (lastDot >= 0 && lastDot + 1 < className.length()) { + return className.substring(lastDot + 1); + } + return className; + } + + protected void addDisplayedClasses(Set> classes) { + find("jme3test", true, classes); + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestDisplayScaling.java b/jme3-examples/src/main/java/jme3test/app/TestDisplayScaling.java new file mode 100644 index 0000000000..16a2495c93 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestDisplayScaling.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.app; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListenerAdapter; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.input.event.TouchEvent; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Quad; +import com.jme3.scene.debug.Grid; +import com.jme3.system.AppSettings; + + +public class TestDisplayScaling extends SimpleApplication implements ActionListener { + + private static final String NEXT_SCALE_MODE = "NextScaleMode"; + private static final float SUPERSAMPLING = 2f; + + private BitmapText infoText; + private Geometry cube; + private Geometry testPanel; + private BitmapText testText; + private Node qualityTarget; + private float requestedMode = Float.NaN; + + public static void main(String[] args) { + TestDisplayScaling app = new TestDisplayScaling(); + AppSettings settings = new AppSettings(true); + settings.setWindowSize(1280, 720); + settings.setDisplayScaleMode(AppSettings.DISPLAY_SCALE_DISABLED); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setEnabled(false); + inputManager.addMapping("ScaleDisabled", new KeyTrigger(KeyInput.KEY_1)); + inputManager.addMapping("ScaleNativePixels", new KeyTrigger(KeyInput.KEY_2)); + inputManager.addMapping("ScaleDpiAware", new KeyTrigger(KeyInput.KEY_3)); + inputManager.addMapping("ScaleSS", new KeyTrigger(KeyInput.KEY_4)); + inputManager.addMapping(NEXT_SCALE_MODE, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(this, "ScaleDisabled", "ScaleNativePixels", "ScaleDpiAware", "ScaleSS", + NEXT_SCALE_MODE); + inputManager.addRawInputListener(new RawInputListenerAdapter() { + @Override + public void onTouchEvent(TouchEvent evt) { + if (evt.getType() == TouchEvent.Type.DOWN) { + requestCycleMode(); + } + } + }); + + viewPort.setBackgroundColor(new ColorRGBA(0.08f, 0.09f, 0.11f, 1f)); + cam.setLocation(new Vector3f(0f, 0f, 6f)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + + Geometry fineGrid = new Geometry("Fine Edge Quality Grid", new Grid(121, 121, 0.05f)); + fineGrid.setMaterial(createMaterial(new ColorRGBA(0.75f, 0.78f, 0.84f, 1f))); + fineGrid.setLocalTranslation(-3f, -3f, -1.35f); + fineGrid.rotate(FastMath.HALF_PI, 0f, FastMath.QUARTER_PI); + rootNode.attachChild(fineGrid); + + cube = new Geometry("High DPI Cube", new Box(1f, 1f, 1f)); + cube.setMaterial(createMaterial(new ColorRGBA(0.1f, 0.65f, 1f, 1f))); + rootNode.attachChild(cube); + + testPanel = new Geometry("Logical GUI Test Panel", new Quad(180f, 80f)); + testPanel.setMaterial(createMaterial(new ColorRGBA(1f, 0.72f, 0.18f, 1f))); + testPanel.setLocalTranslation(40f, 40f, 0f); + guiNode.attachChild(testPanel); + + testText = new BitmapText(guiFont); + testText.setText("TEST"); + testText.setSize(36f); + testText.setColor(ColorRGBA.Black); + testText.setLocalTranslation(60f, 98f, 1f); + guiNode.attachChild(testText); + + createQualityTarget(); + + infoText = new BitmapText(guiFont); + infoText.setSize(18f); + infoText.setLocalTranslation(20f, cam.getHeight() - 20f, 0f); + guiNode.attachChild(infoText); + } + + @Override + public void simpleUpdate(float tpf) { + if (!Float.isNaN(requestedMode)) { + setMode(requestedMode); + return; + } + + cube.rotate(tpf * 0.45f, tpf * 0.7f, 0f); + Vector2f cursor = inputManager.getCursorPosition(); + infoText.setText("Mode: " + getDisplayScaleModeName(settings.getDisplayScaleMode()) + + "\nLogical: " + cam.getWidth() + " x " + cam.getHeight() + + "\nRender target: " + viewPort.getRenderTargetWidth() + " x " + viewPort.getRenderTargetHeight() + + "\nDrawable: " + context.getFramebufferWidth() + " x " + context.getFramebufferHeight() + + "\nMouse: " + Math.round(cursor.x) + ", " + Math.round(cursor.y) + + "\nKeys: 1 disabled, 2 native pixels, 3 DPI aware, 4 supersampling 2x" + + "\nClick/touch: cycle mode"); + infoText.setLocalTranslation(20f, cam.getHeight() - 20f, 0f); + qualityTarget.setLocalTranslation(cam.getWidth() - 390f, 40f, 0f); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) { + return; + } + if ("ScaleDisabled".equals(name)) { + requestMode(AppSettings.DISPLAY_SCALE_DISABLED); + } else if ("ScaleNativePixels".equals(name)) { + requestMode(AppSettings.DISPLAY_SCALE_NATIVE_PIXELS); + } else if ("ScaleDpiAware".equals(name)) { + requestMode(AppSettings.DISPLAY_SCALE_DPI_AWARE); + } else if ("ScaleSS".equals(name)) { + requestMode(SUPERSAMPLING); + } else if (NEXT_SCALE_MODE.equals(name)) { + requestCycleMode(); + } + } + + private void requestCycleMode() { + float currentMode = settings.getDisplayScaleMode(); + if (currentMode == AppSettings.DISPLAY_SCALE_DISABLED) { + requestMode(AppSettings.DISPLAY_SCALE_NATIVE_PIXELS); + } else if (currentMode == AppSettings.DISPLAY_SCALE_NATIVE_PIXELS) { + requestMode(AppSettings.DISPLAY_SCALE_DPI_AWARE); + } else if (currentMode == AppSettings.DISPLAY_SCALE_DPI_AWARE) { + requestMode(SUPERSAMPLING); + } else { + requestMode(AppSettings.DISPLAY_SCALE_DISABLED); + } + } + + private void requestMode(float mode) { + requestedMode = mode; + } + + private String getDisplayScaleModeName(float mode) { + if (mode == AppSettings.DISPLAY_SCALE_DISABLED) { + return "DISABLED"; + } else if (mode == AppSettings.DISPLAY_SCALE_NATIVE_PIXELS) { + return "NATIVE_PIXELS"; + } else if (mode == AppSettings.DISPLAY_SCALE_DPI_AWARE) { + return "DPI_AWARE"; + } + return "SUPERSAMPLING " + mode + "x"; + } + + private void setMode(float mode) { + requestedMode = Float.NaN; + settings.setDisplayScaleMode(mode); + restart(); + } + + private void createQualityTarget() { + qualityTarget = new Node("High DPI Quality Target"); + guiNode.attachChild(qualityTarget); + + Geometry background = new Geometry("Quality Target Background", new Quad(350f, 150f)); + background.setMaterial(createMaterial(new ColorRGBA(0.02f, 0.025f, 0.03f, 1f))); + qualityTarget.attachChild(background); + + Material white = createMaterial(ColorRGBA.White); + Material gray = createMaterial(new ColorRGBA(0.45f, 0.48f, 0.52f, 1f)); + + for (int x = 20; x < 180; x += 2) { + Geometry stripe = new Geometry("One Pixel Stripe", new Quad(1f, 56f)); + stripe.setMaterial(white); + stripe.setLocalTranslation(x, 72f, 1f); + qualityTarget.attachChild(stripe); + } + + for (int i = 0; i < 18; i++) { + Geometry diagonal = new Geometry("Subtle Diagonal Edge", new Quad(155f, 1f)); + diagonal.setMaterial(i % 2 == 0 ? white : gray); + diagonal.setLocalTranslation(190f, 20f + i * 6f, 1f); + diagonal.rotate(0f, 0f, 0.08f * i); + qualityTarget.attachChild(diagonal); + } + + BitmapText label = new BitmapText(guiFont); + label.setText("1px stripes and tiny text"); + label.setSize(10f); + label.setLocalTranslation(20f, 142f, 1f); + qualityTarget.attachChild(label); + + BitmapText tinyText = new BitmapText(guiFont); + tinyText.setText("abcdef0123456789 ABCDEF"); + tinyText.setSize(8f); + tinyText.setLocalTranslation(20f, 58f, 1f); + qualityTarget.attachChild(tinyText); + } + + private Material createMaterial(ColorRGBA color) { + Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + material.setColor("Color", color); + return material; + } +} diff --git a/jme3-examples/src/main/java/jme3test/awt/AppHarness.java b/jme3-examples/src/main/java/jme3test/awt/AppHarness.java deleted file mode 100644 index 0faa1a6507..0000000000 --- a/jme3-examples/src/main/java/jme3test/awt/AppHarness.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.awt; - -import com.jme3.app.LegacyApplication; -import com.jme3.system.AppSettings; -import com.jme3.system.JmeCanvasContext; -import com.jme3.system.JmeSystem; -import java.applet.Applet; -import java.awt.Canvas; -import java.awt.Graphics; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URL; -import javax.swing.SwingUtilities; - -/** - * - * @author Kirill - */ -public class AppHarness extends Applet { - - private JmeCanvasContext context; - private Canvas canvas; - private LegacyApplication app; - - private String appClass; - private URL appCfg = null; - - @SuppressWarnings("unchecked") - private void createCanvas(){ - AppSettings settings = new AppSettings(true); - - // load app cfg - if (appCfg != null){ - try { - InputStream in = appCfg.openStream(); - settings.load(in); - in.close(); - } catch (IOException ex){ - ex.printStackTrace(); - } - } - - settings.setWidth(getWidth()); - settings.setHeight(getHeight()); - settings.setAudioRenderer(null); - - JmeSystem.setLowPermissions(true); - - try{ - Class clazz = Class.forName(appClass); - app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); - }catch (ClassNotFoundException - | InstantiationException - | IllegalAccessException - | IllegalArgumentException - | InvocationTargetException - | NoSuchMethodException - | SecurityException ex) { - ex.printStackTrace(); - } - - app.setSettings(settings); - app.createCanvas(); - - context = (JmeCanvasContext) app.getContext(); - canvas = context.getCanvas(); - canvas.setSize(getWidth(), getHeight()); - - add(canvas); - app.startCanvas(); - } - - @Override - public final void update(Graphics g) { - canvas.setSize(getWidth(), getHeight()); - } - - @Override - public void init(){ - appClass = getParameter("AppClass"); - if (appClass == null) - throw new RuntimeException("The required parameter AppClass isn't specified!"); - - try { - appCfg = new URL(getParameter("AppSettingsURL")); - } catch (MalformedURLException ex) { - ex.printStackTrace(); - appCfg = null; - } - - createCanvas(); - System.out.println("applet:init"); - } - - @Override - public void start(){ - context.setAutoFlushFrames(true); - System.out.println("applet:start"); - } - - @Override - public void stop(){ - context.setAutoFlushFrames(false); - System.out.println("applet:stop"); - } - - @Override - public void destroy(){ - System.out.println("applet:destroyStart"); - SwingUtilities.invokeLater(new Runnable(){ - @Override - public void run(){ - removeAll(); - System.out.println("applet:destroyRemoved"); - } - }); - app.stop(true); - System.out.println("applet:destroyDone"); - } - -} diff --git a/jme3-examples/src/main/java/jme3test/awt/TestApplet.java b/jme3-examples/src/main/java/jme3test/awt/TestApplet.java deleted file mode 100644 index e9e473b852..0000000000 --- a/jme3-examples/src/main/java/jme3test/awt/TestApplet.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.awt; - -import com.jme3.app.LegacyApplication; -import com.jme3.app.SimpleApplication; -import com.jme3.system.AppSettings; -import com.jme3.system.JmeCanvasContext; -import com.jme3.system.JmeSystem; -import java.applet.Applet; -import java.awt.Canvas; -import java.awt.Graphics; -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.Callable; -import javax.swing.SwingUtilities; - -public class TestApplet extends Applet { - - private static JmeCanvasContext context; - private static LegacyApplication app; - private static Canvas canvas; - private static TestApplet applet; - - public TestApplet(){ - } - - @SuppressWarnings("unchecked") - public static void createCanvas(String appClass){ - AppSettings settings = new AppSettings(true); - settings.setWidth(640); - settings.setHeight(480); - settings.setRenderer(AppSettings.LWJGL_OPENGL2); - - JmeSystem.setLowPermissions(true); - - try{ - Class clazz = Class.forName(appClass); - app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); - } catch (ClassNotFoundException - | InstantiationException - | IllegalAccessException - | IllegalArgumentException - | InvocationTargetException - | NoSuchMethodException - | SecurityException ex) { - ex.printStackTrace(); - } - - app.setSettings(settings); - app.createCanvas(); - - context = (JmeCanvasContext) app.getContext(); - canvas = context.getCanvas(); - canvas.setSize(settings.getWidth(), settings.getHeight()); - } - - public static void startApp(){ - applet.add(canvas); - app.startCanvas(); - - app.enqueue(new Callable(){ - @Override - public Void call(){ - if (app instanceof SimpleApplication){ - SimpleApplication simpleApp = (SimpleApplication) app; - simpleApp.getFlyByCamera().setDragToRotate(true); - simpleApp.getInputManager().setCursorVisible(true); - } - return null; - } - }); - } - - public void freezeApp(){ - remove(canvas); - } - - public void unfreezeApp(){ - add(canvas); - } - - @Override - public final void update(Graphics g) { -// canvas.setSize(getWidth(), getHeight()); - } - - @Override - public void init(){ - applet = this; - createCanvas("jme3test.model.shape.TestBox"); - startApp(); - app.setPauseOnLostFocus(false); - System.out.println("applet:init"); - } - - @Override - public void start(){ -// context.setAutoFlushFrames(true); - System.out.println("applet:start"); - } - - @Override - public void stop(){ -// context.setAutoFlushFrames(false); - System.out.println("applet:stop"); - } - - @Override - public void destroy(){ - SwingUtilities.invokeLater(new Runnable(){ - @Override - public void run(){ - removeAll(); - System.out.println("applet:destroyStart"); - } - }); - app.stop(true); - System.out.println("applet:destroyEnd"); - } - -} diff --git a/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java index 0b59110485..18aa0183ca 100644 --- a/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java +++ b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2021 jMonkeyEngine + * Copyright (c) 2009-2026 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -210,6 +210,7 @@ public static void createCanvas(String appClass){ // Note: Only for Linux and Wayland platforms, forces you to // use XWayland (x11) with awt. settings.setX11PlatformPreferred(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); settings.setWidth(640); settings.setHeight(480); diff --git a/jme3-examples/src/main/java/jme3test/awt/TestSafeCanvas.java b/jme3-examples/src/main/java/jme3test/awt/TestSafeCanvas.java index 5c6940df31..135650f809 100644 --- a/jme3-examples/src/main/java/jme3test/awt/TestSafeCanvas.java +++ b/jme3-examples/src/main/java/jme3test/awt/TestSafeCanvas.java @@ -15,6 +15,8 @@ public class TestSafeCanvas extends SimpleApplication { public static void main(String[] args) throws InterruptedException{ AppSettings settings = new AppSettings(true); + settings.setX11PlatformPreferred(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); settings.setWidth(640); settings.setHeight(480); diff --git a/jme3-examples/src/main/java/jme3test/gui/TestBitmapFontLayout.java b/jme3-examples/src/main/java/jme3test/gui/TestBitmapFontLayout.java index 6d2ad6f37b..7a9f9810e6 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestBitmapFontLayout.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestBitmapFontLayout.java @@ -41,8 +41,11 @@ import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.*; +import javax.imageio.ImageIO; import com.jme3.app.DebugKeysAppState; import com.jme3.app.StatsAppState; @@ -64,7 +67,7 @@ import com.jme3.texture.Image; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; -import com.jme3.texture.plugins.AWTLoader; +import com.jme3.texture.plugins.StbImageLoader; /** * @@ -152,8 +155,14 @@ private Texture renderAwtFont( TestConfig test, int width, int height, BitmapFon g2.dispose(); - Image jmeImage = new AWTLoader().load(image, true); - return new Texture2D(jmeImage); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(image, "png", baos); + Image jmeImage = new StbImageLoader().load(new ByteArrayInputStream(baos.toByteArray()), true); + return new Texture2D(jmeImage); + } catch (IOException e) { + throw new RuntimeException("Failed to convert AWT image to jME Image", e); + } } private Node createVisual( TestConfig test ) { diff --git a/jme3-examples/src/main/java/jme3test/input/TestDeviceRumble.java b/jme3-examples/src/main/java/jme3test/input/TestDeviceRumble.java new file mode 100644 index 0000000000..131890a0a9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/TestDeviceRumble.java @@ -0,0 +1,120 @@ +package jme3test.input; + +import com.jme3.app.SimpleApplication; +import com.jme3.collision.CollisionResults; +import com.jme3.font.BitmapText; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListenerAdapter; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.input.event.TouchEvent; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; + +public class TestDeviceRumble extends SimpleApplication { + + private static final String CLICK_MAPPING = "ClickCube"; + + private Geometry cube; + private Material cubeMaterial; + private BitmapText statusText; + private boolean rumbling; + + public static void main(String[] args) { + TestDeviceRumble app = new TestDeviceRumble(); + AppSettings settings = new AppSettings(true); + settings.setUseJoysticks(false); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setEnabled(false); + inputManager.setCursorVisible(true); + + cubeMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + cubeMaterial.setColor("Color", ColorRGBA.Blue); + + cube = new Geometry("Device rumble cube", new Box(1.25f, 1.25f, 1.25f)); + cube.setMaterial(cubeMaterial); + rootNode.attachChild(cube); + + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(-1f, -1f, -1f).normalizeLocal()); + rootNode.addLight(light); + + cam.setLocation(new Vector3f(0f, 0f, 6f)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + + statusText = new BitmapText(guiFont); + statusText.setLocalTranslation(12f, cam.getHeight() - 12f, 0f); + guiNode.attachChild(statusText); + updateStatusText(); + + inputManager.addMapping(CLICK_MAPPING, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { + if (isPressed) { + startRumble(inputManager.getCursorPosition()); + } else { + stopRumble(); + } + }, CLICK_MAPPING); + + inputManager.addRawInputListener(new RawInputListenerAdapter() { + @Override + public void onTouchEvent(TouchEvent evt) { + if (evt.getType() == TouchEvent.Type.DOWN) { + startRumble(new Vector2f(evt.getX(), evt.getY())); + } else if (evt.getType() == TouchEvent.Type.UP) { + stopRumble(); + } + } + }); + } + + private void startRumble(Vector2f screenPosition) { + if (!isCubeHit(screenPosition)) { + return; + } + + rumbling = true; + cubeMaterial.setColor("Color", ColorRGBA.Red); + if (JmeSystem.isDeviceRumbleSupported()) { + JmeSystem.rumble(1f, 1f, Float.POSITIVE_INFINITY); + } + updateStatusText(); + } + + private void stopRumble() { + if (!rumbling) { + return; + } + rumbling = false; + JmeSystem.stopRumble(); + cubeMaterial.setColor("Color", ColorRGBA.Blue); + updateStatusText(); + } + + private boolean isCubeHit(Vector2f screenPosition) { + Vector3f origin = cam.getWorldCoordinates(screenPosition, 0f); + Vector3f direction = cam.getWorldCoordinates(screenPosition, 1f).subtractLocal(origin).normalizeLocal(); + CollisionResults results = new CollisionResults(); + cube.collideWith(new Ray(origin, direction), results); + return results.size() > 0; + } + + private void updateStatusText() { + String state = rumbling ? "rumbling" : "idle"; + String supported = JmeSystem.isDeviceRumbleSupported() ? "supported" : "not supported"; + statusText.setText("Device rumble: " + supported + "\nHold the cube to test\nState: " + state); + } +} diff --git a/jme3-examples/src/main/java/jme3test/input/TestJoystick.java b/jme3-examples/src/main/java/jme3test/input/TestJoystick.java index 6ef8435d98..811387fd99 100644 --- a/jme3-examples/src/main/java/jme3test/input/TestJoystick.java +++ b/jme3-examples/src/main/java/jme3test/input/TestJoystick.java @@ -29,6 +29,9 @@ import com.jme3.scene.Node; import com.jme3.scene.shape.Quad; import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; +import com.jme3.system.Platform; +import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; @@ -42,15 +45,30 @@ public class TestJoystick extends SimpleApplication { private Node joystickInfo; private float yInfo = 0; private JoystickButton lastButton; + private static final float SHORT_RUMBLE_TIME = 0.4f; public static void main(String[] args){ TestJoystick app = new TestJoystick(); AppSettings settings = new AppSettings(true); + configureSettings(settings); + app.setSettings(settings); + app.start(); + } + + public static void configureSettings(AppSettings settings) { settings.setJoysticksMapper(AppSettings.JOYSTICKS_XBOX_MAPPER); settings.setUseJoysticks(true); + settings.setVirtualJoystick(defaultVirtualJoystickMode()); + settings.setVirtualJoystickDefaultLayout(AppSettings.VIRTUAL_JOYSTICK_LAYOUT_XBOX); settings.setX11PlatformPreferred(true); - app.setSettings(settings); - app.start(); + } + + private static String defaultVirtualJoystickMode() { + Platform.Os os = JmeSystem.getPlatform().getOs(); + if (os == Platform.Os.Android || os == Platform.Os.iOS) { + return AppSettings.VIRTUAL_JOYSTICK_ENABLED; + } + return AppSettings.VIRTUAL_JOYSTICK_AUTO; } @Override @@ -62,11 +80,16 @@ public void simpleInitApp() { throw new IllegalStateException("Cannot find any joysticks!"); try { - PrintWriter out = new PrintWriter( new FileWriter( "joysticks-" + System.currentTimeMillis() + ".txt" ) ); - dumpJoysticks( joysticks, out ); - out.close(); + File storageFolder = JmeSystem.getStorageFolder(); + if (storageFolder != null) { + storageFolder.mkdirs(); + } + File dumpFile = new File(storageFolder, "joysticks-" + System.currentTimeMillis() + ".txt"); + try (PrintWriter out = new PrintWriter(new FileWriter(dumpFile))) { + dumpJoysticks(joysticks, out); + } } catch( IOException e ) { - throw new RuntimeException( "Error writing joystick dump", e ); + System.err.println( "Error writing joystick dump: " + e ); } @@ -137,6 +160,7 @@ protected void setViewedJoystick( Joystick stick ) { yInfo = 0; addInfo( "Joystick:\"" + stick.getName() + "\" id:" + stick.getJoyId(), 0 ); + addInfo( "Rumble: buttons=joystick, dpad=device", 0 ); yInfo -= 5; @@ -158,6 +182,118 @@ protected void setViewedJoystick( Joystick stick ) { } } + private void handleRumbleButton(JoystickButton button, boolean pressed) { + String logicalId = button.getLogicalId(); + if (isDpadButton(logicalId)) { + handleDeviceRumbleButton(logicalId, pressed); + return; + } + + Joystick joystick = button.getJoystick(); + if (JoystickButton.BUTTON_XBOX_B.equals(logicalId)) { + if (pressed) { + joystick.rumble(1f, 0.35f, SHORT_RUMBLE_TIME); + } + return; + } + + if (!pressed) { + joystick.stopRumble(); + return; + } + + float[] pattern = getJoystickRumblePattern(button); + joystick.rumble(pattern[0], pattern[1], Float.POSITIVE_INFINITY); + } + + private void handleTriggerRumble(JoystickAxis axis, float value) { + String logicalId = axis.getLogicalId(); + if (!JoystickAxis.AXIS_XBOX_LEFT_TRIGGER.equals(logicalId) + && !JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER.equals(logicalId)) { + return; + } + + Joystick joystick = axis.getJoystick(); + if (value <= 0f) { + joystick.stopRumble(); + return; + } + + float amount = FastMath.clamp(value, 0f, 1f); + if (JoystickAxis.AXIS_XBOX_LEFT_TRIGGER.equals(logicalId)) { + joystick.rumble(0.25f + amount * 0.35f, amount, Float.POSITIVE_INFINITY); + } else { + joystick.rumble(amount, 0.25f + amount * 0.35f, Float.POSITIVE_INFINITY); + } + } + + private boolean isDpadButton(String logicalId) { + return JoystickButton.BUTTON_XBOX_DPAD_UP.equals(logicalId) + || JoystickButton.BUTTON_XBOX_DPAD_RIGHT.equals(logicalId) + || JoystickButton.BUTTON_XBOX_DPAD_DOWN.equals(logicalId) + || JoystickButton.BUTTON_XBOX_DPAD_LEFT.equals(logicalId); + } + + private void handleDeviceRumbleButton(String logicalId, boolean pressed) { + if (!JmeSystem.isDeviceRumbleSupported()) { + return; + } + if (!pressed) { + JmeSystem.stopRumble(); + return; + } + + float high = 0.5f; + float low = 0.5f; + if (JoystickButton.BUTTON_XBOX_DPAD_UP.equals(logicalId)) { + high = 1f; + low = 0.2f; + } else if (JoystickButton.BUTTON_XBOX_DPAD_RIGHT.equals(logicalId)) { + high = 0.2f; + low = 1f; + } else if (JoystickButton.BUTTON_XBOX_DPAD_DOWN.equals(logicalId)) { + high = 0.35f; + low = 0.35f; + } else if (JoystickButton.BUTTON_XBOX_DPAD_LEFT.equals(logicalId)) { + high = 0.75f; + low = 0.75f; + } + + JmeSystem.rumble(high, low, Float.POSITIVE_INFINITY); + } + + private float[] getJoystickRumblePattern(JoystickButton button) { + String logicalId = button.getLogicalId(); + if (JoystickButton.BUTTON_XBOX_A.equals(logicalId)) { + return new float[]{1f, 1f}; + } else if (JoystickButton.BUTTON_XBOX_X.equals(logicalId)) { + return new float[]{0.25f, 1f}; + } else if (JoystickButton.BUTTON_XBOX_Y.equals(logicalId)) { + return new float[]{1f, 0.25f}; + } else if (JoystickButton.BUTTON_XBOX_LB.equals(logicalId)) { + return new float[]{0.15f, 0.7f}; + } else if (JoystickButton.BUTTON_XBOX_RB.equals(logicalId)) { + return new float[]{0.7f, 0.15f}; + } else if (JoystickButton.BUTTON_XBOX_LT.equals(logicalId)) { + return new float[]{0.45f, 0.9f}; + } else if (JoystickButton.BUTTON_XBOX_RT.equals(logicalId)) { + return new float[]{0.9f, 0.45f}; + } else if (JoystickButton.BUTTON_XBOX_BACK.equals(logicalId)) { + return new float[]{0.3f, 0.3f}; + } else if (JoystickButton.BUTTON_XBOX_START.equals(logicalId)) { + return new float[]{0.6f, 0.6f}; + } else if (JoystickButton.BUTTON_XBOX_L3.equals(logicalId)) { + return new float[]{0.2f, 0.5f}; + } else if (JoystickButton.BUTTON_XBOX_R3.equals(logicalId)) { + return new float[]{0.5f, 0.2f}; + } + + int index = Math.abs(button.getButtonId()); + float high = 0.2f + (index % 5) * 0.16f; + float low = 1f - (index % 4) * 0.18f; + return new float[]{high, low}; + } + /** * Easier to watch for all button and axis events with a raw input listener. */ @@ -184,9 +320,9 @@ public void onJoyAxisEvent(JoyAxisEvent evt) { } setViewedJoystick( evt.getAxis().getJoystick() ); gamepad.setAxisValue( evt.getAxis(), value ); + handleTriggerRumble(evt.getAxis(), value); if( value != 0 ) { lastValues.put(evt.getAxis(), value); - evt.getAxis().getJoystick().rumble(0.5f); } } @@ -194,7 +330,7 @@ public void onJoyAxisEvent(JoyAxisEvent evt) { public void onJoyButtonEvent(JoyButtonEvent evt) { setViewedJoystick( evt.getButton().getJoystick() ); gamepad.setButtonValue( evt.getButton(), evt.isPressed() ); - evt.getButton().getJoystick().rumble(1f); + handleRumbleButton(evt.getButton(), evt.isPressed()); } @Override diff --git a/jme3-examples/src/main/java/jme3test/input/TestVirtualJoystick.java b/jme3-examples/src/main/java/jme3test/input/TestVirtualJoystick.java new file mode 100644 index 0000000000..b8a00613c2 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/TestVirtualJoystick.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.input; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.Joystick; +import com.jme3.input.JoystickButton; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.virtual.VirtualJoystick; +import com.jme3.input.virtual.VirtualJoystickDynamicLayout; +import com.jme3.input.virtual.VirtualJoystickXboxLayout; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeSystem; +import com.jme3.system.Platform; +import com.jme3.util.SkyFactory; + +/** + * Manual test for the on-screen virtual joystick. + */ +public class TestVirtualJoystick extends SimpleApplication { + + private static final String TOGGLE_LAYOUT = "ToggleVirtualJoystickLayout"; + + private boolean dynamicLayout; + + public static void main(String[] args) { + TestVirtualJoystick app = new TestVirtualJoystick(); + AppSettings settings = new AppSettings(true); + configureSettings(settings); + app.setSettings(settings); + app.start(); + } + + public static void configureSettings(AppSettings settings) { + settings.setUseJoysticks(true); + settings.setJoysticksMapper(AppSettings.JOYSTICKS_XBOX_MAPPER); + settings.setVirtualJoystick(defaultVirtualJoystickMode()); + } + + private static String defaultVirtualJoystickMode() { + Platform.Os os = JmeSystem.getPlatform().getOs(); + if (os == Platform.Os.Android || os == Platform.Os.iOS) { + return AppSettings.VIRTUAL_JOYSTICK_AUTO; + } + return AppSettings.VIRTUAL_JOYSTICK_ENABLED; + } + + @Override + public void simpleInitApp() { + setDisplayStatView(false); + setDisplayFps(false); + + cam.setLocation(new Vector3f(42f, 18f, 18f)); + cam.lookAt(new Vector3f(-18f, 4f, -10f), Vector3f.UNIT_Y); + cam.setFrustumPerspective(60f, cam.getAspect(), 0.1f, 500f); + + flyCam.setMoveSpeed(18f); + flyCam.setRotationSpeed(2f); + flyCam.setDragToRotate(true); + inputManager.setCursorVisible(true); + setupLayoutToggle(); + + AmbientLight ambient = new AmbientLight(); + ambient.setColor(new ColorRGBA(0.35f, 0.35f, 0.35f, 1f)); + rootNode.addLight(ambient); + + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(new Vector3f(-0.4f, -0.75f, -0.3f).normalizeLocal()); + sun.setColor(ColorRGBA.White.mult(1.1f)); + rootNode.addLight(sun); + + buildScene(); + } + + private void setupLayoutToggle() { + inputManager.addMapping(TOGGLE_LAYOUT, new KeyTrigger(KeyInput.KEY_L)); + Joystick[] joysticks = inputManager.getJoysticks(); + if (joysticks != null) { + for (Joystick joystick : joysticks) { + JoystickButton start = joystick.getButton(JoystickButton.BUTTON_XBOX_START); + if (start != null) { + start.assignButton(TOGGLE_LAYOUT); + } + } + } + inputManager.addListener(layoutListener, TOGGLE_LAYOUT); + } + + private final ActionListener layoutListener = (name, isPressed, tpf) -> { + if (!TOGGLE_LAYOUT.equals(name) || !isPressed) { + return; + } + dynamicLayout = !dynamicLayout; + Joystick[] joysticks = inputManager.getJoysticks(); + if (joysticks == null) { + return; + } + for (Joystick joystick : joysticks) { + if (joystick instanceof VirtualJoystick) { + ((VirtualJoystick) joystick).setLayout(dynamicLayout + ? new VirtualJoystickDynamicLayout(true) + : new VirtualJoystickXboxLayout()); + } + } + }; + + private void buildScene() { + Spatial scene = assetManager.loadModel("BlenderParity/scene.glb"); + rootNode.attachChild(scene); + + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); + rootNode.attachChild(sky); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java index a0e1df1c74..e17aabc406 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java @@ -34,6 +34,7 @@ import com.jme3.app.SimpleApplication; import com.jme3.environment.EnvironmentProbeControl; +import com.jme3.environment.baker.IBLGLEnvBakerLight.SphericalHarmonicsMode; import com.jme3.input.KeyInput; import com.jme3.input.ChaseCamera; import com.jme3.input.controls.ActionListener; @@ -42,6 +43,7 @@ import com.jme3.math.FastMath; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; import com.jme3.util.SkyFactory; import com.jme3.util.mikktspace.MikktspaceTangentGenerator; @@ -52,10 +54,14 @@ public class TestPBRSimple extends SimpleApplication { private boolean REALTIME_BAKING = false; private static final String INCREASE_METALLIC = "IncreaseMetallic"; private static final String DECREASE_METALLIC = "DecreaseMetallic"; + private static final String TOGGLE_SH_FAST_PATH = "ToggleShFastPath"; + private static final String TOGGLE_SH_REALTIME = "ToggleShRealtime"; private Material pbrMat; + private EnvironmentProbeControl envProbe; private float metallic = 0.0f; private float roughness = 1.0f; + private boolean shFastPath = false; public static void main(String[] args) { new TestPBRSimple().start(); @@ -85,14 +91,16 @@ public void simpleInitApp() { rootNode.attachChild(sky); // Create baker control - EnvironmentProbeControl envProbe=new EnvironmentProbeControl(assetManager,256); + envProbe = new EnvironmentProbeControl(assetManager, 256); rootNode.addControl(envProbe); // Tag the sky, only the tagged spatials will be rendered in the env map envProbe.tag(sky); inputManager.addMapping(INCREASE_METALLIC, new KeyTrigger(KeyInput.KEY_N)); inputManager.addMapping(DECREASE_METALLIC, new KeyTrigger(KeyInput.KEY_P)); - inputManager.addListener(metallicListener, INCREASE_METALLIC, DECREASE_METALLIC); + inputManager.addMapping(TOGGLE_SH_FAST_PATH, new KeyTrigger(KeyInput.KEY_F)); + inputManager.addMapping(TOGGLE_SH_REALTIME, new KeyTrigger(KeyInput.KEY_R)); + inputManager.addListener(materialListener, TOGGLE_SH_REALTIME, INCREASE_METALLIC, DECREASE_METALLIC, TOGGLE_SH_FAST_PATH); updateMaterial(); @@ -112,7 +120,7 @@ public void simpleUpdate(float tpf) { } } - private final ActionListener metallicListener = (name, isPressed, tpf) -> { + private final ActionListener materialListener = (name, isPressed, tpf) -> { if (!isPressed) { return; } @@ -123,6 +131,15 @@ public void simpleUpdate(float tpf) { } else if (DECREASE_METALLIC.equals(name)) { metallic = FastMath.clamp(metallic - 0.1f, 0.0f, 1.0f); roughness = FastMath.clamp(roughness + 0.1f, 0.0f, 1.0f); + } else if (TOGGLE_SH_FAST_PATH.equals(name)) { + shFastPath = !shFastPath; + envProbe.setSphericalHarmonicsFastPathEnabled(shFastPath); + envProbe.rebake(); + System.out.println("Spherical harmonics mode -> " + + envProbe.getSphericalHarmonicsMode() + "; rebaking probe"); + } else if (TOGGLE_SH_REALTIME.equals(name)) { + REALTIME_BAKING = !REALTIME_BAKING; + System.out.println("Real-time baking -> " + REALTIME_BAKING); } updateMaterial(); @@ -132,6 +149,7 @@ private void updateMaterial() { pbrMat.setFloat("Metallic", metallic); pbrMat.setFloat("Roughness", roughness); System.out.println( - "Tank material -> metallic: " + metallic + ", roughness: " + roughness + " (N/P)"); + "Tank material -> metallic: " + metallic + ", roughness: " + roughness + + " (N/P, F toggles SH fast path)"); } } diff --git a/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java b/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java index 659a447a48..3b4a7cad74 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java +++ b/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java @@ -8,6 +8,7 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestUnshadedModel extends SimpleApplication { diff --git a/jme3-examples/src/main/java/jme3test/opencl/HelloOpenCL.java b/jme3-examples/src/main/java/jme3test/opencl/HelloOpenCL.java deleted file mode 100644 index 737547fca9..0000000000 --- a/jme3-examples/src/main/java/jme3test/opencl/HelloOpenCL.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.opencl; - -import com.jme3.app.SimpleApplication; -import com.jme3.font.BitmapFont; -import com.jme3.font.BitmapText; -import com.jme3.math.ColorRGBA; -import com.jme3.opencl.*; -import com.jme3.system.AppSettings; -import com.jme3.util.BufferUtils; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.Arrays; -import java.util.Objects; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Simple test checking if the basic functions of the OpenCL wrapper work - * @author shaman - */ -public class HelloOpenCL extends SimpleApplication { - private static final Logger LOG = Logger.getLogger(HelloOpenCL.class.getName()); - - public static void main(String[] args){ - HelloOpenCL app = new HelloOpenCL(); - AppSettings settings = new AppSettings(true); - settings.setOpenCLSupport(true); - settings.setVSync(true); - settings.setRenderer(AppSettings.LWJGL_OPENGL2); - app.setSettings(settings); - app.start(); // start the game - } - - @Override - public void simpleInitApp() { - BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); - Context clContext = context.getOpenCLContext(); - if (clContext == null) { - BitmapText txt = new BitmapText(fnt); - txt.setText("No OpenCL Context created!\nSee output log for details."); - txt.setLocalTranslation(5, settings.getHeight() - 5, 0); - guiNode.attachChild(txt); - return; - } - CommandQueue clQueue = clContext.createQueue(); - - StringBuilder str = new StringBuilder(); - str.append("OpenCL Context created:\n Platform: ") - .append(clContext.getDevices().get(0).getPlatform().getName()) - .append("\n Devices: ").append(clContext.getDevices()); - str.append("\nTests:"); - str.append("\n Buffers: ").append(testBuffer(clContext, clQueue)); - str.append("\n Kernel: ").append(testKernel(clContext, clQueue)); - str.append("\n Images: ").append(testImages(clContext, clQueue)); - - clQueue.release(); - - BitmapText txt1 = new BitmapText(fnt); - txt1.setText(str.toString()); - txt1.setLocalTranslation(5, settings.getHeight() - 5, 0); - guiNode.attachChild(txt1); - - flyCam.setEnabled(false); - inputManager.setCursorVisible(true); - } - - private static void assertEquals(byte expected, byte actual, String message) { - if (expected != actual) { - System.err.println(message+": expected="+expected+", actual="+actual); - throw new AssertionError(); - } - } - private static void assertEquals(long expected, long actual, String message) { - if (expected != actual) { - System.err.println(message+": expected="+expected+", actual="+actual); - throw new AssertionError(); - } - } - private static void assertEquals(double expected, double actual, String message) { - if (Math.abs(expected - actual) >= 0.00001) { - System.err.println(message+": expected="+expected+", actual="+actual); - throw new AssertionError(); - } - } - private static void assertEquals(Object expected, Object actual, String message) { - if (!Objects.equals(expected, actual)) { - System.err.println(message+": expected="+expected+", actual="+actual); - throw new AssertionError(); - } - } - - private boolean testBuffer(Context clContext, CommandQueue clQueue) { - try { - //create two buffers - ByteBuffer h1 = BufferUtils.createByteBuffer(256); - Buffer b1 = clContext.createBuffer(256); - ByteBuffer h2 = BufferUtils.createByteBuffer(256); - Buffer b2 = clContext.createBuffer(256); - - //fill buffer - h2.rewind(); - for (int i=0; i<256; ++i) { - h2.put((byte)i); - } - h2.rewind(); - b2.write(clQueue, h2); - - //copy b2 to b1 - b2.copyTo(clQueue, b1); - - //read buffer - h1.rewind(); - b1.read(clQueue, h1); - h1.rewind(); - for (int i=0; i<256; ++i) { - byte b = h1.get(); - assertEquals((byte) i, b, "Wrong byte read"); - } - - //read buffer with offset - int low = 26; - int high = 184; - h1.position(5); - Event event = b1.readAsync(clQueue, h1, high-low, low); - event.waitForFinished(); - h1.position(5); - for (int i=0; i deviceListBox; - - private static String selectedPlatform; - private static String selectedDevice; - private Context clContext; - private static List availablePlatforms; - private Buffer testBuffer; - private boolean bufferCreated; - - /** - * @param args the command line arguments - */ - public static void main(String[] args) { - new TestContextSwitching().start(); - } - - public TestContextSwitching() { - AppSettings settings = new AppSettings(true); - settings.setOpenCLSupport(true); - settings.setVSync(true); - settings.setWidth(800); - settings.setHeight(600); - settings.setOpenCLPlatformChooser(CustomPlatformChooser.class); - settings.setRenderer(AppSettings.LWJGL_OPENGL2); - - setSettings(settings); - setShowSettings(false); - } - - @Override - public void simpleInitApp() { - - clContext = null; - - NiftyJmeDisplay niftyDisplay = NiftyJmeDisplay.newNiftyJmeDisplay( - assetManager, - inputManager, - audioRenderer, - guiViewPort); - Nifty nifty = niftyDisplay.getNifty(); - nifty.fromXml("jme3test/opencl/ContextSwitchingScreen.xml", "Screen", this); - guiViewPort.addProcessor(niftyDisplay); - inputManager.setCursorVisible(true); - flyCam.setEnabled(false); - } - - @Override - public void simpleUpdate(float tpf) { - if (applyButton != null) { - updateInfos(); - } - } - - @Override - @SuppressWarnings("unchecked") - public void bind(Nifty nifty, Screen screen) { - applyButton = screen.findNiftyControl("ApplyButton", Button.class); - ListBox platformListBox - = screen.findNiftyControl("PlatformListBox", ListBox.class); - deviceListBox = screen.findNiftyControl("DeviceListBox", ListBox.class); - infoLabel = screen.findNiftyControl("InfoLabel", Label.class); - - updateInfos(); - - platformListBox.clear(); - for (Platform p : availablePlatforms) { - platformListBox.addItem(p.getName()); - } - platformListBox.selectItem(selectedPlatform); - changePlatform(selectedPlatform); - } - - private void updateInfos() { - - if (testBuffer == null && clContext != null && !bufferCreated) { - try { - testBuffer = clContext.createBuffer(1024).register(); - LOG.info("Test buffer created"); - } catch (OpenCLException ex) { - LOG.log(Level.SEVERE, "Unable to create buffer", ex); - } - bufferCreated = true; - } - - Context c = context.getOpenCLContext(); - if (c == clContext) { - return; - } - clContext = c; - LOG.info("context changed"); - testBuffer = null; - bufferCreated = false; - StringBuilder text = new StringBuilder(); - text.append("Current context:\n"); - text.append(" Platform: ").append(clContext.getDevices().get(0).getPlatform().getName()).append("\n"); - text.append(" Device: ").append(clContext.getDevices().get(0).getName()).append("\n"); - text.append(" Profile: ").append(clContext.getDevices().get(0).getProfile()).append("\n"); - text.append(" Memory: ").append(clContext.getDevices().get(0).getGlobalMemorySize()).append(" B\n"); - text.append(" Compute Units: ").append(clContext.getDevices().get(0).getComputeUnits()).append("\n"); - infoLabel.setText(text.toString()); - } - - @NiftyEventSubscriber(id="ApplyButton") - public void onButton(String id, ButtonClickedEvent event) { - LOG.log(Level.INFO, "Change context: platform={0}, device={1}", new Object[]{selectedPlatform, selectedDevice}); - restart(); - } - - private void changePlatform(String platform) { - selectedPlatform = platform; - Platform p = null; - for (Platform p2 : availablePlatforms) { - if (p2.getName().equals(selectedPlatform)) { - p = p2; - break; - } - } - deviceListBox.clear(); - if (p == null) { - return; - } - for (Device d : p.getDevices()) { - deviceListBox.addItem(d.getName()); - } - deviceListBox.selectItem(selectedDevice); - } - - @NiftyEventSubscriber(id="PlatformListBox") - public void onPlatformChanged(String id, ListBoxSelectionChangedEvent event) { - String p = event.getSelection().isEmpty() ? null : event.getSelection().get(0); - LOG.log(Level.INFO, "Selected platform changed to {0}", p); - selectedPlatform = p; - changePlatform(p); - } - - @NiftyEventSubscriber(id="DeviceListBox") - public void onDeviceChanged(String id, ListBoxSelectionChangedEvent event) { - String d = event.getSelection().isEmpty() ? null : event.getSelection().get(0); - LOG.log(Level.INFO, "Selected device changed to {0}", d); - selectedDevice = d; - } - - @Override - public void onStartScreen() { - - } - - @Override - public void onEndScreen() { - - } - - public static class CustomPlatformChooser implements PlatformChooser { - - public CustomPlatformChooser() {} - - @Override - public List chooseDevices(List platforms) { - availablePlatforms = platforms; - - Platform platform = null; - for (Platform p : platforms) { - if (p.getName().equals(selectedPlatform)) { - platform = p; - break; - } - } - if (platform == null) { - platform = platforms.get(0); - } - selectedPlatform = platform.getName(); - - Device device = null; - for (Device d : platform.getDevices()) { - if (d.getName().equals(selectedDevice)) { - device = d; - break; - } - } - if (device == null) { - for (Device d : platform.getDevices()) { - if (d.getDeviceType() == Device.DeviceType.GPU) { - device = d; - break; - } - } - } - if (device == null) { - device = platform.getDevices().get(0); - } - selectedDevice = device.getName(); - - return Collections.singletonList(device); - } - - } -} - diff --git a/jme3-examples/src/main/java/jme3test/opencl/TestMultipleApplications.java b/jme3-examples/src/main/java/jme3test/opencl/TestMultipleApplications.java deleted file mode 100644 index 3b3ec8fe7f..0000000000 --- a/jme3-examples/src/main/java/jme3test/opencl/TestMultipleApplications.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2009-2021 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.opencl; - -import com.jme3.app.SimpleApplication; -import com.jme3.font.BitmapFont; -import com.jme3.font.BitmapText; -import com.jme3.opencl.*; -import com.jme3.system.AppSettings; -import java.util.Collections; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * This class creates multiple instances of {@link TestVertexBufferSharing}. - * This is used to test if multiple opencl instances can run in parallel. - * @author Sebastian Weiss - */ -public class TestMultipleApplications extends SimpleApplication { - private static final Logger LOG = Logger.getLogger(TestMultipleApplications.class.getName()); - - private static final Object sync = new Object(); - private static List availableDevices; - private static int currentDeviceIndex; - - private CommandQueue clQueue; - private Kernel kernel; - private Buffer buffer; - private boolean failed; - private BitmapText statusText; - - /** - * @param args the command line arguments - */ - public static void main(String[] args) { - final AppSettings settings = new AppSettings(true); - settings.setOpenCLSupport(true); - settings.setVSync(true); - settings.setOpenCLPlatformChooser(CustomPlatformChooser.class); - settings.setRenderer(AppSettings.LWJGL_OPENGL2); - for (int i=0; i<2; ++i) { - new Thread() { - @Override - public void run() { - if (currentDeviceIndex == -1) { - return; - } - TestMultipleApplications app = new TestMultipleApplications(); - app.setSettings(settings); - app.setShowSettings(false); - app.start(); - } - }.start(); - } - } - - public static class CustomPlatformChooser implements PlatformChooser { - - public CustomPlatformChooser() {} - - @Override - public List chooseDevices(List platforms) { - synchronized(sync) { - if (currentDeviceIndex == -1) { - return Collections.emptyList(); - } - - Platform platform = platforms.get(0); - availableDevices = platform.getDevices(); - - Device device = platform.getDevices().get(currentDeviceIndex); - currentDeviceIndex ++; - if (currentDeviceIndex >= availableDevices.size()) { - currentDeviceIndex = -1; - } - - return Collections.singletonList(device); - } - } - - } - - @Override - public void simpleInitApp() { - Context clContext = context.getOpenCLContext(); - if (clContext == null) { - LOG.severe("No OpenCL context found"); - stop(); - return; - } - Device device = clContext.getDevices().get(0); - clQueue = clContext.createQueue(device); - clQueue.register(); - - String source = "" - + "__kernel void Fill(__global float* vb, float v)\n" - + "{\n" - + " int idx = get_global_id(0);\n" - + " vb[idx] = v;\n" - + "}\n"; - Program program = clContext.createProgramFromSourceCode(source); - program.build(); - program.register(); - kernel = program.createKernel("Fill"); - kernel.register(); - - buffer = clContext.createBuffer(4); - buffer.register(); - - flyCam.setEnabled(false); - inputManager.setCursorVisible(true); - - BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText infoText = new BitmapText(fnt); - //infoText.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight())); - infoText.setText("Device: "+clContext.getDevices()); - infoText.setLocalTranslation(0, settings.getHeight(), 0); - guiNode.attachChild(infoText); - statusText = new BitmapText(fnt); - //statusText.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight())); - statusText.setText("Running"); - statusText.setLocalTranslation(0, settings.getHeight() - infoText.getHeight() - 2, 0); - guiNode.attachChild(statusText); - } - - @Override - public void simpleUpdate(float tpf) { - //call kernel to test if it is still working - if (!failed) { - try { - kernel.Run1NoEvent(clQueue, new Kernel.WorkSize(1), buffer, 1.0f); - } catch (OpenCLException ex) { - LOG.log(Level.SEVERE, "Kernel call not working anymore", ex); - failed = true; - statusText.setText("Failed"); - } - } - } -} diff --git a/jme3-examples/src/main/java/jme3test/opencl/TestOpenCLLibraries.java b/jme3-examples/src/main/java/jme3test/opencl/TestOpenCLLibraries.java deleted file mode 100644 index 2a712314cb..0000000000 --- a/jme3-examples/src/main/java/jme3test/opencl/TestOpenCLLibraries.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.opencl; - -import com.jme3.app.SimpleApplication; -import com.jme3.font.BitmapFont; -import com.jme3.font.BitmapText; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix3f; -import com.jme3.math.Matrix4f; -import com.jme3.opencl.*; -import com.jme3.system.AppSettings; -import com.jme3.util.BufferUtils; -import java.nio.*; -import java.util.Objects; -import java.util.Random; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Test class for the build in libraries - * @author shaman - */ -public class TestOpenCLLibraries extends SimpleApplication { - private static final Logger LOG = Logger.getLogger(TestOpenCLLibraries.class.getName()); - - public static void main(String[] args){ - TestOpenCLLibraries app = new TestOpenCLLibraries(); - AppSettings settings = new AppSettings(true); - settings.setOpenCLSupport(true); - settings.setVSync(true); - settings.setRenderer(AppSettings.LWJGL_OPENGL2); - app.setSettings(settings); - app.start(); // start the game - } - - @Override - public void simpleInitApp() { - BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); - Context clContext = context.getOpenCLContext(); - if (clContext == null) { - BitmapText txt = new BitmapText(fnt); - txt.setText("No OpenCL Context created!\nSee output log for details."); - txt.setLocalTranslation(5, settings.getHeight() - 5, 0); - guiNode.attachChild(txt); - return; - } - CommandQueue clQueue = clContext.createQueue(clContext.getDevices().get(0)); - - StringBuilder str = new StringBuilder(); - str.append("OpenCL Context created:\n Platform: ") - .append(clContext.getDevices().get(0).getPlatform().getName()) - .append("\n Devices: ").append(clContext.getDevices()); - str.append("\nTests:"); - str.append("\n Random numbers: ").append(testRandom(clContext, clQueue)); - str.append("\n Matrix3f: ").append(testMatrix3f(clContext, clQueue)); - str.append("\n Matrix4f: ").append(testMatrix4f(clContext, clQueue)); - - clQueue.release(); - - BitmapText txt1 = new BitmapText(fnt); - txt1.setText(str.toString()); - txt1.setLocalTranslation(5, settings.getHeight() - 5, 0); - guiNode.attachChild(txt1); - - flyCam.setEnabled(false); - inputManager.setCursorVisible(true); - } - - private static void assertEquals(byte expected, byte actual, String message) { - if (expected != actual) { - System.err.println(message+": expected="+expected+", actual="+actual); - throw new AssertionError(); - } - } - private static void assertEquals(long expected, long actual, String message) { - if (expected != actual) { - System.err.println(message+": expected="+expected+", actual="+actual); - throw new AssertionError(); - } - } - private static void assertEquals(double expected, double actual, String message) { - if (Math.abs(expected - actual) >= 0.00001) { - System.err.println(message+": expected="+expected+", actual="+actual); - throw new AssertionError(); - } - } - private static void assertEquals(Object expected, Object actual, String message) { - if (!Objects.equals(expected, actual)) { - System.err.println(message+": expected="+expected+", actual="+actual); - throw new AssertionError(); - } - } - - private boolean testRandom(Context clContext, CommandQueue clQueue) { - try { - //test for doubles - boolean supportsDoubles = clContext.getDevices().get(0).hasDouble(); - - //create code - String code = "" - + "#import \"Common/OpenCL/Random.clh\"\n" - + "__kernel void TestBool(__global ulong* seeds, __global uchar* results) {\n" - + " results[get_global_id(0)] = randBool(seeds + get_global_id(0)) ? 1 : 0;\n" - + "}\n" - + "__kernel void TestInt(__global ulong* seeds, __global int* results) {\n" - + " results[get_global_id(0)] = randInt(seeds + get_global_id(0));\n" - + "}\n" - + "__kernel void TestIntN(__global ulong* seeds, int n, __global int* results) {\n" - + " results[get_global_id(0)] = randIntN(n, seeds + get_global_id(0));\n" - + "}\n" - + "__kernel void TestLong(__global ulong* seeds, __global long* results) {\n" - + " results[get_global_id(0)] = randLong(seeds + get_global_id(0));\n" - + "}\n" - + "__kernel void TestFloat(__global ulong* seeds, __global float* results) {\n" - + " results[get_global_id(0)] = randFloat(seeds + get_global_id(0));\n" - + "}\n" - + "#ifdef RANDOM_DOUBLES\n" - + "__kernel void TestDouble(__global ulong* seeds, __global double* results) {\n" - + " results[get_global_id(0)] = randDouble(seeds + get_global_id(0));\n" - + "}\n" - + "#endif\n"; - if (supportsDoubles) { - code = "#define RANDOM_DOUBLES\n" + code; - } - Program program = clContext.createProgramFromSourceCodeWithDependencies(code, assetManager); - program.build(); - - int count = 256; - Kernel.WorkSize ws = new Kernel.WorkSize(count); - - //create seeds - Random initRandom = new Random(); - long[] seeds = new long[count]; - Random[] randoms = new Random[count]; - for (int i=0; i - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/opencl/JuliaSet.cl b/jme3-examples/src/main/resources/jme3test/opencl/JuliaSet.cl deleted file mode 100644 index d2a42d3384..0000000000 --- a/jme3-examples/src/main/resources/jme3test/opencl/JuliaSet.cl +++ /dev/null @@ -1,99 +0,0 @@ - - -//2 component vector to hold the real and imaginary parts of a complex number: -typedef float2 cfloat; - -#define I ((cfloat)(0.0, 1.0)) - -inline float real(cfloat a){ - return a.x; -} -inline float imag(cfloat a){ - return a.y; -} - -inline float cmod(cfloat a){ - return (sqrt(a.x*a.x + a.y*a.y)); -} - -inline cfloat cadd(cfloat a, cfloat b){ - return (cfloat)( a.x + b.x, a.y + b.y); -} - -inline float carg(cfloat a){ - if(a.x > 0){ - return atan(a.y / a.x); - - }else if(a.x < 0 && a.y >= 0){ - return atan(a.y / a.x) + M_PI_F; - - }else if(a.x < 0 && a.y < 0){ - return atan(a.y / a.x) - M_PI_F; - - }else if(a.x == 0 && a.y > 0){ - return M_PI_F/2; - - }else if(a.x == 0 && a.y < 0){ - return -M_PI_F/2; - - }else{ - return 0; - } -} - -inline cfloat cmult(cfloat a, cfloat b){ - return (cfloat)( a.x*b.x - a.y*b.y, a.x*b.y + a.y*b.x); -} - -inline cfloat csqrt(cfloat a){ - return (cfloat)( sqrt(cmod(a)) * cos(carg(a)/2), sqrt(cmod(a)) * sin(carg(a)/2)); -} - -inline float4 getColor(int iteration, int numIterations) { - //color transition: black -> red -> blue -> white - int step = numIterations / 2; - if (iteration < step) { - return mix( (float4)(0,0,0,1), (float4)(1,0,0,1), iteration / (float) step); - } else { - return mix( (float4)(1,0,0,1), (float4)(0,0,1,1), (iteration-step) / (float) (numIterations - step)); - } -} - -__kernel void JuliaSet(write_only image2d_t outputImage, const cfloat C, int numIterations) -{ - // get id of element in array - int x = get_global_id(0); - int y = get_global_id(1); - int w = get_global_size(0); - int h = get_global_size(1); - - cfloat Z = { ( -w / 2 + x) / (w/4.0f) , ( -h / 2 + y) / (h/4.0f) }; - int iteration = 0; - - while (iteration < numIterations) - { - cfloat Zpow2 = cmult(Z, Z); - cfloat Zn = cadd(Zpow2, C); - Z.x = Zn.x; - Z.y = Zn.y; - iteration++; - if(cmod(Z) > 2) - { - break; - } - } - - float4 color; - - // threshold reached mark pixel as white - if (iteration == numIterations) - { - color = (float4)(1,1,1,1); - } - else - { - color = getColor(iteration, numIterations); - } - - write_imagef(outputImage, (int2)(x, y), color); -} \ No newline at end of file diff --git a/jme3-ios-examples/build.gradle b/jme3-ios-examples/build.gradle new file mode 100644 index 0000000000..d68d66da84 --- /dev/null +++ b/jme3-ios-examples/build.gradle @@ -0,0 +1,345 @@ +buildscript { + repositories { + gradlePluginPortal() + mavenCentral() + } + dependencies { + classpath libs.libjglios.gradle.plugin + } +} + +import groovy.json.JsonOutput +import java.io.DataInputStream +import java.util.zip.ZipFile + +apply plugin: 'org.ngengine.libjglios' + +description = 'iOS libJGLIOS launcher for jme3-examples.' + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +def examplesJar = project(':jme3-examples').tasks.named('jar') +def generateExamplesTestChooserClassList = project(':jme3-examples').tasks.named('generateTestChooserClassList') +def generatedExamplesTestChooserResourcesDir = project(':jme3-examples').layout.buildDirectory.dir('generated/testchooser/resources') +def examplesTestClassListFile = generatedExamplesTestChooserResourcesDir.map { it.file('jme3test/test-classes.txt') } +def iosChooserLauncherClass = 'jme3test.ios.IosTestChooserLauncher' +def requestedExampleClass = findProperty('example')?.toString()?.trim() +def exampleClass = requestedExampleClass ?: iosChooserLauncherClass +def iosExampleClassesDir = layout.buildDirectory.dir('ios-example-classes') +def iosInitialExampleSourceDir = layout.buildDirectory.dir('generated/ios-initial-example/sources') +def iosNativeImageMetadataDir = layout.buildDirectory.dir('generated/ios-native-image-metadata/resources') +def iosChooserExcludedPrefixes = [ + 'jme3test.awt.', + 'jme3test.bullet.', + 'jme3test.niftygui.', + 'jme3test.opencl.', + 'jme3test.terrain.' +] +def iosChooserExcludedNames = [ + 'jme3test.app.TestChangeAppIcon', + 'jme3test.app.TestContextRestart', + 'jme3test.app.TestMonitorApp', + 'jme3test.app.TestResizableApp', + 'jme3test.asset.TestOnlineJar', + 'jme3test.audio.TestAudioDeviceDisconnect' +] +def isIosChooserClass = { String className -> + (!iosChooserExcludedPrefixes.any { className.startsWith(it) } + && !iosChooserExcludedNames.contains(className) + && !className.contains('Jogl') + && !className.contains('Lwjgl')) +} +def readExamplesTestClassList = { + def file = examplesTestClassListFile.get().asFile + if (!file.exists()) { + return [] + } + file.readLines('UTF-8') + .collect { it.trim() } + .findAll { it } + .findAll { isIosChooserClass(it) } +} +def selectedIosExampleClasses = { + requestedExampleClass ? [requestedExampleClass] : readExamplesTestClassList() +} +def javaStringLiteral = { String value -> + if (value == null) { + return 'null' + } + '"' + value + .replace('\\', '\\\\') + .replace('"', '\\"') + .replace('\n', '\\n') + .replace('\r', '\\r') + '"' +} +def readClassReferences = { ZipFile zip, String classPath -> + def entry = zip.getEntry("${classPath}.class") + if (entry == null) { + return [] as Set + } + zip.getInputStream(entry).withCloseable { input -> + def data = new DataInputStream(input) + if (data.readInt() != (int) 0xCAFEBABE) { + return [] as Set + } + data.readUnsignedShort() + data.readUnsignedShort() + def constantPool = new Object[data.readUnsignedShort()] + for (int index = 1; index < constantPool.length; index++) { + int tag = data.readUnsignedByte() + switch (tag) { + case 1: + constantPool[index] = data.readUTF() + break + case 3: + case 4: + data.skipBytes(4) + break + case 5: + case 6: + data.skipBytes(8) + index++ + break + case 7: + case 8: + case 16: + case 19: + case 20: + constantPool[index] = [tag: tag, nameIndex: data.readUnsignedShort()] + break + case 9: + case 10: + case 11: + case 12: + case 18: + data.skipBytes(4) + break + case 15: + data.skipBytes(3) + break + case 17: + data.skipBytes(4) + break + default: + throw new GradleException("Unsupported constant-pool tag ${tag} in ${classPath}.class") + } + } + constantPool.findAll { it instanceof Map && it.tag == 7 } + .collect { constantPool[it.nameIndex] } + .findAll { it instanceof String && it.startsWith('jme3test/') && !it.startsWith('jme3test/ios/') } + .collect { it.replace('/', '.') } + .findAll { !it.contains('[') } as Set + } +} +def expandExamplesClassNames = { Collection rootClasses -> + def expanded = new LinkedHashSet(rootClasses) + def jarFile = examplesJar.flatMap { it.archiveFile }.get().asFile + new ZipFile(jarFile).withCloseable { zip -> + def availablePaths = new LinkedHashSet() + zip.entries().each { entry -> + if (entry.directory || !entry.name.endsWith('.class')) { + return + } + def classPath = entry.name.substring(0, entry.name.length() - '.class'.length()) + if (classPath.startsWith('jme3test/')) { + availablePaths.add(classPath) + } + } + + def pending = new ArrayDeque(expanded) + while (!pending.isEmpty()) { + def className = pending.removeFirst() + def rootPath = className.replace('.', '/') + availablePaths.findAll { it.startsWith("${rootPath}\$") }.each { classPath -> + def nestedClass = classPath.replace('/', '.') + if (expanded.add(nestedClass)) { + pending.add(nestedClass) + } + } + readClassReferences(zip, rootPath).each { referencedClass -> + def referencedPath = referencedClass.replace('.', '/') + if (availablePaths.contains(referencedPath) && expanded.add(referencedClass)) { + pending.add(referencedClass) + } + } + } + } + expanded as List +} +def collectRuntimeClassNamesByPrefix = { Collection prefixes -> + def classes = new LinkedHashSet() + sourceSets.main.compileClasspath.files.findAll { it.exists() }.each { file -> + if (file.isDirectory()) { + file.eachFileRecurse { classFile -> + if (!classFile.name.endsWith('.class')) { + return + } + def relativePath = file.toPath().relativize(classFile.toPath()).toString().replace(File.separatorChar, (char) '/') + def className = relativePath.substring(0, relativePath.length() - '.class'.length()).replace('/', '.') + if (prefixes.any { className.startsWith(it) } && !className.endsWith('module-info') && !className.endsWith('package-info')) { + classes.add(className) + } + } + } else if (file.name.endsWith('.jar')) { + new ZipFile(file).withCloseable { zip -> + zip.entries().each { entry -> + if (entry.directory || !entry.name.endsWith('.class')) { + return + } + def className = entry.name.substring(0, entry.name.length() - '.class'.length()).replace('/', '.') + if (prefixes.any { className.startsWith(it) } && !className.endsWith('module-info') && !className.endsWith('package-info')) { + classes.add(className) + } + } + } + } + } + classes as List +} + +def prepareIosExampleClasses = tasks.register('prepareIosExampleClasses', Sync) { + dependsOn examplesJar, generateExamplesTestChooserClassList + inputs.property('exampleClass', exampleClass) + inputs.property('requestedExampleClass', requestedExampleClass ?: '') + inputs.property('iosExampleClassExpansionVersion', 'bytecode-reference-v1') + inputs.property('iosExampleIncludes', findProperty('iosExampleIncludes')?.toString() ?: '') + inputs.file(examplesTestClassListFile) + inputs.file(examplesJar.flatMap { it.archiveFile }) + from(zipTree(examplesJar.flatMap { it.archiveFile })) { + def expandedClasses + include { details -> + expandedClasses = expandedClasses ?: expandExamplesClassNames(selectedIosExampleClasses()) + expandedClasses.any { className -> details.path == "${className.replace('.', '/')}.class" } + } + def extraIncludes = findProperty('iosExampleIncludes')?.toString() + if (extraIncludes) { + extraIncludes.split(',').collect { it.trim() }.findAll { it }.each { include it } + } + } + into iosExampleClassesDir +} + +def generateIosInitialExampleSource = tasks.register('generateIosInitialExampleSource') { + def outputFile = iosInitialExampleSourceDir.map { it.file('jme3test/ios/IosInitialExample.java') } + outputs.file(outputFile) + inputs.property('requestedExampleClass', requestedExampleClass ?: '') + doLast { + def file = outputFile.get().asFile + file.parentFile.mkdirs() + file.text = """package jme3test.ios; + +final class IosInitialExample { + private static final String CLASS_NAME = ${javaStringLiteral(requestedExampleClass)}; + + private IosInitialExample() { + } + + static String className() { + return CLASS_NAME; + } +} +""" + } +} + +def generateIosNativeImageMetadata = tasks.register('generateIosNativeImageMetadata') { + dependsOn examplesJar, generateExamplesTestChooserClassList, tasks.named('prepareGraalHostNik') + def reflectConfig = iosNativeImageMetadataDir.map { + it.file('META-INF/native-image/org.jmonkeyengine/jme3-ios-testchooser/reflect-config.json') + } + outputs.file(reflectConfig) + inputs.property('exampleClass', exampleClass) + inputs.property('requestedExampleClass', requestedExampleClass ?: '') + inputs.property('iosExampleClassExpansionVersion', 'bytecode-reference-v1') + inputs.file(examplesTestClassListFile) + inputs.file(examplesJar.flatMap { it.archiveFile }) + inputs.files({ sourceSets.main.compileClasspath }) + doLast { + def exampleClasses = selectedIosExampleClasses() + def classes = (expandExamplesClassNames(exampleClasses) + + collectRuntimeClassNamesByPrefix(['com.bulletphysics.']) + + [iosChooserLauncherClass, 'jme3test.ios.IosTestChooser', 'jme3test.ios.IosInitialExample']).unique() + def metadata = classes.collect { className -> + def metadataEntry = [ + name: className, + allDeclaredConstructors: true, + allPublicConstructors: true, + allPublicMethods: true + ] + if (className == 'jme3test.ios.IosInitialExample') { + metadataEntry.allDeclaredMethods = true + } + metadataEntry + } + def outputFile = reflectConfig.get().asFile + outputFile.parentFile.mkdirs() + outputFile.text = JsonOutput.prettyPrint(JsonOutput.toJson(metadata)) + System.lineSeparator() + } +} + +iOS { + mainClass = iosChooserLauncherClass + bundleId = 'org.jmonkeyengine.jme3iosexamples' + appName = 'JmeIosExamples' + simulatorDevice = (findProperty('iosSimulatorDevice') ?: 'iPhone 16').toString() + appIcon = file('../jme3-android-examples/src/main/res/mipmap-xxxhdpi/mipmap_monkey.png') + assets.from '../jme3-testdata/src/main/resources', '../jme3-examples/src/main/resources' +} + +sourceSets { + main { + java.srcDir iosInitialExampleSourceDir + output.dir(iosExampleClassesDir, builtBy: prepareIosExampleClasses) + resources { + srcDir iosNativeImageMetadataDir + srcDir generatedExamplesTestChooserResourcesDir + } + } +} + +dependencies { + implementation project(':jme3-core') + implementation project(':jme3-ios') + implementation project(':jme3-saferallocator') + implementation libs.libjglios.core.ios + implementation libs.libjglios.gles.ios + implementation libs.libjglios.sdl3.ios + implementation libs.libjglios.openal.ios + implementation libs.libjglios.angle.ios + implementation project(':jme3-effects') + implementation project(':jme3-jbullet') + implementation project(':jme3-jogg') + implementation project(':jme3-networking') + implementation project(':jme3-plugins') + implementation project(':jme3-plugins-json') + implementation project(':jme3-plugins-json-gson') + + if ((findProperty('iosIncludeAwtUnsafeModules') ?: 'false').toString().toBoolean()) { + implementation project(':jme3-niftygui') + implementation project(':jme3-terrain') + } +} + +tasks.named('compileJava') { + dependsOn prepareIosExampleClasses, generateIosInitialExampleSource + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + options.release = 11 +} + +tasks.named('javadoc') { + exclude('org/ngengine/libjglios/generated/**') +} + +tasks.named('processResources') { + dependsOn generateIosNativeImageMetadata, generateExamplesTestChooserClassList +} + +tasks.register('runIosExamples') { + group = 'verification' + description = 'Builds, installs, and launches the jme3-examples iOS app.' + dependsOn tasks.named('runIosDebugApp') +} diff --git a/jme3-ios-examples/src/main/java/jme3test/ios/IosTestChooser.java b/jme3-ios-examples/src/main/java/jme3test/ios/IosTestChooser.java new file mode 100644 index 0000000000..8d41ea518b --- /dev/null +++ b/jme3-ios-examples/src/main/java/jme3test/ios/IosTestChooser.java @@ -0,0 +1,439 @@ +package jme3test.ios; + +import com.jme3.app.DebugKeysAppState; +import com.jme3.app.SimpleApplication; +import com.jme3.app.StatsAppState; +import com.jme3.app.state.ConstantVerifierState; +import com.jme3.audio.AudioListenerState; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.RawInputListenerAdapter; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.TouchEvent; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; +import com.jme3.system.JmeSystem; +import java.util.ArrayList; +import java.util.List; + +public final class IosTestChooser extends SimpleApplication implements ActionListener { + private static final String SELECT_MAPPING = "IosTestChooserSelect"; + private static final float MIN_TEXT_SIZE = 10f; + private static final long TAP_DEBOUNCE_NANOS = 180_000_000L; + private static final ColorRGBA BACKGROUND_COLOR = new ColorRGBA(0.045f, 0.060f, 0.050f, 1f); + private static final ColorRGBA EXAMPLE_BUTTON_COLOR = new ColorRGBA(0.135f, 0.305f, 0.145f, 1f); + private static final ColorRGBA ACTION_BUTTON_COLOR = new ColorRGBA(0.760f, 0.360f, 0.060f, 1f); + private static final ColorRGBA DISABLED_BUTTON_COLOR = new ColorRGBA(0.090f, 0.105f, 0.095f, 1f); + private static final ColorRGBA SEARCH_BUTTON_COLOR = new ColorRGBA(0.070f, 0.085f, 0.075f, 1f); + private static final ColorRGBA PRIMARY_TEXT_COLOR = new ColorRGBA(0.930f, 0.960f, 0.900f, 1f); + private static final ColorRGBA MUTED_TEXT_COLOR = new ColorRGBA(0.500f, 0.560f, 0.480f, 1f); + private static final ColorRGBA SEARCH_TEXT_COLOR = new ColorRGBA(0.765f, 0.910f, 0.555f, 1f); + + private final List