├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── mobile ├── .gitignore ├── build.gradle ├── mobile.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── org │ │ └── nxsy │ │ └── ndk_handmade │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── test │ │ └── .gitignore │ ├── handmade │ └── .gitignore │ ├── jni │ ├── android_native_app_glue.c │ ├── android_native_app_glue.h │ └── app.cpp │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ └── values │ ├── strings.xml │ └── styles.xml ├── ndk_handmade.iml ├── settings.gradle └── tv ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src ├── androidTest │ └── java │ │ └── org │ │ └── nxsy │ │ └── ndk_handmade │ │ └── ApplicationTest.java └── main │ ├── AndroidManifest.xml │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ └── values │ ├── strings.xml │ └── styles.xml └── tv.iml /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ndk_handmade -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ndk_handmade 2 | 3 | This is an implementation of an Android platform layer for [Handmade Hero](http://handmadehero.org/), 4 | using the [Android Native Development Kit (NDK)](https://developer.android.com/tools/sdk/ndk/index.html). 5 | 6 | # Prerequisites 7 | 8 | You'll obviously need the Handmade Hero source code for the platform-indepenent game code and the 9 | game assets. 10 | 11 | Install Android Studio and the Android NDK. 12 | 13 | # Build process 14 | 15 | Copy the Handmade Hero source files into `ndk_handmade/mobile/src/main/handmade`. 16 | 17 | Copy the Handmade Hero test assets into `ndk_handmade/mobile/src/assets/test`. 18 | 19 | Open in Android Studio and configure local.properties file with links to the Android SDK and Android NDK: 20 | 21 | sdk.dir=S\:\\ 22 | ndk.dir=U\:\\ 23 | 24 | Press the "Play" button, and select a device or emulator. 25 | 26 | # Implementation progress 27 | 28 | Completed (at least partially): 29 | 30 | * Game memory allocation 31 | * Graphics 32 | * Frame timing and locking 33 | * Debug platform function - enough to read the test assets 34 | * Calling UpdateAndRender 35 | 36 | Still needed: 37 | 38 | * Input - touch, key, controller, ... 39 | * Audio 40 | 41 | Not planned: 42 | 43 | * Hot reloading 44 | * Save state/record/replay -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.0.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /mobile/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mobile/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "21.1.2" 6 | 7 | defaultConfig { 8 | applicationId "org.nxsy.ndk_handmade" 9 | minSdkVersion 17 10 | targetSdkVersion 21 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | ndk { 15 | moduleName "NdkHandmadeModule" 16 | ldLibs "android", "log", "EGL", "GLESv2" 17 | cFlags "-DHANDMADE_SLOW=1 -DHANDMADE_INTERNAL=1 -std=c++11 -I${project.buildDir}/../src/main/handmade" 18 | } 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | sourceSets { main { assets.srcDirs = ['src/main/assets'] } } 27 | } 28 | 29 | dependencies { 30 | compile fileTree(dir: 'libs', include: ['*.jar']) 31 | compile 'com.android.support:appcompat-v7:21.0.3' 32 | } 33 | -------------------------------------------------------------------------------- /mobile/mobile.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /mobile/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in S:\/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /mobile/src/androidTest/java/org/nxsy/ndk_handmade/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package org.nxsy.ndk_handmade; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /mobile/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /mobile/src/main/assets/test/.gitignore: -------------------------------------------------------------------------------- 1 | /test_* 2 | -------------------------------------------------------------------------------- /mobile/src/main/handmade/.gitignore: -------------------------------------------------------------------------------- 1 | /handmade.cpp 2 | /handmade.h 3 | /handmade_* 4 | -------------------------------------------------------------------------------- /mobile/src/main/jni/android_native_app_glue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "android_native_app_glue.h" 26 | #include 27 | 28 | #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__)) 29 | #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__)) 30 | 31 | /* For debug builds, always enable the debug traces in this library */ 32 | #ifndef NDEBUG 33 | # define LOGV(...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, "threaded_app", __VA_ARGS__)) 34 | #else 35 | # define LOGV(...) ((void)0) 36 | #endif 37 | 38 | static void free_saved_state(struct android_app* android_app) { 39 | pthread_mutex_lock(&android_app->mutex); 40 | if (android_app->savedState != NULL) { 41 | free(android_app->savedState); 42 | android_app->savedState = NULL; 43 | android_app->savedStateSize = 0; 44 | } 45 | pthread_mutex_unlock(&android_app->mutex); 46 | } 47 | 48 | int8_t android_app_read_cmd(struct android_app* android_app) { 49 | int8_t cmd; 50 | if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) { 51 | switch (cmd) { 52 | case APP_CMD_SAVE_STATE: 53 | free_saved_state(android_app); 54 | break; 55 | } 56 | return cmd; 57 | } else { 58 | LOGE("No data on command pipe!"); 59 | } 60 | return -1; 61 | } 62 | 63 | static void print_cur_config(struct android_app* android_app) { 64 | char lang[2], country[2]; 65 | AConfiguration_getLanguage(android_app->config, lang); 66 | AConfiguration_getCountry(android_app->config, country); 67 | 68 | LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d " 69 | "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d " 70 | "modetype=%d modenight=%d", 71 | AConfiguration_getMcc(android_app->config), 72 | AConfiguration_getMnc(android_app->config), 73 | lang[0], lang[1], country[0], country[1], 74 | AConfiguration_getOrientation(android_app->config), 75 | AConfiguration_getTouchscreen(android_app->config), 76 | AConfiguration_getDensity(android_app->config), 77 | AConfiguration_getKeyboard(android_app->config), 78 | AConfiguration_getNavigation(android_app->config), 79 | AConfiguration_getKeysHidden(android_app->config), 80 | AConfiguration_getNavHidden(android_app->config), 81 | AConfiguration_getSdkVersion(android_app->config), 82 | AConfiguration_getScreenSize(android_app->config), 83 | AConfiguration_getScreenLong(android_app->config), 84 | AConfiguration_getUiModeType(android_app->config), 85 | AConfiguration_getUiModeNight(android_app->config)); 86 | } 87 | 88 | void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { 89 | switch (cmd) { 90 | case APP_CMD_INPUT_CHANGED: 91 | LOGV("APP_CMD_INPUT_CHANGED\n"); 92 | pthread_mutex_lock(&android_app->mutex); 93 | if (android_app->inputQueue != NULL) { 94 | AInputQueue_detachLooper(android_app->inputQueue); 95 | } 96 | android_app->inputQueue = android_app->pendingInputQueue; 97 | if (android_app->inputQueue != NULL) { 98 | LOGV("Attaching input queue to looper"); 99 | AInputQueue_attachLooper(android_app->inputQueue, 100 | android_app->looper, LOOPER_ID_INPUT, NULL, 101 | &android_app->inputPollSource); 102 | } 103 | pthread_cond_broadcast(&android_app->cond); 104 | pthread_mutex_unlock(&android_app->mutex); 105 | break; 106 | 107 | case APP_CMD_INIT_WINDOW: 108 | LOGV("APP_CMD_INIT_WINDOW\n"); 109 | pthread_mutex_lock(&android_app->mutex); 110 | android_app->window = android_app->pendingWindow; 111 | pthread_cond_broadcast(&android_app->cond); 112 | pthread_mutex_unlock(&android_app->mutex); 113 | break; 114 | 115 | case APP_CMD_TERM_WINDOW: 116 | LOGV("APP_CMD_TERM_WINDOW\n"); 117 | pthread_cond_broadcast(&android_app->cond); 118 | break; 119 | 120 | case APP_CMD_RESUME: 121 | case APP_CMD_START: 122 | case APP_CMD_PAUSE: 123 | case APP_CMD_STOP: 124 | LOGV("activityState=%d\n", cmd); 125 | pthread_mutex_lock(&android_app->mutex); 126 | android_app->activityState = cmd; 127 | pthread_cond_broadcast(&android_app->cond); 128 | pthread_mutex_unlock(&android_app->mutex); 129 | break; 130 | 131 | case APP_CMD_CONFIG_CHANGED: 132 | LOGV("APP_CMD_CONFIG_CHANGED\n"); 133 | AConfiguration_fromAssetManager(android_app->config, 134 | android_app->activity->assetManager); 135 | print_cur_config(android_app); 136 | break; 137 | 138 | case APP_CMD_DESTROY: 139 | LOGV("APP_CMD_DESTROY\n"); 140 | android_app->destroyRequested = 1; 141 | break; 142 | } 143 | } 144 | 145 | void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) { 146 | switch (cmd) { 147 | case APP_CMD_TERM_WINDOW: 148 | LOGV("APP_CMD_TERM_WINDOW\n"); 149 | pthread_mutex_lock(&android_app->mutex); 150 | android_app->window = NULL; 151 | pthread_cond_broadcast(&android_app->cond); 152 | pthread_mutex_unlock(&android_app->mutex); 153 | break; 154 | 155 | case APP_CMD_SAVE_STATE: 156 | LOGV("APP_CMD_SAVE_STATE\n"); 157 | pthread_mutex_lock(&android_app->mutex); 158 | android_app->stateSaved = 1; 159 | pthread_cond_broadcast(&android_app->cond); 160 | pthread_mutex_unlock(&android_app->mutex); 161 | break; 162 | 163 | case APP_CMD_RESUME: 164 | free_saved_state(android_app); 165 | break; 166 | } 167 | } 168 | 169 | void app_dummy() { 170 | 171 | } 172 | 173 | static void android_app_destroy(struct android_app* android_app) { 174 | LOGV("android_app_destroy!"); 175 | free_saved_state(android_app); 176 | pthread_mutex_lock(&android_app->mutex); 177 | if (android_app->inputQueue != NULL) { 178 | AInputQueue_detachLooper(android_app->inputQueue); 179 | } 180 | AConfiguration_delete(android_app->config); 181 | android_app->destroyed = 1; 182 | pthread_cond_broadcast(&android_app->cond); 183 | pthread_mutex_unlock(&android_app->mutex); 184 | // Can't touch android_app object after this. 185 | } 186 | 187 | static void process_input(struct android_app* app, struct android_poll_source* source) { 188 | AInputEvent* event = NULL; 189 | while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) { 190 | LOGV("New input event: type=%d\n", AInputEvent_getType(event)); 191 | if (AInputQueue_preDispatchEvent(app->inputQueue, event)) { 192 | continue; 193 | } 194 | int32_t handled = 0; 195 | if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event); 196 | AInputQueue_finishEvent(app->inputQueue, event, handled); 197 | } 198 | } 199 | 200 | static void process_cmd(struct android_app* app, struct android_poll_source* source) { 201 | int8_t cmd = android_app_read_cmd(app); 202 | android_app_pre_exec_cmd(app, cmd); 203 | if (app->onAppCmd != NULL) app->onAppCmd(app, cmd); 204 | android_app_post_exec_cmd(app, cmd); 205 | } 206 | 207 | static void* android_app_entry(void* param) { 208 | struct android_app* android_app = (struct android_app*)param; 209 | 210 | android_app->config = AConfiguration_new(); 211 | AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager); 212 | 213 | print_cur_config(android_app); 214 | 215 | android_app->cmdPollSource.id = LOOPER_ID_MAIN; 216 | android_app->cmdPollSource.app = android_app; 217 | android_app->cmdPollSource.process = process_cmd; 218 | android_app->inputPollSource.id = LOOPER_ID_INPUT; 219 | android_app->inputPollSource.app = android_app; 220 | android_app->inputPollSource.process = process_input; 221 | 222 | ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); 223 | ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, 224 | &android_app->cmdPollSource); 225 | android_app->looper = looper; 226 | 227 | pthread_mutex_lock(&android_app->mutex); 228 | android_app->running = 1; 229 | pthread_cond_broadcast(&android_app->cond); 230 | pthread_mutex_unlock(&android_app->mutex); 231 | 232 | android_main(android_app); 233 | 234 | android_app_destroy(android_app); 235 | return NULL; 236 | } 237 | 238 | // -------------------------------------------------------------------- 239 | // Native activity interaction (called from main thread) 240 | // -------------------------------------------------------------------- 241 | 242 | static struct android_app* android_app_create(ANativeActivity* activity, 243 | void* savedState, size_t savedStateSize) { 244 | struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app)); 245 | memset(android_app, 0, sizeof(struct android_app)); 246 | android_app->activity = activity; 247 | 248 | pthread_mutex_init(&android_app->mutex, NULL); 249 | pthread_cond_init(&android_app->cond, NULL); 250 | 251 | if (savedState != NULL) { 252 | android_app->savedState = malloc(savedStateSize); 253 | android_app->savedStateSize = savedStateSize; 254 | memcpy(android_app->savedState, savedState, savedStateSize); 255 | } 256 | 257 | int msgpipe[2]; 258 | if (pipe(msgpipe)) { 259 | LOGE("could not create pipe: %s", strerror(errno)); 260 | return NULL; 261 | } 262 | android_app->msgread = msgpipe[0]; 263 | android_app->msgwrite = msgpipe[1]; 264 | 265 | pthread_attr_t attr; 266 | pthread_attr_init(&attr); 267 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 268 | pthread_create(&android_app->thread, &attr, android_app_entry, android_app); 269 | 270 | // Wait for thread to start. 271 | pthread_mutex_lock(&android_app->mutex); 272 | while (!android_app->running) { 273 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 274 | } 275 | pthread_mutex_unlock(&android_app->mutex); 276 | 277 | return android_app; 278 | } 279 | 280 | static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) { 281 | if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { 282 | LOGE("Failure writing android_app cmd: %s\n", strerror(errno)); 283 | } 284 | } 285 | 286 | static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) { 287 | pthread_mutex_lock(&android_app->mutex); 288 | android_app->pendingInputQueue = inputQueue; 289 | android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED); 290 | while (android_app->inputQueue != android_app->pendingInputQueue) { 291 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 292 | } 293 | pthread_mutex_unlock(&android_app->mutex); 294 | } 295 | 296 | static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { 297 | pthread_mutex_lock(&android_app->mutex); 298 | if (android_app->pendingWindow != NULL) { 299 | android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW); 300 | } 301 | android_app->pendingWindow = window; 302 | if (window != NULL) { 303 | android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW); 304 | } 305 | while (android_app->window != android_app->pendingWindow) { 306 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 307 | } 308 | pthread_mutex_unlock(&android_app->mutex); 309 | } 310 | 311 | static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) { 312 | pthread_mutex_lock(&android_app->mutex); 313 | android_app_write_cmd(android_app, cmd); 314 | while (android_app->activityState != cmd) { 315 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 316 | } 317 | pthread_mutex_unlock(&android_app->mutex); 318 | } 319 | 320 | static void android_app_free(struct android_app* android_app) { 321 | pthread_mutex_lock(&android_app->mutex); 322 | android_app_write_cmd(android_app, APP_CMD_DESTROY); 323 | while (!android_app->destroyed) { 324 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 325 | } 326 | pthread_mutex_unlock(&android_app->mutex); 327 | 328 | close(android_app->msgread); 329 | close(android_app->msgwrite); 330 | pthread_cond_destroy(&android_app->cond); 331 | pthread_mutex_destroy(&android_app->mutex); 332 | free(android_app); 333 | } 334 | 335 | static void onDestroy(ANativeActivity* activity) { 336 | LOGV("Destroy: %p\n", activity); 337 | android_app_free((struct android_app*)activity->instance); 338 | } 339 | 340 | static void onStart(ANativeActivity* activity) { 341 | LOGV("Start: %p\n", activity); 342 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START); 343 | } 344 | 345 | static void onResume(ANativeActivity* activity) { 346 | LOGV("Resume: %p\n", activity); 347 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME); 348 | } 349 | 350 | static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { 351 | struct android_app* android_app = (struct android_app*)activity->instance; 352 | void* savedState = NULL; 353 | 354 | LOGV("SaveInstanceState: %p\n", activity); 355 | pthread_mutex_lock(&android_app->mutex); 356 | android_app->stateSaved = 0; 357 | android_app_write_cmd(android_app, APP_CMD_SAVE_STATE); 358 | while (!android_app->stateSaved) { 359 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 360 | } 361 | 362 | if (android_app->savedState != NULL) { 363 | savedState = android_app->savedState; 364 | *outLen = android_app->savedStateSize; 365 | android_app->savedState = NULL; 366 | android_app->savedStateSize = 0; 367 | } 368 | 369 | pthread_mutex_unlock(&android_app->mutex); 370 | 371 | return savedState; 372 | } 373 | 374 | static void onPause(ANativeActivity* activity) { 375 | LOGV("Pause: %p\n", activity); 376 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE); 377 | } 378 | 379 | static void onStop(ANativeActivity* activity) { 380 | LOGV("Stop: %p\n", activity); 381 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP); 382 | } 383 | 384 | static void onConfigurationChanged(ANativeActivity* activity) { 385 | struct android_app* android_app = (struct android_app*)activity->instance; 386 | LOGV("ConfigurationChanged: %p\n", activity); 387 | android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED); 388 | } 389 | 390 | static void onLowMemory(ANativeActivity* activity) { 391 | struct android_app* android_app = (struct android_app*)activity->instance; 392 | LOGV("LowMemory: %p\n", activity); 393 | android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY); 394 | } 395 | 396 | static void onWindowFocusChanged(ANativeActivity* activity, int focused) { 397 | LOGV("WindowFocusChanged: %p -- %d\n", activity, focused); 398 | android_app_write_cmd((struct android_app*)activity->instance, 399 | focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); 400 | } 401 | 402 | static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { 403 | LOGV("NativeWindowCreated: %p -- %p\n", activity, window); 404 | android_app_set_window((struct android_app*)activity->instance, window); 405 | } 406 | 407 | static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { 408 | LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window); 409 | android_app_set_window((struct android_app*)activity->instance, NULL); 410 | } 411 | 412 | static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { 413 | LOGV("InputQueueCreated: %p -- %p\n", activity, queue); 414 | android_app_set_input((struct android_app*)activity->instance, queue); 415 | } 416 | 417 | static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { 418 | LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue); 419 | android_app_set_input((struct android_app*)activity->instance, NULL); 420 | } 421 | 422 | void ANativeActivity_onCreate(ANativeActivity* activity, 423 | void* savedState, size_t savedStateSize) { 424 | LOGV("Creating: %p\n", activity); 425 | activity->callbacks->onDestroy = onDestroy; 426 | activity->callbacks->onStart = onStart; 427 | activity->callbacks->onResume = onResume; 428 | activity->callbacks->onSaveInstanceState = onSaveInstanceState; 429 | activity->callbacks->onPause = onPause; 430 | activity->callbacks->onStop = onStop; 431 | activity->callbacks->onConfigurationChanged = onConfigurationChanged; 432 | activity->callbacks->onLowMemory = onLowMemory; 433 | activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; 434 | activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; 435 | activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; 436 | activity->callbacks->onInputQueueCreated = onInputQueueCreated; 437 | activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; 438 | 439 | activity->instance = android_app_create(activity, savedState, savedStateSize); 440 | } 441 | -------------------------------------------------------------------------------- /mobile/src/main/jni/android_native_app_glue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #ifndef _ANDROID_NATIVE_APP_GLUE_H 19 | #define _ANDROID_NATIVE_APP_GLUE_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | /** 34 | * The native activity interface provided by 35 | * is based on a set of application-provided callbacks that will be called 36 | * by the Activity's main thread when certain events occur. 37 | * 38 | * This means that each one of this callbacks _should_ _not_ block, or they 39 | * risk having the system force-close the application. This programming 40 | * model is direct, lightweight, but constraining. 41 | * 42 | * The 'android_native_app_glue' static library is used to provide a different 43 | * execution model where the application can implement its own main event 44 | * loop in a different thread instead. Here's how it works: 45 | * 46 | * 1/ The application must provide a function named "android_main()" that 47 | * will be called when the activity is created, in a new thread that is 48 | * distinct from the activity's main thread. 49 | * 50 | * 2/ android_main() receives a pointer to a valid "android_app" structure 51 | * that contains references to other important objects, e.g. the 52 | * ANativeActivity obejct instance the application is running in. 53 | * 54 | * 3/ the "android_app" object holds an ALooper instance that already 55 | * listens to two important things: 56 | * 57 | * - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX 58 | * declarations below. 59 | * 60 | * - input events coming from the AInputQueue attached to the activity. 61 | * 62 | * Each of these correspond to an ALooper identifier returned by 63 | * ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT, 64 | * respectively. 65 | * 66 | * Your application can use the same ALooper to listen to additional 67 | * file-descriptors. They can either be callback based, or with return 68 | * identifiers starting with LOOPER_ID_USER. 69 | * 70 | * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event, 71 | * the returned data will point to an android_poll_source structure. You 72 | * can call the process() function on it, and fill in android_app->onAppCmd 73 | * and android_app->onInputEvent to be called for your own processing 74 | * of the event. 75 | * 76 | * Alternatively, you can call the low-level functions to read and process 77 | * the data directly... look at the process_cmd() and process_input() 78 | * implementations in the glue to see how to do this. 79 | * 80 | * See the sample named "native-activity" that comes with the NDK with a 81 | * full usage example. Also look at the JavaDoc of NativeActivity. 82 | */ 83 | 84 | struct android_app; 85 | 86 | /** 87 | * Data associated with an ALooper fd that will be returned as the "outData" 88 | * when that source has data ready. 89 | */ 90 | struct android_poll_source { 91 | // The identifier of this source. May be LOOPER_ID_MAIN or 92 | // LOOPER_ID_INPUT. 93 | int32_t id; 94 | 95 | // The android_app this ident is associated with. 96 | struct android_app* app; 97 | 98 | // Function to call to perform the standard processing of data from 99 | // this source. 100 | void (*process)(struct android_app* app, struct android_poll_source* source); 101 | }; 102 | 103 | /** 104 | * This is the interface for the standard glue code of a threaded 105 | * application. In this model, the application's code is running 106 | * in its own thread separate from the main thread of the process. 107 | * It is not required that this thread be associated with the Java 108 | * VM, although it will need to be in order to make JNI calls any 109 | * Java objects. 110 | */ 111 | struct android_app { 112 | // The application can place a pointer to its own state object 113 | // here if it likes. 114 | void* userData; 115 | 116 | // Fill this in with the function to process main app commands (APP_CMD_*) 117 | void (*onAppCmd)(struct android_app* app, int32_t cmd); 118 | 119 | // Fill this in with the function to process input events. At this point 120 | // the event has already been pre-dispatched, and it will be finished upon 121 | // return. Return 1 if you have handled the event, 0 for any default 122 | // dispatching. 123 | int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); 124 | 125 | // The ANativeActivity object instance that this app is running in. 126 | ANativeActivity* activity; 127 | 128 | // The current configuration the app is running in. 129 | AConfiguration* config; 130 | 131 | // This is the last instance's saved state, as provided at creation time. 132 | // It is NULL if there was no state. You can use this as you need; the 133 | // memory will remain around until you call android_app_exec_cmd() for 134 | // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL. 135 | // These variables should only be changed when processing a APP_CMD_SAVE_STATE, 136 | // at which point they will be initialized to NULL and you can malloc your 137 | // state and place the information here. In that case the memory will be 138 | // freed for you later. 139 | void* savedState; 140 | size_t savedStateSize; 141 | 142 | // The ALooper associated with the app's thread. 143 | ALooper* looper; 144 | 145 | // When non-NULL, this is the input queue from which the app will 146 | // receive user input events. 147 | AInputQueue* inputQueue; 148 | 149 | // When non-NULL, this is the window surface that the app can draw in. 150 | ANativeWindow* window; 151 | 152 | // Current content rectangle of the window; this is the area where the 153 | // window's content should be placed to be seen by the user. 154 | ARect contentRect; 155 | 156 | // Current state of the app's activity. May be either APP_CMD_START, 157 | // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below. 158 | int activityState; 159 | 160 | // This is non-zero when the application's NativeActivity is being 161 | // destroyed and waiting for the app thread to complete. 162 | int destroyRequested; 163 | 164 | // ------------------------------------------------- 165 | // Below are "private" implementation of the glue code. 166 | 167 | pthread_mutex_t mutex; 168 | pthread_cond_t cond; 169 | 170 | int msgread; 171 | int msgwrite; 172 | 173 | pthread_t thread; 174 | 175 | struct android_poll_source cmdPollSource; 176 | struct android_poll_source inputPollSource; 177 | 178 | int running; 179 | int stateSaved; 180 | int destroyed; 181 | int redrawNeeded; 182 | AInputQueue* pendingInputQueue; 183 | ANativeWindow* pendingWindow; 184 | ARect pendingContentRect; 185 | }; 186 | 187 | enum { 188 | /** 189 | * Looper data ID of commands coming from the app's main thread, which 190 | * is returned as an identifier from ALooper_pollOnce(). The data for this 191 | * identifier is a pointer to an android_poll_source structure. 192 | * These can be retrieved and processed with android_app_read_cmd() 193 | * and android_app_exec_cmd(). 194 | */ 195 | LOOPER_ID_MAIN = 1, 196 | 197 | /** 198 | * Looper data ID of events coming from the AInputQueue of the 199 | * application's window, which is returned as an identifier from 200 | * ALooper_pollOnce(). The data for this identifier is a pointer to an 201 | * android_poll_source structure. These can be read via the inputQueue 202 | * object of android_app. 203 | */ 204 | LOOPER_ID_INPUT = 2, 205 | 206 | /** 207 | * Start of user-defined ALooper identifiers. 208 | */ 209 | LOOPER_ID_USER = 3, 210 | }; 211 | 212 | enum { 213 | /** 214 | * Command from main thread: the AInputQueue has changed. Upon processing 215 | * this command, android_app->inputQueue will be updated to the new queue 216 | * (or NULL). 217 | */ 218 | APP_CMD_INPUT_CHANGED, 219 | 220 | /** 221 | * Command from main thread: a new ANativeWindow is ready for use. Upon 222 | * receiving this command, android_app->window will contain the new window 223 | * surface. 224 | */ 225 | APP_CMD_INIT_WINDOW, 226 | 227 | /** 228 | * Command from main thread: the existing ANativeWindow needs to be 229 | * terminated. Upon receiving this command, android_app->window still 230 | * contains the existing window; after calling android_app_exec_cmd 231 | * it will be set to NULL. 232 | */ 233 | APP_CMD_TERM_WINDOW, 234 | 235 | /** 236 | * Command from main thread: the current ANativeWindow has been resized. 237 | * Please redraw with its new size. 238 | */ 239 | APP_CMD_WINDOW_RESIZED, 240 | 241 | /** 242 | * Command from main thread: the system needs that the current ANativeWindow 243 | * be redrawn. You should redraw the window before handing this to 244 | * android_app_exec_cmd() in order to avoid transient drawing glitches. 245 | */ 246 | APP_CMD_WINDOW_REDRAW_NEEDED, 247 | 248 | /** 249 | * Command from main thread: the content area of the window has changed, 250 | * such as from the soft input window being shown or hidden. You can 251 | * find the new content rect in android_app::contentRect. 252 | */ 253 | APP_CMD_CONTENT_RECT_CHANGED, 254 | 255 | /** 256 | * Command from main thread: the app's activity window has gained 257 | * input focus. 258 | */ 259 | APP_CMD_GAINED_FOCUS, 260 | 261 | /** 262 | * Command from main thread: the app's activity window has lost 263 | * input focus. 264 | */ 265 | APP_CMD_LOST_FOCUS, 266 | 267 | /** 268 | * Command from main thread: the current device configuration has changed. 269 | */ 270 | APP_CMD_CONFIG_CHANGED, 271 | 272 | /** 273 | * Command from main thread: the system is running low on memory. 274 | * Try to reduce your memory use. 275 | */ 276 | APP_CMD_LOW_MEMORY, 277 | 278 | /** 279 | * Command from main thread: the app's activity has been started. 280 | */ 281 | APP_CMD_START, 282 | 283 | /** 284 | * Command from main thread: the app's activity has been resumed. 285 | */ 286 | APP_CMD_RESUME, 287 | 288 | /** 289 | * Command from main thread: the app should generate a new saved state 290 | * for itself, to restore from later if needed. If you have saved state, 291 | * allocate it with malloc and place it in android_app.savedState with 292 | * the size in android_app.savedStateSize. The will be freed for you 293 | * later. 294 | */ 295 | APP_CMD_SAVE_STATE, 296 | 297 | /** 298 | * Command from main thread: the app's activity has been paused. 299 | */ 300 | APP_CMD_PAUSE, 301 | 302 | /** 303 | * Command from main thread: the app's activity has been stopped. 304 | */ 305 | APP_CMD_STOP, 306 | 307 | /** 308 | * Command from main thread: the app's activity is being destroyed, 309 | * and waiting for the app thread to clean up and exit before proceeding. 310 | */ 311 | APP_CMD_DESTROY, 312 | }; 313 | 314 | /** 315 | * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next 316 | * app command message. 317 | */ 318 | int8_t android_app_read_cmd(struct android_app* android_app); 319 | 320 | /** 321 | * Call with the command returned by android_app_read_cmd() to do the 322 | * initial pre-processing of the given command. You can perform your own 323 | * actions for the command after calling this function. 324 | */ 325 | void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd); 326 | 327 | /** 328 | * Call with the command returned by android_app_read_cmd() to do the 329 | * final post-processing of the given command. You must have done your own 330 | * actions for the command before calling this function. 331 | */ 332 | void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); 333 | 334 | /** 335 | * Dummy function you can call to ensure glue code isn't stripped. 336 | */ 337 | void app_dummy(); 338 | 339 | /** 340 | * This is the function that application code must implement, representing 341 | * the main entry to the app. 342 | */ 343 | extern void android_main(struct android_app* app); 344 | 345 | #ifdef __cplusplus 346 | } 347 | #endif 348 | 349 | #endif /* _ANDROID_NATIVE_APP_GLUE_H */ 350 | -------------------------------------------------------------------------------- /mobile/src/main/jni/app.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include "android_native_app_glue.h" 11 | 12 | #include "handmade_platform.h" 13 | 14 | #include "handmade.cpp" 15 | 16 | struct pan_state { 17 | bool32 in_pan; 18 | v2 start_pos; 19 | v2 magnitude; 20 | v2 stick; 21 | }; 22 | 23 | struct motion_state { 24 | pan_state pan; 25 | }; 26 | 27 | struct user_data { 28 | char app_name[64]; 29 | EGLDisplay display; 30 | EGLSurface surface; 31 | EGLContext context; 32 | 33 | bool drawable; 34 | 35 | uint program; 36 | uint a_pos_id; 37 | uint a_tex_coord_id; 38 | uint texture_id; 39 | uint sampler_id; 40 | 41 | uint8_t *texture_buffer; 42 | 43 | uint64_t total_size; 44 | void *game_memory_block; 45 | 46 | char binary_name[1024]; 47 | char *one_past_binary_filename_slash; 48 | 49 | motion_state motion; 50 | 51 | game_input *new_input; 52 | game_input *old_input; 53 | }; 54 | 55 | char *cmd_names[] = { 56 | "APP_CMD_INPUT_CHANGED", 57 | "APP_CMD_INIT_WINDOW", 58 | "APP_CMD_TERM_WINDOW", 59 | "APP_CMD_WINDOW_RESIZED", 60 | "APP_CMD_WINDOW_REDRAW_NEEDED", 61 | "APP_CMD_CONTENT_RECT_CHANGED", 62 | "APP_CMD_GAINED_FOCUS", 63 | "APP_CMD_LOST_FOCUS", 64 | "APP_CMD_CONFIG_CHANGED", 65 | "APP_CMD_LOW_MEMORY", 66 | "APP_CMD_START", 67 | "APP_CMD_RESUME", 68 | "APP_CMD_SAVE_STATE", 69 | "APP_CMD_PAUSE", 70 | "APP_CMD_STOP", 71 | "APP_CMD_DESTROY", 72 | }; 73 | 74 | void init(android_app *app) 75 | { 76 | user_data *p = (user_data *)app->userData; 77 | 78 | p->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 79 | eglInitialize(p->display, 0, 0); 80 | 81 | int attrib_list[] = { 82 | EGL_RED_SIZE, 8, 83 | EGL_GREEN_SIZE, 8, 84 | EGL_BLUE_SIZE, 8, 85 | EGL_ALPHA_SIZE, 8, 86 | EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 87 | EGL_NONE 88 | }; 89 | 90 | EGLConfig config; 91 | int num_config; 92 | eglChooseConfig(p->display, attrib_list, &config, 1, &num_config); 93 | 94 | int format; 95 | eglGetConfigAttrib(p->display, config, EGL_NATIVE_VISUAL_ID, &format); 96 | 97 | ANativeWindow_setBuffersGeometry(app->window, 0, 0, format); 98 | p->surface = eglCreateWindowSurface(p->display, config, app->window, 0); 99 | 100 | const int context_attribs[] = { 101 | EGL_CONTEXT_CLIENT_VERSION, 2, 102 | EGL_NONE 103 | }; 104 | eglBindAPI(EGL_OPENGL_ES_API); 105 | p->context = eglCreateContext(p->display, config, EGL_NO_CONTEXT, context_attribs); 106 | 107 | eglMakeCurrent(p->display, p->surface, p->surface, p->context); 108 | 109 | p->program = glCreateProgram(); 110 | char *vertex_shader_source = 111 | "attribute vec2 a_pos; \n" 112 | "attribute vec2 a_tex_coord; \n" 113 | "varying vec2 v_tex_coord; \n" 114 | "void main() \n" 115 | "{ \n" 116 | " gl_Position = vec4(a_pos, 0, 1); \n" 117 | " v_tex_coord = a_tex_coord; \n" 118 | "} \n"; 119 | 120 | 121 | char *fragment_shader_source = 122 | "precision mediump float;\n" 123 | "varying vec2 v_tex_coord;\n" 124 | "uniform sampler2D tex;\n" 125 | "void main() \n" 126 | "{ \n" 127 | " vec4 texture_color = vec4(texture2D( tex, v_tex_coord ).bgr, 1.0);\n" 128 | " gl_FragColor = texture_color;\n" 129 | "} \n"; 130 | 131 | uint vertex_shader_id; 132 | uint fragment_shader_id; 133 | 134 | { 135 | uint shader = vertex_shader_id = glCreateShader(GL_VERTEX_SHADER); 136 | int compiled; 137 | glShaderSource(shader, 1, (const char* const *)&vertex_shader_source, 0); 138 | glCompileShader(shader); 139 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); 140 | if (!compiled) 141 | { 142 | char info_log[1024]; 143 | glGetShaderInfoLog(shader, sizeof(info_log), 0, info_log); 144 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "vertex shader failed to compile: %s", info_log); 145 | } 146 | } 147 | { 148 | uint shader = fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER); 149 | int compiled; 150 | glShaderSource(shader, 1, (const char* const *)&fragment_shader_source, 0); 151 | glCompileShader(shader); 152 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); 153 | if (!compiled) 154 | { 155 | char info_log[1024]; 156 | glGetShaderInfoLog(shader, sizeof(info_log), 0, info_log); 157 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "fragment shader failed to compile: %s", info_log); 158 | } 159 | } 160 | glAttachShader(p->program, vertex_shader_id); 161 | glAttachShader(p->program, fragment_shader_id); 162 | glLinkProgram(p->program); 163 | 164 | int linked; 165 | glGetProgramiv(p->program, GL_LINK_STATUS, &linked); 166 | if (!linked) 167 | { 168 | char info_log[1024]; 169 | glGetProgramInfoLog(p->program, sizeof(info_log), 0, info_log); 170 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "program failed to link: %s", info_log); 171 | } 172 | 173 | glUseProgram(p->program); 174 | p->a_pos_id = glGetAttribLocation(p->program, "a_pos"); 175 | p->a_tex_coord_id = glGetAttribLocation(p->program, "a_tex_coord"); 176 | p->sampler_id = glGetAttribLocation(p->program, "tex"); 177 | glEnableVertexAttribArray(p->a_pos_id); 178 | glEnableVertexAttribArray(p->a_tex_coord_id); 179 | 180 | glGenTextures(1, &p->texture_id); 181 | glBindTexture(GL_TEXTURE_2D, p->texture_id); 182 | 183 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 184 | 960, 540, 0, 185 | GL_RGBA, GL_UNSIGNED_BYTE, p->texture_buffer); 186 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 187 | 188 | glDepthFunc(GL_ALWAYS); 189 | glDisable(GL_DEPTH_TEST); 190 | glDisable(GL_STENCIL_TEST); 191 | glDisable(GL_CULL_FACE); 192 | 193 | p->drawable = 1; 194 | } 195 | 196 | void term(android_app *app) 197 | { 198 | user_data *p = (user_data *)app->userData; 199 | p->drawable = 0; 200 | eglMakeCurrent(p->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 201 | eglDestroyContext(p->display, p->context); 202 | eglDestroySurface(p->display, p->surface); 203 | eglTerminate(p->display); 204 | } 205 | 206 | void on_app_cmd(android_app *app, int32_t cmd) { 207 | user_data *p = (user_data *)app->userData; 208 | if (cmd < sizeof(cmd_names)) 209 | { 210 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "cmd is %s", cmd_names[cmd]); 211 | } 212 | else 213 | { 214 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "unknown cmd is %d", cmd); 215 | } 216 | if (cmd == APP_CMD_INIT_WINDOW) 217 | { 218 | init(app); 219 | } 220 | if (cmd == APP_CMD_TERM_WINDOW) 221 | { 222 | term(app); 223 | } 224 | if (cmd == APP_CMD_DESTROY) 225 | { 226 | exit(0); 227 | } 228 | } 229 | 230 | int32_t on_motion_event(android_app *app, AInputEvent *event) 231 | { 232 | /* 233 | AMotionEvent_getAction(); 234 | AMotionEvent_getEventTime(); 235 | AMotionEvent_getPointerCount(); 236 | AMotionEvent_getPointerId(); 237 | AMotionEvent_getX(); 238 | AMotionEvent_getY(); 239 | 240 | AMOTION_EVENT_ACTION_DOWN 241 | AMOTION_EVENT_ACTION_UP 242 | AMOTION_EVENT_ACTION_MOVE 243 | AMOTION_EVENT_ACTION_CANCEL 244 | AMOTION_EVENT_ACTION_POINTER_DOWN 245 | AMOTION_EVENT_ACTION_POINTER_UP 246 | */ 247 | user_data *p = (user_data *)app->userData; 248 | pan_state *pan = &p->motion.pan; 249 | 250 | pan->stick = {}; 251 | 252 | uint action = AMotionEvent_getAction(event); 253 | uint num_pointers = AMotionEvent_getPointerCount(event); 254 | if (num_pointers != 2 || action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) 255 | { 256 | if (pan->in_pan) 257 | { 258 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "ending pan"); 259 | } 260 | pan->in_pan = 0; 261 | } 262 | else 263 | { 264 | uint pointer_id_0 = AMotionEvent_getPointerId(event, 0); 265 | uint pointer_id_1 = AMotionEvent_getPointerId(event, 1); 266 | v2 pointer_pos_0 = {AMotionEvent_getX(event, pointer_id_0), AMotionEvent_getY(event, pointer_id_0)}; 267 | v2 pointer_pos_1 = {AMotionEvent_getX(event, pointer_id_1), AMotionEvent_getY(event, pointer_id_1)}; 268 | v2 center_pos = (pointer_pos_0 + pointer_pos_1) * 0.5f; 269 | 270 | if (!pan->in_pan) 271 | { 272 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "starting pan"); 273 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "pointer_pos_0: %0.2f, %0.2f", pointer_pos_0.X, pointer_pos_0.Y); 274 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "pointer_pos_1: %0.2f, %0.2f", pointer_pos_1.X, pointer_pos_1.Y); 275 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "center_pos: %0.2f, %0.2f", center_pos.X, center_pos.Y); 276 | pan->in_pan = 1; 277 | pan->start_pos = center_pos; 278 | } 279 | 280 | if (pan->in_pan) 281 | { 282 | v2 distance = center_pos - pan->start_pos; 283 | if ((abs(distance.X) > 100) || (abs(distance.Y) > 100)) 284 | { 285 | if (abs(distance.X) > 100) 286 | { 287 | pan->stick.X = distance.X > 0 ? 1.0 : -1.0; 288 | } 289 | if (abs(distance.Y) > 100) 290 | { 291 | pan->stick.Y = distance.Y < 0 ? 1.0 : -1.0; 292 | } 293 | } 294 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "pan->stick: %0.2f, %0.2f", pan->stick.X, pan->stick.Y); 295 | } 296 | } 297 | return 1; 298 | } 299 | 300 | internal void 301 | process_keyboard_message(game_button_state *new_state, bool32 is_down) 302 | { 303 | if (new_state->EndedDown != is_down) 304 | { 305 | new_state->EndedDown = is_down; 306 | ++new_state->HalfTransitionCount; 307 | } 308 | } 309 | 310 | int32_t on_key_event(android_app *app, AInputEvent *event) 311 | { 312 | user_data *p = (user_data *)app->userData; 313 | game_controller_input *old_keyboard_controller = GetController(p->old_input, 0); 314 | game_controller_input *new_keyboard_controller = GetController(p->new_input, 0); 315 | 316 | uint action = AKeyEvent_getAction(event); 317 | if (action == AKEY_EVENT_ACTION_MULTIPLE) 318 | { 319 | return 1; 320 | } 321 | bool32 is_down = action == AKEY_EVENT_ACTION_DOWN; 322 | 323 | int keycode = AKeyEvent_getKeyCode(event); 324 | int meta_state = AKeyEvent_getMetaState(event); 325 | if (keycode == 4) 326 | { 327 | return 0; 328 | } 329 | else if ((keycode == 51) || (keycode == 19)) 330 | { 331 | process_keyboard_message(&new_keyboard_controller->MoveUp, is_down); 332 | } 333 | else if ((keycode == 29) || (keycode == 21)) 334 | { 335 | process_keyboard_message(&new_keyboard_controller->MoveLeft, is_down); 336 | } 337 | else if ((keycode == 47) || (keycode == 20)) 338 | { 339 | process_keyboard_message(&new_keyboard_controller->MoveDown, is_down); 340 | } 341 | else if ((keycode == 32) || (keycode == 22)) 342 | { 343 | process_keyboard_message(&new_keyboard_controller->MoveRight, is_down); 344 | } 345 | else 346 | { 347 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "key event: down %d, keycode %d, meta_state %x", is_down, keycode, meta_state); 348 | } 349 | return 1; 350 | } 351 | 352 | int32_t on_input_event(android_app *app, AInputEvent *event) { 353 | user_data *p = (user_data *)app->userData; 354 | int event_type = AInputEvent_getType(event); 355 | 356 | switch (event_type) 357 | { 358 | case AINPUT_EVENT_TYPE_KEY: 359 | { 360 | return on_key_event(app, event); 361 | } 362 | case AINPUT_EVENT_TYPE_MOTION: 363 | { 364 | return on_motion_event(app, event); 365 | } 366 | default: 367 | { 368 | __android_log_print(ANDROID_LOG_INFO, p->app_name, "unknown event_type was %d", event_type); 369 | break; 370 | } 371 | } 372 | return 0; 373 | } 374 | 375 | void draw(android_app *app) 376 | { 377 | user_data *p = (user_data *)app->userData; 378 | if (!p->drawable) 379 | { 380 | return; 381 | } 382 | eglMakeCurrent(p->display, p->surface, p->surface, p->context); 383 | static uint8_t grey_value = 0; 384 | grey_value += 1; 385 | 386 | glClearColor(grey_value / 255.0, grey_value / 255.0, grey_value / 255.0, 1.0); 387 | glClear(GL_COLOR_BUFFER_BIT); 388 | 389 | float vertexCoords[] = 390 | { 391 | -1, 1, 392 | -1, -1, 393 | 1, 1, 394 | 1, -1, 395 | }; 396 | float texCoords[] = 397 | { 398 | 0.0, 0.0, 399 | 0.0, 1.0, 400 | 1.0, 0.0, 401 | 1.0, 1.0, 402 | }; 403 | 404 | uint16_t indices[] = { 0, 1, 2, 1, 2, 3 }; 405 | 406 | glUseProgram(p->program); 407 | 408 | uint a_pos_id = glGetAttribLocation(p->program, "a_pos"); 409 | uint a_tex_coord_id = glGetAttribLocation(p->program, "a_tex_coord"); 410 | uint sampler_id = glGetAttribLocation(p->program, "tex"); 411 | 412 | if (!((a_pos_id == p->a_pos_id) && (a_tex_coord_id == p->a_tex_coord_id) && (sampler_id == p->sampler_id))) 413 | { 414 | __android_log_print(ANDROID_LOG_INFO, "org.nxsy.ndk_handmade", "program mismatch: pos_id %d/%d, tex_coord %d/%d, sampler_id %d/%d", a_pos_id, p->a_pos_id, a_tex_coord_id, p->a_tex_coord_id, sampler_id, p->sampler_id); 415 | } 416 | 417 | glVertexAttribPointer(p->a_pos_id, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), vertexCoords); 418 | glVertexAttribPointer(p->a_tex_coord_id, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), texCoords); 419 | glEnableVertexAttribArray(p->a_pos_id); 420 | glEnableVertexAttribArray(p->a_tex_coord_id); 421 | glBindTexture(GL_TEXTURE_2D, p->texture_id); 422 | glTexSubImage2D(GL_TEXTURE_2D, 423 | 0, 424 | 0, 425 | 0, 426 | 960, 427 | 540, 428 | GL_RGBA, 429 | GL_UNSIGNED_BYTE, 430 | p->texture_buffer); 431 | 432 | glUniform1i(p->sampler_id, 0); 433 | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices); 434 | 435 | eglSwapBuffers(p->display, p->surface); 436 | } 437 | 438 | static AAssetManager *asset_manager; 439 | 440 | DEBUG_PLATFORM_READ_ENTIRE_FILE(debug_read_entire_file) 441 | { 442 | debug_read_file_result result = {}; 443 | 444 | AAsset *asset = AAssetManager_open(asset_manager, Filename, AASSET_MODE_BUFFER); 445 | 446 | if (asset == 0) 447 | { 448 | __android_log_print(ANDROID_LOG_INFO, "org.nxsy.ndk_handmade", "Failed to open file %s", Filename); 449 | return result; 450 | } 451 | 452 | uint64_t asset_size = AAsset_getLength64(asset); 453 | 454 | char *buf = (char *)malloc(asset_size + 1); 455 | AAsset_read(asset, buf, asset_size); 456 | AAsset_close(asset); 457 | 458 | buf[asset_size] = 0; 459 | 460 | result.Contents = buf; 461 | result.ContentsSize = asset_size; 462 | 463 | return(result); 464 | } 465 | 466 | internal void 467 | process_button(bool down, game_button_state *old_state, game_button_state *new_state) 468 | { 469 | new_state->EndedDown = down; 470 | new_state->HalfTransitionCount = (old_state->EndedDown != new_state->EndedDown) ? 1 : 0; 471 | } 472 | 473 | internal void 474 | hh_process_events(android_app *app, game_input *new_input, game_input *old_input) 475 | { 476 | game_controller_input *old_controller = GetController(old_input, 1); 477 | game_controller_input *new_controller = GetController(new_input, 1); 478 | 479 | user_data *p = (user_data *)app->userData; 480 | pan_state *pan = &p->motion.pan; 481 | 482 | process_button((pan->stick.X > 0), &old_controller->MoveRight, &new_controller->MoveRight); 483 | process_button((pan->stick.X < 0), &old_controller->MoveLeft, &new_controller->MoveLeft); 484 | process_button((pan->stick.Y > 0), &old_controller->MoveUp, &new_controller->MoveUp); 485 | process_button((pan->stick.Y < 0), &old_controller->MoveDown, &new_controller->MoveDown); 486 | } 487 | 488 | void android_main(android_app *app) { 489 | app_dummy(); 490 | 491 | asset_manager = app->activity->assetManager; 492 | 493 | user_data p = {}; 494 | p.texture_buffer = (uint8_t *)malloc(4 * 960 * 540); 495 | strcpy(p.app_name, "org.nxsy.ndk_handmade"); 496 | app->userData = &p; 497 | 498 | app->onAppCmd = on_app_cmd; 499 | app->onInputEvent = on_input_event; 500 | uint64_t counter; 501 | uint start_row = 0; 502 | uint start_col = 0; 503 | 504 | game_memory m = {}; 505 | m.PermanentStorageSize = 64 * 1024 * 1024; 506 | m.TransientStorageSize = 64 * 1024 * 1024; 507 | p.total_size = m.PermanentStorageSize + m.TransientStorageSize; 508 | p.game_memory_block = calloc(p.total_size, sizeof(uint8)); 509 | m.PermanentStorage = (uint8 *)p.game_memory_block; 510 | m.TransientStorage = 511 | (uint8_t *)m.PermanentStorage + m.TransientStorageSize; 512 | 513 | #ifdef HANDMADE_INTERNAL 514 | m.DEBUGPlatformReadEntireFile = debug_read_entire_file; 515 | #endif 516 | 517 | thread_context t = {}; 518 | 519 | game_input input[2] = {}; 520 | p.new_input = &input[0]; 521 | p.old_input = &input[1]; 522 | 523 | int monitor_refresh_hz = 60; 524 | real32 game_update_hz = (monitor_refresh_hz / 2.0f); // Should almost always be an int... 525 | long target_nanoseconds_per_frame = (1000 * 1000 * 1000) / game_update_hz; 526 | 527 | while (++counter) { 528 | timespec start_time = {}; 529 | clock_gettime(CLOCK_MONOTONIC_RAW, &start_time); 530 | 531 | game_controller_input *old_keyboard_controller = GetController(p.old_input, 0); 532 | game_controller_input *new_keyboard_controller = GetController(p.new_input, 0); 533 | *new_keyboard_controller = {}; 534 | new_keyboard_controller->IsConnected = true; 535 | for ( 536 | uint button_index = 0; 537 | button_index < ArrayCount(new_keyboard_controller->Buttons); 538 | ++button_index) 539 | { 540 | new_keyboard_controller->Buttons[button_index].EndedDown = 541 | old_keyboard_controller->Buttons[button_index].EndedDown; 542 | } 543 | 544 | int poll_result, events; 545 | android_poll_source *source; 546 | 547 | while((poll_result = ALooper_pollAll(0, 0, &events, (void**)&source)) >= 0) 548 | { 549 | source->process(app, source); 550 | } 551 | 552 | switch (poll_result) 553 | { 554 | case ALOOPER_POLL_WAKE: 555 | { 556 | __android_log_print(ANDROID_LOG_INFO, p.app_name, "poll_result was ALOOPER_POLL_WAKE"); 557 | break; 558 | } 559 | case ALOOPER_POLL_CALLBACK: 560 | { 561 | __android_log_print(ANDROID_LOG_INFO, p.app_name, "poll_result was ALOOPER_POLL_CALLBACK"); 562 | break; 563 | } 564 | case ALOOPER_POLL_TIMEOUT: 565 | { 566 | //__android_log_print(ANDROID_LOG_INFO, p.app_name, "poll_result was ALOOPER_POLL_TIMEOUT"); 567 | break; 568 | } 569 | case ALOOPER_POLL_ERROR: 570 | { 571 | __android_log_print(ANDROID_LOG_INFO, p.app_name, "poll_result was ALOOPER_POLL_ERROR"); 572 | break; 573 | } 574 | default: 575 | { 576 | __android_log_print(ANDROID_LOG_INFO, p.app_name, "poll_result was %d", poll_result); 577 | break; 578 | } 579 | } 580 | 581 | p.new_input->dtForFrame = target_nanoseconds_per_frame / (1024.0 * 1024 * 1024); 582 | 583 | hh_process_events(app, p.new_input, p.old_input); 584 | 585 | game_offscreen_buffer game_buffer = {}; 586 | game_buffer.Memory = p.texture_buffer; 587 | game_buffer.Width = 960; 588 | game_buffer.Height = 540; 589 | game_buffer.Pitch = 960 * 4; 590 | game_buffer.BytesPerPixel = 4; 591 | 592 | GameUpdateAndRender(&t, &m, p.new_input, &game_buffer); 593 | draw(app); 594 | 595 | timespec end_time = {}; 596 | clock_gettime(CLOCK_MONOTONIC_RAW, &end_time); 597 | 598 | end_time.tv_sec -= start_time.tv_sec; 599 | start_time.tv_sec -= start_time.tv_sec; 600 | 601 | int64_t time_taken = ((end_time.tv_sec * 1000000000 + end_time.tv_nsec) - 602 | (start_time.tv_sec * 1000000000 + start_time.tv_nsec)); 603 | 604 | int64_t time_to_sleep = 33 * 1000000; 605 | if (time_taken <= time_to_sleep) 606 | { 607 | timespec sleep_time = {}; 608 | sleep_time.tv_nsec = time_to_sleep - time_taken; 609 | timespec remainder = {}; 610 | nanosleep(&sleep_time, &remainder); 611 | } 612 | else 613 | { 614 | if (counter % 10 == 0) 615 | { 616 | __android_log_print(ANDROID_LOG_INFO, p.app_name, "Skipped frame! Took %" PRId64 " ns total", time_taken); 617 | } 618 | } 619 | 620 | game_input *temp_input = p.new_input; 621 | p.new_input = p.old_input; 622 | p.old_input = temp_input; 623 | } 624 | } -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/mobile/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/mobile/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/mobile/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/mobile/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /mobile/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ndk_handmade 3 | 4 | -------------------------------------------------------------------------------- /mobile/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ndk_handmade.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':mobile', ':tv' 2 | -------------------------------------------------------------------------------- /tv/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /tv/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | 4 | android { 5 | compileSdkVersion 21 6 | buildToolsVersion "21.1.2" 7 | 8 | defaultConfig { 9 | applicationId "org.nxsy.ndk_handmade" 10 | minSdkVersion 21 11 | targetSdkVersion 21 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | } 22 | 23 | dependencies { 24 | compile fileTree(dir: 'libs', include: ['*.jar']) 25 | compile 'com.android.support:recyclerview-v7:21.0.3' 26 | compile 'com.android.support:leanback-v17:21.0.3' 27 | } 28 | -------------------------------------------------------------------------------- /tv/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in S:\/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /tv/src/androidTest/java/org/nxsy/ndk_handmade/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package org.nxsy.ndk_handmade; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /tv/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tv/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/tv/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /tv/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/tv/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /tv/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/tv/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /tv/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxsy/ndk_handmade/50f83085ff6bf770ebd8f3b23094fe97c076783a/tv/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /tv/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ndk_handmade 3 | 4 | -------------------------------------------------------------------------------- /tv/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tv/tv.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | --------------------------------------------------------------------------------