Improve Android native sample, add basic Java bridge. (#1606)

This commit is contained in:
Nikolay Igotti
2018-05-24 18:20:59 +03:00
committed by GitHub
parent 41ea12a4e7
commit 5b087ae04c
20 changed files with 445 additions and 261 deletions
@@ -57,7 +57,7 @@ internal class KonanIr(context: Context, irModule: IrModuleFragment): Ir<Context
fun get(descriptor: FunctionDescriptor): IrFunction { fun get(descriptor: FunctionDescriptor): IrFunction {
return moduleIndexForCodegen.functions[descriptor] return moduleIndexForCodegen.functions[descriptor]
?: symbols.symbolTable.referenceFunction(descriptor).owner as IrFunction ?: symbols.symbolTable.referenceFunction(descriptor).owner
} }
fun get(descriptor: ClassDescriptor): IrClass { fun get(descriptor: ClassDescriptor): IrClass {
@@ -1690,7 +1690,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
//-------------------------------------------------------------------------// //-------------------------------------------------------------------------//
private fun evaluateSpecialIntrinsicCall(expression: IrFunctionAccessExpression): LLVMValueRef? { private fun evaluateSpecialIntrinsicCall(expression: IrFunctionAccessExpression): LLVMValueRef? {
val function = expression.symbol.owner as IrFunction val function = expression.symbol.owner
if (function.isIntrinsic) { if (function.isIntrinsic) {
when (function.descriptor) { when (function.descriptor) {
@@ -1715,7 +1715,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
val initializer = callee.getValueArgument(1) as IrCall val initializer = callee.getValueArgument(1) as IrCall
val thiz = evaluateExpression(callee.getValueArgument(0)!!) val thiz = evaluateExpression(callee.getValueArgument(0)!!)
evaluateSimpleFunctionCall( evaluateSimpleFunctionCall(
initializer.symbol.owner as IrFunction, initializer.symbol.owner,
listOf(thiz) + evaluateExplicitArgs(initializer), listOf(thiz) + evaluateExplicitArgs(initializer),
resultLifetime(initializer) resultLifetime(initializer)
) )
@@ -1901,7 +1901,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
assert (expression.getArguments().isEmpty()) assert (expression.getArguments().isEmpty())
val descriptor = expression.symbol.owner as IrFunction val descriptor = expression.symbol.owner
assert (descriptor.dispatchReceiverParameter == null) assert (descriptor.dispatchReceiverParameter == null)
val entry = codegen.functionEntryPointAddress(descriptor) val entry = codegen.functionEntryPointAddress(descriptor)
@@ -1983,7 +1983,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
private fun evaluateFunctionCall(callee: IrCall, args: List<LLVMValueRef>, private fun evaluateFunctionCall(callee: IrCall, args: List<LLVMValueRef>,
resultLifetime: Lifetime): LLVMValueRef { resultLifetime: Lifetime): LLVMValueRef {
val descriptor = callee.symbol.owner as IrFunction val descriptor = callee.symbol.owner
val argsWithContinuationIfNeeded = if (descriptor.isSuspend) val argsWithContinuationIfNeeded = if (descriptor.isSuspend)
args + getContinuation() args + getContinuation()
@@ -2021,7 +2021,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
) )
} }
return call(callee.symbol.owner as IrFunction, function, args, resultLifetime) return call(callee.symbol.owner, function, args, resultLifetime)
} }
//-------------------------------------------------------------------------// //-------------------------------------------------------------------------//
@@ -2086,7 +2086,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
} else { } else {
functionGenerationContext.allocInstance(constructedClass, resultLifetime(callee)) functionGenerationContext.allocInstance(constructedClass, resultLifetime(callee))
} }
evaluateSimpleFunctionCall(callee.symbol.owner as IrFunction, evaluateSimpleFunctionCall(callee.symbol.owner,
listOf(thisValue) + args, Lifetime.IRRELEVANT /* constructor doesn't return anything */) listOf(thisValue) + args, Lifetime.IRRELEVANT /* constructor doesn't return anything */)
thisValue thisValue
} }
@@ -2401,7 +2401,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
private fun evaluateOperatorCall(callee: IrCall, args: List<LLVMValueRef>): LLVMValueRef { private fun evaluateOperatorCall(callee: IrCall, args: List<LLVMValueRef>): LLVMValueRef {
context.log{"evaluateOperatorCall : origin:${ir2string(callee)}"} context.log{"evaluateOperatorCall : origin:${ir2string(callee)}"}
val descriptor = callee.symbol.owner as IrFunction val descriptor = callee.symbol.owner
val ib = context.irModule!!.irBuiltins val ib = context.irModule!!.irBuiltins
with(functionGenerationContext) { with(functionGenerationContext) {
@@ -305,7 +305,7 @@ internal class CallableReferenceLowering(val context: Context): FileLoweringPass
IrConstKind.String, functionDescriptor.name.asString()) IrConstKind.String, functionDescriptor.name.asString())
putValueArgument(0, name) putValueArgument(0, name)
val fqName = IrConstImpl(startOffset, endOffset, context.builtIns.stringType, IrConstKind.String, val fqName = IrConstImpl(startOffset, endOffset, context.builtIns.stringType, IrConstKind.String,
(functionReference.symbol.owner as IrFunction).fullName) (functionReference.symbol.owner).fullName)
putValueArgument(1, fqName) putValueArgument(1, fqName)
val bound = IrConstImpl.boolean(startOffset, endOffset, context.builtIns.booleanType, val bound = IrConstImpl.boolean(startOffset, endOffset, context.builtIns.booleanType,
boundFunctionParameters.isNotEmpty()) boundFunctionParameters.isNotEmpty())
@@ -295,7 +295,7 @@ class DefaultParameterInjector constructor(val context: CommonBackendContext): B
private fun parametersForCall(expression: IrFunctionAccessExpression): Pair<IrFunctionSymbol, List<Pair<ValueParameterDescriptor, IrExpression?>>> { private fun parametersForCall(expression: IrFunctionAccessExpression): Pair<IrFunctionSymbol, List<Pair<ValueParameterDescriptor, IrExpression?>>> {
val descriptor = expression.descriptor val descriptor = expression.descriptor
val keyFunction = (expression.symbol.owner as IrFunction).findSuperMethodWithDefaultArguments()!! val keyFunction = expression.symbol.owner.findSuperMethodWithDefaultArguments()!!
val keyDescriptor = keyFunction.descriptor val keyDescriptor = keyFunction.descriptor
val realFunction = keyDescriptor.generateDefaultsFunction(context) val realFunction = keyDescriptor.generateDefaultsFunction(context)
realFunction.parent = keyFunction.parent realFunction.parent = keyFunction.parent
@@ -61,7 +61,7 @@ internal class ExpectDeclarationsRemoving(val context: Context) : FileLoweringPa
} }
private fun IrFunction.findActualForExpected(): IrFunction = private fun IrFunction.findActualForExpected(): IrFunction =
context.ir.symbols.symbolTable.referenceFunction(descriptor.findActualForExpect()).owner as IrFunction context.ir.symbols.symbolTable.referenceFunction(descriptor.findActualForExpect()).owner
private fun IrClass.findActualForExpected(): IrClass = private fun IrClass.findActualForExpected(): IrClass =
context.ir.symbols.symbolTable.referenceClass(descriptor.findActualForExpect()).owner context.ir.symbols.symbolTable.referenceClass(descriptor.findActualForExpect()).owner
@@ -587,7 +587,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag
expressionToEdge(value.getValueArgument(0)!!), expressionToEdge(value.getValueArgument(1)!!)) expressionToEdge(value.getValueArgument(0)!!), expressionToEdge(value.getValueArgument(1)!!))
else -> { else -> {
val callee = value.symbol.owner as IrFunction val callee = value.symbol.owner
val arguments = value.getArguments() val arguments = value.getArguments()
.map { expressionToEdge(it.second) } .map { expressionToEdge(it.second) }
.let { .let {
@@ -214,7 +214,6 @@ extern "C" void RUNTIME_USED Konan_main(
if (launchThread) { if (launchThread) {
launcherState = (LauncherState*)calloc(sizeof(LauncherState), 1); launcherState = (LauncherState*)calloc(sizeof(LauncherState), 1);
launcherState->nativeActivityState = {activity, savedState, savedStateSize, nullptr}; launcherState->nativeActivityState = {activity, savedState, savedStateSize, nullptr};
activity->instance = launcherState; activity->instance = launcherState;
activity->callbacks->onDestroy = onDestroy; activity->callbacks->onDestroy = onDestroy;
activity->callbacks->onStart = onStart; activity->callbacks->onStart = onStart;
+4 -2
View File
@@ -1,12 +1,14 @@
# Android Native Activity # Android Native Activity
This example shows how to build an 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. 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 in Android Studio. Please make sure that Android SDK version 25 is installed, using Android SDK manager in Android Studio.
See https://developer.android.com/studio/index.html for more details on Android Studio or See https://developer.android.com/studio/index.html for more details on Android Studio or
`$ANDROID_HOME/tools/bin/sdkmanager "platforms;android-25" "build-tools;25.0.2"` from command line. `$ANDROID_HOME/tools/bin/sdkmanager "platforms;android-25" "build-tools;25.0.2"` 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 buildApk`. To build use `ANDROID_HOME=<your path to android sdk> ../gradlew build`.
Run `$ANDROID_HOME/platform-tools/adb install -r build/outputs/apk/debug/androidNativeActivity-debug.apk` 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 only ARM-based devices are currently supported). to deploy the apk on the Android device or emulator (note that only ARM-based devices are currently supported).
@@ -1,8 +0,0 @@
// TODO: androidLauncher.h will become part of the platform library.
headers = stdio.h stdlib.h string.h math.h time.h android/log.h androidLauncher.h EGL/egl.h GLES/gl.h
linkerOpst = -landroid -lEGL -lGLESv1_CM -lz
---
static int interop_errno() {
return errno;
}
@@ -1,85 +0,0 @@
/*
* 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.
*/
#ifndef ANDROID_LAUNCHER_H
#define ANDROID_LAUNCHER_H
#include <unistd.h>
#include <android/configuration.h>
#include <android/looper.h>
#include <android/native_activity.h>
#ifdef __cplusplus
extern "C" {
#endif
struct NativeActivityState {
struct ANativeActivity* activity;
void* savedState;
size_t savedStateSize;
struct ALooper* looper;
};
void getNativeActivityState(struct NativeActivityState* state);
void notifySysEventProcessed();
#define LOOPER_ID_SYS 1
typedef enum NativeActivityEventKind {
UNKNOWN,
DESTROY,
START,
RESUME,
SAVE_INSTANCE_STATE,
PAUSE,
STOP,
CONFIGURATION_CHANGED,
LOW_MEMORY,
WINDOW_GAINED_FOCUS,
WINDOW_LOST_FOCUS,
NATIVE_WINDOW_CREATED,
NATIVE_WINDOW_DESTROYED,
INPUT_QUEUE_CREATED,
INPUT_QUEUE_DESTROYED
} NativeActivityEventKind;
struct NativeActivityEvent {
NativeActivityEventKind eventKind;
};
struct NativeActivitySaveStateEvent {
NativeActivityEventKind eventKind;
void* savedState;
size_t savedStateSize;
};
struct NativeActivityWindowEvent {
NativeActivityEventKind eventKind;
struct ANativeWindow* window;
};
struct NativeActivityQueueEvent {
NativeActivityEventKind eventKind;
struct AInputQueue* queue;
};
#ifdef __cplusplus
}
#endif
#endif // ANDROID_LAUNCHER_H
+1 -1
View File
@@ -9,7 +9,7 @@ buildscript {
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:${project.property('konan.plugin.version')}" classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:${project.property('konan.plugin.version')}"
classpath "com.android.tools.build:gradle:3.1.0" classpath 'com.android.tools.build:gradle:3.1.2'
} }
} }
@@ -5,6 +5,8 @@
android:versionCode="1" android:versionCode="1"
android:versionName="1.0"> android:versionName="1.0">
<uses-permission android:name="android.permission.VIBRATE" />
<!-- This .apk has no Java code itself, so set hasCode to false. --> <!-- This .apk has no Java code itself, so set hasCode to false. -->
<application <application
android:allowBackup="false" android:allowBackup="false"
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2018 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.
*/
package NativeApplication
import kotlinx.cinterop.*
internal class BMPHeader(val rawPtr: NativePtr) {
inline fun <reified T : CPointed> memberAt(offset: Long): T {
return interpretPointed<T>(this.rawPtr + offset)
}
val magic get() = memberAt<ShortVar>(0).value.toInt()
val size get() = memberAt<IntVar>(2).value
val zero get() = memberAt<IntVar>(6).value
val width get() = memberAt<IntVar>(18).value
val height get() = memberAt<IntVar>(22).value
val bits get() = memberAt<ShortVar>(28).value.toInt()
val data get() = interpretCPointer<ByteVar>(rawPtr + 54) as CArrayPointer<ByteVar>
}
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2018 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.
*/
package NativeApplication
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() })
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2017 JetBrains s.r.o. * Copyright 2010-2018 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package NativeApplication
import kotlinx.cinterop.* import kotlinx.cinterop.*
import platform.android.* import platform.android.*
@@ -28,27 +29,14 @@ fun logInfo(message: String) {
__android_log_write(ANDROID_LOG_INFO, "KonanActivity", message) __android_log_write(ANDROID_LOG_INFO, "KonanActivity", message)
} }
val errno: Int private fun getUnixError() = strerror(posix_errno())!!.toKString()
get() = posix_errno()
fun getUnixError() = strerror(errno)!!.toKString()
const val LOOPER_ID_INPUT = 2 const val LOOPER_ID_INPUT = 2
fun main(args: Array<String>) {
logInfo("Hello world!")
memScoped {
val state = alloc<NativeActivityState>()
getNativeActivityState(state.ptr)
val engine = Engine(this, state)
engine.mainLoop()
}
}
inline fun <reified T : CPointed> CPointer<*>?.dereferenceAs(): T = this!!.reinterpret<T>().pointed inline fun <reified T : CPointed> CPointer<*>?.dereferenceAs(): T = this!!.reinterpret<T>().pointed
class Engine(val arena: NativePlacement, val state: NativeActivityState) { class Engine(val state: NativeActivityState): DisposableContainer() {
private val renderer = Renderer(arena, state.activity!!.pointed, state.savedState) private val renderer = Renderer(this, state.activity!!.pointed, state.savedState)
private var queue: CPointer<AInputQueue>? = null private var queue: CPointer<AInputQueue>? = null
private var rendererState: COpaquePointer? = null private var rendererState: COpaquePointer? = null
@@ -63,22 +51,23 @@ class Engine(val arena: NativePlacement, val state: NativeActivityState) {
private var animating = false private var animating = false
private val pointerSize = CPointerVar.size 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() { fun mainLoop() {
callToManagedAPI()
val fd = arena.alloc<IntVar>()
while (true) { while (true) {
// Process events. // Process events.
memScoped { eventLoop@ while (true) {
val fd = alloc<IntVar>() val id = ALooper_pollAll(if (needRedraw || animating) 0 else -1, fd.ptr, null, null)
eventLoop@while (true) { if (id < 0) break@eventLoop
val id = ALooper_pollAll(if (needRedraw || animating) 0 else -1, fd.ptr, null, null) when (id) {
if (id < 0) break@eventLoop LOOPER_ID_SYS -> if (!processSysEvent(fd)) return // An error occured.
when (id) { LOOPER_ID_INPUT -> processUserInput()
LOOPER_ID_SYS -> { else -> logError("Unprocessed event: $id")
if (!processSysEvent(fd))
return // An error occured.
}
LOOPER_ID_INPUT -> processUserInput()
}
} }
} }
when { when {
@@ -97,8 +86,29 @@ class Engine(val arena: NativePlacement, val state: NativeActivityState) {
} }
} }
private fun processSysEvent(fd: IntVar): Boolean = memScoped { override fun dispose() {
val eventPointer = alloc<COpaquePointerVar>() 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.narrow()).toLong() val readBytes = read(fd.value, eventPointer.ptr, pointerSize.narrow()).toLong()
if (readBytes != pointerSize.toLong()) { if (readBytes != pointerSize.toLong()) {
logError("Failure reading event, $readBytes read: ${getUnixError()}") logError("Failure reading event, $readBytes read: ${getUnixError()}")
@@ -106,11 +116,22 @@ class Engine(val arena: NativePlacement, val state: NativeActivityState) {
} }
try { try {
val event = eventPointer.value.dereferenceAs<NativeActivityEvent>() val event = eventPointer.value.dereferenceAs<NativeActivityEvent>()
println("got ${event.eventKind}")
when (event.eventKind) { when (event.eventKind) {
NativeActivityEventKind.START -> logInfo("START event received") NativeActivityEventKind.START -> {
logInfo("Activity started")
renderer.start()
}
NativeActivityEventKind.STOP -> {
renderer.stop()
}
NativeActivityEventKind.DESTROY -> { NativeActivityEventKind.DESTROY -> {
rendererState?.let { free(it) } rendererState?.let {
free(it)
rendererState = null
}
return false return false
} }
@@ -145,6 +166,7 @@ class Engine(val arena: NativePlacement, val state: NativeActivityState) {
val dataSize = state.second.signExtend<size_t>() val dataSize = state.second.signExtend<size_t>()
rendererState = realloc(rendererState, dataSize) rendererState = realloc(rendererState, dataSize)
memcpy(rendererState, state.first, dataSize) memcpy(rendererState, state.first, dataSize)
logInfo("Saving instance state to $rendererState: $dataSize bytes")
saveStateEvent.savedState = rendererState saveStateEvent.savedState = rendererState
saveStateEvent.savedStateSize = dataSize saveStateEvent.savedStateSize = dataSize
} }
@@ -156,11 +178,8 @@ class Engine(val arena: NativePlacement, val state: NativeActivityState) {
} }
private fun getTime(): Float { private fun getTime(): Float {
memScoped { clock_gettime(CLOCK_MONOTONIC, now.ptr)
val now = alloc<timespec>() return now.tv_sec + now.tv_nsec / 1_000_000_000.0f
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) = private fun getEventPoint(event: CPointer<AInputEvent>?, i: Int) =
@@ -169,27 +188,26 @@ class Engine(val arena: NativePlacement, val state: NativeActivityState) {
private fun getEventTime(event: CPointer<AInputEvent>?) = private fun getEventTime(event: CPointer<AInputEvent>?) =
AMotionEvent_getEventTime(event) / 1_000_000_000.0f AMotionEvent_getEventTime(event) / 1_000_000_000.0f
private fun processUserInput(): Unit = memScoped { private fun processUserInput(): Unit {
logInfo("Processing user input") if (AInputQueue_getEvent(queue, inputEvent.ptr) < 0) {
val event = alloc<CPointerVar<AInputEvent>>()
if (AInputQueue_getEvent(queue, event.ptr) < 0) {
logError("Failure reading input event") logError("Failure reading input event")
return return
} }
val eventType = AInputEvent_getType(event.value) val event = inputEvent.value
val eventType = AInputEvent_getType(event)
if (eventType == AINPUT_EVENT_TYPE_MOTION) { if (eventType == AINPUT_EVENT_TYPE_MOTION) {
val action = AKeyEvent_getAction(event.value) and AMOTION_EVENT_ACTION_MASK val action = AKeyEvent_getAction(event) and AMOTION_EVENT_ACTION_MASK
when (action) { when (action) {
AMOTION_EVENT_ACTION_DOWN -> { AMOTION_EVENT_ACTION_DOWN -> {
animating = false animating = false
currentPoint = getEventPoint(event.value, 0) currentPoint = getEventPoint(event, 0)
startTime = getEventTime(event.value) startTime = getEventTime(event)
startPoint = currentPoint startPoint = currentPoint
} }
AMOTION_EVENT_ACTION_UP -> { AMOTION_EVENT_ACTION_UP -> {
val endPoint = getEventPoint(event.value, 0) val endPoint = getEventPoint(event, 0)
val endTime = getEventTime(event.value) val endTime = getEventTime(event)
animating = true animating = true
velocity = (endPoint - startPoint) / (endTime - startTime + 1e-9f) velocity = (endPoint - startPoint) / (endTime - startTime + 1e-9f)
if (velocity.length > renderer.screen.length) if (velocity.length > renderer.screen.length)
@@ -202,13 +220,13 @@ class Engine(val arena: NativePlacement, val state: NativeActivityState) {
} }
AMOTION_EVENT_ACTION_MOVE -> { AMOTION_EVENT_ACTION_MOVE -> {
val numberOfPointers = AMotionEvent_getPointerCount(event.value).toInt() val numberOfPointers = AMotionEvent_getPointerCount(event).toInt()
for (i in 0 until numberOfPointers) for (i in 0 until numberOfPointers)
move(getEventPoint(event.value, i)) move(getEventPoint(event, i))
} }
} }
} }
AInputQueue_finishEvent(queue, event.value, 1) AInputQueue_finishEvent(queue, event, 1)
} }
private fun move(newPoint: Vector2) { private fun move(newPoint: Vector2) {
@@ -0,0 +1,118 @@
/*
* Copyright 2010-2018 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.
*/
package NativeApplication
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.toByte()) {
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)
}
}
}
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2017 JetBrains s.r.o. * Copyright 2010-2018 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package NativeApplication
const val Zero = 0.0f const val Zero = 0.0f
const val DodeA = 0.93417235896f // (Sqrt(5) + 1) / (2 * Sqrt(3)) const val DodeA = 0.93417235896f // (Sqrt(5) + 1) / (2 * Sqrt(3))
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2017 JetBrains s.r.o. * Copyright 2010-2018 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -14,6 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package NativeApplication
import kotlinx.cinterop.* import kotlinx.cinterop.*
import platform.android.* import platform.android.*
import platform.egl.* import platform.egl.*
@@ -55,10 +57,9 @@ import platform.gles.glTexCoordPointer
import platform.gles.glNormalPointer import platform.gles.glNormalPointer
import platform.gles.GL_TEXTURE_ENV_COLOR import platform.gles.GL_TEXTURE_ENV_COLOR
class Renderer(val container: DisposableContainer,
class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActivity, val savedMatrix: COpaquePointer?) { val nativeActivity: ANativeActivity,
val savedMatrix: COpaquePointer?) {
private val arena = MemScope()
private var display: EGLDisplay? = null private var display: EGLDisplay? = null
private var surface: EGLSurface? = null private var surface: EGLSurface? = null
private var context: EGLContext? = null private var context: EGLContext? = null
@@ -66,20 +67,20 @@ class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActi
var screen = Vector2.Zero var screen = Vector2.Zero
private val matrix = parentArena.allocArray<FloatVar>(16) private val matrix = container.arena.allocArray<FloatVar>(16)
init { init {
if (savedMatrix != null) { if (savedMatrix != null) {
memcpy(matrix, savedMatrix, 16 * 4) memcpy(matrix, savedMatrix, 16 * 4)
} else { } else {
for (i in 0..3) for (i in 0 .. 3)
for (j in 0..3) for (j in 0 .. 3)
matrix[i * 4 + j] = if (i == j) 1.0f else 0.0f matrix[i * 4 + j] = if (i == j) 1.0f else 0.0f
} }
} }
fun initialize(window: CPointer<ANativeWindow>): Boolean { fun initialize(window: CPointer<ANativeWindow>): Boolean {
with(arena) { with (container.arena) {
logInfo("Initializing context..") logInfo("Initializing context..")
display = eglGetDisplay(null) display = eglGetDisplay(null)
if (display == null) { if (display == null) {
@@ -100,15 +101,11 @@ class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActi
) )
val numConfigs = alloc<EGLintVar>() val numConfigs = alloc<EGLintVar>()
if (eglChooseConfig(display, attribs, null, 0, numConfigs.ptr) == 0) { if (eglChooseConfig(display, attribs, null, 0, numConfigs.ptr) == 0) {
logError("eglChooseConfig()#1 returned error ${eglGetError()}") throw Error("eglChooseConfig()#1 returned error ${eglGetError()}")
destroy()
return false
} }
val supportedConfigs = allocArray<EGLConfigVar>(numConfigs.value) val supportedConfigs = allocArray<EGLConfigVar>(numConfigs.value)
if (eglChooseConfig(display, attribs, supportedConfigs, numConfigs.value, numConfigs.ptr) == 0) { if (eglChooseConfig(display, attribs, supportedConfigs, numConfigs.value, numConfigs.ptr) == 0) {
logError("eglChooseConfig()#2 returned error ${eglGetError()}") throw Error("eglChooseConfig()#2 returned error ${eglGetError()}")
destroy()
return false
} }
var configIndex = 0 var configIndex = 0
while (configIndex < numConfigs.value) { while (configIndex < numConfigs.value) {
@@ -116,13 +113,11 @@ class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActi
val g = alloc<EGLintVar>() val g = alloc<EGLintVar>()
val b = alloc<EGLintVar>() val b = alloc<EGLintVar>()
val d = alloc<EGLintVar>() val d = alloc<EGLintVar>()
if (eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_RED_SIZE, r.ptr) != 0 && 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_GREEN_SIZE, g.ptr) != 0 &&
eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_BLUE_SIZE, b.ptr) != 0 && eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_BLUE_SIZE, b.ptr) != 0 &&
eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_DEPTH_SIZE, d.ptr) != 0 && eglGetConfigAttrib(display, supportedConfigs[configIndex], EGL_DEPTH_SIZE, d.ptr) != 0 &&
r.value == 8 && g.value == 8 && b.value == 8 && d.value == 0 ) { r.value == 8 && g.value == 8 && b.value == 8 && d.value == 0) break
break
}
++configIndex ++configIndex
} }
if (configIndex >= numConfigs.value) if (configIndex >= numConfigs.value)
@@ -130,31 +125,23 @@ class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActi
surface = eglCreateWindowSurface(display, supportedConfigs[configIndex], window, null) surface = eglCreateWindowSurface(display, supportedConfigs[configIndex], window, null)
if (surface == null) { if (surface == null) {
logError("eglCreateWindowSurface() returned error ${eglGetError()}") throw Error("eglCreateWindowSurface() returned error ${eglGetError()}")
destroy()
return false
} }
context = eglCreateContext(display, supportedConfigs[configIndex], null, null) context = eglCreateContext(display, supportedConfigs[configIndex], null, null)
if (context == null) { if (context == null) {
logError("eglCreateContext() returned error ${eglGetError()}") throw Error("eglCreateContext() returned error ${eglGetError()}")
destroy()
return false
} }
if (eglMakeCurrent(display, surface, surface, context) == 0) { if (eglMakeCurrent(display, surface, surface, context) == 0) {
logError("eglMakeCurrent() returned error ${eglGetError()}") throw Error("eglMakeCurrent() returned error ${eglGetError()}")
destroy()
return false
} }
val width = alloc<EGLintVar>() val width = alloc<EGLintVar>()
val height = alloc<EGLintVar>() val height = alloc<EGLintVar>()
if (eglQuerySurface(display, surface, EGL_WIDTH, width.ptr) == 0 if (eglQuerySurface(display, surface, EGL_WIDTH, width.ptr) == 0
|| eglQuerySurface(display, surface, EGL_HEIGHT, height.ptr) == 0) { || eglQuerySurface(display, surface, EGL_HEIGHT, height.ptr) == 0) {
logError("eglQuerySurface() returned error ${eglGetError()}") throw Error("eglQuerySurface() returned error ${eglGetError()}")
destroy()
return false
} }
this@Renderer.screen = Vector2(width.value.toFloat(), height.value.toFloat()) this@Renderer.screen = Vector2(width.value.toFloat(), height.value.toFloat())
@@ -192,14 +179,14 @@ class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActi
fun getState() = matrix to 16 * 4 fun getState() = matrix to 16 * 4
fun rotateBy(vec: Vector2) { fun rotateBy(vector: Vector2) {
if (!initialized) return if (!initialized) return
val len = vec.length val length = vector.length
if (len < 1e-9f) return if (length < 1e-9f) return
val angle = 180 * len / screen.length val angle = 180 * length / screen.length
val x = - vec.y / len val x = -vector.y / length
val y = - vec.x / len val y = -vector.x / length
glPushMatrix() glPushMatrix()
glMatrixMode(GL_MODELVIEW) glMatrixMode(GL_MODELVIEW)
@@ -210,51 +197,38 @@ class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActi
glPopMatrix() glPopMatrix()
} }
private class BMPHeader(val rawPtr: NativePtr) {
inline fun <reified T : CPointed> memberAt(offset: Long): T {
return interpretPointed<T>(this.rawPtr + offset)
}
val magic get() = memberAt<ShortVar>(0).value.toInt()
val size get() = memberAt<IntVar>(2).value
val zero get() = memberAt<IntVar>(6).value
val width get() = memberAt<IntVar>(18).value
val height get() = memberAt<IntVar>(22).value
val bits get() = memberAt<ShortVar>(28).value.toInt()
val data get() = interpretCPointer<ByteVar>(rawPtr + 54) as CArrayPointer<ByteVar>
}
private fun loadTexture(assetName: String): Unit = memScoped { private fun loadTexture(assetName: String): Unit = memScoped {
val asset = AAssetManager_open(nativeActivity.assetManager, assetName, AASSET_MODE_BUFFER) val asset = AAssetManager_open(nativeActivity.assetManager, assetName, AASSET_MODE_BUFFER)
if (asset == null) { ?: throw Error("Error opening asset $assetName")
logError("Error opening asset") try {
return val length = AAsset_getLength(asset)
} val buffer = allocArray<ByteVar>(length)
val length = AAsset_getLength(asset) if (AAsset_read(asset, buffer, length) != length.toInt()) {
val buf = allocArray<ByteVar>(length) throw Error("Error reading asset")
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 with(BMPHeader(buffer.rawValue)) {
for (i in 0 until numberOfBytes step 3) { if (magic != 0x4d42 || zero != 0 || size != length.toInt() || bits != 24) {
val t = data[i] throw Error("Error parsing texture file")
data[i] = data[i + 2] }
data[i + 2] = t 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
}
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)
} }
glBindTexture(GL_TEXTURE_2D, 1) } finally {
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) AAsset_close(asset)
} }
} }
@@ -265,7 +239,7 @@ class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActi
private val scale = 1.25f private val scale = 1.25f
fun draw() = memScoped { fun draw(): Unit {
if (!initialized) return if (!initialized) return
glPushMatrix() glPushMatrix()
@@ -279,61 +253,83 @@ class Renderer(val parentArena: NativePlacement, val nativeActivity: ANativeActi
glEnableClientState(GL_NORMAL_ARRAY) glEnableClientState(GL_NORMAL_ARRAY)
glEnableClientState(GL_TEXTURE_COORD_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY)
val poly = RegularPolyhedra.Dodecahedron val polygon = RegularPolyhedra.Dodecahedron
val vertices = mutableListOf<Float>() val vertices = mutableListOf<Float>()
val texCoords = mutableListOf<Float>() val texCoords = mutableListOf<Float>()
val triangles = mutableListOf<Byte>() val triangles = mutableListOf<Byte>()
val normals = mutableListOf<Float>() val normals = mutableListOf<Float>()
for (face in poly.faces) { for (face in polygon.faces) {
val u = poly.vertices[face[2].toInt()] - poly.vertices[face[1].toInt()] val u = polygon.vertices[face[2].toInt()] - polygon.vertices[face[1].toInt()]
val v = poly.vertices[face[0].toInt()] - poly.vertices[face[1].toInt()] val v = polygon.vertices[face[0].toInt()] - polygon.vertices[face[1].toInt()]
val normal = u.crossProduct(v).normalized() val normal = u.crossProduct(v).normalized()
val copiedFace = ByteArray(face.size) val copiedFace = ByteArray(face.size)
for (j in face.indices) { for (j in face.indices) {
copiedFace[j] = (vertices.size / 4).toByte() copiedFace[j] = (vertices.size / 4).toByte()
poly.vertices[face[j].toInt()].copyCoordinatesTo(vertices) polygon.vertices[face[j].toInt()].copyCoordinatesTo(vertices)
vertices.add(scale) vertices.add(scale)
normal.copyCoordinatesTo(normals) normal.copyCoordinatesTo(normals)
texturePoints[j].copyCoordinatesTo(texCoords) texturePoints[j].copyCoordinatesTo(texCoords)
} }
for (j in 1..face.size-2) { for (j in 1..face.size - 2) {
triangles.add(copiedFace[0]) triangles.add(copiedFace[0])
triangles.add(copiedFace[j]) triangles.add(copiedFace[j])
triangles.add(copiedFace[j + 1]) triangles.add(copiedFace[j + 1])
} }
} }
glFrontFace(GL_CW) memScoped {
glVertexPointer(4, GL_FLOAT, 0, vertices.toFloatArray().toCValues().ptr) glFrontFace(GL_CW)
glTexCoordPointer(2, GL_FLOAT, 0, texCoords.toFloatArray().toCValues().ptr) glVertexPointer(4, GL_FLOAT, 0, vertices.toFloatArray().toCValues().ptr)
glNormalPointer(GL_FLOAT, 0, normals.toFloatArray().toCValues().ptr) glTexCoordPointer(2, GL_FLOAT, 0, texCoords.toFloatArray().toCValues().ptr)
glDrawElements(GL_TRIANGLES, triangles.size, GL_UNSIGNED_BYTE, triangles.toByteArray().toCValues().ptr) glNormalPointer(GL_FLOAT, 0, normals.toFloatArray().toCValues().ptr)
glDrawElements(GL_TRIANGLES, triangles.size, GL_UNSIGNED_BYTE, triangles.toByteArray().toCValues().ptr)
}
glPopMatrix() glPopMatrix()
if (eglSwapBuffers(display, surface) == 0) { if (eglSwapBuffers(display, surface) == 0) {
logError("eglSwapBuffers() returned error ${eglGetError()}") val error = eglGetError()
destroy() if (error != EGL_BAD_SURFACE)
throw Error("eglSwapBuffers() returned error $error")
else {
if (eglMakeCurrent(display, surface, surface, context) == 0) {
throw Error("Reinit eglMakeCurrent() returned error ${eglGetError()}")
}
if (eglSwapBuffers(display, surface) == 0)
throw Error("Bad eglSwapBuffers() after surface reinit: ${eglGetError()}")
}
} }
} }
fun start() {
logInfo("Starting renderer..")
if (initialized) {
if (eglMakeCurrent(display, surface, surface, context) == 0) {
throw Error("eglMakeCurrent() returned error ${eglGetError()}")
}
}
}
fun stop() {
logInfo("Stopping renderer..")
eglMakeCurrent(display, null, null, null)
}
fun destroy() { fun destroy() {
if (!initialized) return if (!initialized) return
logInfo("Destroying context..") logInfo("Destroying renderer..")
eglMakeCurrent(display, null, null, null) eglMakeCurrent(display, null, null, null)
context?.let { eglDestroyContext(display, it) } context?.let { eglDestroyContext(display, it) }
surface?.let { eglDestroySurface(display, it) } surface?.let { eglDestroySurface(display, it) }
eglTerminate(display) display?.let { eglTerminate(display) }
display = null display = null
surface = null surface = null
context = null context = null
initialized = false initialized = false
// TODO: What should be called here?
//arena.clear()
} }
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright 2010-2017 JetBrains s.r.o. * Copyright 2010-2018 JetBrains s.r.o.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package NativeApplication
import kotlinx.cinterop.* import kotlinx.cinterop.*
import platform.android.* import platform.android.*
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2018 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 platform.android.*
import NativeApplication.*
fun main(args: Array<String>) {
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)
}