Move everything under kotlin-native folder
I was forced to manually do update the following files, because otherwise they would be ignored according .gitignore settings. Probably they should be deleted from repo. Interop/.idea/compiler.xml Interop/.idea/gradle.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_3.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_3.xml Interop/.idea/modules.xml Interop/.idea/modules/Indexer/Indexer.iml Interop/.idea/modules/Runtime/Runtime.iml Interop/.idea/modules/StubGenerator/StubGenerator.iml backend.native/backend.native.iml backend.native/bc.frontend/bc.frontend.iml backend.native/cli.bc/cli.bc.iml backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt backend.native/tests/link/lib/foo.kt backend.native/tests/link/lib/foo2.kt backend.native/tests/teamcity-test.property
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# Android Native Activity
|
||||
|
||||
This example shows how to build an Android Native Activity. Also, we provide an example
|
||||
bridging mechanism for the Java APIs, callable from Native side.
|
||||
|
||||
The example will render a textured dodecahedron using OpenGL ES library. It can be rotated with fingers.
|
||||
Please make sure that Android SDK version 28 is installed, using Android SDK manager in Android Studio.
|
||||
See https://developer.android.com/studio/index.html for more details on Android Studio or
|
||||
`$ANDROID_HOME/tools/bin/sdkmanager "platforms;android-28" "build-tools;28.0.3"` from command line.
|
||||
We use JniBridge to call vibration service on the Java side for short tremble on startup.
|
||||
|
||||
To build use `ANDROID_HOME=<your path to android sdk> ../gradlew assemble`.
|
||||
|
||||
Run `$ANDROID_HOME/platform-tools/adb install -r build/outputs/apk/debug/androidNativeActivity-debug.apk`
|
||||
to deploy the apk on the Android device or emulator.
|
||||
|
||||
Note that "Emulated Performance - Graphics" in AVD manager must be set to "Software - GLES 2.0".
|
||||
|
||||
Note: If you are importing project to IDEA for the first time, you might need to put `local.properties` file
|
||||
with the following content:
|
||||
|
||||
sdk.dir=<your path to Android SDK>
|
||||
@@ -0,0 +1,108 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetPreset
|
||||
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
|
||||
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-dev")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
}
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("com.android.application") version("3.6.0")
|
||||
}
|
||||
|
||||
val appDir = buildDir.resolve("Polyhedron")
|
||||
val libsDir = appDir.resolve("libs")
|
||||
// Set to true to build only for the simulator.
|
||||
val simulatorOnly = true
|
||||
|
||||
val androidPresets = mapOf(
|
||||
"arm32" to ("androidNativeArm32" to "$libsDir/armeabi-v7a"),
|
||||
"arm64" to ("androidNativeArm64" to "$libsDir/arm64-v8a"),
|
||||
"x86" to ("androidNativeX86" to "$libsDir/x86"),
|
||||
"x64" to ("androidNativeX64" to "$libsDir/x86_64")
|
||||
)
|
||||
|
||||
android {
|
||||
compileSdkVersion(28)
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.jetbrains.konan_activity2"
|
||||
minSdkVersion(9)
|
||||
targetSdkVersion(28)
|
||||
|
||||
ndk {
|
||||
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val main by getting {
|
||||
setRoot("src/x86Main")
|
||||
jniLibs.srcDir(libsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
androidPresets.forEach { (targetName, presetInfo) ->
|
||||
val (presetName, _) = presetInfo
|
||||
val preset = kotlin.presets[presetName] as KotlinNativeTargetPreset
|
||||
targetFromPreset(preset, targetName) {
|
||||
binaries {
|
||||
executable {
|
||||
entryPoint = "sample.androidnative.main"
|
||||
}
|
||||
}
|
||||
compilations["main"].cinterops {
|
||||
val bmpformat by creating
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val x86Main by getting
|
||||
if (!simulatorOnly) {
|
||||
val x64Main by getting
|
||||
val arm32Main by getting
|
||||
val arm64Main by getting
|
||||
arm32Main.dependsOn(x86Main)
|
||||
arm64Main.dependsOn(x86Main)
|
||||
x64Main.dependsOn(x86Main)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disable generating Kotlin metadata.
|
||||
tasks.compileKotlinMetadata {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
androidPresets.forEach { (targetName, presetInfo) ->
|
||||
val target = kotlin.targets[targetName] as KotlinNativeTarget
|
||||
val (_, libDir) = presetInfo
|
||||
|
||||
NativeBuildType.values().forEach {
|
||||
val executable = target.binaries.getExecutable(it)
|
||||
val linkTask = executable.linkTask
|
||||
val binaryFile = executable.outputFile
|
||||
|
||||
linkTask.doLast {
|
||||
copy {
|
||||
from(binaryFile)
|
||||
into(libDir)
|
||||
rename { "libpoly.so" }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.preBuild {
|
||||
dependsOn(linkTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
DIR=$(cd "$(dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
|
||||
$DIR/../gradlew -p $DIR assemble
|
||||
@@ -0,0 +1,15 @@
|
||||
kotlin.code.style=official
|
||||
|
||||
# Run parallel builds in Gradle:
|
||||
org.gradle.parallel=true
|
||||
org.gradle.workers.max=4
|
||||
|
||||
# Pin Kotlin version:
|
||||
# CHANGE_VERSION_WITH_RELEASE
|
||||
kotlin_version=1.4.10
|
||||
|
||||
# Use custom Kotlin/Native home:
|
||||
kotlin.native.home=../../dist
|
||||
|
||||
# Increase memory for in-process compiler execution.
|
||||
org.gradle.jvmargs=-Xmx3g
|
||||
BIN
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,22 @@
|
||||
pluginManagement {
|
||||
val kotlin_version: String by settings
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
if (requested.id.id == "org.jetbrains.kotlin.multiplatform") {
|
||||
useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
||||
}
|
||||
if (requested.id.id == "com.android.application") {
|
||||
useModule("com.android.tools.build:gradle:${requested.version}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-dev")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package = sample.androidnative.bmpformat
|
||||
|
||||
---
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t magic;
|
||||
uint32_t size;
|
||||
uint32_t zero;
|
||||
uint8_t padding1[8];
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint8_t padding2[2];
|
||||
uint16_t bits;
|
||||
uint8_t padding3[24];
|
||||
} BMPHeader;
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- BEGIN_INCLUDE(manifest) -->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="polyhedron"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
|
||||
|
||||
<!-- This .apk has no Java code itself, so set hasCode to false. -->
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:fullBackupContent="false"
|
||||
android:icon="@mipmap/konan_activity"
|
||||
android:label="@string/app_name"
|
||||
android:hasCode="false">
|
||||
|
||||
<!-- Our activity is the built-in NativeActivity framework class.
|
||||
This will take care of integrating with our NDK code. -->
|
||||
<activity android:name="android.app.NativeActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<!-- Tell NativeActivity the name of our .so -->
|
||||
<meta-data android:name="android.app.lib_name"
|
||||
android:value="poly" />
|
||||
<meta-data android:name="android.app.func_name"
|
||||
android:value="Konan_main" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
<!-- END_INCLUDE(manifest) -->
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,7 @@
|
||||
package sample.androidnative
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import sample.androidnative.bmpformat.BMPHeader
|
||||
|
||||
val BMPHeader.data
|
||||
get() = (ptr.reinterpret<ByteVar>() + sizeOf<BMPHeader>()) as CArrayPointer<ByteVar>
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.androidnative
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
/**
|
||||
* Disposable class manages and owns all its native resources. It allocates some resources
|
||||
* during construction and may allocate some additional resources during operation.
|
||||
* It must free all its resource once [dispose] is invoked. "Disposed" is a final state of the disposable
|
||||
* class. It is not supposed to be used after being disposed.
|
||||
*/
|
||||
interface Disposable {
|
||||
/**
|
||||
* Disposes all native resources owned by this class. This function must be invoked
|
||||
* exactly once as the last operation on the corresponding class.
|
||||
*/
|
||||
fun dispose()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to implement [Disposable] interface. It contains an [arena] for native
|
||||
* memory allocations and a number of helper methods to simplify management of other
|
||||
* kinds of native resources.
|
||||
*
|
||||
* It is important to wrap all potentially exception-throwing code in the class constructor
|
||||
* into [tryConstruct] invocation, because, when object construction fails with exception,
|
||||
* it ensures that all the resource that were allocated so far will get freed.
|
||||
*/
|
||||
abstract class DisposableContainer : Disposable {
|
||||
val arena = Arena()
|
||||
|
||||
override fun dispose() {
|
||||
arena.clear()
|
||||
}
|
||||
|
||||
inline fun <T> tryConstruct(init: () -> T): T =
|
||||
try { init() }
|
||||
catch (e: Throwable) {
|
||||
dispose()
|
||||
throw e
|
||||
}
|
||||
|
||||
inline fun <T> disposable(
|
||||
message: String = "disposable",
|
||||
create: () -> T?,
|
||||
crossinline dispose: (T) -> Unit
|
||||
): T =
|
||||
tryConstruct {
|
||||
create()?.also {
|
||||
arena.defer { dispose(it) }
|
||||
} ?: throw Error(message)
|
||||
}
|
||||
|
||||
inline fun <T : Disposable> disposable(create: () -> T): T =
|
||||
disposable(
|
||||
create = create,
|
||||
dispose = { it.dispose() })
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.androidnative
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.android.*
|
||||
import platform.posix.*
|
||||
import platform.gles3.*
|
||||
import platform.linux.*
|
||||
|
||||
fun logError(message: String) {
|
||||
__android_log_write(ANDROID_LOG_ERROR.convert(), "KonanActivity", message)
|
||||
}
|
||||
|
||||
fun logInfo(message: String) {
|
||||
__android_log_write(ANDROID_LOG_INFO.convert(), "KonanActivity", message)
|
||||
}
|
||||
|
||||
private fun getUnixError() = strerror(posix_errno())!!.toKString()
|
||||
|
||||
const val LOOPER_ID_INPUT = 2
|
||||
|
||||
inline fun <reified T : CPointed> CPointer<*>?.dereferenceAs(): T = this!!.reinterpret<T>().pointed
|
||||
|
||||
class Engine(val state: NativeActivityState) : DisposableContainer() {
|
||||
private val renderer = Renderer(this, state.activity!!.pointed, state.savedState)
|
||||
private var queue: CPointer<AInputQueue>? = null
|
||||
private var rendererState: COpaquePointer? = null
|
||||
|
||||
private var currentPoint = Vector2.Zero
|
||||
private var startPoint = Vector2.Zero
|
||||
private var startTime = 0.0f
|
||||
private var animationEndTime = 0.0f
|
||||
private var velocity = Vector2.Zero
|
||||
private var acceleration = Vector2.Zero
|
||||
|
||||
private var needRedraw = true
|
||||
private var animating = false
|
||||
private val pointerSize = CPointerVar.size
|
||||
|
||||
private val now = arena.alloc<timespec>()
|
||||
private val eventPointer = arena.alloc<COpaquePointerVar>()
|
||||
private val inputEvent = arena.alloc<CPointerVar<AInputEvent>>()
|
||||
|
||||
fun mainLoop() {
|
||||
callToManagedAPI()
|
||||
|
||||
val fd = arena.alloc<IntVar>()
|
||||
while (true) {
|
||||
// Process events.
|
||||
eventLoop@ while (true) {
|
||||
val id = ALooper_pollAll(if (needRedraw || animating) 0 else -1, fd.ptr, null, null)
|
||||
if (id < 0) break@eventLoop
|
||||
when (id) {
|
||||
LOOPER_ID_SYS -> if (!processSysEvent(fd)) return // An error occured.
|
||||
LOOPER_ID_INPUT -> processUserInput()
|
||||
else -> logError("Unprocessed event: $id")
|
||||
}
|
||||
}
|
||||
when {
|
||||
animating -> {
|
||||
val elapsed = getTime() - startTime
|
||||
if (elapsed >= animationEndTime) {
|
||||
animating = false
|
||||
} else {
|
||||
move(startPoint + velocity * elapsed + acceleration * (elapsed * elapsed * 0.5f))
|
||||
renderer.draw()
|
||||
}
|
||||
}
|
||||
|
||||
needRedraw -> renderer.draw()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
renderer.destroy()
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
private val jniBrigde = JniBridge(state.activity!!.pointed.vm!!)
|
||||
|
||||
private fun callToManagedAPI() = jniBrigde.withLocalFrame {
|
||||
// Actually, this is a context pointer.
|
||||
val context = JniObject(state.activity!!.pointed.clazz!!)
|
||||
|
||||
val contextClass = FindClass("android/content/Context")
|
||||
val getSystemServiceMethod = GetMethodID(
|
||||
contextClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;")!!
|
||||
val vibrator = CallObjectMethod(context, getSystemServiceMethod, "vibrator")
|
||||
if (vibrator != null) {
|
||||
val vibratorClass = FindClass("android/os/Vibrator")
|
||||
val vibrateMethod = GetMethodID(vibratorClass, "vibrate", "(J)V")!!
|
||||
CallVoidMethod(vibrator, vibrateMethod, 500L)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processSysEvent(fd: IntVar): Boolean {
|
||||
val readBytes = read(fd.value, eventPointer.ptr, pointerSize.convert()).toLong()
|
||||
if (readBytes != pointerSize.toLong()) {
|
||||
logError("Failure reading event, $readBytes read: ${getUnixError()}")
|
||||
return true
|
||||
}
|
||||
try {
|
||||
val event = eventPointer.value.dereferenceAs<NativeActivityEvent>()
|
||||
println("got ${event.eventKind}")
|
||||
when (event.eventKind) {
|
||||
NativeActivityEventKind.START -> {
|
||||
logInfo("Activity started")
|
||||
renderer.start()
|
||||
}
|
||||
|
||||
NativeActivityEventKind.STOP -> {
|
||||
renderer.stop()
|
||||
}
|
||||
|
||||
NativeActivityEventKind.DESTROY -> {
|
||||
rendererState?.let {
|
||||
free(it)
|
||||
rendererState = null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
NativeActivityEventKind.NATIVE_WINDOW_CREATED -> {
|
||||
val windowEvent = eventPointer.value.dereferenceAs<NativeActivityWindowEvent>()
|
||||
if (!renderer.initialize(windowEvent.window!!))
|
||||
return false
|
||||
logInfo("Renderer initialized")
|
||||
renderer.draw()
|
||||
}
|
||||
|
||||
NativeActivityEventKind.INPUT_QUEUE_CREATED -> {
|
||||
val queueEvent = eventPointer.value.dereferenceAs<NativeActivityQueueEvent>()
|
||||
if (queue != null)
|
||||
AInputQueue_detachLooper(queue)
|
||||
queue = queueEvent.queue
|
||||
AInputQueue_attachLooper(queue, state.looper, LOOPER_ID_INPUT, null, null)
|
||||
}
|
||||
|
||||
NativeActivityEventKind.INPUT_QUEUE_DESTROYED -> {
|
||||
val queueEvent = eventPointer.value.dereferenceAs<NativeActivityQueueEvent>()
|
||||
AInputQueue_detachLooper(queueEvent.queue)
|
||||
}
|
||||
|
||||
NativeActivityEventKind.NATIVE_WINDOW_DESTROYED -> {
|
||||
renderer.destroy()
|
||||
}
|
||||
|
||||
NativeActivityEventKind.SAVE_INSTANCE_STATE -> {
|
||||
val saveStateEvent = eventPointer.value.dereferenceAs<NativeActivitySaveStateEvent>()
|
||||
val state = renderer.getState()
|
||||
val dataSize = state.second
|
||||
rendererState = realloc(rendererState, dataSize.convert())
|
||||
memcpy(rendererState, state.first, dataSize.convert())
|
||||
logInfo("Saving instance state to $rendererState: $dataSize bytes")
|
||||
saveStateEvent.savedState = rendererState
|
||||
saveStateEvent.savedStateSize = dataSize.convert()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
notifySysEventProcessed()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getTime(): Float {
|
||||
clock_gettime(CLOCK_MONOTONIC, now.ptr)
|
||||
return now.tv_sec + now.tv_nsec / 1_000_000_000.0f
|
||||
}
|
||||
|
||||
private fun getEventPoint(event: CPointer<AInputEvent>?, i: Int) =
|
||||
Vector2(AMotionEvent_getRawX(event, i.convert()), AMotionEvent_getRawY(event, i.convert()))
|
||||
|
||||
private fun getEventTime(event: CPointer<AInputEvent>?) =
|
||||
AMotionEvent_getEventTime(event) / 1_000_000_000.0f
|
||||
|
||||
private fun processUserInput(): Unit {
|
||||
if (AInputQueue_getEvent(queue, inputEvent.ptr) < 0) {
|
||||
logError("Failure reading input event")
|
||||
return
|
||||
}
|
||||
val event = inputEvent.value
|
||||
val eventType = AInputEvent_getType(event)
|
||||
if (eventType.toUInt() == AINPUT_EVENT_TYPE_MOTION) {
|
||||
val action = AKeyEvent_getAction(event).toUInt() and AMOTION_EVENT_ACTION_MASK
|
||||
when (action) {
|
||||
AMOTION_EVENT_ACTION_DOWN -> {
|
||||
animating = false
|
||||
currentPoint = getEventPoint(event, 0)
|
||||
startTime = getEventTime(event)
|
||||
startPoint = currentPoint
|
||||
}
|
||||
|
||||
AMOTION_EVENT_ACTION_UP -> {
|
||||
val endPoint = getEventPoint(event, 0)
|
||||
val endTime = getEventTime(event)
|
||||
animating = true
|
||||
velocity = (endPoint - startPoint) / (endTime - startTime + 1e-9f)
|
||||
if (velocity.length > renderer.screen.length)
|
||||
velocity = velocity * (renderer.screen.length / velocity.length)
|
||||
acceleration = velocity.normalized() * (-renderer.screen.length * 0.5f)
|
||||
animationEndTime = velocity.length / acceleration.length
|
||||
startPoint = endPoint
|
||||
startTime = endTime
|
||||
move(endPoint)
|
||||
}
|
||||
|
||||
AMOTION_EVENT_ACTION_MOVE -> {
|
||||
val numberOfPointers = AMotionEvent_getPointerCount(event).toInt()
|
||||
for (i in 0 until numberOfPointers)
|
||||
move(getEventPoint(event, i))
|
||||
}
|
||||
}
|
||||
}
|
||||
AInputQueue_finishEvent(queue, event, 1)
|
||||
}
|
||||
|
||||
private fun move(newPoint: Vector2) {
|
||||
renderer.rotateBy(newPoint - currentPoint)
|
||||
currentPoint = newPoint
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.androidnative
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.android.*
|
||||
|
||||
data class JniClass(val jclass: jclass)
|
||||
data class JniObject(val jobject: jobject)
|
||||
data class JniMethod(val jmethod: jmethodID)
|
||||
|
||||
fun asJniClass(jclass: jclass?) =
|
||||
if (jclass != null) JniClass(jclass) else null
|
||||
|
||||
fun asJniObject(jobject: jobject?) =
|
||||
if (jobject != null) JniObject(jobject) else null
|
||||
|
||||
fun asJniMethod(jmethodID: jmethodID?) =
|
||||
if (jmethodID != null) JniMethod(jmethodID) else null
|
||||
|
||||
class JniBridge(val vm: CPointer<JavaVMVar>) {
|
||||
private val vmFunctions: JNIInvokeInterface = vm.pointed.pointed!!
|
||||
val jniEnv = memScoped {
|
||||
val envStorage = alloc<CPointerVar<JNIEnvVar>>()
|
||||
if (vmFunctions.AttachCurrentThreadAsDaemon!!(vm, envStorage.ptr, null) != 0)
|
||||
throw Error("Cannot attach thread to the VM")
|
||||
envStorage.value!!
|
||||
}
|
||||
private val envFunctions: JNINativeInterface = jniEnv.pointed.pointed!!
|
||||
|
||||
// JNI operations.
|
||||
private val fNewStringUTF = envFunctions.NewStringUTF!!
|
||||
private val fFindClass = envFunctions.FindClass!!
|
||||
private val fGetMethodID = envFunctions.GetMethodID!!
|
||||
private val fCallVoidMethodA = envFunctions.CallVoidMethodA!!
|
||||
private val fCallObjectMethodA = envFunctions.CallObjectMethodA!!
|
||||
private val fExceptionCheck = envFunctions.ExceptionCheck!!
|
||||
private val fExceptionDescribe = envFunctions.ExceptionDescribe!!
|
||||
private val fExceptionClear = envFunctions.ExceptionClear!!
|
||||
val fPushLocalFrame = envFunctions.PushLocalFrame!!
|
||||
val fPopLocalFrame = envFunctions.PopLocalFrame!!
|
||||
|
||||
private fun check() {
|
||||
if (fExceptionCheck(jniEnv) != 0.toUByte()) {
|
||||
fExceptionDescribe(jniEnv)
|
||||
fExceptionClear(jniEnv)
|
||||
throw Error("JVM exception thrown")
|
||||
}
|
||||
}
|
||||
|
||||
fun toJString(string: String) = memScoped {
|
||||
val result = asJniObject(fNewStringUTF(jniEnv, string.cstr.ptr))
|
||||
check()
|
||||
result
|
||||
}
|
||||
|
||||
fun toJValues(arguments: Array<out Any?>, scope: MemScope): CPointer<jvalue>? {
|
||||
val result = scope.allocArray<jvalue>(arguments.size)
|
||||
arguments.mapIndexed { index, it ->
|
||||
when (it) {
|
||||
null -> result[index].l = null
|
||||
is JniObject -> result[index].l = it.jobject
|
||||
is String -> result[index].l = toJString(it)?.jobject
|
||||
is Int -> result[index].i = it
|
||||
is Long -> result[index].j = it
|
||||
else -> throw Error("Unsupported conversion for ${it::class.simpleName}")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun FindClass(name: String) = memScoped {
|
||||
val result = asJniClass(fFindClass(jniEnv, name.cstr.ptr))
|
||||
check()
|
||||
result
|
||||
}
|
||||
|
||||
fun GetMethodID(clazz: JniClass?, name: String, signature: String) = memScoped {
|
||||
val result = asJniMethod(fGetMethodID(jniEnv, clazz?.jclass, name.cstr.ptr, signature.cstr.ptr))
|
||||
check()
|
||||
result
|
||||
}
|
||||
|
||||
fun CallVoidMethod(receiver: JniObject?, method: JniMethod, vararg arguments: Any?) = memScoped {
|
||||
fCallVoidMethodA(jniEnv, receiver?.jobject, method.jmethod,
|
||||
toJValues(arguments, this@memScoped))
|
||||
check()
|
||||
}
|
||||
|
||||
fun CallObjectMethod(receiver: JniObject?, method: JniMethod, vararg arguments: Any?) = memScoped {
|
||||
val result = asJniObject(fCallObjectMethodA(jniEnv, receiver?.jobject, method.jmethod,
|
||||
toJValues(arguments, this@memScoped)))
|
||||
check()
|
||||
result
|
||||
}
|
||||
|
||||
// Usually, use this
|
||||
inline fun <T> withLocalFrame(block: JniBridge.() -> T): T {
|
||||
if (fPushLocalFrame(jniEnv, 0) < 0)
|
||||
throw Error("Cannot push new local frame")
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
fPopLocalFrame(jniEnv, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.androidnative
|
||||
|
||||
const val Zero = 0.0f
|
||||
const val DodeA = 0.93417235896f // (Sqrt(5) + 1) / (2 * Sqrt(3))
|
||||
const val DodeB = 0.35682208977f // (Sqrt(5) - 1) / (2 * Sqrt(3))
|
||||
const val DodeC = 0.57735026919f // 1 / Sqrt(3)
|
||||
const val IcosA = 0.52573111212f // Sqrt(5 - Sqrt(5)) / Sqrt(10)
|
||||
const val IcosB = 0.85065080835f // Sqrt(5 + Sqrt(5)) / Sqrt(10)
|
||||
|
||||
enum class RegularPolyhedra(val vertices: Array<Vector3>, val faces: Array<ByteArray>) {
|
||||
Dodecahedron(
|
||||
arrayOf(
|
||||
Vector3(-DodeA, Zero, DodeB), Vector3(-DodeA, Zero, -DodeB), Vector3(DodeA, Zero, -DodeB),
|
||||
Vector3(DodeA, Zero, DodeB), Vector3(DodeB, -DodeA, Zero), Vector3(-DodeB, -DodeA, Zero),
|
||||
Vector3(-DodeB, DodeA, Zero), Vector3(DodeB, DodeA, Zero), Vector3(Zero, DodeB, -DodeA),
|
||||
Vector3(Zero, -DodeB, -DodeA), Vector3(Zero, -DodeB, DodeA), Vector3(Zero, DodeB, DodeA),
|
||||
Vector3(-DodeC, -DodeC, DodeC), Vector3(-DodeC, -DodeC, -DodeC), Vector3(DodeC, -DodeC, -DodeC),
|
||||
Vector3(DodeC, -DodeC, DodeC), Vector3(-DodeC, DodeC, DodeC), Vector3(-DodeC, DodeC, -DodeC),
|
||||
Vector3(DodeC, DodeC, -DodeC), Vector3(DodeC, DodeC, DodeC)
|
||||
),
|
||||
arrayOf(
|
||||
byteArrayOf(0, 12, 10, 11, 16), byteArrayOf(1, 17, 8, 9, 13), byteArrayOf(2, 14, 9, 8, 18),
|
||||
byteArrayOf(3, 19, 11, 10, 15), byteArrayOf(4, 14, 2, 3, 15), byteArrayOf(5, 12, 0, 1, 13),
|
||||
byteArrayOf(6, 17, 1, 0, 16), byteArrayOf(7, 19, 3, 2, 18), byteArrayOf(8, 17, 6, 7, 18),
|
||||
byteArrayOf(9, 14, 4, 5, 13), byteArrayOf(10, 12, 5, 4, 15), byteArrayOf(11, 19, 7, 6, 16)
|
||||
)),
|
||||
Icosahedron(
|
||||
arrayOf(
|
||||
Vector3(-IcosA, Zero, IcosB), Vector3(IcosA, Zero, IcosB), Vector3(-IcosA, Zero, -IcosB),
|
||||
Vector3(IcosA, Zero, -IcosB), Vector3(Zero, IcosB, IcosA), Vector3(Zero, IcosB, -IcosA),
|
||||
Vector3(Zero, -IcosB, IcosA), Vector3(Zero, -IcosB, -IcosA), Vector3(IcosB, IcosA, Zero),
|
||||
Vector3(-IcosB, IcosA, Zero), Vector3(IcosB, -IcosA, Zero), Vector3(-IcosB, -IcosA, Zero)
|
||||
),
|
||||
arrayOf(
|
||||
byteArrayOf(1, 4, 0), byteArrayOf(4, 9, 0), byteArrayOf(4, 5, 9), byteArrayOf(8, 5, 4),
|
||||
byteArrayOf(1, 8, 4), byteArrayOf(1, 10, 8), byteArrayOf(10, 3, 8), byteArrayOf(8, 3, 5),
|
||||
byteArrayOf(3, 2, 5), byteArrayOf(3, 7, 2), byteArrayOf(3, 10, 7), byteArrayOf(10, 6, 7),
|
||||
byteArrayOf(6, 11, 7), byteArrayOf(6, 0, 11), byteArrayOf(6, 1, 0), byteArrayOf(10, 1, 6),
|
||||
byteArrayOf(11, 0, 9), byteArrayOf(2, 11, 9), byteArrayOf(5, 2, 9), byteArrayOf(11, 2, 7)
|
||||
)
|
||||
);
|
||||
|
||||
val verticesPerFace get() = faces[0].size
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.androidnative
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.android.*
|
||||
import platform.egl.*
|
||||
import platform.posix.*
|
||||
import platform.gles.*
|
||||
import sample.androidnative.bmpformat.BMPHeader
|
||||
|
||||
class Renderer(val container: DisposableContainer,
|
||||
val nativeActivity: ANativeActivity,
|
||||
val savedMatrix: COpaquePointer?) {
|
||||
private var display: EGLDisplay? = null
|
||||
private var surface: EGLSurface? = null
|
||||
private var context: EGLContext? = null
|
||||
private var initialized = false
|
||||
|
||||
var screen = Vector2.Zero
|
||||
|
||||
private val matrix = container.arena.allocArray<FloatVar>(16)
|
||||
|
||||
init {
|
||||
if (savedMatrix != null) {
|
||||
memcpy(matrix, savedMatrix, 16 * 4)
|
||||
} else {
|
||||
for (i in 0..3)
|
||||
for (j in 0..3)
|
||||
matrix[i * 4 + j] = if (i == j) 1.0f else 0.0f
|
||||
}
|
||||
}
|
||||
|
||||
fun initialize(window: CPointer<ANativeWindow>): Boolean {
|
||||
with(container.arena) {
|
||||
logInfo("Initializing context..")
|
||||
display = eglGetDisplay(null)
|
||||
if (display == null) {
|
||||
logError("eglGetDisplay() returned error ${eglGetError()}")
|
||||
return false
|
||||
}
|
||||
if (eglInitialize(display, null, null) == 0u) {
|
||||
logError("eglInitialize() returned error ${eglGetError()}")
|
||||
return false
|
||||
}
|
||||
|
||||
val attribs = cValuesOf(
|
||||
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_NONE
|
||||
)
|
||||
val numConfigs = alloc<EGLintVar>()
|
||||
if (eglChooseConfig(display, attribs, null, 0, numConfigs.ptr) == 0u) {
|
||||
throw Error("eglChooseConfig()#1 returned error ${eglGetError()}")
|
||||
}
|
||||
val supportedConfigs = allocArray<EGLConfigVar>(numConfigs.value)
|
||||
if (eglChooseConfig(display, attribs, supportedConfigs, numConfigs.value, numConfigs.ptr) == 0u) {
|
||||
throw Error("eglChooseConfig()#2 returned error ${eglGetError()}")
|
||||
}
|
||||
var configIndex = 0
|
||||
while (configIndex < numConfigs.value) {
|
||||
val r = alloc<EGLintVar>()
|
||||
val g = alloc<EGLintVar>()
|
||||
val b = alloc<EGLintVar>()
|
||||
val d = alloc<EGLintVar>()
|
||||
if (eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_RED_SIZE, r.ptr) != 0u &&
|
||||
eglGetConfigAttrib (display, supportedConfigs[configIndex], EGL_GREEN_SIZE, g.ptr) != 0u &&
|
||||
eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_BLUE_SIZE, b.ptr) != 0u &&
|
||||
eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_DEPTH_SIZE, d.ptr) != 0u &&
|
||||
r.value == 8 && g.value == 8 && b.value == 8 && d.value == 0) break
|
||||
++configIndex
|
||||
}
|
||||
if (configIndex >= numConfigs.value)
|
||||
configIndex = 0
|
||||
|
||||
surface = eglCreateWindowSurface(display, supportedConfigs[configIndex], window, null)
|
||||
if (surface == null) {
|
||||
throw Error("eglCreateWindowSurface() returned error ${eglGetError()}")
|
||||
}
|
||||
|
||||
context = eglCreateContext(display, supportedConfigs[configIndex], null, null)
|
||||
if (context == null) {
|
||||
throw Error("eglCreateContext() returned error ${eglGetError()}")
|
||||
}
|
||||
|
||||
if (eglMakeCurrent(display, surface, surface, context) == 0u) {
|
||||
throw Error("eglMakeCurrent() returned error ${eglGetError()}")
|
||||
}
|
||||
|
||||
val width = alloc<EGLintVar>()
|
||||
val height = alloc<EGLintVar>()
|
||||
if (eglQuerySurface(display, surface, EGL_WIDTH, width.ptr) == 0u
|
||||
|| eglQuerySurface (display, surface, EGL_HEIGHT, height.ptr) == 0u) {
|
||||
throw Error("eglQuerySurface() returned error ${eglGetError()}")
|
||||
}
|
||||
|
||||
this@Renderer.screen = Vector2(width.value.toFloat(), height.value.toFloat())
|
||||
|
||||
glDisable(GL_DITHER)
|
||||
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST)
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
|
||||
glEnable(GL_CULL_FACE)
|
||||
glShadeModel(GL_SMOOTH)
|
||||
glEnable(GL_DEPTH_TEST)
|
||||
|
||||
glViewport(0, 0, width.value, height.value)
|
||||
|
||||
val ratio = width.value.toFloat() / height.value
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
checkErrors()
|
||||
glLoadIdentity()
|
||||
glFrustumf(-ratio, ratio, -1.0f, 1.0f, 1.0f, 10.0f)
|
||||
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
checkErrors()
|
||||
glTranslatef(0.0f, 0.0f, -2.0f)
|
||||
glLightfv(GL_LIGHT0, GL_POSITION, cValuesOf(1.25f, 1.25f, -2.0f, 0.0f))
|
||||
glEnable(GL_LIGHTING)
|
||||
glEnable(GL_LIGHT0)
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, cValuesOf(0.0f, 1.0f, 1.0f, 1.0f))
|
||||
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, cValuesOf(0.3f, 0.3f, 0.3f, 1.0f))
|
||||
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 30.0f)
|
||||
|
||||
loadTexture("kotlin_logo.bmp")
|
||||
|
||||
initialized = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun checkErrors() {
|
||||
val error = glGetError()
|
||||
if (error.toInt() != GL_NO_ERROR)
|
||||
throw Error("OpenGL error 0x${error.toInt().toString(16)}")
|
||||
}
|
||||
|
||||
fun getState() = matrix to 16 * 4
|
||||
|
||||
fun rotateBy(vector: Vector2) {
|
||||
if (!initialized) return
|
||||
|
||||
val length = vector.length
|
||||
if (length < 1e-9f) return
|
||||
val angle = 180 * length / screen.length
|
||||
val x = -vector.y / length
|
||||
val y = -vector.x / length
|
||||
|
||||
glPushMatrix()
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
checkErrors()
|
||||
glLoadIdentity()
|
||||
glRotatef(angle, x, y, 0.0f)
|
||||
glMultMatrixf(matrix)
|
||||
glGetFloatv(GL_MODELVIEW_MATRIX, matrix)
|
||||
glPopMatrix()
|
||||
}
|
||||
|
||||
|
||||
private fun loadTexture(assetName: String): Unit = memScoped {
|
||||
val asset = AAssetManager_open(nativeActivity.assetManager, assetName, AASSET_MODE_BUFFER.convert())
|
||||
?: throw Error("Error opening asset $assetName")
|
||||
println("loading texture $assetName")
|
||||
try {
|
||||
val length = AAsset_getLength(asset)
|
||||
val buffer = allocArray<ByteVar>(length)
|
||||
if (AAsset_read(asset, buffer, length.convert()) != length.toInt()) {
|
||||
throw Error("Error reading asset")
|
||||
}
|
||||
|
||||
with(buffer.reinterpret<BMPHeader>().pointed) {
|
||||
if (magic != 0x4d42.toUShort() || zero != 0u || size != length.toUInt() || bits != 24.toUShort()) {
|
||||
throw Error("Error parsing texture file")
|
||||
}
|
||||
val numberOfBytes = width * height * 3
|
||||
// Swap BGR in bitmap to RGB.
|
||||
for (i in 0 until numberOfBytes step 3) {
|
||||
val t = data[i]
|
||||
data[i] = data[i + 2]
|
||||
data[i + 2] = t
|
||||
}
|
||||
println("loaded texture ${width}x${height}")
|
||||
glBindTexture(GL_TEXTURE_2D, 1)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
||||
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND.toFloat())
|
||||
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, cValuesOf(1.0f, 1.0f, 1.0f, 1.0f))
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data)
|
||||
|
||||
}
|
||||
} finally {
|
||||
AAsset_close(asset)
|
||||
}
|
||||
}
|
||||
|
||||
private val texturePoints = arrayOf(
|
||||
Vector2(0.0f, 0.2f), Vector2(0.0f, 0.8f), Vector2(0.6f, 1.0f), Vector2(1.0f, 0.5f), Vector2(0.8f, 0.0f)
|
||||
)
|
||||
|
||||
private val scale = 1.25f
|
||||
|
||||
fun draw(): Unit {
|
||||
if (!initialized) return
|
||||
|
||||
glPushMatrix()
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
checkErrors()
|
||||
|
||||
glMultMatrixf(matrix)
|
||||
|
||||
glClear((GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT).toUInt())
|
||||
|
||||
glEnableClientState(GL_VERTEX_ARRAY)
|
||||
glEnableClientState(GL_NORMAL_ARRAY)
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY)
|
||||
|
||||
val polygon = RegularPolyhedra.Dodecahedron
|
||||
val vertices = mutableListOf<Float>()
|
||||
val texCoords = mutableListOf<Float>()
|
||||
val triangles = mutableListOf<Byte>()
|
||||
val normals = mutableListOf<Float>()
|
||||
for (face in polygon.faces) {
|
||||
val u = polygon.vertices[face[2].toInt()] - polygon.vertices[face[1].toInt()]
|
||||
val v = polygon.vertices[face[0].toInt()] - polygon.vertices[face[1].toInt()]
|
||||
val normal = u.crossProduct(v).normalized()
|
||||
|
||||
val copiedFace = ByteArray(face.size)
|
||||
for (j in face.indices) {
|
||||
copiedFace[j] = (vertices.size / 4).toByte()
|
||||
polygon.vertices[face[j].toInt()].copyCoordinatesTo(vertices)
|
||||
vertices.add(scale)
|
||||
normal.copyCoordinatesTo(normals)
|
||||
texturePoints[j].copyCoordinatesTo(texCoords)
|
||||
}
|
||||
|
||||
for (j in 1..face.size - 2) {
|
||||
triangles.add(copiedFace[0])
|
||||
triangles.add(copiedFace[j])
|
||||
triangles.add(copiedFace[j + 1])
|
||||
}
|
||||
}
|
||||
|
||||
memScoped {
|
||||
glFrontFace(GL_CW)
|
||||
glVertexPointer(4, GL_FLOAT, 0, vertices.toFloatArray().toCValues().ptr)
|
||||
glTexCoordPointer(2, GL_FLOAT, 0, texCoords.toFloatArray().toCValues().ptr)
|
||||
glNormalPointer(GL_FLOAT, 0, normals.toFloatArray().toCValues().ptr)
|
||||
glDrawElements(GL_TRIANGLES, triangles.size, GL_UNSIGNED_BYTE, triangles.toByteArray().toCValues().ptr)
|
||||
}
|
||||
|
||||
glPopMatrix()
|
||||
|
||||
if (eglSwapBuffers(display, surface) == 0u) {
|
||||
val error = eglGetError()
|
||||
if (error != EGL_BAD_SURFACE)
|
||||
throw Error("eglSwapBuffers() returned error $error")
|
||||
else {
|
||||
if (eglMakeCurrent(display, surface, surface, context) == 0u) {
|
||||
throw Error("Reinit eglMakeCurrent() returned error ${eglGetError()}")
|
||||
}
|
||||
if (eglSwapBuffers(display, surface) == 0u)
|
||||
throw Error("Bad eglSwapBuffers() after surface reinit: ${eglGetError()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun start() {
|
||||
logInfo("Starting renderer.")
|
||||
if (initialized) {
|
||||
if (eglMakeCurrent(display, surface, surface, context) == 0u) {
|
||||
throw Error("eglMakeCurrent() returned error ${eglGetError()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
logInfo("Stopping renderer..")
|
||||
eglMakeCurrent(display, null, null, null)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
if (!initialized) return
|
||||
|
||||
logInfo("Destroying renderer..")
|
||||
eglMakeCurrent(display, null, null, null)
|
||||
|
||||
context?.let { eglDestroyContext(display, it) }
|
||||
surface?.let { eglDestroySurface(display, it) }
|
||||
display?.let { eglTerminate(display) }
|
||||
|
||||
display = null
|
||||
surface = null
|
||||
context = null
|
||||
initialized = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.androidnative
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.android.*
|
||||
import platform.posix.*
|
||||
|
||||
class Vector2(val x: Float, val y: Float) {
|
||||
val length by lazy { sqrtf(x * x + y * y) }
|
||||
|
||||
fun normalized(): Vector2 {
|
||||
val len = length
|
||||
return Vector2(x / len, y / len)
|
||||
}
|
||||
|
||||
fun copyCoordinatesTo(arr: MutableList<Float>) {
|
||||
arr.add(x)
|
||||
arr.add(y)
|
||||
}
|
||||
|
||||
operator fun minus(other: Vector2) = Vector2(x - other.x, y - other.y)
|
||||
operator fun plus(other: Vector2) = Vector2(x + other.x, y + other.y)
|
||||
operator fun times(other: Float) = Vector2(x * other, y * other)
|
||||
operator fun div(other: Float) = Vector2(x / other, y / other)
|
||||
|
||||
companion object {
|
||||
val Zero = Vector2(0.0f, 0.0f)
|
||||
}
|
||||
}
|
||||
|
||||
class Vector3(val x: Float, val y: Float, val z: Float) {
|
||||
val length by lazy { sqrtf(x * x + y * y + z * z) }
|
||||
|
||||
fun crossProduct(other: Vector3): Vector3 =
|
||||
Vector3(y * other.z - z * other.y, z * other.x - x * other.z, x * other.y - y * other.x)
|
||||
|
||||
fun normalized(): Vector3 {
|
||||
val len = length
|
||||
return Vector3(x / len, y / len, z / len)
|
||||
}
|
||||
|
||||
fun copyCoordinatesTo(arr: MutableList<Float>) {
|
||||
arr.add(x)
|
||||
arr.add(y)
|
||||
arr.add(z)
|
||||
}
|
||||
|
||||
operator fun minus(other: Vector3) = Vector3(x - other.x, y - other.y, z - other.z)
|
||||
operator fun plus(other: Vector3) = Vector3(x + other.x, y + other.y, z + other.z)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package sample.androidnative
|
||||
|
||||
import kotlinx.cinterop.*
|
||||
import platform.android.*
|
||||
|
||||
fun main() {
|
||||
logInfo("Entering main().")
|
||||
memScoped {
|
||||
val state = alloc<NativeActivityState>()
|
||||
getNativeActivityState(state.ptr)
|
||||
val engine = Engine(state)
|
||||
try {
|
||||
engine.mainLoop()
|
||||
} finally {
|
||||
engine.dispose()
|
||||
}
|
||||
}
|
||||
kotlin.system.exitProcess(0)
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">KonanActivity</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user