Improve Android native sample, add basic Java bridge. (#1606)
This commit is contained in:
+1
-1
@@ -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 {
|
||||||
|
|||||||
+7
-7
@@ -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) {
|
||||||
|
|||||||
+1
-1
@@ -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())
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
|
|||||||
+1
-1
@@ -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;
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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() })
|
||||||
|
}
|
||||||
+70
-52
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -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))
|
||||||
+94
-98
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-1
@@ -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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user