diff --git a/build.gradle b/build.gradle index fdc79f87a39..4f814ea1818 100644 --- a/build.gradle +++ b/build.gradle @@ -138,14 +138,14 @@ void setupCompilationFlags() { "android_arm32": ["-target", getTargetArg("android_arm32"), "--sysroot=$android_arm32SysrootDir", - "-D__ANDROID__", "-DUSE_GCC_UNWIND=1", "-DUSE_ELF_SYMBOLS=1", "-DELFSIZE=32", "-DANDROID", + "-D__ANDROID__", "-DUSE_GCC_UNWIND=1", "-DUSE_ELF_SYMBOLS=1", "-DELFSIZE=32", "-DKONAN_ANDROID", // HACKS! "-I$android_arm32SysrootDir/usr/include/c++/4.9.x", "-I$android_arm32SysrootDir/usr/include/c++/4.9.x/arm-linux-androideabi"], "android_arm64": ["-target", getTargetArg("android_arm64"), "--sysroot=$android_arm64SysrootDir", - "-D__ANDROID__", "-DUSE_GCC_UNWIND=1", "-DUSE_ELF_SYMBOLS=1", "-DELFSIZE=64", "-DANDROID", + "-D__ANDROID__", "-DUSE_GCC_UNWIND=1", "-DUSE_ELF_SYMBOLS=1", "-DELFSIZE=64", "-DKONAN_ANDROID", // HACKS! "-I$android_arm64SysrootDir/usr/include/c++/4.9.x", "-I$android_arm64SysrootDir/usr/include/c++/4.9.x/aarch64-linux-android"] diff --git a/runtime/src/launcher/cpp/androidLauncher.cpp b/runtime/src/launcher/cpp/androidLauncher.cpp index d0f32deb001..aa40c34cb02 100644 --- a/runtime/src/launcher/cpp/androidLauncher.cpp +++ b/runtime/src/launcher/cpp/androidLauncher.cpp @@ -20,7 +20,8 @@ #include "KString.h" #include "Types.h" -#ifdef ANDROID +#ifdef KONAN_ANDROID + #include #include #include @@ -43,9 +44,11 @@ //--- main --------------------------------------------------------------------// extern "C" KInt Konan_start(const ObjHeader*); -static int pipeC, pipeKonan; +namespace { +int pipeC, pipeKonan; -static NativeActivityState nativeActivityState; +NativeActivityState nativeActivityState; +} extern "C" void getNativeActivityState(NativeActivityState* state) { state->activity = nativeActivityState.activity; @@ -54,13 +57,13 @@ extern "C" void getNativeActivityState(NativeActivityState* state) { state->looper = nativeActivityState.looper; } -extern "C" void notifySysEventProcessed(int fd) { - if (fd != pipeKonan) return; +extern "C" void notifySysEventProcessed() { int8_t message; write(pipeKonan, &message, sizeof(message)); } -static void* entry(void* param) { +namespace { +void* entry(void* param) { ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); ALooper_addFd(looper, pipeKonan, LOOPER_ID_SYS, ALOOPER_EVENT_INPUT, NULL, NULL); nativeActivityState.looper = looper; @@ -83,7 +86,7 @@ static void* entry(void* param) { return nullptr; } -static void runKonan_start() { +void runKonan_start() { int pipes[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipes)) { LOGE("Could not create pipe: %s", strerror(errno)); @@ -99,7 +102,7 @@ static void runKonan_start() { pthread_create(&thread, &attr, entry, nullptr); } -static void putEventSynchronously(void* event) { +void putEventSynchronously(void* event) { uint64_t value = static_cast(reinterpret_cast(event)); if (write(pipeC, &value, sizeof(value)) != sizeof(value)) { LOGE("Failure writing event: %s\n", strerror(errno)); @@ -110,25 +113,25 @@ static void putEventSynchronously(void* event) { } } -static void onDestroy(ANativeActivity* activity) { +void onDestroy(ANativeActivity* activity) { LOGV("onDestroy called"); NativeActivityEvent event = { DESTROY }; putEventSynchronously(&event); } -static void onStart(ANativeActivity* activity) { +void onStart(ANativeActivity* activity) { LOGV("onStart called"); NativeActivityEvent event = { START }; putEventSynchronously(&event); } -static void onResume(ANativeActivity* activity) { +void onResume(ANativeActivity* activity) { LOGV("onResume called"); NativeActivitySaveStateEvent event = { RESUME }; putEventSynchronously(&event); } -static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { +void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { LOGV("onSaveInstanceState called"); NativeActivitySaveStateEvent event = { SAVE_INSTANCE_STATE }; putEventSynchronously(&event); @@ -136,61 +139,61 @@ static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { return event.savedState; } -static void onPause(ANativeActivity* activity) { +void onPause(ANativeActivity* activity) { LOGV("onPause called"); NativeActivityEvent event = { PAUSE }; putEventSynchronously(&event); } -static void onStop(ANativeActivity* activity) { +void onStop(ANativeActivity* activity) { LOGV("onStop called"); NativeActivityEvent event = { STOP }; putEventSynchronously(&event); } -static void onConfigurationChanged(ANativeActivity* activity) { +void onConfigurationChanged(ANativeActivity* activity) { LOGV("onConfigurationChanged called"); NativeActivityEvent event = { CONFIGURATION_CHANGED }; putEventSynchronously(&event); } -static void onLowMemory(ANativeActivity* activity) { +void onLowMemory(ANativeActivity* activity) { LOGV("onLowMemory called"); NativeActivityEvent event = { LOW_MEMORY }; putEventSynchronously(&event); } -static void onWindowFocusChanged(ANativeActivity* activity, int focused) { +void onWindowFocusChanged(ANativeActivity* activity, int focused) { LOGV("onWindowFocusChanged called"); NativeActivityEvent event = { focused ? WINDOW_GAINED_FOCUS : WINDOW_LOST_FOCUS }; putEventSynchronously(&event); } -static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { +void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { LOGV("onNativeWindowCreated called"); NativeActivityWindowEvent event = { NATIVE_WINDOW_CREATED, window }; putEventSynchronously(&event); } -static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { +void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { LOGV("onNativeWindowDestroyed called"); NativeActivityWindowEvent event = { NATIVE_WINDOW_DESTROYED, window }; putEventSynchronously(&event); } -static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { +void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { LOGV("onInputQueueCreated called"); NativeActivityQueueEvent event = { INPUT_QUEUE_CREATED, queue }; putEventSynchronously(&event); } -static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { +void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { LOGV("onInputQueueDestroyed called"); NativeActivityQueueEvent event = { INPUT_QUEUE_DESTROYED, queue }; putEventSynchronously(&event); } +} -// ANativeActivity_onCreate. extern "C" void Konan_main(ANativeActivity* activity, void* savedState, size_t savedStateSize) { nativeActivityState = {activity, savedState, savedStateSize}; @@ -211,4 +214,4 @@ extern "C" void Konan_main(ANativeActivity* activity, void* savedState, size_t s runKonan_start(); } -#endif \ No newline at end of file +#endif // KONAN_ANDROID \ No newline at end of file diff --git a/runtime/src/launcher/cpp/androidLauncher.h b/runtime/src/launcher/cpp/androidLauncher.h index 4e6ff4b405a..d1a3cb359ff 100644 --- a/runtime/src/launcher/cpp/androidLauncher.h +++ b/runtime/src/launcher/cpp/androidLauncher.h @@ -36,7 +36,7 @@ struct NativeActivityState { void getNativeActivityState(struct NativeActivityState* state); -void notifySysEventProcessed(int fd); +void notifySysEventProcessed(); #define LOOPER_ID_SYS 1 diff --git a/runtime/src/launcher/cpp/launcher.cpp b/runtime/src/launcher/cpp/launcher.cpp index 4b73b97397e..7f92019342e 100644 --- a/runtime/src/launcher/cpp/launcher.cpp +++ b/runtime/src/launcher/cpp/launcher.cpp @@ -20,7 +20,7 @@ #include "KString.h" #include "Types.h" -#ifndef ANDROID +#ifndef KONAN_ANDROID //--- Setup args --------------------------------------------------------------// diff --git a/runtime/src/main/cpp/KString.cpp b/runtime/src/main/cpp/KString.cpp index c54c40790db..fba50b24ec4 100644 --- a/runtime/src/main/cpp/KString.cpp +++ b/runtime/src/main/cpp/KString.cpp @@ -34,7 +34,7 @@ #include "utf8.h" -#ifdef ANDROID +#ifdef KONAN_ANDROID #include #endif @@ -1133,7 +1133,7 @@ void Kotlin_io_Console_print(KString message) { const KChar* utf16 = CharArrayAddressOfElementAt(message, 0); std::string utf8; utf8::utf16to8(utf16, utf16 + message->count_, back_inserter(utf8)); - #ifdef ANDROID + #ifdef KONAN_ANDROID __android_log_print(ANDROID_LOG_INFO, "Konan_main", "%s", utf8.c_str()); #else write(STDOUT_FILENO, utf8.c_str(), utf8.size()); @@ -1146,7 +1146,7 @@ void Kotlin_io_Console_println(KString message) { } void Kotlin_io_Console_println0() { - #ifdef ANDROID + #ifdef KONAN_ANDROID __android_log_print(ANDROID_LOG_INFO, "Konan_main", "\n"); #else write(STDOUT_FILENO, "\n", 1); diff --git a/samples/androidNativeActivity/README.md b/samples/androidNativeActivity/README.md new file mode 100644 index 00000000000..04b8f5c1f03 --- /dev/null +++ b/samples/androidNativeActivity/README.md @@ -0,0 +1,10 @@ +# Android Native Activity + This example shows how to build an Android Native Activity. + +The example will render a textured dodecahedron using OpenGL ES library. It can be rotated with fingers. +Please make sure that Android SDK version 25 is installed, using Android SDK manager. + +To build use `ANDROID_HOME= ../gradlew buildApk`. + +Run `adb install build/outputs/apk/androidNativeActivity-arm-debug.apk` to deploy the apk on Android device or emulator +(note that only ARM-based devices are currently supported). diff --git a/samples/androidNativeActivity/android.def b/samples/androidNativeActivity/android.def new file mode 100644 index 00000000000..d81fd536871 --- /dev/null +++ b/samples/androidNativeActivity/android.def @@ -0,0 +1,8 @@ +headers = stdio.h stdlib.h string.h math.h time.h android/log.h androidLauncher.h EGL/egl.h GLES/gl.h +excludeDependentModules.osx = true + +--- + +static int interop_errno() { + return errno; +} diff --git a/samples/androidNativeActivity/build.gradle b/samples/androidNativeActivity/build.gradle new file mode 100644 index 00000000000..4e1bd8ef218 --- /dev/null +++ b/samples/androidNativeActivity/build.gradle @@ -0,0 +1,118 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle-experimental:0.9.2' + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: "konan" + +konanInterop { + arm32 { + defFile "android.def" + pkg "android" + includeDirs "../../runtime/src/launcher/cpp" + target "android_arm32" + } + arm64 { + defFile "android.def" + pkg "android" + includeDirs "../../runtime/src/launcher/cpp" + target "android_arm64" + } +} + +konanArtifacts { + PolyhedronArm32 { + useInterop "arm32" + target "android_arm32" + } + PolyhedronArm64 { + useInterop "arm64" + target "android_arm64" + } +} + +def platforms = ["armeabi-v7a", "arm64-v8a"] +def artifacts = ["PolyhedronArm32", "PolyhedronArm64"] + +apply plugin: "com.android.model.application" + +model { + android { + compileSdkVersion = 25 + buildToolsVersion = '25.0.2' + + defaultConfig { + applicationId = 'com.android.konan_activity' + minSdkVersion.apiLevel 9 + targetSdkVersion.apiLevel 25 + } + + ndk { + moduleName = "polyhedron" + } + + productFlavors { + create("arm") { + ndk { + abiFilters.addAll(platforms) + } + } + } + } + + repositories { + libs(PrebuiltLibraries) { + libpoly { + binaries.withType(SharedLibraryBinary) { + def name = targetPlatform.getName() + def index = platforms.indexOf(name) + if (index >= 0) + sharedLibraryFile = file("build/konan/bin/${artifacts[index]}/libpoly.so") + } + } + } + } + android.sources { + main { + jniLibs { + dependencies { + library "libpoly" + } + } + } + } +} + +task renameArtifacts << { + for (artifact in artifacts) { + copy { + from "build/konan/bin/${artifact}" + into "build/konan/bin/${artifact}" + rename "${artifact}.kexe", "libpoly.so" + } + } +} + +renameArtifacts.dependsOn "compileKonanPolyhedronArm32" +renameArtifacts.dependsOn "compileKonanPolyhedronArm64" + +tasks.matching { it.name == 'preBuild' }.all { + it.dependsOn 'renameArtifacts' +} + +task buildApk(type: DefaultTask) { + dependsOn "compileKonanPolyhedronArm32" + dependsOn "compileKonanPolyhedronArm64" + dependsOn "renameArtifacts" + dependsOn "assembleDebug" +} \ No newline at end of file diff --git a/samples/androidNativeActivity/src/main/AndroidManifest.xml b/samples/androidNativeActivity/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..e9c2ffe931c --- /dev/null +++ b/samples/androidNativeActivity/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/androidNativeActivity/src/main/assets/kotlin_logo.bmp b/samples/androidNativeActivity/src/main/assets/kotlin_logo.bmp new file mode 100644 index 00000000000..3311764c70d Binary files /dev/null and b/samples/androidNativeActivity/src/main/assets/kotlin_logo.bmp differ diff --git a/samples/androidNativeActivity/src/main/kotlin/engine.kt b/samples/androidNativeActivity/src/main/kotlin/engine.kt new file mode 100644 index 00000000000..d6010647a98 --- /dev/null +++ b/samples/androidNativeActivity/src/main/kotlin/engine.kt @@ -0,0 +1,198 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 + * + * http://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. + */ + +import kotlinx.cinterop.* +import android.* + +private fun logError(message: String) { + __android_log_write(ANDROID_LOG_ERROR, "KonanActivity", message) +} + +private fun logInfo(message: String) { + __android_log_write(ANDROID_LOG_INFO, "KonanActivity", message) +} + +val errno: Int + get() = interop_errno() + +fun getUnixError() = strerror(errno)!!.toKString() + +const val LOOPER_ID_INPUT = 2 + +fun main(args: Array) { + logInfo("Hello world!") + memScoped { + val state = alloc() + getNativeActivityState(state.ptr) + val engine = Engine(this, state) + engine.mainLoop() + } +} + +class Engine(val arena: NativePlacement, val state: NativeActivityState) { + private val renderer = Renderer(arena, state.activity!!.pointed) + private var queue: CPointer? = 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 + + fun mainLoop() { + while (true) { + // Process events. + memScoped { + eventLoop@while (true) { + val fd = alloc() + 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() + } + } + } + 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() + } + } + } + + private fun processSysEvent(fd: IntVar): Boolean = memScoped { + val ptr = allocArray(1) + val readBytes = read(fd.value, ptr, 8).toLong() + if (readBytes != 8L) { + logError("Failure reading event, $readBytes read: ${getUnixError()}") + return true + } + try { + val event = ptr[0].toCPointer()!!.pointed + when (event.eventKind) { + NativeActivityEventKind.START -> logInfo("START event received") + + NativeActivityEventKind.DESTROY -> return false + + NativeActivityEventKind.NATIVE_WINDOW_CREATED -> { + val windowEvent = ptr[0].toCPointer()!!.pointed + if (!renderer.initialize(windowEvent.window!!)) + return false + logInfo("Renderer initialized") + renderer.draw() + } + + NativeActivityEventKind.INPUT_QUEUE_CREATED -> { + val queueEvent = ptr[0].toCPointer()!!.pointed + 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 = ptr[0].toCPointer()!!.pointed + AInputQueue_detachLooper(queueEvent.queue) + } + + NativeActivityEventKind.NATIVE_WINDOW_DESTROYED -> { + renderer.destroy() + } + } + } finally { + notifySysEventProcessed() + } + return true + } + + private fun getTime(): Float { + memScoped { + val now = alloc() + clock_gettime(CLOCK_MONOTONIC, now.ptr) + return now.tv_sec + now.tv_nsec / 1_000_000_000.0f + } + } + + private fun getEventPoint(event: CPointer?, i: Int) = + Vector2(AMotionEvent_getRawX(event, i.signExtend()), AMotionEvent_getRawY(event, i.signExtend())) + + private fun getEventTime(event: CPointer?) = + AMotionEvent_getEventTime(event) / 1_000_000_000.0f + + private fun processUserInput(): Unit = memScoped { + logInfo("Processing user input") + val event = alloc>() + if (AInputQueue_getEvent(queue, event.ptr) < 0) { + logError("Failure reading input event") + return + } + val eventType = AInputEvent_getType(event.value) + if (eventType == AINPUT_EVENT_TYPE_MOTION) { + val action = AKeyEvent_getAction(event.value) and AMOTION_EVENT_ACTION_MASK + when (action) { + AMOTION_EVENT_ACTION_DOWN -> { + animating = false + currentPoint = getEventPoint(event.value, 0) + startTime = getEventTime(event.value) + startPoint = currentPoint + } + + AMOTION_EVENT_ACTION_UP -> { + val endPoint = getEventPoint(event.value, 0) + val endTime = getEventTime(event.value) + 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.value).toInt() + for (i in 0 until numberOfPointers) + move(getEventPoint(event.value, i)) + } + } + } + AInputQueue_finishEvent(queue, event.value, 1) + } + + private fun move(newPoint: Vector2) { + renderer.rotateBy(newPoint - currentPoint) + currentPoint = newPoint + } +} diff --git a/samples/androidNativeActivity/src/main/kotlin/polyhedra.kt b/samples/androidNativeActivity/src/main/kotlin/polyhedra.kt new file mode 100644 index 00000000000..3eaa9962b39 --- /dev/null +++ b/samples/androidNativeActivity/src/main/kotlin/polyhedra.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 + * + * http://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. + */ + +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, val faces: Array) { + 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 +} \ No newline at end of file diff --git a/samples/androidNativeActivity/src/main/kotlin/renderer.kt b/samples/androidNativeActivity/src/main/kotlin/renderer.kt new file mode 100644 index 00000000000..9291b6a9fd1 --- /dev/null +++ b/samples/androidNativeActivity/src/main/kotlin/renderer.kt @@ -0,0 +1,291 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 + * + * http://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. + */ + +import kotlinx.cinterop.* +import android.* + +private fun logError(message: String) { + __android_log_write(ANDROID_LOG_ERROR, "KonanActivity", message) +} + +private fun logInfo(message: String) { + __android_log_write(ANDROID_LOG_INFO, "KonanActivity", message) +} + +class Renderer(val arena: NativePlacement, val nativeActivity: ANativeActivity) { + + private var display: EGLDisplay? = null + private var surface: EGLSurface? = null + private var context: EGLContext? = null + private var initialized = false + + var screen = Vector2.Zero + + fun initialize(window: CPointer): Boolean { + with(arena) { + logInfo("Initializing context..") + display = eglGetDisplay(null) + if (display == null) { + logError("eglGetDisplay() returned error ${eglGetError()}") + return false + } + if (eglInitialize(display, null, null) == 0) { + 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() + + val supportedConfigs = allocArray(numConfigs.value) + if (eglChooseConfig(display, attribs, null, 0, numConfigs.ptr) == 0 + || eglChooseConfig(display, attribs, supportedConfigs, numConfigs.value, numConfigs.ptr) == 0) { + logError("eglChooseConfig() returned error ${eglGetError()}") + destroy() + return false + } + var configIndex = 0 + while (configIndex < numConfigs.value) { + val r = alloc() + val g = alloc() + val b = alloc() + val d = alloc() + if (eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_RED_SIZE, r.ptr) != 0 && + eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_GREEN_SIZE, g.ptr) != 0 && + eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_BLUE_SIZE, b.ptr) != 0 && + eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_DEPTH_SIZE, d.ptr) != 0 && + 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) { + logError("eglCreateWindowSurface() returned error ${eglGetError()}") + destroy() + return false + } + + context = eglCreateContext(display, supportedConfigs[configIndex], null, null) + if (context == null) { + logError("eglCreateContext() returned error ${eglGetError()}") + destroy() + return false + } + + if (eglMakeCurrent(display, surface, surface, context) == 0) { + logError("eglMakeCurrent() returned error ${eglGetError()}") + destroy() + return false + } + + val width = alloc() + val height = alloc() + if (eglQuerySurface(display, surface, EGL_WIDTH, width.ptr) == 0 + || eglQuerySurface(display, surface, EGL_HEIGHT, height.ptr) == 0) { + logError("eglQuerySurface() returned error ${eglGetError()}") + destroy() + return false + } + + 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) + glLoadIdentity() + glFrustumf(-ratio, ratio, -1.0f, 1.0f, 1.0f, 10.0f) + + glMatrixMode(GL_MODELVIEW) + 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, GL_DIFFUSE, cValuesOf(0.0f, 1.0f, 1.0f, 1.0f)) + glMaterialfv(GL_FRONT, GL_SPECULAR, cValuesOf(0.3f, 0.3f, 0.3f, 1.0f)) + glMaterialf(GL_FRONT, GL_SHININESS, 30.0f) + + loadTexture("kotlin_logo.bmp") + + glPushMatrix() + glLoadIdentity() + glGetFloatv(GL_MODELVIEW_MATRIX, matrix) + glPopMatrix() + + initialized = true + return true + } + } + + private val matrix = arena.allocArray(16) + + fun rotateBy(vec: Vector2) { + val len = vec.length + if (len < 1e-9f) return + val angle = 180 * len / screen.length + val x = - vec.y / len + val y = - vec.x / len + + glPushMatrix() + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + glRotatef(angle, x, y, 0.0f) + glMultMatrixf(matrix) + glGetFloatv(GL_MODELVIEW_MATRIX, matrix) + glPopMatrix() + } + + private class BMPHeader(val rawPtr: NativePtr) { + inline fun memberAt(offset: Long): T { + return interpretPointed(this.rawPtr + offset) + } + + val magic get() = memberAt(0).value.toInt() + val size get() = memberAt(2).value + val zero get() = memberAt(6).value + val width get() = memberAt(18).value + val height get() = memberAt(22).value + val bits get() = memberAt(28).value.toInt() + val data get() = interpretCPointer(rawPtr + 54) as CArrayPointer + } + + private fun loadTexture(assetName: String): Unit = with(arena) { + val asset = AAssetManager_open(nativeActivity.assetManager, assetName, AASSET_MODE_BUFFER) + if (asset == null) { + logError("Error opening asset") + return + } + val length = AAsset_getLength(asset) + val buf = allocArray(length) + if (AAsset_read(asset, buf, length) != length.toInt()) { + logError("Error reading asset") + AAsset_close(asset) + } + with (BMPHeader(buf.rawValue)) { + if (magic != 0x4d42 || zero != 0 || size != length.toInt() || bits != 24) { + logError("Error parsing texture file") + AAsset_close(asset) + return + } + val numberOfBytes = width * height * 3 + for (i in 0 until numberOfBytes step 3) { + val t = data[i] + data[i] = data[i + 2] + data[i + 2] = t + } + 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) + 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() = with (arena) { + if (!initialized) return + + glPushMatrix() + glMatrixMode(GL_MODELVIEW) + + glMultMatrixf(matrix) + + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) + + glEnableClientState(GL_VERTEX_ARRAY) + glEnableClientState(GL_NORMAL_ARRAY) + glEnableClientState(GL_TEXTURE_COORD_ARRAY) + + val poly = RegularPolyhedra.Dodecahedron + val vertices = mutableListOf() + val texCoords = mutableListOf() + val triangles = mutableListOf() + val normals = mutableListOf() + for (face in poly.faces) { + val u = poly.vertices[face[2].toInt()] - poly.vertices[face[1].toInt()] + val v = poly.vertices[face[0].toInt()] - poly.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() + poly.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]) + } + } + + glFrontFace(GL_CW) + glVertexPointer(4, GL_FLOAT, 0, vertices.toFloatArray().toCValues().getPointer(this)) + glTexCoordPointer(2, GL_FLOAT, 0, texCoords.toFloatArray().toCValues().getPointer(this)) + glNormalPointer(GL_FLOAT, 0, normals.toFloatArray().toCValues().getPointer(this)) + glDrawElements(GL_TRIANGLES, triangles.size, GL_UNSIGNED_BYTE, triangles.toByteArray().toCValues().getPointer(this)) + + glPopMatrix() + + if (eglSwapBuffers(display, surface) == 0) { + logError("eglSwapBuffers() returned error ${eglGetError()}") + destroy() + } + } + + fun destroy() { + logInfo("Destroying context..") + + eglMakeCurrent(display, null, null, null) + context?.let { eglDestroyContext(display, it) } + surface?.let { eglDestroySurface(display, it) } + eglTerminate(display) + + display = null + surface = null + context = null + initialized = false + } +} \ No newline at end of file diff --git a/samples/androidNativeActivity/src/main/kotlin/vectors.kt b/samples/androidNativeActivity/src/main/kotlin/vectors.kt new file mode 100644 index 00000000000..86d30335e13 --- /dev/null +++ b/samples/androidNativeActivity/src/main/kotlin/vectors.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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 + * + * http://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. + */ + +import kotlinx.cinterop.* +import android.* + +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) { + 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) { + 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) +} + diff --git a/samples/androidNativeActivity/src/main/res/mipmap-hdpi/konan_activity.png b/samples/androidNativeActivity/src/main/res/mipmap-hdpi/konan_activity.png new file mode 100755 index 00000000000..deb3f79d059 Binary files /dev/null and b/samples/androidNativeActivity/src/main/res/mipmap-hdpi/konan_activity.png differ diff --git a/samples/androidNativeActivity/src/main/res/mipmap-mdpi/konan_activity.png b/samples/androidNativeActivity/src/main/res/mipmap-mdpi/konan_activity.png new file mode 100755 index 00000000000..63a53c0b2f8 Binary files /dev/null and b/samples/androidNativeActivity/src/main/res/mipmap-mdpi/konan_activity.png differ diff --git a/samples/androidNativeActivity/src/main/res/mipmap-xhdpi/konan_activity.png b/samples/androidNativeActivity/src/main/res/mipmap-xhdpi/konan_activity.png new file mode 100755 index 00000000000..cb7cfc14d03 Binary files /dev/null and b/samples/androidNativeActivity/src/main/res/mipmap-xhdpi/konan_activity.png differ diff --git a/samples/androidNativeActivity/src/main/res/mipmap-xxhdpi/konan_activity.png b/samples/androidNativeActivity/src/main/res/mipmap-xxhdpi/konan_activity.png new file mode 100755 index 00000000000..8d427824430 Binary files /dev/null and b/samples/androidNativeActivity/src/main/res/mipmap-xxhdpi/konan_activity.png differ diff --git a/samples/androidNativeActivity/src/main/res/values/strings.xml b/samples/androidNativeActivity/src/main/res/values/strings.xml new file mode 100644 index 00000000000..e1033450618 --- /dev/null +++ b/samples/androidNativeActivity/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + KonanActivity + diff --git a/samples/settings.gradle b/samples/settings.gradle index 05b061c3dc6..776a5d98095 100644 --- a/samples/settings.gradle +++ b/samples/settings.gradle @@ -8,4 +8,5 @@ include ':socket' include ':tetris' include ':tensorflow' include ':concurrent' +include ':androidNativeActivity' includeBuild '../'