├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 |
4 |
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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
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 |
7 |
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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
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 |
--------------------------------------------------------------------------------