diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c4c1c51514d7971e36cc3f371c30d0c002faaf4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.DS_Store + +**/.idea + +**/.gradle + +**/local.properties +**/captures +**/build + +.temp +.externalNativeBuild + +**/gradle/wrapper/ + +**/*.iml diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..d3b461365a17124fb92116ddbdeb0c9056fbbd83 --- /dev/null +++ b/OWNERS @@ -0,0 +1,3 @@ +jimblackler@google.com +idries@google.com +willosborn@google.com diff --git a/README b/README new file mode 100644 index 0000000000000000000000000000000000000000..32e61bf38bb9df7979d77bf35005e94cb673db69 --- /dev/null +++ b/README @@ -0,0 +1,17 @@ + +In order to build using prebuild NDK versions, this project must be initialized from a custom repo using: +mkdir android-games-sdk +cd android-games-sdk +Corp -> repo init -u persistent-https://googleplex-android.git.corp.google.com/platform/manifest -b android-games-sdk +AOSP -> repo init -u https://android.googlesource.com/platform/manifest -b android-games-sdk +repo sync -c -j8 + +Then: +./gradlew gamesdkZip +will build static and dynamic libraries for several NDK versions. + +By default, the gradle script builds target archiveZip, which will use a locally installed SDK/NDK pointed +to by ANDROID_HOME (and ANDROID_NDK, if the ndk isn't in ANDROID_HOME/ndk-bundle). + +It is also possible to build from a direct AOSP checkout, but then you won't be able to build for multiple +NDKs: archiveZip will still work but the gamesdkZip target will fail. diff --git a/ab_info.py b/ab_info.py new file mode 100644 index 0000000000000000000000000000000000000000..fd5ad0e5f2df40f7177dedcb7ceae7653d4e33f9 --- /dev/null +++ b/ab_info.py @@ -0,0 +1,8 @@ +from __future__ import print_function +import google.protobuf as pb + +print('python protobuf contents:') +for attr in dir(pb): + if attr == '__builtins__': + continue + print('{}: {}'.format(attr, getattr(pb, attr))) diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..53405a08abf14323468373650acb4ed34f9c5ed4 --- /dev/null +++ b/build.gradle @@ -0,0 +1,501 @@ + +import java.nio.file.Paths +import org.gradle.internal.logging.text.StyledTextOutput; +import org.gradle.internal.logging.text.StyledTextOutputFactory; +import static org.gradle.internal.logging.text.StyledTextOutput.Style; +import org.gradle.internal.os.OperatingSystem; +import org.gradle.api.Project; + +defaultTasks 'cleanPath', 'archiveZip' + +def protobufInstallDir() { + return new File("./third_party/protobuf-3.0.0/install/linux-x86").getCanonicalPath() +} + +def joinPath(String first, String... more) { + return Paths.get(first, more).toString() +} + +task prepare_proto_before { + def protocBinDir = protobufInstallDir() + "/bin" + doLast { + // Install python-protobuf + exec { + workingDir "./third_party/protobuf-3.0.0/python" + environment 'PATH', "$protocBinDir:${environment.PATH}" + commandLine "python", "setup.py", "install", "--user" + } + // Generate nano-pb requirements + exec { + workingDir getExternalPath() + '/nanopb-c/generator/proto' + environment 'PATH', "$protocBinDir:${environment.PATH}" + commandLine 'make' + } + } +} + +task prepare_proto(dependsOn: prepare_proto_before) { + doLast { + exec { + commandLine "python" + args = ["ab_info.py"] + } + exec { + commandLine "dpkg-query" + args = ["--list", "*env*"] + } + } +} + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + } +} + +allprojects { + buildDir = getOutPath() + repositories { + google() + jcenter() + } +} + +abstract class Toolchain { + protected String androidVersion_; + protected String ndkVersion_; + abstract String getAndroidNDKPath(); + abstract String getCMakePath(); + abstract String getNinjaPath(); + public String getAndroidVersion() { return androidVersion_; } + public String getNdkVersion() { return ndkVersion_; } + public String getBuildKey(String arch, String stl) { + return arch + '_SDK' + androidVersion_ + "_NDK" + ndkVersion_ + '_' + stl + } + protected String getNdkVersionFromPropertiesFile() { + def f = new File(getAndroidNDKPath(), 'source.properties') + if (!f.exists()) { + println "Warning: can't get NDK version" + return "UNKNOWN" + } + else { + Properties props = new Properties() + f.withInputStream { + props.load(it) + } + def ver = props."Pkg.Revision" + def parts = ver.findAll("[^.]+") + if (parts.size()>1) + return parts[0] + "." + parts[1] + else + return parts[0] + } + } +} +class SpecificToolchain extends Toolchain { + SpecificToolchain(String androidVersion, String ndkVersion) { + androidVersion_ = androidVersion + ndkVersion_ = ndkVersion + } + public String getAndroidNDKPath() { + return new File("../prebuilts/ndk/" + ndkVersion_).getCanonicalPath() + } + public String getCMakePath() { + return new File("../prebuilts/cmake/linux-x86/bin/cmake").getCanonicalPath() + } + public String getNinjaPath() { + return new File("../prebuilts/cmake/linux-x86/bin/ninja").getCanonicalPath() + } +} +class LocalToolchain extends Toolchain { + Project project_; + String sdkPath_; + String ndkPath_; + String cmakePath_; + String ninjaPath_; + String adbPath_; + LocalToolchain(Project project, String androidVersion) { + project_ = project + androidVersion_ = androidVersion + sdkPath_ = System.getenv("ANDROID_HOME") + if(sdkPath_ == null) { + Properties properties = new Properties() + properties.load(project.rootProject.file('local.properties').newDataInputStream()) + sdkPath_ = properties.getProperty('sdk.dir') + } + if(sdkPath_ == null) + throw new GradleException('Must set ANDROID_HOME or sdk.dir in local.properties') + + ndkPath_ = System.getenv("ANDROID_NDK") + if(ndkPath_ == null) { + ndkPath_ = project_.joinPath(sdkPath_, 'ndk-bundle') + if (!project_.file(ndkPath_).exists()) + throw new GradleException('No NDK found in SDK: must set ANDROID_NDK') + } + ndkVersion_ = getNdkVersionFromPropertiesFile() + cmakePath_ = findCMakeTool("CMAKE", "cmake") + ninjaPath_ = findCMakeTool("NINJA", "ninja") + adbPath_ = project_.joinPath(sdkPath_, 'platform-tools', 'adb') + } + String getAndroidNDKPath() { + return ndkPath_; + } + String getCMakePath() { + return cmakePath_; + } + String getNinjaPath() { + return ninjaPath_; + } + String getAdbPath() { + return adbPath_; + } + String findCMakeTool(String envVar, String name) { + def tool = System.getenv(envVar); + if (tool) { + return tool; + } + def osname = OperatingSystem.current().isWindows() ? name + '.exe' : name + def tools = project_.fileTree( dir: project_.joinPath(sdkPath_, 'cmake'), include: ['**/bin/'+osname] ) + if (tools==null || tools.size() == 0) { + throw new GradleException('No ' + osname + ' found in ' + sdkPath_ + '/cmake') + } + return tools.getFiles().last().toString(); + } +} + +def getBuildPath() { + return new File("./build").getCanonicalPath() +} + +def getOutPath() { + return new File("../out").getCanonicalPath() +} + +def getPackagePath() { + return new File("../package").getCanonicalPath() +} + +def getTempPath() { + return new File("../.temp").getCanonicalPath() +} + +def getExternalPath() { + def f = new File("../external/"); + if ( !f.exists() ) + f = new File("../../../external/"); + return f.getCanonicalPath() +} + +def useNinja() { + return true; +} + +def cmake(projectFolder, workingFolder, outputFolder, arch, toolchain, stl, + threadChecks, buildTuningFork) { + def ndkPath = toolchain.getAndroidNDKPath() + def toolchainFilePath = ndkPath + "/build/cmake/android.toolchain.cmake" + def externalPath = getExternalPath(); + def androidVersion = toolchain.getAndroidVersion() + def ninjaPath = toolchain.getNinjaPath() + def buildtfval = buildTuningFork ? "ON" : "OFF" + def protocBinDir = protobufInstallDir() + "/bin" + mkdir workingFolder + mkdir outputFolder + + def threadFlags = "" + if (threadChecks) { + threadFlags = "-DGAMESDK_THREAD_CHECKS=1" + } else { + threadFlags = "-DGAMESDK_THREAD_CHECKS=0" + } + + def cmdLine = [toolchain.getCMakePath(), + "$projectFolder", + "-DCMAKE_BUILD_TYPE=Release", + "-DANDROID_PLATFORM=android-$androidVersion", + "-DANDROID_NDK=$ndkPath", + "-DANDROID_STL=$stl", + "-DANDROID_ABI=$arch", + "-DCMAKE_CXX_FLAGS=", + "-DCMAKE_TOOLCHAIN_FILE=$toolchainFilePath", + "-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=$outputFolder", + "-DGAMESDK_BUILD_TUNINGFORK=$buildtfval", + "-DEXTERNAL_ROOT=$externalPath", + threadFlags] + if (useNinja()) { + cmdLine += ["-DCMAKE_MAKE_PROGRAM=" + "$ninjaPath", + "-GNinja"] + } + exec { + environment "PATH", "$protocBinDir:${environment.PATH}" + workingDir workingFolder + commandLine cmdLine + } +} + +def buildNativeModules(arch, Toolchain toolchain, stl, threadChecks, buildtf, subdir = "src") { + def buildKey = toolchain.getBuildKey(arch, stl) + def workingFolder = joinPath(getTempPath(), buildKey, '.cmake') + def outputFolder = joinPath(getOutPath(), buildKey) + def cmakeProjectLocation = joinPath("$projectDir", subdir) + + cmake(cmakeProjectLocation, workingFolder, outputFolder, arch, toolchain, + stl, threadChecks, buildtf) + + def cmdLine = useNinja() ? [toolchain.getNinjaPath()] : ["make", "-j"] + exec { + workingDir workingFolder + commandLine cmdLine + } + return [arch: arch, buildKey: buildKey] +} + +def buildNativeModules(arch, androidVersion, ndkVersion, stl, threadChecks, + buildtf) { + return buildNativeModules(arch, + new SpecificToolchain(androidVersion, ndkVersion), + stl, threadChecks, buildtf) +} + +task cleanPath(type: Delete) { + delete getOutPath() + delete getPackagePath() + delete getTempPath() + delete getBuildPath() +} + +// Create outAr from the contents of inArs +// All files taken/created in dir +def repackArchives(dir, inArs, outAr) { + def cmd = /pushd $dir &&/ + inArs.each { + cmd <<= /ar -x $it &&/ + } + cmd <<= /ar -qc $outAr *.o && rm *.o && popd/ + ['/bin/bash', '-c', cmd.toString()].execute().waitFor() +} + +def sdkCopy(buildInfo, outFolder, all = true, staticToo = false, + useFullBuildKey = false, flattenLibDirs = false, shared = true) { + def arch = buildInfo.arch + def buildKey = buildInfo.buildKey + def cmakeFolder = joinPath(getTempPath(), buildKey, '.cmake') + def buildFolder = joinPath(getPackagePath(), outFolder) + def libBuildFolder = joinPath(buildFolder, 'libs', + useFullBuildKey ? buildKey : arch, 'lib') + + if (shared) { + copy { + from file(cmakeFolder) + include all ? "*/lib*.so" : "swappy*/lib*.so" + into file(libBuildFolder) + includeEmptyDirs = false + if (flattenLibDirs) { + eachFile { + path = name + } + } + } + } + if (staticToo) { + def staticsFolder = joinPath(getOutPath(), buildKey) + def staticLibsBuildFolder = joinPath(buildFolder, 'libs', buildKey) + def staticLibs = ['libswappy_static.a', + 'libswappyVk_static.a'] + if (all) + staticLibs += 'libtuningfork_static.a' + repackArchives(staticsFolder, staticLibs, 'libgamesdk.a') + copy { + from file(staticsFolder) + include "libgamesdk.a" + into file(staticLibsBuildFolder) + } + } +} + +def copyExtras(outFolder, all = true) { + def buildFolder = getPackagePath() + '/' + outFolder + def headerFolder = './include' + def aarFolder = joinPath(getOutPath(), 'outputs', 'aar') + def includeBuildFolder = joinPath(buildFolder, 'include') + def aarBuildFolder = joinPath(buildFolder, 'aar') + + copy { + from file(headerFolder) + include all ? "*/*.h" : "swappy*/*.h" + into file(includeBuildFolder) + includeEmptyDirs = false + } + copy { + from file(aarFolder) + into file(aarBuildFolder) + includeEmptyDirs = false + } +} + +// The latest Unity is using NDK 16b and SDK 21 with gnu stl +def defaultAbis() { return ["armeabi-v7a", "arm64-v8a", "x86", "x86_64"] } + +def unityNativeBuild(withTuningFork=false) { + def threadChecks = false + return defaultAbis().collect { + buildNativeModules(it, "21", "r16", "gnustl_static", threadChecks, withTuningFork) + } +} + +def sdkNativeBuild(withTuningFork = true) { + def threadChecks = false + return defaultAbis().collectMany { + [ buildNativeModules(it, "26", "r16", "gnustl_static", threadChecks, withTuningFork) ] + + [ buildNativeModules(it, "28", "r17", "gnustl_static", threadChecks, withTuningFork) ] + + [ buildNativeModules(it, "28", "r17", "c++_static", threadChecks, withTuningFork) ] + } +} + +def getLocalToolchain() { + def kLocalMinSdk = project.hasProperty("GAMESDK_ANDROID_SDK_VERSION") ? + project.GAMESDK_ANDROID_SDK_VERSION : "26" + return new LocalToolchain(project, kLocalMinSdk) +} + +def localNativeBuild(withTuningFork = false, subdir = "src") { + def toolchain = getLocalToolchain(); + def stl = "c++_static" + def threadChecks = true + return defaultAbis().collect { + buildNativeModules(it, toolchain , stl, threadChecks, withTuningFork, subdir) + } +} + +class BuildTask extends DefaultTask { +} + +// Just build swappy shared libraries +task swappyUnityBuild(type: BuildTask) { + dependsOn ':extras:assembleRelease' + ext.packageName = 'swappyUnity' + ext.flattenLibs = true + ext.withTuningFork = false + ext.withSharedLibs = true + ext.withStaticLibs = true + ext.withFullBuildKey = false + ext.nativeBuild = { tf -> unityNativeBuild(tf) } +} + +// Full build including tuning fork for unity, shared libraries only +task unityBuild(type: BuildTask) { + dependsOn ':extras:assembleRelease', prepare_proto + ext.packageName = 'unity' + ext.flattenLibs = true + ext.withTuningFork = true + ext.withSharedLibs = true + ext.withStaticLibs = false + ext.withFullBuildKey = false + ext.nativeBuild = { tf -> unityNativeBuild(tf) } +} + +// Build everything +task sdkBuild(type: BuildTask) { + dependsOn ':extras:assembleRelease', prepare_proto + ext.packageName = 'gamesdk' + ext.flattenLibs = false + ext.withTuningFork = true + ext.withSharedLibs = true + ext.withStaticLibs = true + ext.withFullBuildKey = true + ext.nativeBuild = { tf -> sdkNativeBuild(tf) } +} + +// Build using local SDK, no tuning fork +task localBuild(type: BuildTask) { + dependsOn ':extras:assembleRelease' + ext.packageName = 'local' + ext.flattenLibs = false + ext.withTuningFork = false; + ext.withSharedLibs = true + ext.withStaticLibs = true; + ext.withFullBuildKey = false; + ext.nativeBuild = { tf -> localNativeBuild(tf) } +} + +// Build using local SDK, with tuning fork +task localTfBuild(type: BuildTask) { + dependsOn ':extras:assembleRelease', prepare_proto + ext.packageName = 'localtf' + ext.flattenLibs = false + ext.withTuningFork = true; + ext.withSharedLibs = true + ext.withStaticLibs = true; + ext.withFullBuildKey = false; + ext.nativeBuild = { tf -> localNativeBuild(tf) } +} + +tasks.withType(BuildTask) { + doLast { + nativeBuild(withTuningFork).each { + sdkCopy(it, packageName, withTuningFork, withStaticLibs, + withFullBuildKey, flattenLibs, withSharedLibs) + } + copyExtras(packageName, withTuningFork) + } +} + +task localUnitTests { + // These unit tests require a connected ARM64 device to run + doLast { + def buildInfo = localNativeBuild(true, "test") + def buildKey = buildInfo.buildKey.findAll{it.contains('arm64-v8a')}[0] + def cmakeFolder = getTempPath() + '/' + buildKey + '/.cmake/tuningfork' + def toolchain = getLocalToolchain(); + def adb = toolchain.getAdbPath(); + exec { + workingDir cmakeFolder + commandLine adb, "push", "tuningfork_test", "/data/local/tmp" + } + exec { + workingDir cmakeFolder + commandLine adb, "shell", "/data/local/tmp/tuningfork_test" + } + } +} + +// Zipping things up +def addZipTask(name, buildTask, zipName, rootName = "gamesdk") { + def packPath = buildTask.packageName + tasks.register(name, Zip) { + dependsOn buildTask + def buildFolder = joinPath(getPackagePath(), packPath) + baseName = joinPath(buildFolder, zipName) + + from fileTree(buildFolder) + exclude "*.zip" + + into rootName + + doLast { + def outFolder = getBuildPath(); + mkdir outFolder; + + copy { + from file(baseName + '.zip') + into outFolder + } + + def out = services.get(StyledTextOutputFactory).create("output") + out.style(Style.Identifier).text('\n' + rootName +'.zip is in ') + .style(Style.ProgressStatus) + .println(baseName + '.zip' ); + } + } + +} + +addZipTask("swappyUnityZip", swappyUnityBuild, "builds") +addZipTask("unityZip", unityBuild, "builds") +addZipTask("archiveZip", localBuild, "gamesdk") +addZipTask("archiveTfZip", localTfBuild, "gamesdk") +addZipTask("gamesdkZip", sdkBuild, "gamesdk") diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000000000000000000000000000000000..80464881f9c57451c4662ee079623cb021295ebf --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +android.builder.sdkDownload=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..29953ea141f55e3b8fc691d31b5ca8816d89fa87 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..e0b3fb8d70b1bbf790f6f8ed1c928ddf09f54628 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000000000000000000000000000000000..cccdd3d517fc5249beaefa600691cf150f2fa3e6 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000000000000000000000000000000000..e95643d6a2ca62258464e83c72f5156dc941c609 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/include/device_info/device_info.h b/include/device_info/device_info.h new file mode 100644 index 0000000000000000000000000000000000000000..81bb684178f8c55b904c65585835e144f29a2721 --- /dev/null +++ b/include/device_info/device_info.h @@ -0,0 +1,22 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "lite/device_info.pb.h" + +namespace androidgamesdk_deviceinfo { +// returns number of errors +int createProto(InfoWithErrors& proto); +} // namespace androidgamesdk_deviceinfo diff --git a/include/device_info/device_info.proto b/include/device_info/device_info.proto new file mode 100644 index 0000000000000000000000000000000000000000..c895148e0ba81d586608a7e8ed53c1c64cb49e03 --- /dev/null +++ b/include/device_info/device_info.proto @@ -0,0 +1,240 @@ +syntax = "proto2"; + +package androidgamesdk_deviceinfo; + +option java_package = "com.google.androidgamesdk"; +option java_outer_classname = "DeviceInfoProto"; + +message Info { + message CpuCore { + optional int64 freq_max = 1; + } + message Gl { + optional string renderer = 1; + optional string vendor = 2; + optional string version = 3; + optional int32 version_major = 4; + optional int32 version_minor = 5; + optional string shading_language_version = 6; + + repeated string extension = 7; + + // gles min: 2.0 + optional float GL_ALIASED_LINE_WIDTH_RANGE = 2001; + optional float GL_ALIASED_POINT_SIZE_RANGE = 2002; + optional int32 GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS = 2003; + optional int32 GL_MAX_CUBE_MAP_TEXTURE_SIZE = 2005; + optional int32 GL_MAX_FRAGMENT_UNIFORM_VECTORS = 2006; + optional int32 GL_MAX_RENDERBUFFER_SIZE = 2007; + optional int32 GL_MAX_TEXTURE_IMAGE_UNITS = 2008; + optional int32 GL_MAX_TEXTURE_SIZE = 2009; + optional int32 GL_MAX_VARYING_VECTORS = 2010; + optional int32 GL_MAX_VERTEX_ATTRIBS = 2011; + optional int32 GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 2012; + optional int32 GL_MAX_VERTEX_UNIFORM_VECTORS = 2013; + optional int32 GL_MAX_VIEWPORT_DIMS = 2014; + optional bool GL_SHADER_COMPILER = 2015; + optional int32 GL_SUBPIXEL_BITS = 2016; + + optional int32 GL_NUM_COMPRESSED_TEXTURE_FORMATS = 2021; + repeated int32 GL_COMPRESSED_TEXTURE_FORMATS = 2022; + optional int32 GL_NUM_SHADER_BINARY_FORMATS = 2023; + repeated int32 GL_SHADER_BINARY_FORMATS = 2024; + + // glGetShaderPrecisionFormat + optional int32 SPF_VERTEX_FLOAT_LOW_RANGE = 2031; + optional int32 SPF_VERTEX_FLOAT_LOW_PREC = 2032; + optional int32 SPF_VERTEX_FLOAT_MED_RANGE = 2033; + optional int32 SPF_VERTEX_FLOAT_MED_PREC = 2034; + optional int32 SPF_VERTEX_FLOAT_HIG_RANGE = 2035; + optional int32 SPF_VERTEX_FLOAT_HIG_PREC = 2036; + optional int32 SPF_VERTEX_INT_LOW_RANGE = 2037; + optional int32 SPF_VERTEX_INT_LOW_PREC = 2038; + optional int32 SPF_VERTEX_INT_MED_RANGE = 2039; + optional int32 SPF_VERTEX_INT_MED_PREC = 2040; + optional int32 SPF_VERTEX_INT_HIG_RANGE = 2041; + optional int32 SPF_VERTEX_INT_HIG_PREC = 2042; + optional int32 SPF_FRAGMENT_FLOAT_LOW_RANGE = 2043; + optional int32 SPF_FRAGMENT_FLOAT_LOW_PREC = 2044; + optional int32 SPF_FRAGMENT_FLOAT_MED_RANGE = 2045; + optional int32 SPF_FRAGMENT_FLOAT_MED_PREC = 2046; + optional int32 SPF_FRAGMENT_FLOAT_HIG_RANGE = 2047; + optional int32 SPF_FRAGMENT_FLOAT_HIG_PREC = 2048; + optional int32 SPF_FRAGMENT_INT_LOW_RANGE = 2049; + optional int32 SPF_FRAGMENT_INT_LOW_PREC = 2050; + optional int32 SPF_FRAGMENT_INT_MED_RANGE = 2051; + optional int32 SPF_FRAGMENT_INT_MED_PREC = 2052; + optional int32 SPF_FRAGMENT_INT_HIG_RANGE = 2053; + optional int32 SPF_FRAGMENT_INT_HIG_PREC = 2054; + + // gles min: 3.0 + optional int32 GL_MAX_3D_TEXTURE_SIZE = 3001; + optional int32 GL_MAX_ARRAY_TEXTURE_LAYERS = 3002; + optional int32 GL_MAX_COLOR_ATTACHMENTS = 3003; + optional int64 GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS = 3004; + optional int32 GL_MAX_COMBINED_UNIFORM_BLOCKS = 3005; + optional int64 GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS = 3006; + optional int32 GL_MAX_DRAW_BUFFERS = 3007; + optional int32 GL_MAX_ELEMENTS_INDICES = 3008; + optional int32 GL_MAX_ELEMENTS_VERTICES = 3009; + optional int64 GL_MAX_ELEMENT_INDEX = 3010; + optional int32 GL_MAX_FRAGMENT_INPUT_COMPONENTS = 3011; + optional int32 GL_MAX_FRAGMENT_UNIFORM_BLOCKS = 3012; + optional int32 GL_MAX_FRAGMENT_UNIFORM_COMPONENTS = 3013; + optional int32 GL_MAX_PROGRAM_TEXEL_OFFSET = 3014; + optional int64 GL_MAX_SERVER_WAIT_TIMEOUT = 3015; + optional float GL_MAX_TEXTURE_LOD_BIAS = 3016; + optional int32 GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS = 3017; + optional int32 GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 3018; + optional int32 GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS = 3019; + optional int64 GL_MAX_UNIFORM_BLOCK_SIZE = 3020; + optional int32 GL_MAX_UNIFORM_BUFFER_BINDINGS = 3021; + optional int32 GL_MAX_VARYING_COMPONENTS = 3022; + optional int32 GL_MAX_VERTEX_OUTPUT_COMPONENTS = 3023; + optional int32 GL_MAX_VERTEX_UNIFORM_BLOCKS = 3024; + optional int32 GL_MAX_VERTEX_UNIFORM_COMPONENTS = 3025; + optional int32 GL_MIN_PROGRAM_TEXEL_OFFSET = 3026; + optional int32 GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 3027; + optional int32 GL_MAX_SAMPLES = 3028; + + optional int32 GL_NUM_PROGRAM_BINARY_FORMATS = 3031; + repeated int32 GL_PROGRAM_BINARY_FORMATS = 3032; + + // gles min: 3.1 + optional int32 GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS = 3101; + optional int32 GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE = 3102; + optional int32 GL_MAX_COLOR_TEXTURE_SAMPLES = 3103; + optional int32 GL_MAX_COMBINED_ATOMIC_COUNTERS = 3104; + optional int32 GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS = 3105; + optional int32 GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS = 3106; + optional int32 GL_MAX_COMBINED_IMAGE_UNIFORMS = 3107; + optional int32 GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES = 3108; + optional int32 GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS = 3109; + optional int32 GL_MAX_COMPUTE_ATOMIC_COUNTERS = 3110; + optional int32 GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS = 3111; + optional int32 GL_MAX_COMPUTE_IMAGE_UNIFORMS = 3112; + optional int32 GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS = 3113; + optional int32 GL_MAX_COMPUTE_SHARED_MEMORY_SIZE = 3114; + optional int32 GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS = 3115; + optional int32 GL_MAX_COMPUTE_UNIFORM_BLOCKS = 3116; + optional int32 GL_MAX_COMPUTE_UNIFORM_COMPONENTS = 3117; + optional int32 GL_MAX_COMPUTE_WORK_GROUP_COUNT_0 = 3118; + optional int32 GL_MAX_COMPUTE_WORK_GROUP_COUNT_1 = 3119; + optional int32 GL_MAX_COMPUTE_WORK_GROUP_COUNT_2 = 3120; + optional int32 GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS = 3121; + optional int32 GL_MAX_COMPUTE_WORK_GROUP_SIZE_0 = 3122; + optional int32 GL_MAX_COMPUTE_WORK_GROUP_SIZE_1 = 3123; + optional int32 GL_MAX_COMPUTE_WORK_GROUP_SIZE_2 = 3124; + optional int32 GL_MAX_DEPTH_TEXTURE_SAMPLES = 3125; + optional int32 GL_MAX_FRAGMENT_ATOMIC_COUNTERS = 3126; + optional int32 GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS = 3127; + optional int32 GL_MAX_FRAGMENT_IMAGE_UNIFORMS = 3128; + optional int32 GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS = 3129; + optional int32 GL_MAX_FRAMEBUFFER_HEIGHT = 3130; + optional int32 GL_MAX_FRAMEBUFFER_SAMPLES = 3131; + optional int32 GL_MAX_FRAMEBUFFER_WIDTH = 3132; + optional int32 GL_MAX_IMAGE_UNITS = 3133; + optional int32 GL_MAX_INTEGER_SAMPLES = 3134; + optional int32 GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET = 3135; + optional int32 GL_MAX_SAMPLE_MASK_WORDS = 3136; + optional int64 GL_MAX_SHADER_STORAGE_BLOCK_SIZE = 3137; + optional int32 GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS = 3138; + optional int32 GL_MAX_UNIFORM_LOCATIONS = 3139; + optional int32 GL_MAX_VERTEX_ATOMIC_COUNTERS = 3140; + optional int32 GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS = 3141; + optional int32 GL_MAX_VERTEX_ATTRIB_BINDINGS = 3142; + optional int32 GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET = 3143; + optional int32 GL_MAX_VERTEX_ATTRIB_STRIDE = 3144; + optional int32 GL_MAX_VERTEX_IMAGE_UNIFORMS = 3145; + optional int32 GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = 3146; + optional int32 GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET = 3147; + optional int32 GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT = 3148; + + // gles min: 3.2 + optional int32 GL_CONTEXT_FLAGS = 3201; + optional int32 GL_FRAGMENT_INTERPOLATION_OFFSET_BITS = 3202; + optional int32 GL_LAYER_PROVOKING_VERTEX = 3203; + optional int32 GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS = 3204; + optional int32 GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS = 3205; + optional int32 GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS = 3206; + optional int32 GL_MAX_DEBUG_GROUP_STACK_DEPTH = 3207; + optional int32 GL_MAX_DEBUG_LOGGED_MESSAGES = 3208; + optional int32 GL_MAX_DEBUG_MESSAGE_LENGTH = 3209; + optional float GL_MAX_FRAGMENT_INTERPOLATION_OFFSET = 3210; + optional int32 GL_MAX_FRAMEBUFFER_LAYERS = 3211; + optional int32 GL_MAX_GEOMETRY_ATOMIC_COUNTERS = 3212; + optional int32 GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS = 3213; + optional int32 GL_MAX_GEOMETRY_IMAGE_UNIFORMS = 3214; + optional int32 GL_MAX_GEOMETRY_INPUT_COMPONENTS = 3215; + optional int32 GL_MAX_GEOMETRY_OUTPUT_COMPONENTS = 3216; + optional int32 GL_MAX_GEOMETRY_OUTPUT_VERTICES = 3217; + optional int32 GL_MAX_GEOMETRY_SHADER_INVOCATIONS = 3218; + optional int32 GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS = 3219; + optional int32 GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS = 3220; + optional int32 GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS = 3221; + optional int32 GL_MAX_GEOMETRY_UNIFORM_BLOCKS = 3222; + optional int32 GL_MAX_GEOMETRY_UNIFORM_COMPONENTS = 3223; + optional int32 GL_MAX_LABEL_LENGTH = 3224; + optional int32 GL_MAX_PATCH_VERTICES = 3225; + optional int32 GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS = 3226; + optional int32 GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS = 3227; + optional int32 GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS = 3228; + optional int32 GL_MAX_TESS_CONTROL_INPUT_COMPONENTS = 3229; + optional int32 GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS = 3230; + optional int32 GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS = 3231; + optional int32 GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS = 3232; + optional int32 GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS = 3233; + optional int32 GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS = 3234; + optional int32 GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS = 3235; + optional int32 GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS = 3236; + optional int32 GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS = 3237; + optional int32 GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS = 3238; + optional int32 GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS = 3239; + optional int32 GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS = 3240; + optional int32 GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS = 3241; + optional int32 GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS = 3242; + optional int32 GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS = 3243; + optional int32 GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS = 3244; + optional int32 GL_MAX_TESS_GEN_LEVEL = 3245; + optional int32 GL_MAX_TESS_PATCH_COMPONENTS = 3246; + optional int32 GL_MAX_TEXTURE_BUFFER_SIZE = 3247; + optional float GL_MIN_FRAGMENT_INTERPOLATION_OFFSET = 3248; + optional float GL_MULTISAMPLE_LINE_WIDTH_GRANULARITY = 3249; + optional float GL_MULTISAMPLE_LINE_WIDTH_RANGE = 3250; + optional bool GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED = 3251; + optional int32 GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT = 3252; + optional int32 GL_RESET_NOTIFICATION_STRATEGY = 3254; + } + + optional int32 version = 1; + + optional int32 cpu_max_index = 11; + repeated CpuCore cpu_core = 12; + optional string cpu_present = 13; + optional string cpu_possible = 14; + repeated string cpu_extension = 15; + repeated string hardware = 16; + + optional string ro_build_version_sdk = 21; + optional string ro_chipname = 22; + optional string ro_board_platform = 23; + optional string ro_product_board = 24; + optional string ro_mediatek_platform = 25; + optional string ro_arch = 26; + optional string ro_build_fingerprint = 27; + + optional Gl gl = 31; +} + +message Errors { + optional string hardware = 1; + optional string features = 2; + repeated string system_props = 3; + optional string egl = 4; + repeated string gl = 5; +} + +message InfoWithErrors { + optional Info info = 1; + optional Errors errors = 2; +} \ No newline at end of file diff --git a/include/swappy/OWNERS b/include/swappy/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..d79d8611db0f3aa457a98bb43a6c0356ff1beb0f --- /dev/null +++ b/include/swappy/OWNERS @@ -0,0 +1 @@ +include ../../src/swappy/OWNERS diff --git a/include/swappy/swappy.h b/include/swappy/swappy.h new file mode 100644 index 0000000000000000000000000000000000000000..5a5bfa61c42d27a5032171b2027aea7e94084fa1 --- /dev/null +++ b/include/swappy/swappy.h @@ -0,0 +1,60 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// swap interval constant helpers +#define SWAPPY_SWAP_60FPS (16666667L) +#define SWAPPY_SWAP_30FPS (33333333L) +#define SWAPPY_SWAP_20FPS (50000000L) + +// Initialize Swappy, getting the required Android parameters from the display subsystem via JNI +void Swappy_init(JNIEnv *env, jobject jactivity); + +// Returns true if Swappy was successfully initialized. +// Returns false if either the 'swappy.disable' system property is not 'false' +// or the required OpenGL extensions are not available for Swappy to work. +bool Swappy_isEnabled(); + +// Destroy resources and stop all threads that swappy has created +void Swappy_destroy(); + +// Replace calls to eglSwapBuffers with this. Swappy will wait for the previous frame's +// buffer to be processed by the GPU before actually calling eglSwapBuffers. +bool Swappy_swap(EGLDisplay display, EGLSurface surface); + +// Parameter setters +void Swappy_setRefreshPeriod(uint64_t period_ns); +void Swappy_setUseAffinity(bool tf); +void Swappy_setSwapIntervalNS(uint64_t swap_ns); + +// Parameter getters +uint64_t Swappy_getRefreshPeriodNanos(); +uint64_t Swappy_getSwapIntervalNS(); +bool Swappy_getUseAffinity(); + +#ifdef __cplusplus +}; +#endif diff --git a/include/swappy/swappy_extra.h b/include/swappy/swappy_extra.h new file mode 100644 index 0000000000000000000000000000000000000000..1b8e347dc89d3e754d49d7d4b21ee6f7789c2464 --- /dev/null +++ b/include/swappy/swappy_extra.h @@ -0,0 +1,109 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#define MAX_FRAME_BUCKETS 6 + + +#ifdef __cplusplus +extern "C" { +#endif + +// If an app wishes to use the Android choreographer to provide ticks to Swappy, it can +// call the function below. +// This function *must* be called before the first Swappy_swap() call. +// Afterwards, call this function every choreographer tick. +void Swappy_onChoreographer(int64_t frameTimeNanos); + +// Pass callbacks to be called each frame to trace execution +struct SwappyTracer { + void (*preWait)(void*); + void (*postWait)(void*); + void (*preSwapBuffers)(void*); + void (*postSwapBuffers)(void*, long desiredPresentationTimeMillis); + void (*startFrame)(void*, int currentFrame, long currentFrameTimeStampMillis); + void* userData; + void (*swapIntervalChanged)(void*); + +}; +void Swappy_injectTracer(const SwappyTracer *t); + +// Toggle auto-swap interval detection on/off +// By default, Swappy will adjust the swap interval based on actual frame rendering time. +// If an app wants to override the swap interval calculated by Swappy, it can call +// Swappy_setSwapIntervalNS. This will temporarily override Swappy's frame timings but, unless +// Swappy_setAutoSwapInterval(false) is called, the timings will continue to be be updated +// dynamically, so the swap interval may change. +void Swappy_setAutoSwapInterval(bool enabled); + +// Toggle auto-pipeline mode on/off +// By default, if auto-swap interval is on, auto-pipelining is on and Swappy will try to reduce +// latency by scheduling cpu and gpu work in the same pipeline stage, if it fits. +void Swappy_setAutoPipelineMode(bool enabled); + +// Toggle statistics collection on/off +// By default, stats collection is off and there is no overhead related to stats. +// An app can turn on stats collection by calling Swappy_setStatsMode(true). +// Then, the app is expected to call Swappy_recordFrameStart for each frame before starting to +// do any CPU related work. +// Stats will be logged to logcat with a 'FrameStatistics' tag. +// An app can get the stats by calling Swappy_getStats. +void Swappy_enableStats(bool enabled); + +struct Swappy_Stats { + // total frames swapped by swappy + uint64_t totalFrames; + + // Histogram of the number of screen refreshes a frame waited in the compositor queue after + // rendering was completed. + // for example: + // if a frame waited 2 refresh periods in the compositor queue after rendering was done, + // the frame will be counted in idleFrames[2] + uint64_t idleFrames[MAX_FRAME_BUCKETS]; + + // Histogram of the number of screen refreshes passed between the requested presentation time + // and the actual present time. + // for example: + // if a frame was presented 2 refresh periods after the requested timestamp swappy set, + // the frame will be counted in lateFrames[2] + uint64_t lateFrames[MAX_FRAME_BUCKETS]; + + // Histogram of the number of screen refreshes passed between two consecutive frames + // for example: + // if frame N was presented 2 refresh periods after frame N-1 + // frame N will be counted in offsetFromPreviousFrame[2] + uint64_t offsetFromPreviousFrame[MAX_FRAME_BUCKETS]; + + // Histogram of the number of screen refreshes passed between the call to + // Swappy_recordFrameStart and the actual present time. + // if a frame was presented 2 refresh periods after the call to Swappy_recordFrameStart + // the frame will be counted in latencyFrames[2] + uint64_t latencyFrames[MAX_FRAME_BUCKETS]; +}; + +void Swappy_recordFrameStart(EGLDisplay display, EGLSurface surface); + +void Swappy_getStats(Swappy_Stats *); + +#ifdef __cplusplus +}; +#endif diff --git a/include/swappyVk/SwappyVk.h b/include/swappyVk/SwappyVk.h new file mode 100644 index 0000000000000000000000000000000000000000..eaa11c48c309761360237d930e06cc02a989309b --- /dev/null +++ b/include/swappyVk/SwappyVk.h @@ -0,0 +1,200 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SWAPPYVK_H +#define SWAPPYVK_H + +#if (defined ANDROID) && (defined SWAPPYVK_USE_WRAPPER) +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Determine any Vulkan device extensions that must be enabled for a new + * VkDevice. + * + * Swappy-for-Vulkan (SwappyVk) benefits from certain Vulkan device extensions + * (e.g. VK_GOOGLE_display_timing). Before the application calls + * vkCreateDevice, SwappyVk needs to look at the list of available extensions + * (returned by vkEnumerateDeviceExtensionProperties) and potentially identify + * one or more extensions that the application must add to: + * + * - VkDeviceCreateInfo::enabledExtensionCount + * - VkDeviceCreateInfo::ppEnabledExtensionNames + * + * before the application calls vkCreateDevice. For each VkPhysicalDevice that + * the application will call vkCreateDevice for, the application must call this + * function, and then must add the identified extension(s) to the list that are + * enabled for the VkDevice. Similar to many Vulkan functions, this function + * can be called twice, once to identify the number of required extensions, and + * again with application-allocated memory that the function can write into. + * + * Parameters: + * + * (IN) physicalDevice - The VkPhysicalDevice associated with the + * available extensions. + * (IN) availableExtensionCount - This is the returned value of + * pPropertyCount from vkEnumerateDeviceExtensionProperties. + * (IN) pAvailableExtensions - This is the returned value of + * pProperties from vkEnumerateDeviceExtensionProperties. + * (INOUT) pRequiredExtensionCount - If pRequiredExtensions is nullptr, the + * function sets this to the number of extensions that are + * required. If pRequiredExtensions is non-nullptr, this + * is the number of required extensions that the function + * should write into pRequiredExtensions. + * (INOUT) pRequiredExtensions - If non-nullptr, this is application-allocated + * memory into which the function will write the names of + * required extensions. It is a pointer to an array of + * char* strings (i.e. the same as + * VkDeviceCreateInfo::ppEnabledExtensionNames). + */ +void SwappyVk_determineDeviceExtensions( + VkPhysicalDevice physicalDevice, + uint32_t availableExtensionCount, + VkExtensionProperties* pAvailableExtensions, + uint32_t* pRequiredExtensionCount, + char** pRequiredExtensions); + +/** + * Tell Swappy The queueFamilyIndex used to create a specific VkQueue + * + * Swappy needs to know the queueFamilyIndex used for creating a specific VkQueue + * so it can use it when presenting. + * + * Parameters: + * + * (IN) device - The VkDevice associated with the queue + * (IN) queue - A device queue. + * (IN) queueFamilyIndex - The queue family index used to create the VkQueue. + * + */ +void SwappyVk_setQueueFamilyIndex( + VkDevice device, + VkQueue queue, + uint32_t queueFamilyIndex); + + +// TBD: For now, SwappyVk assumes only one VkSwapchainKHR per VkDevice, and that +// applications don't re-create swapchains. Is this long-term sufficient? + +/** + * Initialize SwappyVk for a given device and swapchain, and obtain the + * approximate time duration between vertical-blanking periods. + * + * If your application presents to more than one swapchain at a time, you must + * call this for each swapchain before calling swappyVkSetSwapInterval() for it. + * + * The duration between vertical-blanking periods (an interval) is expressed as + * the approximate number of nanoseconds between vertical-blanking periods of + * the swapchain’s physical display. + * + * If the application converts this number to a fraction (e.g. 16,666,666 nsec + * to 0.016666666) and divides one by that fraction, it will be the approximate + * refresh rate of the display (e.g. 16,666,666 nanoseconds corresponds to a + * 60Hz display, 11,111,111 nsec corresponds to a 90Hz display). + * + * Parameters: + * + * (IN) physicalDevice - The VkPhysicalDevice associated with the swapchain + * (IN) device - The VkDevice associated with the swapchain + * (IN) swapchain - The VkSwapchainKHR the application wants Swappy to swap + * (OUT) pRefreshDuration - The returned refresh cycle duration + * + * Return value: + * + * bool - true if the value returned by pRefreshDuration is valid, + * otherwise false if an error. + */ +bool SwappyVk_initAndGetRefreshCycleDuration( + VkPhysicalDevice physicalDevice, + VkDevice device, + VkSwapchainKHR swapchain, + uint64_t* pRefreshDuration); + + +/** + * Tell Swappy the number of vertical-blanking intervals that each presented + * image should be visible for. + * + * For example, if the refresh duration is approximately 16,666,666 nses (60Hz + * display), and if the application wants to present at 30 frames-per-second + * (FPS), the application would set the swap interval to 2. For 30FPS on a 90Hz + * display, set the swap interval to 3. + * + * If your application presents to more than one swapchain at a time, you must + * call this for each swapchain before presenting to it. + * + * Parameters: + * + * (IN) device - The VkDevice associated with the swapchain + * (IN) swapchain - The VkSwapchainKHR the application wants Swappy to swap + * (IN) interval - The number of vertical-blanking intervals each image + * should be visible + */ +void SwappyVk_setSwapInterval( + VkDevice device, + VkSwapchainKHR swapchain, + uint32_t interval); + +/** + * Tell Swappy to present one or more images to corresponding swapchains. + * + * Swappy will call vkQueuePresentKHR for your application. Swappy may insert a + * struct to the pNext-chain of VkPresentInfoKHR, or it may insert other Vulkan + * commands in order to attempt to honor the desired swap interval. + * + * Note: If your application presents to more than one swapchain at a time, and + * if you use a different swap interval for each swapchain, Swappy will attempt + * to honor the swap interval for each swapchain (being more successful on + * devices that support an underlying presentation-timing extension, such as + * VK_GOOGLE_display_timing). + * + * Parameters: + * + * (IN) queue - The VkQueue associated with the device and swapchain + * (IN) pPresentInfo - A pointer to the VkPresentInfoKHR containing the + * information about what image(s) to present on which + * swapchain(s). + */ +VkResult SwappyVk_queuePresent( + VkQueue queue, + const VkPresentInfoKHR* pPresentInfo); + + +/** + * Destroy SwappyVk instance associated to the swapchain + * + * This API is expected to be called before calling to vkDestroySwapchainKHR() + * so Swappy could cleanup its internal state. + * + * Parameters: + * + * (IN) device - The VkDevice associated with SwappyVk + */ +void SwappyVk_destroySwapchain( + VkDevice device, + VkSwapchainKHR swapchain); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif //SWAPPYVK_H diff --git a/include/tuningfork/protobuf_nano_util.h b/include/tuningfork/protobuf_nano_util.h new file mode 100644 index 0000000000000000000000000000000000000000..d0591d77555e1d407a274259cd8f1849c269bc89 --- /dev/null +++ b/include/tuningfork/protobuf_nano_util.h @@ -0,0 +1,33 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +namespace tuningfork { + +struct VectorStream { + std::vector* vec; + size_t it; + static bool Read(pb_istream_t *stream, uint8_t *buf, size_t count); + static bool Write(pb_ostream_t *stream, const uint8_t *buf, size_t count); +}; + +} // namespace tuningfork { diff --git a/include/tuningfork/protobuf_util.h b/include/tuningfork/protobuf_util.h new file mode 100644 index 0000000000000000000000000000000000000000..4138d868591f0cc137a3922366a13b82c8f7a637 --- /dev/null +++ b/include/tuningfork/protobuf_util.h @@ -0,0 +1,56 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "tuningfork/tuningfork.h" + +#include +#include +#include + +namespace tuningfork { + +template +bool Deserialize(const std::vector &ser, T &pb) { + return pb.ParseFromArray(ser.data(), ser.size()); +} +template +bool Serialize(const T &pb, std::vector &ser) { + ser.resize(pb.ByteSize()); + return pb.SerializeToArray(ser.data(), ser.size()); +} +template +std::vector Serialize(const T &pb) { + std::vector ser(pb.ByteSize()); + pb.SerializeToArray(ser.data(), ser.size()); + return ser; +} +// Serialize to a CProtobuf. The caller takes ownership of the returned serialization and must +// call CProtobufSerialization_Free to deallocate any memory. +template +CProtobufSerialization CProtobufSerialization_Alloc(const T &pb) { + CProtobufSerialization cser; + cser.bytes = (uint8_t*)::malloc(pb.ByteSize()); + cser.size = pb.ByteSize(); + cser.dealloc = ::free; + pb.SerializeToArray(cser.bytes, cser.size); + return cser; +} + +void CProtobufSerialization_Free(CProtobufSerialization* ser); + +} // namespace tuningfork { diff --git a/include/tuningfork/tuningfork.h b/include/tuningfork/tuningfork.h new file mode 100644 index 0000000000000000000000000000000000000000..24ff52f1ee18e37d9bf0b8bc191f88bd847f2472 --- /dev/null +++ b/include/tuningfork/tuningfork.h @@ -0,0 +1,81 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#define TUNINGFORK_MAJOR_VERSION 0 +#define TUNINGFORK_MINOR_VERSION 1 +#define TUNINGFORK_PACKED_VERSION ((TUNINGFORK_MAJOR_VERSION<<16)|(TUNINGFORK_MINOR_VERSION)) + +// These are reserved instrumentation keys +enum { + TFTICK_SYSCPU = 0, + TFTICK_SYSGPU = 1 +}; + +typedef struct { + uint8_t* bytes; + size_t size; + void (*dealloc)(void*); +} CProtobufSerialization; + +// The instrumentation key identifies a tick point within a frame or a trace segment +typedef uint16_t TFInstrumentKey; +typedef uint64_t TFTraceHandle; +typedef uint64_t TFTimePoint; +typedef uint64_t TFDuration; + +#ifdef __cplusplus +extern "C" { +#endif + +// init must be called before any other functions +void TuningFork_init(const CProtobufSerialization *settings, JNIEnv* env, jobject activity); + +// Blocking call to get fidelity parameters from the server. +// Returns true if parameters could be downloaded within the timeout, false otherwise. +// Note that once fidelity parameters are downloaded, any timing information is recorded +// as being associated with those parameters. +// If you subsequently call GetFidelityParameters and a new set of parameters is downloaded, +// any data that is already collected will be submitted to the backend. +// Ownership of 'params' is transferred to the caller, so they must call params->dealloc +// when they are done with it. +bool TuningFork_getFidelityParameters(const CProtobufSerialization *defaultParams, + CProtobufSerialization *params, size_t timeout_ms); + +// Protobuf serialization of the current annotation +// Returns 0 if the annotation could be set, -1 if not +int TuningFork_setCurrentAnnotation(const CProtobufSerialization *annotation); + +// Record a frame tick that will be associated with the instrumentation key and the current +// annotation +void TuningFork_frameTick(TFInstrumentKey id); + +// Record a frame tick using an external time, rather than system time +void TuningFork_frameDeltaTimeNanos(TFInstrumentKey id, TFDuration dt); + +// Start a trace segment +TFTraceHandle TuningFork_startTrace(TFInstrumentKey key); + +// Record a trace with the key and annotation set using startTrace +void TuningFork_endTrace(TFTraceHandle h); + +#ifdef __cplusplus +} +#endif diff --git a/include/tuningfork/tuningfork_extra.h b/include/tuningfork/tuningfork_extra.h new file mode 100644 index 0000000000000000000000000000000000000000..09d721cf4a47328aa316ced7f34cf57d520c87c1 --- /dev/null +++ b/include/tuningfork/tuningfork_extra.h @@ -0,0 +1,99 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "tuningfork.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*VoidCallback)(); +typedef void (*ProtoCallback)(const CProtobufSerialization*); + +// Load settings from assets/tuningfork/tuningfork_settings.bin. +// Returns true and fills 'settings' if the file could be loaded. +// Ownership of settings is passed to the caller: call +// CProtobufSerialization_Free to deallocate any memory. +// Returns false if the file was not found or there was an error. +bool TuningFork_findSettingsInAPK(JNIEnv* env, jobject activity, + CProtobufSerialization* settings); + +// Load fidelity params from assets/tuningfork/dev_tuningfork_fidelityparams_#.bin. +// Call once with fps=NULL to get the number of files in fp_count. +// The call a second time with a pre-allocated array of size fp_count in fps. +// Ownership of serializations is passed to the caller: call +// CProtobufSerialization_Free to deallocate any memory. +void TuningFork_findFidelityParamsInAPK(JNIEnv* env, jobject activity, + CProtobufSerialization* fps, + int* fp_count); + +// Initialize tuning fork and automatically inject tracers into Swappy. +// If Swappy is not available or could not be initialized, the function returns +// false. +// When using Swappy, there will be 2 default tick points added and the +// histogram settings need to be coordinated with your swap rate. +// If you know the shared library in which Swappy is, pass it in libraryName. +// If libraryName is NULL or TuningFork cannot find Swappy in the library, the +// function will look in the current module and then try in order: +// [libgamesdk.so, libswappy.so, libunity.so] +// frame_callback is called once per frame: you can set any Annotations +// during this callback if you wish. +bool TuningFork_initWithSwappy(const CProtobufSerialization* settings, + JNIEnv* env, jobject activity, + const char* libraryName, + VoidCallback frame_callback); + +// Set a callback to be called on a separate thread every time TuningFork +// performs an upload. +void TuningFork_setUploadCallback(ProtoCallback cbk); + +// This function calls initWithSwappy and also performs the following: +// 1) Settings and default fidelity params are retrieved from the APK. +// 2) A download thread is activated to retrieve fideloty params and retries are +// performed until a download is successful or a timeout occurs. +// 3) Downloaded params are stored locally and used in preference of default +// params when the app is started in future. +// fpDefaultFileNum is the index of the dev_tuningfork_fidelityparams_#.bin file you +// wish to use when there is no download connection and no saved params. It has a +// special meaning if it is negative: in this case, saved params are reset to +// dev_tuningfork_fidelity_params_(-$fpDefaultFileNum).bin +// fidelity_params_callback is called with any downloaded params or with default / +// saved params. +// initialTimeoutMs is the time to wait for an initial download. The fidelity_params_callback +// will be called after this time with the default / saved params if no params +// could be downloaded.. +// ultimateTimeoutMs is the time after which to stop retrying the download. +// The following error codes may be returned: +enum TFErrorCode { + TFERROR_OK = 0, // No error + TFERROR_NO_SETTINGS = 1, // No tuningfork_settings.bin found in assets/tuningfork. + TFERROR_NO_SWAPPY = 2, // Not able to find Swappy. + TFERROR_INVALID_DEFAULT_FIDELITY_PARAMS = 3, // fpDefaultFileNum is out of range. + TFERROR_NO_FIDELITY_PARAMS = 4 // No dev_tuningfork_fidelityparams_#.bin found + // in assets/tuningfork. +}; +TFErrorCode TuningFork_initFromAssetsWithSwappy(JNIEnv* env, jobject activity, + const char* libraryName, + VoidCallback frame_callback, + int fpDefaultFileNum, + ProtoCallback fidelity_params_callback, + int initialTimeoutMs, int ultimateTimeoutMs); + +#ifdef __cplusplus +} +#endif diff --git a/samples/bouncyball/.gitignore b/samples/bouncyball/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bb9fe02d405966a22b1899c386d5529a931bd741 --- /dev/null +++ b/samples/bouncyball/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea/**/gradle.xml +/.idea/caches/build_file_checksums.ser +/.idea/libraries +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/samples/bouncyball/OWNERS b/samples/bouncyball/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..d79d8611db0f3aa457a98bb43a6c0356ff1beb0f --- /dev/null +++ b/samples/bouncyball/OWNERS @@ -0,0 +1 @@ +include ../../src/swappy/OWNERS diff --git a/samples/bouncyball/app/.gitignore b/samples/bouncyball/app/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..796b96d1c402326528b4ba3c12ee9d92d0e212e9 --- /dev/null +++ b/samples/bouncyball/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/samples/bouncyball/app/CMakeLists.txt b/samples/bouncyball/app/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..eecc9826f459cd9a243731e71020c205da832cf3 --- /dev/null +++ b/samples/bouncyball/app/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.4.1) + +set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Werror -Wthread-safety -D _LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS" ) + +# ============== Games SDK + +# This builds and uses swappy as a static library from a gamesdk package +include("../../gamesdk.cmake") +add_gamesdk_target("package/local" "localBuild") + +include_directories( ../../common/include ) # Samples Includes + +# ============== Bouncy Ball + +include_directories( src/main/cpp ) + +add_library( native-lib + + SHARED + + src/main/cpp/Circle.cpp + src/main/cpp/Orbit.cpp + src/main/cpp/Renderer.cpp + src/main/cpp/Settings.cpp + ../../../samples/common/src/Thread.cpp + ) + +target_link_libraries( native-lib + + android + EGL + GLESv2 + log + gamesdk + ) diff --git a/samples/bouncyball/app/build.gradle b/samples/bouncyball/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..a801380fac6ffdd14b8879b928d63d1787d0b228 --- /dev/null +++ b/samples/bouncyball/app/build.gradle @@ -0,0 +1,57 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.prefabulated.swappy" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { + cmake { + } + } + } + buildTypes { + debug { + ndk { + abiFilters "arm64-v8a", "armeabi-v7a", "x86" + } + } + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + ndk { + abiFilters "arm64-v8a", "armeabi-v7a", "x86" + } + } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +allprojects { + gradle.projectsEvaluated { + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:deprecation" + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:1.0.0-rc01' + implementation 'androidx.constraintlayout:constraintlayout:1.1.2' + implementation 'androidx.preference:preference:1.0.0-rc01' + implementation project(':extras') + testImplementation 'junit:junit:4.12' +} diff --git a/samples/bouncyball/app/proguard-rules.pro b/samples/bouncyball/app/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f1b424510da51fd82143bc74a0a801ae5a1e2fcd --- /dev/null +++ b/samples/bouncyball/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/samples/bouncyball/app/src/main/AndroidManifest.xml b/samples/bouncyball/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..9f6f129c5ae14dc38dde19e0519678a799765841 --- /dev/null +++ b/samples/bouncyball/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/bouncyball/app/src/main/cpp/Circle.cpp b/samples/bouncyball/app/src/main/cpp/Circle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b531febe5810851103e85bee8274be080651aeea --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/Circle.cpp @@ -0,0 +1,207 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Circle.h" + +#define LOG_TAG "Circle" + +#include +#include +#include +#include + +#include "Log.h" + +namespace { + +std::vector initializeVertices(int segments) { + std::vector vertices = std::vector(2 * segments + 2); + const float dTheta = static_cast(2 * M_PI / segments); + for (size_t i = 0; i < segments + 1; i++) { + vertices[(i + 1) * 2] = cos(dTheta * i); + vertices[(i + 1) * 2 + 1] = sin(dTheta * i); + } + return vertices; +} + +auto const gVertexShader = + "uniform float uRadius;\n" + "uniform mat4 uMVPMatrix;\n" + "uniform bool uAddLoad;\n" + "attribute vec4 vPosition;\n" + "void main() {\n" + " gl_Position = uMVPMatrix * (vPosition * vec4(vec3(uRadius), 1.0));\n" + "}\n"; +auto const gFragmentShader = + "uniform mediump vec3 uColor;\n" + "void main() {\n" + " gl_FragColor = vec4(uColor, 1.0);\n" + "}\n"; + +void checkGlError(const char *op) { + for (GLint error = glGetError(); error; error = glGetError()) { + ALOGI("after %s() glError (0x%x)\n", op, error); + } +} + +GLuint loadShader(GLenum shaderType, const char *pSource) { + GLuint shader = glCreateShader(shaderType); + if (shader == 0) { + return shader; + } + + glShaderSource(shader, 1, &pSource, NULL); + glCompileShader(shader); + GLint compiled = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + GLint infoLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLength); + if (infoLength > 0) { + std::vector info(infoLength, '\0'); + glGetShaderInfoLog(shader, infoLength, NULL, info.data()); + ALOGE("Could not compile shader %d:\n%s\n", shaderType, info.data()); + glDeleteShader(shader); + shader = 0; + } + } + + return shader; +} + +GLuint createProgram(const char *pVertexSource, const char *pFragmentSource) { + GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource); + if (!vertexShader) { + return 0; + } + + GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource); + if (!pixelShader) { + return 0; + } + + GLuint program = glCreateProgram(); + if (program == 0) { + return program; + } + + glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + glLinkProgram(program); + GLint linkStatus = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (!linkStatus) { + GLint infoLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLength); + if (infoLength > 0) { + std::vector info(infoLength, '\0'); + glGetProgramInfoLog(program, infoLength, NULL, info.data()); + ALOGE("Could not link program:\n%s\n", info.data()); + } + glDeleteProgram(program); + program = 0; + } + return program; +} + +struct ProgramState { + ProgramState() { + program = createProgram(gVertexShader, gFragmentShader); + if (program == 0) { + ALOGE("Failed to create program"); + checkGlError("createProgram"); + return; + } + + colorHandle = glGetUniformLocation(program, "uColor"); + checkGlError("glGetUniformLocation(uColor)"); + radiusHandle = glGetUniformLocation(program, "uRadius"); + checkGlError("glGetUniformLocation(uRadius)"); + mvpMatrixHandle = glGetUniformLocation(program, "uMVPMatrix"); + checkGlError("glGetUniformLocation(uMVPMatrix)"); + vPositionHandle = glGetAttribLocation(program, "vPosition"); + checkGlError("glGetAttribLocation(vPosition)"); + } + + GLuint program; + GLint colorHandle; + GLint radiusHandle; + GLint mvpMatrixHandle; + GLint vPositionHandle; +}; + +std::array getMvpMatrix(GLfloat aspectRatio, GLfloat x, GLfloat y) { + std::array mvpMatrix = {0.0f}; + mvpMatrix[0] = 1.0f / aspectRatio; + mvpMatrix[5] = 1.0f; + mvpMatrix[10] = 1.0f; + mvpMatrix[12] = x; + mvpMatrix[13] = y; + mvpMatrix[15] = 1.0f; + return mvpMatrix; +}; + +} // anonymous namespace + +namespace samples { + +void Circle::draw(float aspectRatio, const std::vector &circles, int workload) { + static ProgramState state; + + int segments = getSegmentsForWorkload(workload); + + glUseProgram(state.program); + checkGlError("glUseProgram"); + + glVertexAttribPointer(static_cast(state.vPositionHandle), 2, GL_FLOAT, GL_FALSE, 0, + getVertices(segments).data()); + checkGlError("glVertexAttribPointer"); + + glEnableVertexAttribArray(static_cast(state.vPositionHandle)); + checkGlError("glEnableVertexAttribArray"); + + for (const Circle &circle : circles) { + glUniform3fv(state.colorHandle, 1, circle.color.values.data()); + checkGlError("glUniform3fv(color)"); + + glUniform1f(state.radiusHandle, circle.radius); + checkGlError("glUniform1f(radius)"); + + auto mvpMatrix = getMvpMatrix(aspectRatio, circle.x, circle.y); + glUniformMatrix4fv(state.mvpMatrixHandle, 1, GL_FALSE, mvpMatrix.data()); + checkGlError("glUniformMatrix4fv(mvpMatrix)"); + + + glDrawArrays(GL_TRIANGLE_FAN, 0, segments + 2); + checkGlError("glDrawArrays"); + } +} + +// getVertices will return the corresponding based on segments parameter. +// This function needs to be called with the same segments number in the same frame +std::vector &Circle::getVertices(int segments) { + static auto vertices = initializeVertices(segments); + static int prev = segments; + if (prev != segments) { + vertices = initializeVertices(segments); + prev = segments; + } + return vertices; +} + +} // namespace samples diff --git a/samples/bouncyball/app/src/main/cpp/Circle.h b/samples/bouncyball/app/src/main/cpp/Circle.h new file mode 100644 index 0000000000000000000000000000000000000000..6f70f53b9dea047e2f09ebaffb8418980c61e080 --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/Circle.h @@ -0,0 +1,64 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include + +namespace samples { + +struct Circle { + struct Color { + Color(GLfloat r, GLfloat g, GLfloat b) : r(r), g(g), b(b) {} + + union { + std::array values; + struct { + GLfloat r; + GLfloat g; + GLfloat b; + }; + }; + }; + + Circle(const Color &color, float radius, float x, float y) : color(color), radius(radius), x(x), + y(y) {}; + + static void draw(float aspectRatio, const std::vector &circles, int workload); + + static int getSegmentsForWorkload(int workload) { + float loadF = workload / 100.0f; + + int num_segmets = (MAX_SEGMENTS - MIN_SEGMENTS) * loadF + MIN_SEGMENTS; + // make sure we get full triangles + num_segmets = (num_segmets / 3) * 3; + return num_segmets; + } + + static const long MAX_SEGMENTS = 28800000; + static const long MIN_SEGMENTS = 36; + + static std::vector &getVertices(int); + + const Color color; + const float radius; + const float x; + const float y; +}; + +} // namespace samples diff --git a/samples/bouncyball/app/src/main/cpp/Orbit.cpp b/samples/bouncyball/app/src/main/cpp/Orbit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d9a710dc763b4b5b6578da396ca7a5906b2a9f8c --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/Orbit.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Orbit" + +#include +#include + +#include + +#include + +#include "Log.h" +#include "Settings.h" + +#include "swappy/swappy.h" +#include "swappy/swappy_extra.h" + +#include "Renderer.h" + +using std::chrono::nanoseconds; +using namespace samples; + +namespace { + +std::string to_string(jstring jstr, JNIEnv *env) { + const char *utf = env->GetStringUTFChars(jstr, nullptr); + std::string str(utf); + env->ReleaseStringUTFChars(jstr, utf); + return str; +} + +} // anonymous namespace + +extern "C" { + +void startFrameCallback(void *, int, long) { +} + +void swapIntervalChangedCallback(void *) { + uint64_t swap_ns = Swappy_getSwapIntervalNS(); + ALOGI("Swappy changed swap interval to %.2fms", swap_ns / 1e6f); +} + +JNIEXPORT void JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nInit(JNIEnv *env, jobject activity) { + // Get the Renderer instance to create it + Renderer::getInstance(); + + Swappy_init(env, activity); + + SwappyTracer tracers; + tracers.preWait = nullptr; + tracers.postWait = nullptr; + tracers.preSwapBuffers = nullptr; + tracers.postSwapBuffers = nullptr; + tracers.startFrame = startFrameCallback; + tracers.userData = nullptr; + tracers.swapIntervalChanged = swapIntervalChangedCallback; + + Swappy_injectTracer(&tracers); +} + +JNIEXPORT void JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nSetSurface(JNIEnv *env, jobject /* this */, + jobject surface, jint width, jint height) { + ANativeWindow *window = ANativeWindow_fromSurface(env, surface); + Renderer::getInstance()->setWindow(window, + static_cast(width), + static_cast(height)); +} + +JNIEXPORT void JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nClearSurface(JNIEnv * /* env */, jobject /* this */) { + Renderer::getInstance()->setWindow(nullptr, 0, 0); +} + +JNIEXPORT void JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nStart(JNIEnv * /* env */, jobject /* this */) { + ALOGI("start"); + Renderer::getInstance()->start(); +} + +JNIEXPORT void JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nStop(JNIEnv * /* env */, jobject /* this */) { + ALOGI("stop"); + Renderer::getInstance()->stop(); +} + +JNIEXPORT void JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nSetPreference(JNIEnv *env, jobject /* this */, + jstring key, jstring value) { + Settings::getInstance()->setPreference(to_string(key, env), to_string(value, env)); +} + +JNIEXPORT void JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nSetAutoSwapInterval(JNIEnv *env, jobject /* this */, + jboolean enabled) { + Swappy_setAutoSwapInterval(enabled); +} + +JNIEXPORT float JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nGetAverageFps(JNIEnv * /* env */, jobject /* this */) { + return Renderer::getInstance()->getAverageFps(); +} + +JNIEXPORT void JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nSetWorkload(JNIEnv * /* env */, jobject /* this */, + jint load) { + Renderer::getInstance()->setWorkload(load); +} + +JNIEXPORT int JNICALL +Java_com_prefabulated_bouncyball_OrbitActivity_nGetSwappyStats(JNIEnv * /* env */, + jobject /* this */, + jint stat, + jint bin) { + static bool enabled = false; + if (!enabled) { + Swappy_enableStats(true); + enabled = true; + } + + // stats are read one by one, query once per stat + static Swappy_Stats stats; + static int stat_idx = -1; + + if (stat_idx != stat) { + Swappy_getStats(&stats); + stat_idx = stat; + } + + int value = 0; + + if (stats.totalFrames) { + switch (stat) { + case 0: + value = stats.idleFrames[bin]; + break; + case 1: + value = stats.lateFrames[bin]; + break; + case 2: + value = stats.offsetFromPreviousFrame[bin]; + break; + case 3: + value = stats.latencyFrames[bin]; + break; + default: + return stats.totalFrames; + } + value = std::round(value * 100.0f / stats.totalFrames); + } + + return value; +} + +} // extern "C" \ No newline at end of file diff --git a/samples/bouncyball/app/src/main/cpp/Renderer.cpp b/samples/bouncyball/app/src/main/cpp/Renderer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c0c4c1e043f9ecf393789196f9c82a14fa04b78f --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/Renderer.cpp @@ -0,0 +1,254 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Renderer.h" + +#define LOG_TAG "Renderer" + +#include + +#include + +#include + +#include "Log.h" + +#include "swappy/swappy.h" +#include "swappy/swappy_extra.h" + +#include "Circle.h" +using namespace std::chrono_literals; + +namespace samples { + +Renderer *Renderer::getInstance() { + static std::unique_ptr sRenderer = std::make_unique(ConstructorTag{}); + return sRenderer.get(); +} + +void Renderer::setWindow(ANativeWindow *window, int32_t width, int32_t height) { + mWorkerThread.run([=](ThreadState *threadState) { + threadState->clearSurface(); + + ALOGE("Creating window surface %dx%d", width, height); + + if (!window) return; + + threadState->surface = + eglCreateWindowSurface(threadState->display, threadState->config, window, NULL); + ANativeWindow_release(window); + if (!threadState->makeCurrent(threadState->surface)) { + ALOGE("Unable to eglMakeCurrent"); + threadState->surface = EGL_NO_SURFACE; + return; + } + + threadState->width = width; + threadState->height = height; + }); +} + +void Renderer::start() { + mWorkerThread.run([this](ThreadState *threadState) { + threadState->isStarted = true; + requestDraw(); + }); + + mHotPocketThread.run([this](HotPocketState *hotPocketState) { + hotPocketState->isStarted = true; + }); + spin(); +} + +void Renderer::stop() { + mWorkerThread.run([=](ThreadState *threadState) { threadState->isStarted = false; }); + mHotPocketThread.run([](HotPocketState *hotPocketState) { hotPocketState->isStarted = false; }); +} + +float Renderer::getAverageFps() { + return averageFps; +} + +void Renderer::requestDraw() { + mWorkerThread.run( + [=](ThreadState *threadState) { if (threadState->isStarted) draw(threadState); }); +} + +Renderer::ThreadState::ThreadState() { + display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + eglInitialize(display, 0, 0); + + const EGLint configAttributes[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_NONE + }; + + EGLint numConfigs = 0; + eglChooseConfig(display, configAttributes, nullptr, 0, &numConfigs); + std::vector supportedConfigs(static_cast(numConfigs)); + eglChooseConfig(display, configAttributes, supportedConfigs.data(), numConfigs, &numConfigs); + + // Choose a config, either a match if possible or the first config otherwise + + const auto configMatches = [&](EGLConfig config) { + if (!configHasAttribute(EGL_RED_SIZE, 8)) return false; + if (!configHasAttribute(EGL_GREEN_SIZE, 8)) return false; + if (!configHasAttribute(EGL_BLUE_SIZE, 8)) return false; + return configHasAttribute(EGL_DEPTH_SIZE, 0); + }; + + const auto configIter = std::find_if(supportedConfigs.cbegin(), supportedConfigs.cend(), + configMatches); + + config = (configIter != supportedConfigs.cend()) ? *configIter : supportedConfigs[0]; + + const EGLint contextAttributes[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE + }; + + context = eglCreateContext(display, config, nullptr, contextAttributes); + + glEnable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); +} + +Renderer::ThreadState::~ThreadState() { + clearSurface(); + if (context != EGL_NO_CONTEXT) eglDestroyContext(display, context); + if (display != EGL_NO_DISPLAY) eglTerminate(display); +} + +void Renderer::ThreadState::onSettingsChanged(const Settings *settings) { + refreshPeriod = settings->getRefreshPeriod(); + swapIntervalNS = settings->getSwapIntervalNS(); +} + +void Renderer::ThreadState::clearSurface() { + if (surface == EGL_NO_SURFACE) return; + + makeCurrent(EGL_NO_SURFACE); + eglDestroySurface(display, surface); + surface = EGL_NO_SURFACE; +} + +bool Renderer::ThreadState::configHasAttribute(EGLint attribute, EGLint value) { + EGLint outValue = 0; + EGLBoolean result = eglGetConfigAttrib(display, config, attribute, &outValue); + return result && (outValue == value); +} + +EGLBoolean Renderer::ThreadState::makeCurrent(EGLSurface surface) { + return eglMakeCurrent(display, surface, surface, context); +} + +void Renderer::HotPocketState::onSettingsChanged(const Settings *settings) { + isEnabled = settings->getHotPocket(); +} + +void Renderer::spin() { + mHotPocketThread.run([this](HotPocketState *hotPocketState) { + if (!hotPocketState->isEnabled || !hotPocketState->isStarted) return; + for (int i = 1; i < 1000; ++i) { + int value = i; + while (value != 1) { + if (value == 0) { + ALOGI("This will never run, but hopefully the compiler doesn't notice"); + } + if (value % 2 == 0) { + value /= 2; + } else { + value = 3 * value + 1; + } + } + } + spin(); + }); +} + +// should be called once per draw as this function maintains the time delta between calls +void Renderer::calculateFps() { + static constexpr int FPS_SAMPLES = 10; + static std::chrono::steady_clock::time_point prev = std::chrono::steady_clock::now(); + static float fpsSum = 0; + static int fpsCount = 0; + + + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + fpsSum += 1.0f / ((now - prev).count() / 1e9f); + fpsCount++; + if (fpsCount == FPS_SAMPLES) { + averageFps = fpsSum / fpsCount; + fpsSum = 0; + fpsCount = 0; + } + prev = now; +} + +void Renderer::setWorkload(int load) { + mWorkload = load; +} + +void Renderer::draw(ThreadState *threadState) { + // Don't render if we have no surface + if (threadState->surface == EGL_NO_SURFACE) { + // Sleep a bit so we don't churn too fast + std::this_thread::sleep_for(50ms); + requestDraw(); + return; + } + + Swappy_recordFrameStart(threadState->display, threadState->surface); + + calculateFps(); + + float deltaSeconds = threadState->swapIntervalNS / 1e9f; + if (threadState->lastUpdate - std::chrono::steady_clock::now() <= 100ms) { + deltaSeconds = (threadState->lastUpdate - std::chrono::steady_clock::now()).count() / 1e9f; + } + threadState->lastUpdate = std::chrono::steady_clock::now(); + + threadState->x += threadState->velocity * deltaSeconds; + + if (threadState->x > 0.8f) { + threadState->velocity *= -1.0f; + threadState->x = 1.6f - threadState->x; + } else if (threadState->x < -0.8f) { + threadState->velocity *= -1.0f; + threadState->x = -1.6f - threadState->x; + } + + // Just fill the screen with a color. + glClearColor(0.3f, 0.3f, 0.3f, 1.0f); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + const float aspectRatio = static_cast(threadState->width) / threadState->height; + + const std::vector + circles = {{Circle::Color{0.0f, 1.0f, 1.0f}, 0.1f, threadState->x, 0.0f}}; + + Circle::draw(aspectRatio, circles, mWorkload); + + Swappy_swap(threadState->display, threadState->surface); + + // If we're still started, request another frame + requestDraw(); +} + +} // namespace samples diff --git a/samples/bouncyball/app/src/main/cpp/Renderer.h b/samples/bouncyball/app/src/main/cpp/Renderer.h new file mode 100644 index 0000000000000000000000000000000000000000..4c3f59253924444f8b0ddd38daea3ffef0d1f324 --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/Renderer.h @@ -0,0 +1,114 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "Thread.h" + +#include "WorkerThread.h" + +namespace samples { + +class Settings; + +class Renderer { + // Allows construction with std::unique_ptr from a static method, but disallows construction + // outside of the class since no one else can construct a ConstructorTag + struct ConstructorTag { + }; + +public: + explicit Renderer(ConstructorTag) {} + + static Renderer *getInstance(); + + // Sets the active window to render into + // Takes ownership of window and will release its reference + void setWindow(ANativeWindow *window, int32_t width, int32_t height); + + void start(); + + void stop(); + + float getAverageFps(); + + void requestDraw(); + + void setWorkload(int load); + +private: + class ThreadState { + public: + ThreadState(); + + ~ThreadState(); + + void onSettingsChanged(const Settings *); + + void clearSurface(); + + bool configHasAttribute(EGLint attribute, EGLint value); + + EGLBoolean makeCurrent(EGLSurface surface); + + EGLDisplay display = EGL_NO_DISPLAY; + EGLConfig config = static_cast(0); + EGLSurface surface = EGL_NO_SURFACE; + EGLContext context = EGL_NO_CONTEXT; + + bool isStarted = false; + + std::chrono::time_point lastUpdate = std::chrono::steady_clock::now(); + float x = 0.0f; + float velocity = 1.6f; + + std::chrono::nanoseconds refreshPeriod = std::chrono::nanoseconds{0}; + int64_t swapIntervalNS = 0; + int32_t width = 0; + int32_t height = 0; + }; + + void draw(ThreadState *threadState); + void calculateFps(); + + WorkerThread mWorkerThread = {"Renderer", Affinity::Odd}; + + class HotPocketState { + public: + void onSettingsChanged(const Settings *); + + bool isEnabled = false; + bool isStarted = false; + }; + + WorkerThread mHotPocketThread = {"HotPocket", Affinity::Even}; + + void spin(); + + float averageFps = -1.0f; + + int mWorkload = 0; +}; + +} // namespace samples diff --git a/samples/bouncyball/app/src/main/cpp/Settings.cpp b/samples/bouncyball/app/src/main/cpp/Settings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..525187d6a8517344f0cc3c83fd42b412373effe5 --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/Settings.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Settings.h" + +#define LOG_TAG "Settings" + +#include + +#include "swappy/swappy.h" +#include "Log.h" + +namespace samples { + +Settings *Settings::getInstance() { + static auto settings = std::make_unique(ConstructorTag{}); + return settings.get(); +} + +void Settings::addListener(Listener listener) { + std::lock_guard lock(mMutex); + mListeners.emplace_back(std::move(listener)); +} + +void Settings::setPreference(std::string key, std::string value) { + if (key == "refresh_period") { + Swappy_setRefreshPeriod(std::stoll(value)); + } else if (key == "swap_interval") { + Swappy_setSwapIntervalNS(std::stoi(value) * 1e6); + } else if (key == "use_affinity") { + Swappy_setUseAffinity(value == "true"); + } else if (key == "hot_pocket") { + std::lock_guard lock(mMutex); + mHotPocket = (value == "true"); + } else { + ALOGI("Can't find matching preference for %s", key.c_str()); + return; + } + + // Notify the listeners without the lock held + notifyListeners(); +} + +std::chrono::nanoseconds Settings::getRefreshPeriod() const { + return std::chrono::nanoseconds(Swappy_getRefreshPeriodNanos()); +} + +int32_t Settings::getSwapIntervalNS() const { + return Swappy_getSwapIntervalNS(); +} + +bool Settings::getUseAffinity() const { + return Swappy_getUseAffinity(); +} + +bool Settings::getHotPocket() const { + return mHotPocket; +} + +void Settings::notifyListeners() { + // Grab a local copy of the listeners + std::vector listeners; + { + std::lock_guard lock(mMutex); + listeners = mListeners; + } + + // Call the listeners without the lock held + for (const auto &listener : listeners) { + listener(); + } +} + +} // namespace samples diff --git a/samples/bouncyball/app/src/main/cpp/Settings.h b/samples/bouncyball/app/src/main/cpp/Settings.h new file mode 100644 index 0000000000000000000000000000000000000000..74be4852e53f331ae4f5241afd0e6e783afe94b6 --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/Settings.h @@ -0,0 +1,63 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "Thread.h" + +#include +#include +#include +#include +#include + +namespace samples { + +class Settings { +private: + // Allows construction with std::unique_ptr from a static method, but disallows construction + // outside of the class since no one else can construct a ConstructorTag + struct ConstructorTag { + }; +public: + explicit Settings(ConstructorTag) : mHotPocket(false) {} + + static Settings *getInstance(); + + using Listener = std::function; + + void addListener(Listener listener); + + void setPreference(std::string key, std::string value); + + std::chrono::nanoseconds getRefreshPeriod() const; + + int32_t getSwapIntervalNS() const; + + bool getUseAffinity() const; + + bool getHotPocket() const; + +private: + void notifyListeners(); + + mutable std::mutex mMutex; + std::vector mListeners GUARDED_BY(mMutex); + + std::atomic mHotPocket; +}; + +} // namespace samples diff --git a/samples/bouncyball/app/src/main/cpp/Thread.cpp b/samples/bouncyball/app/src/main/cpp/Thread.cpp new file mode 120000 index 0000000000000000000000000000000000000000..915be28452950234c1f384772e419fbbc41a5965 --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/Thread.cpp @@ -0,0 +1 @@ +../../../../../common/src/Thread.cpp \ No newline at end of file diff --git a/samples/bouncyball/app/src/main/cpp/WorkerThread.h b/samples/bouncyball/app/src/main/cpp/WorkerThread.h new file mode 100644 index 0000000000000000000000000000000000000000..086e522c02e90bd250d2357c8466c7c20d25b32b --- /dev/null +++ b/samples/bouncyball/app/src/main/cpp/WorkerThread.h @@ -0,0 +1,118 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include "Settings.h" +#include "Thread.h" + +namespace samples { + +template +class WorkerThread { +public: + using Work = std::function; + + WorkerThread(const char *name, Affinity affinity) + : mName(name), + mAffinity(affinity) { + launchThread(); + auto settingsChanged = [this](ThreadState *threadState) { onSettingsChanged(threadState); }; + Settings::getInstance()->addListener( + [this, work = std::move(settingsChanged)]() { run(work); }); + } + + ~WorkerThread() { + std::lock_guard threadLock(mThreadMutex); + terminateThread(); + } + + void run(Work work) { + std::lock_guard workLock(mWorkMutex); + mWorkQueue.emplace(std::move(work)); + mWorkCondition.notify_all(); + } + + void reset() { + launchThread(); + } + +private: + void launchThread() { + std::lock_guard threadLock(mThreadMutex); + if (mThread.joinable()) { + terminateThread(); + } + mThread = std::thread([this]() { threadMain(); }); + } + + void terminateThread() REQUIRES(mThreadMutex) { + { + std::lock_guard workLock(mWorkMutex); + mIsActive = false; + mWorkCondition.notify_all(); + } + mThread.join(); + } + + void onSettingsChanged(ThreadState *threadState) { + const Settings *settings = Settings::getInstance(); + threadState->onSettingsChanged(settings); + setAffinity(settings->getUseAffinity() ? mAffinity : Affinity::None); + } + + void threadMain() { + setAffinity(Settings::getInstance()->getUseAffinity() ? mAffinity : Affinity::None); + pthread_setname_np(pthread_self(), mName.c_str()); + + ThreadState threadState; + + std::lock_guard lock(mWorkMutex); + while (mIsActive) { + mWorkCondition.wait(mWorkMutex, + [this]() REQUIRES(mWorkMutex) { + return !mWorkQueue.empty() || !mIsActive; + }); + if (!mWorkQueue.empty()) { + auto head = mWorkQueue.front(); + mWorkQueue.pop(); + + // Drop the mutex while we execute + mWorkMutex.unlock(); + head(&threadState); + mWorkMutex.lock(); + } + } + } + + const std::string mName; + const Affinity mAffinity; + + std::mutex mThreadMutex; + std::thread mThread GUARDED_BY(mThreadMutex); + + std::mutex mWorkMutex; + bool mIsActive GUARDED_BY(mWorkMutex) = true; + std::queue> mWorkQueue GUARDED_BY(mWorkMutex); + std::condition_variable_any mWorkCondition; +}; + +} // namespace samples diff --git a/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/OrbitActivity.java b/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/OrbitActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..aa663f5035a4a6695d61312efdeff5b76d97574a --- /dev/null +++ b/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/OrbitActivity.java @@ -0,0 +1,487 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.prefabulated.bouncyball; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Trace; +import android.preference.ListPreference; +import android.preference.PreferenceManager; +import android.text.Layout; +import android.util.Log; +import android.view.Choreographer; +import android.view.Display; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.widget.GridLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayDeque; +import java.util.Locale; +import java.util.Queue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatTextView; + +public class OrbitActivity extends AppCompatActivity implements Choreographer.FrameCallback, SurfaceHolder.Callback { + + // Used to load the 'native-lib' library on application startup. + static { + System.loadLibrary("native-lib"); + } + + private static final long ONE_MS_IN_NS = 1000000; + private static final long ONE_S_IN_NS = 1000 * ONE_MS_IN_NS; + + private static final String LOG_TAG = "OrbitActivity.java"; + + private void configureGridCell(AppCompatTextView cell) { + // A bunch of optimizations to reduce the cost of setText + cell.setEllipsize(null); + cell.setMaxLines(1); + cell.setSingleLine(true); + if (android.os.Build.VERSION.SDK_INT >= 23) { + cell.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE); + cell.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE); + } + + cell.setTextAppearance(getApplicationContext(), R.style.InfoTextSmall); + cell.setText("0"); + } + + private void buildChoreographerInfoGrid() { + GridLayout infoGrid = findViewById(R.id.choreographer_info_grid); + + // Add the header row + GridLayout.Spec headerRowSpec = GridLayout.spec(0); + for (int column = 0; column < mChoreographerInfoGrid[0].length; ++column) { + AppCompatTextView cell = new AppCompatTextView(getApplicationContext()); + GridLayout.Spec colSpec = GridLayout.spec(column, 1.0f); + cell.setLayoutParams(new GridLayout.LayoutParams(headerRowSpec, colSpec)); + configureGridCell(cell); + + if (column == 0) { + cell.setText(""); + } else { + cell.setText(String.format(Locale.US, "%d", column - 1)); + } + infoGrid.addView(cell); + mChoreographerInfoGrid[0][column] = cell; + } + + // Add the data rows + for (int row = 1; row < mChoreographerInfoGrid.length; ++row) { + GridLayout.Spec rowSpec = GridLayout.spec(row); + + for (int column = 0; column < mChoreographerInfoGrid[row].length; ++column) { + AppCompatTextView cell = new AppCompatTextView(getApplicationContext()); + GridLayout.Spec colSpec = GridLayout.spec(column, 1.0f); + cell.setLayoutParams(new GridLayout.LayoutParams(rowSpec, colSpec)); + cell.setTextAppearance(getApplicationContext(), R.style.InfoTextSmall); + configureGridCell(cell); + + if (column == 0) { + switch (row) { + case 1: + cell.setText(R.string.grid_1s_arr); + break; + case 2: + cell.setText(R.string.grid_1s_ts); + break; + case 3: + cell.setText(R.string.grid_1m_arr); + break; + case 4: + cell.setText(R.string.grid_1m_ts); + break; + } + } + infoGrid.addView(cell); + mChoreographerInfoGrid[row][column] = cell; + } + } + + for (TextView[] row : mChoreographerInfoGrid) { + for (TextView column : row) { + column.setWidth(infoGrid.getWidth() / infoGrid.getColumnCount()); + } + } + } + + private void buildSwappyStatsGrid() { + GridLayout infoGrid = findViewById(R.id.swappy_stats_grid); + + // Add the header row + GridLayout.Spec headerRowSpec = GridLayout.spec(0); + for (int column = 0; column < mSwappyGrid[0].length; ++column) { + AppCompatTextView cell = new AppCompatTextView(getApplicationContext()); + GridLayout.Spec colSpec = GridLayout.spec(column, 1.0f); + cell.setLayoutParams(new GridLayout.LayoutParams(headerRowSpec, colSpec)); + configureGridCell(cell); + + if (column == 0) { + cell.setText(""); + } else { + cell.setText(String.format(Locale.US, "%d", column - 1)); + } + infoGrid.addView(cell); + mSwappyGrid[0][column] = cell; + } + + // Add the data rows + for (int row = 1; row < mSwappyGrid.length; ++row) { + GridLayout.Spec rowSpec = GridLayout.spec(row); + + for (int column = 0; column < mSwappyGrid[row].length; ++column) { + AppCompatTextView cell = new AppCompatTextView(getApplicationContext()); + GridLayout.Spec colSpec = GridLayout.spec(column, 1.0f); + cell.setLayoutParams(new GridLayout.LayoutParams(rowSpec, colSpec)); + cell.setTextAppearance(getApplicationContext(), R.style.InfoTextSmall); + configureGridCell(cell); + + if (column == 0) { + switch (row) { + case 1: + cell.setText(R.string.idle_frames); + break; + case 2: + cell.setText(R.string.late_frames); + break; + case 3: + cell.setText(R.string.offset_frames); + break; + case 4: + cell.setText(R.string.latency_frames); + break; + } + } else { + cell.setText("0%"); + } + infoGrid.addView(cell); + mSwappyGrid[row][column] = cell; + } + } + + for (TextView[] row : mSwappyGrid) { + for (TextView column : row) { + column.setWidth(infoGrid.getWidth() / infoGrid.getColumnCount()); + } + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_orbit); + + // Get display metrics + + WindowManager wm = getWindowManager(); + Display display = wm.getDefaultDisplay(); + float refreshRateHz = display.getRefreshRate(); + Log.i(LOG_TAG, String.format("Refresh rate: %.1f Hz", refreshRateHz)); + long refreshPeriodNanos = (long) (ONE_S_IN_NS / refreshRateHz); + long appVsyncOffsetNanos = display.getAppVsyncOffsetNanos(); + long sfVsyncOffsetNanos = refreshPeriodNanos - (display.getPresentationDeadlineNanos() - ONE_MS_IN_NS); + + // Initialize UI + + SurfaceView surfaceView = findViewById(R.id.surface_view); + surfaceView.getHolder().addCallback(this); + + mInfoOverlay = findViewById(R.id.info_overlay); + mInfoOverlay.setBackgroundColor(0x80000000); + + buildChoreographerInfoGrid(); + buildSwappyStatsGrid(); + + TextView appOffsetView = findViewById(R.id.app_offset); + appOffsetView.setText(String.format(Locale.US, "App Offset: %.1f ms", appVsyncOffsetNanos / (float) ONE_MS_IN_NS)); + TextView sfOffsetView = findViewById(R.id.sf_offset); + sfOffsetView.setText(String.format(Locale.US, "SF Offset: %.1f ms", sfVsyncOffsetNanos / (float) ONE_MS_IN_NS)); + + // Initialize the native renderer + + nInit(); + + nSetPreference("refresh_period", String.valueOf(refreshPeriodNanos)); + } + + private void infoOverlayToggle() { + if (mInfoOverlay == null) { + return; + } + + mInfoOverlayEnabled = !mInfoOverlayEnabled; + if (mInfoOverlayEnabled) { + mInfoOverlay.setVisibility(View.VISIBLE); + mInfoOverlayButton.setIcon(R.drawable.ic_info_solid_white_24dp); + } else { + mInfoOverlay.setVisibility(View.INVISIBLE); + mInfoOverlayButton.setIcon(R.drawable.ic_info_outline_white_24dp); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_orbit, menu); + + mInfoOverlayButton = menu.findItem(R.id.info_overlay_button); + if (mInfoOverlayButton != null) { + mInfoOverlayButton.setOnMenuItemClickListener((MenuItem item) -> { + infoOverlayToggle(); + return true; + }); + } + + MenuItem settingsButton = menu.findItem(R.id.settings_item); + if (settingsButton != null) { + settingsButton.setOnMenuItemClickListener((MenuItem) -> { + Intent intent = new Intent(); + intent.setClass(getApplicationContext(), SettingsActivity.class); + startActivity(intent); + return true; + }); + } + + return true; + } + + @Override + protected void onStart() { + super.onStart(); + + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + for (String key : sharedPreferences.getAll().keySet()) { + if (key.equals("use_affinity")) { + nSetPreference(key, sharedPreferences.getBoolean(key, true) ? "true" : "false"); + continue; + } else if (key.equals("hot_pocket")) { + nSetPreference(key, sharedPreferences.getBoolean(key, false) ? "true" : "false"); + continue; + } else if (key.equals("use_auto_swap_interval")) { + nSetAutoSwapInterval(sharedPreferences.getBoolean(key, true)); + continue; + } else if (key.equals("workload")) { + nSetWorkload(sharedPreferences.getInt(key, 0)); + continue; + } + nSetPreference(key, sharedPreferences.getString(key, null)); + } + + mIsRunning = true; + nStart(); + Choreographer.getInstance().postFrameCallback(this); + } + + @Override + protected void onStop() { + super.onStop(); + + mIsRunning = false; + nStop(); + clearChoreographerInfo(); + } + + private int deltaToBin(long delta) { + long framePeriodNanos = 16666666; + return (int) ((delta + framePeriodNanos / 2) / framePeriodNanos); + } + + private void clearChoreographerInfo() { + for (int i = 0; i < CHOREOGRAPHER_INFO_BIN_COUNT; ++i) { + mArrivalBinsLastSecond[i] = 0; + mArrivalBinsLastMinute[i] = 0; + mTimestampBinsLastSecond[i] = 0; + mTimestampBinsLastMinute[i] = 0; + } + mArrivalBinQueue.clear(); + mTimestampBinQueue.clear(); + } + + private void updateChoregrapherInfoBin(int row, int bin, int value) { + if (value == mLastChoreographerInfoValues[row - 1][bin]) { + return; + } + + mLastChoreographerInfoValues[row - 1][bin] = value; + mChoreographerInfoGrid[row][bin + 1].setText(String.valueOf(value)); + } + + private void updateSwappyStatsBin(int row, int bin, int value) { + if (value == mLastSwappyStatsValues[row - 1][bin]) { + return; + } + + mLastSwappyStatsValues[row - 1][bin] = value; + mSwappyGrid[row][bin + 1].setText(String.valueOf(value) + "%"); + } + + private void dumpBins() { + Trace.beginSection("dumpBins"); + + Trace.beginSection("addToQueues"); + mArrivalBinQueue.add(mArrivalBinsLastSecond.clone()); + mTimestampBinQueue.add(mTimestampBinsLastSecond.clone()); + Trace.endSection(); + + Trace.beginSection("updateMinuteBins"); + for (int bin = 0; bin < CHOREOGRAPHER_INFO_BIN_COUNT; ++bin) { + mArrivalBinsLastMinute[bin] += mArrivalBinsLastSecond[bin]; + mTimestampBinsLastMinute[bin] += mTimestampBinsLastSecond[bin]; + } + Trace.endSection(); + + Trace.beginSection("pruneQueue"); + if (mArrivalBinQueue.size() > 60) { + int[] oldestArrivalBin = mArrivalBinQueue.remove(); + int[] oldestTimestampBin = mTimestampBinQueue.remove(); + for (int bin = 0; bin < CHOREOGRAPHER_INFO_BIN_COUNT; ++bin) { + mArrivalBinsLastMinute[bin] -= oldestArrivalBin[bin]; + mTimestampBinsLastMinute[bin] -= oldestTimestampBin[bin]; + } + } + Trace.endSection(); + + Trace.beginSection("updateInfoGrid"); + for (int bin = 0; bin < CHOREOGRAPHER_INFO_BIN_COUNT; ++bin) { + updateChoregrapherInfoBin(1, bin, mArrivalBinsLastSecond[bin]); + updateChoregrapherInfoBin(2, bin, mTimestampBinsLastSecond[bin]); + updateChoregrapherInfoBin(3, bin, mArrivalBinsLastMinute[bin]); + updateChoregrapherInfoBin(4, bin, mTimestampBinsLastMinute[bin]); + } + Trace.endSection(); + + Trace.beginSection("updateSwappyStatsGrid"); + for (int stat = 0; stat < 4; ++stat) { + for (int bin = 0; bin < SWAPPY_STATS_BIN_COUNT; ++bin) { + updateSwappyStatsBin(stat +1, bin, nGetSwappyStats(stat, bin)); + } + } + TextView appOffsetView = findViewById(R.id.swappy_stats); + appOffsetView.setText(String.format(Locale.US, "SwappyStats: %d Total Frames", nGetSwappyStats(-1, 0))); + Trace.endSection(); + + Trace.beginSection("clearSecondBins"); + for (int bin = 0; bin < CHOREOGRAPHER_INFO_BIN_COUNT; ++bin) { + mArrivalBinsLastSecond[bin] = 0; + mTimestampBinsLastSecond[bin] = 0; + } + Trace.endSection(); + + Trace.endSection(); + } + + @Override + public void doFrame(long frameTimeNanos) { + Trace.beginSection("doFrame"); + + TextView fpsView = findViewById(R.id.fps); + fpsView.setText(String.format(Locale.US, "FPS: %.1f", nGetAverageFps())); + + long now = System.nanoTime(); + + if (mIsRunning) { + Trace.beginSection("Requesting callback"); + Choreographer.getInstance().postFrameCallback(this); + Trace.endSection(); + } + + long arrivalDelta = now - mLastArrivalTime; + long timestampDelta = now - mLastFrameTimestamp; + mLastArrivalTime = now; + mLastFrameTimestamp = frameTimeNanos; + + mArrivalBinsLastSecond[Math.min(deltaToBin(arrivalDelta), mArrivalBinsLastSecond.length - 1)] += 1; + mTimestampBinsLastSecond[Math.min(deltaToBin(timestampDelta), mTimestampBinsLastSecond.length - 1)] += 1; + + if (now - mLastDumpTime > 1000000000) { + dumpBins(); + // Trim off excess precision so we don't drift forward over time + mLastDumpTime = now - (now % 1000000000); + } + + Trace.endSection(); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + // Do nothing here, waiting for surfaceChanged instead + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Surface surface = holder.getSurface(); + nSetSurface(surface, width, height); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + nClearSurface(); + } + + public native void nInit(); + public native void nSetSurface(Surface surface, int width, int height); + public native void nClearSurface(); + public native void nStart(); + public native void nStop(); + public native void nSetPreference(String key, String value); + public native void nSetWorkload(int load); + public native void nSetAutoSwapInterval(boolean enabled); + public native float nGetAverageFps(); + public native int nGetSwappyStats(int stat, int bin); + + private MenuItem mInfoOverlayButton; + + private LinearLayout mInfoOverlay; + private final TextView[][] mChoreographerInfoGrid = new TextView[5][7]; + private final int[][] mLastChoreographerInfoValues = new int[4][6]; + + private final TextView[][] mSwappyGrid = new TextView[5][7]; + private final int[][] mLastSwappyStatsValues = new int[4][6]; + + private boolean mIsRunning; + private boolean mInfoOverlayEnabled = false; + + private final ExecutorService mChoreographerExecutor = Executors.newSingleThreadExecutor(); + private final Handler mUIThreadHandler = new Handler(Looper.getMainLooper()); + + private static final int CHOREOGRAPHER_INFO_BIN_COUNT = 6; + private static final int SWAPPY_STATS_BIN_COUNT = 6; + private long mLastDumpTime; + private long mLastArrivalTime; + private long mLastFrameTimestamp; + private final int[] mArrivalBinsLastSecond = new int[CHOREOGRAPHER_INFO_BIN_COUNT]; + private final int[] mArrivalBinsLastMinute = new int[CHOREOGRAPHER_INFO_BIN_COUNT]; + private final Queue mArrivalBinQueue = new ArrayDeque<>(60); + private final int[] mTimestampBinsLastSecond = new int[CHOREOGRAPHER_INFO_BIN_COUNT]; + private final int[] mTimestampBinsLastMinute = new int[CHOREOGRAPHER_INFO_BIN_COUNT]; + private final Queue mTimestampBinQueue = new ArrayDeque<>(60); +} diff --git a/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/SettingsActivity.java b/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/SettingsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..fd47bf84d6cf9ccdcbb2d2c9a3bbc1be1b12fb26 --- /dev/null +++ b/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/SettingsActivity.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.prefabulated.bouncyball; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.util.Log; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; + +public class SettingsActivity + extends AppCompatActivity + implements SharedPreferences.OnSharedPreferenceChangeListener { + + private final SettingsFragment mSettingsFragment = new SettingsFragment(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getSupportFragmentManager().beginTransaction() + .replace(android.R.id.content, mSettingsFragment) + .commit(); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle("Settings"); + } + + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) + .registerOnSharedPreferenceChangeListener(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) + .unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) { + Log.i("SettingsActivity.java", "onSharedPreferenceChanged"); + } +} diff --git a/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/SettingsFragment.java b/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/SettingsFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a6d5138b96237bfe0c0eb79c07234d4421cc4bca --- /dev/null +++ b/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/SettingsFragment.java @@ -0,0 +1,112 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.prefabulated.bouncyball; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Bundle; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import androidx.preference.Preference; +import androidx.preference.ListPreference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +public class SettingsFragment + extends PreferenceFragmentCompat + implements SharedPreferences.OnSharedPreferenceChangeListener { + private ListPreference mSwapIntervalPreference; + private String mSwapIntervalKey; + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + mSwapIntervalKey = getResources().getString(R.string.swap_interval_key); + addPreferencesFromResource(R.xml.preferences); + + PreferenceScreen preferenceScreen = getPreferenceScreen(); + for (int i = 0; i < preferenceScreen.getPreferenceCount(); ++i) { + Preference preference = preferenceScreen.getPreference(i); + final String key = preference.getKey(); + if (key != null && key.equals(mSwapIntervalKey)) { + mSwapIntervalPreference = (ListPreference)preference; + } + } + + // fill the swap interval list based on the screen refresh rate + float refreshRate = getActivity().getWindowManager().getDefaultDisplay().getRefreshRate(); + int numEntries = (int)(refreshRate / 20 + 1); + String[] entries = new String[numEntries]; + String[] entryValues = new String[numEntries]; + + for(int interval = 0; interval < numEntries - 1; interval++) { + float fps = refreshRate / (interval + 1); + float ms = 1000 / fps; + + entries[interval] = String.format(Locale.US, "%.2fms (%.0ffps)", ms, fps); + entryValues[interval] = Float.toString(ms); + } + + entries[numEntries - 1] = String.format(Locale.US, "No pacing"); + entryValues[numEntries - 1] = Float.toString(100); + + mSwapIntervalPreference.setEntries(entries); + mSwapIntervalPreference.setEntryValues(entryValues); + mSwapIntervalPreference.setDefaultValue(entries[0]); + + Context context = getContext(); + if (context != null) { + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context); + sharedPreferences.registerOnSharedPreferenceChangeListener(this); + // set default if it doesn't exist + if (sharedPreferences.getString(mSwapIntervalKey, null) == null) + sharedPreferences.edit() + .putString(mSwapIntervalKey, entryValues[0]) + .apply(); + updateSwapIntervalSummary(sharedPreferences.getString(mSwapIntervalKey, null)); + } + } + + private void updateSwapIntervalSummary(String swapIntervalString) { + Resources resources; + try { + resources = getResources(); + } catch (IllegalStateException e) { + // Swallow this and return early if we're not currently associated with an Activity + return; + } + + float swapInterval = Float.parseFloat(swapIntervalString); + mSwapIntervalPreference.setSummary(String.format(Locale.US, + "%s %.2f %s", + resources.getString(R.string.swap_interval_summary_prologue), + swapInterval, + resources.getString(R.string.swap_interval_summary_epilogue))); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(mSwapIntervalKey)) { + updateSwapIntervalSummary(sharedPreferences.getString(mSwapIntervalKey, null)); + } + } +} diff --git a/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_info_outline_white_24dp.xml b/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_info_outline_white_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..9567ecd7754dd13715860c5dfeae0f92256d4eb4 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_info_outline_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_info_solid_white_24dp.xml b/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_info_solid_white_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..f9e842c7963535af4621fecf04d98656391662f9 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_info_solid_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_settings_white_24dp.xml b/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_settings_white_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..ce997a727dd81ccf01eceb695f1082418150c0f9 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/drawable-anydpi/ic_settings_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/samples/bouncyball/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/samples/bouncyball/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000000000000000000000000000000000..c7bd21dbd86990cde81fea8abd3bf904b4546749 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/samples/bouncyball/app/src/main/res/drawable/ic_launcher_background.xml b/samples/bouncyball/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..d5fccc538c179838bfdce779c26eebb4fa0b5ce9 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/bouncyball/app/src/main/res/layout/activity_orbit.xml b/samples/bouncyball/app/src/main/res/layout/activity_orbit.xml new file mode 100644 index 0000000000000000000000000000000000000000..f87ae4618eb4e743bb7989e072749201a0319700 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/layout/activity_orbit.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/bouncyball/app/src/main/res/menu/menu_orbit.xml b/samples/bouncyball/app/src/main/res/menu/menu_orbit.xml new file mode 100644 index 0000000000000000000000000000000000000000..98c08b564126caa4aa20dbd1b9cbf8c5595debb1 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/menu/menu_orbit.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/bouncyball/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/samples/bouncyball/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000000000000000000000000000000000..eca70cfe52eac1ba66ba280a68ca7be8fcf88a16 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samples/bouncyball/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/samples/bouncyball/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000000000000000000000000000000000..eca70cfe52eac1ba66ba280a68ca7be8fcf88a16 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samples/bouncyball/app/src/main/res/mipmap-hdpi/ic_launcher.png b/samples/bouncyball/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f5908281d070150700378b64a84c7db1f97aa1 Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/samples/bouncyball/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..1b523998081149a985cef0cdf89045b9ed29964a Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-mdpi/ic_launcher.png b/samples/bouncyball/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..ff10afd6e182edb2b1a63c8f984e9070d9f950ba Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/samples/bouncyball/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..115a4c768a20c9e13185c17043f4c4d12dd4632a Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/samples/bouncyball/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..dcd3cd8083358269d6ed7894726283bb9bcbbfea Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/samples/bouncyball/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..459ca609d3ae0d3943ab44cdc27feef9256dc6d7 Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/samples/bouncyball/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca12fe024be86e868d14e91120a6902f8e88ac6 Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/samples/bouncyball/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..8e19b410a1b15ff180f3dacac19395fe3046cdec Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/samples/bouncyball/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..b824ebdd48db917eea2e67a82260a100371f8a24 Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/samples/bouncyball/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/samples/bouncyball/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4c19a13c239cb67b8a2134ddd5f325db1d2d5bee Binary files /dev/null and b/samples/bouncyball/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/samples/bouncyball/app/src/main/res/values/colors.xml b/samples/bouncyball/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000000000000000000000000000000000..3ab3e9cbce07f7cdc941fc8ba424c05e83ed80f0 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/samples/bouncyball/app/src/main/res/values/strings.xml b/samples/bouncyball/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..984d43568241463200a4eb232749a2ac029a5dc8 --- /dev/null +++ b/samples/bouncyball/app/src/main/res/values/strings.xml @@ -0,0 +1,37 @@ + + Swappy + Rendering FPS: + App Offset: + Choreographer Consistency + 1m Arr + 1m TS + 1s Arr + 1s TS + Info Overlay + Settings + SF Offset: + Swappy Stats + Idle + Late + Offset + Latency + + + + swap_interval + Swap Interval + Present a newly-rendered frame every\u0020 + ms + + Use Affinity + Allow worker threads to specify their CPU affinity + + Auto Swap Interval Mode + Swappy calculates swap interval based on frame rendering time + + Hot Pocket + Create a spinning thread to encourage higher frequencies + + Work load + Work load for rendering bouncyball (based on number of vertices) + diff --git a/samples/bouncyball/app/src/main/res/values/styles.xml b/samples/bouncyball/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..d2ee17001e93ffbb77a26ee8b76833a23cf8cbaf --- /dev/null +++ b/samples/bouncyball/app/src/main/res/values/styles.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/samples/bouncyball/app/src/main/res/xml/preferences.xml b/samples/bouncyball/app/src/main/res/xml/preferences.xml new file mode 100644 index 0000000000000000000000000000000000000000..ecfd40a000626eaa7c87a846ef3c84738a14fdde --- /dev/null +++ b/samples/bouncyball/app/src/main/res/xml/preferences.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/bouncyball/build.gradle b/samples/bouncyball/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..4699ea769be186b333ba919c53dbf37cba7a0de1 --- /dev/null +++ b/samples/bouncyball/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.3.2' + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/samples/bouncyball/gradle.properties b/samples/bouncyball/gradle.properties new file mode 100644 index 0000000000000000000000000000000000000000..aac7c9b4614ccfde6c721f24994cf30885a791d0 --- /dev/null +++ b/samples/bouncyball/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/samples/bouncyball/gradle/wrapper/gradle-wrapper.jar b/samples/bouncyball/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 Binary files /dev/null and b/samples/bouncyball/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/bouncyball/gradle/wrapper/gradle-wrapper.properties b/samples/bouncyball/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..92f5b28901302eb3cdd63f268ec425645b52a2d9 --- /dev/null +++ b/samples/bouncyball/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Mar 07 15:44:58 GMT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/samples/bouncyball/gradlew b/samples/bouncyball/gradlew new file mode 100755 index 0000000000000000000000000000000000000000..9d82f78915133e1c35a6ea51252590fb38efac2f --- /dev/null +++ b/samples/bouncyball/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/samples/bouncyball/gradlew.bat b/samples/bouncyball/gradlew.bat new file mode 100644 index 0000000000000000000000000000000000000000..aec99730b4e8fcd90b57a0e8e01544fea7c31a89 --- /dev/null +++ b/samples/bouncyball/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/bouncyball/settings.gradle b/samples/bouncyball/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..a0abcdb66314a443d14f23f61e92097f66f7caec --- /dev/null +++ b/samples/bouncyball/settings.gradle @@ -0,0 +1,3 @@ +include ':app' +include ':extras' +project(':extras').projectDir = new File('../../src/extras') diff --git a/samples/common/include/Log.h b/samples/common/include/Log.h new file mode 100644 index 0000000000000000000000000000000000000000..aec1ec25348fdbd4c3a3b82aee9dd5a96c4ad659 --- /dev/null +++ b/samples/common/include/Log.h @@ -0,0 +1,23 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); +#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); +#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); diff --git a/samples/common/include/Thread.h b/samples/common/include/Thread.h new file mode 100644 index 0000000000000000000000000000000000000000..3f8dda99d7d2240e3b6fb8638cfaf30b294722c2 --- /dev/null +++ b/samples/common/include/Thread.h @@ -0,0 +1,49 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +// Enable thread safety attributes only with clang. +// The attributes can be safely erased when compiling with other compilers. +#if defined(__clang__) && (!defined(SWIG)) +#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) +#else +#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op +#endif + +#define GUARDED_BY(x) \ + THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) + +#define REQUIRES(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__)) + +namespace samples { + +enum class Affinity { + None, + Even, + Odd +}; + +int32_t getNumCpus(); + +void setAffinity(int32_t cpu); + +void setAffinity(Affinity affinity); + +} diff --git a/samples/common/include/Trace.h b/samples/common/include/Trace.h new file mode 100644 index 0000000000000000000000000000000000000000..dce1937fdc1c5a78f79ce301d3e3d3be87b2c2e0 --- /dev/null +++ b/samples/common/include/Trace.h @@ -0,0 +1,134 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include +#include + +namespace samples { + +class Trace { +public: + using ATrace_beginSection_type = void (*)(const char *sectionName); + using ATrace_endSection_type = void (*)(); + using ATrace_isEnabled_type = bool (*)(); + + Trace() { + __android_log_print(ANDROID_LOG_INFO, "Trace", "Unable to load NDK tracing APIs"); + } + + Trace(ATrace_beginSection_type beginSection, + ATrace_endSection_type endSection, + ATrace_isEnabled_type isEnabled) + : ATrace_beginSection(beginSection), + ATrace_endSection(endSection), + ATrace_isEnabled(isEnabled) {} + + static std::unique_ptr create() { + void *libandroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL); + if (!libandroid) { + return std::make_unique(); + } + + auto beginSection = reinterpret_cast( + dlsym(libandroid, "ATrace_beginSection")); + if (!beginSection) { + return std::make_unique(); + } + + auto endSection = reinterpret_cast( + dlsym(libandroid, "ATrace_endSection")); + if (!endSection) { + return std::make_unique(); + } + + auto isEnabled = reinterpret_cast( + dlsym(libandroid, "ATrace_isEnabled")); + if (!isEnabled) { + return std::make_unique(); + } + + return std::make_unique(beginSection, endSection, isEnabled); + } + + bool isAvailable() const { + return ATrace_beginSection != nullptr; + } + + bool isEnabled() const { + return (ATrace_isEnabled != nullptr) && ATrace_isEnabled(); + } + + void beginSection(const char *name) const { + if (!ATrace_beginSection) { + return; + } + + ATrace_beginSection(name); + } + + void endSection() const { + if (!ATrace_endSection) { + return; + } + + ATrace_endSection(); + } + + static Trace *getInstance() { + static std::unique_ptr trace = Trace::create(); + return trace.get(); + }; + +private: + const ATrace_beginSection_type ATrace_beginSection = nullptr; + const ATrace_endSection_type ATrace_endSection = nullptr; + const ATrace_isEnabled_type ATrace_isEnabled = nullptr; +}; + +struct ScopedTrace { + ScopedTrace(const char *name) { + Trace *trace = Trace::getInstance(); + if (!trace->isAvailable() || !trace->isEnabled()) { + return; + } + + trace->beginSection(name); + mIsTracing = true; + } + + ~ScopedTrace() { + if (!mIsTracing) { + return; + } + + Trace *trace = Trace::getInstance(); + trace->endSection(); + } + +private: + bool mIsTracing = false; +}; + +} // namespace samples { + +#define PASTE_HELPER_HELPER(a, b) a ## b +#define PASTE_HELPER(a, b) PASTE_HELPER_HELPER(a, b) +#define SAMPLES_TRACE_CALL() samples::ScopedTrace PASTE_HELPER(scopedTrace, __LINE__)(__PRETTY_FUNCTION__) diff --git a/samples/common/src/Thread.cpp b/samples/common/src/Thread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f055c782f8293aac5ee31d71399071097fb8c4c9 --- /dev/null +++ b/samples/common/src/Thread.cpp @@ -0,0 +1,71 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Thread.h" + +#include +#include + +namespace samples { + +int32_t getNumCpus() { + static int32_t sNumCpus = []() { + pid_t pid = gettid(); + cpu_set_t cpuSet; + CPU_ZERO(&cpuSet); + sched_getaffinity(pid, sizeof(cpuSet), &cpuSet); + + int32_t numCpus = 0; + while (CPU_ISSET(numCpus, &cpuSet)) { + ++numCpus; + } + + return numCpus; + }(); + + return sNumCpus; +} + +void setAffinity(int32_t cpu) { + cpu_set_t cpuSet; + CPU_ZERO(&cpuSet); + CPU_SET(cpu, &cpuSet); + sched_setaffinity(gettid(), sizeof(cpuSet), &cpuSet); +} + +void setAffinity(Affinity affinity) { + const int32_t numCpus = getNumCpus(); + + cpu_set_t cpuSet; + CPU_ZERO(&cpuSet); + for (int32_t cpu = 0; cpu < numCpus; ++cpu) { + switch (affinity) { + case Affinity::None: + CPU_SET(cpu, &cpuSet); + break; + case Affinity::Even: + if (cpu % 2 == 0) CPU_SET(cpu, &cpuSet); + break; + case Affinity::Odd: + if (cpu % 2 == 1) CPU_SET(cpu, &cpuSet); + break; + } + } + + sched_setaffinity(gettid(), sizeof(cpuSet), &cpuSet); +} + +} // namespace samples { diff --git a/samples/cube/OWNERS b/samples/cube/OWNERS new file mode 100644 index 0000000000000000000000000000000000000000..d79d8611db0f3aa457a98bb43a6c0356ff1beb0f --- /dev/null +++ b/samples/cube/OWNERS @@ -0,0 +1 @@ +include ../../src/swappy/OWNERS diff --git a/samples/cube/build.gradle b/samples/cube/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..4699ea769be186b333ba919c53dbf37cba7a0de1 --- /dev/null +++ b/samples/cube/build.gradle @@ -0,0 +1,24 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.3.2' + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/samples/cube/gradle.properties b/samples/cube/gradle.properties new file mode 100644 index 0000000000000000000000000000000000000000..aac7c9b4614ccfde6c721f24994cf30885a791d0 --- /dev/null +++ b/samples/cube/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/samples/cube/gradlew b/samples/cube/gradlew new file mode 100755 index 0000000000000000000000000000000000000000..9d82f78915133e1c35a6ea51252590fb38efac2f --- /dev/null +++ b/samples/cube/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/samples/cube/gradlew.bat b/samples/cube/gradlew.bat new file mode 100644 index 0000000000000000000000000000000000000000..aec99730b4e8fcd90b57a0e8e01544fea7c31a89 --- /dev/null +++ b/samples/cube/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/cube/settings.gradle b/samples/cube/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..9dde8835a1943bdf31236223c5ca6a6db87f86c6 --- /dev/null +++ b/samples/cube/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +project(':app').projectDir = new File('../../third_party/cube/app') diff --git a/samples/device_info_app/AndroidManifest.xml b/samples/device_info_app/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..3e34183db61d6642e0bd328599a2c417ba6d6b0a --- /dev/null +++ b/samples/device_info_app/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/device_info_app/MainActivity.java b/samples/device_info_app/MainActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..70210dbc83b9d7424291296248ded728db415db9 --- /dev/null +++ b/samples/device_info_app/MainActivity.java @@ -0,0 +1,59 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.deviceinfotest; + +import android.os.Bundle; +import android.app.Activity; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.os.Build; + +import com.google.androidgamesdk.DeviceInfoProto; +import com.google.androidgamesdk.DeviceInfoJni; + +public class MainActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Button sample_button = (Button) findViewById(R.id.sample_button); + sample_button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + TextView tv = (TextView) findViewById(R.id.sample_text); + String msg = "Fingerprint(JAVA):\n" + Build.FINGERPRINT; + try{ + DeviceInfoProto.InfoWithErrors proto; + byte[] nativeBytes = DeviceInfoJni.getProtoSerialized(); + proto = DeviceInfoProto.InfoWithErrors.parseFrom(nativeBytes); + + DeviceInfoProto.Info info = proto.getInfo(); + msg += "\nFingerprint(ro.build.fingerprint):\n" + info.getRoBuildFingerprint(); + msg += "\nro_chipname:\n" + info.getRoChipname(); + msg += "\nro_board_platform:\n" + info.getRoBoardPlatform(); + msg += "\nro_product_board:\n" + info.getRoProductBoard(); + msg += "\nro_mediatek_platform:\n" + info.getRoMediatekPlatform(); + msg += "\nro_arch:\n" + info.getRoArch(); + msg += "\nro_build_version_sdk:\n" + info.getRoBuildVersionSdk(); + }catch(Exception e){ + android.util.Log.e("device_info", "could not create proto.", e); + } + tv.setText(msg); + } + }); + } +} diff --git a/samples/device_info_app/res/drawable-v24/ic_launcher_foreground.xml b/samples/device_info_app/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000000000000000000000000000000000..b1517edf496ef5800b97d046b92012a9f94a34d0 --- /dev/null +++ b/samples/device_info_app/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/samples/device_info_app/res/drawable/ic_launcher_background.xml b/samples/device_info_app/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..bb497f21f70f4f627ec98b38cf4a2c4a3fde36d1 --- /dev/null +++ b/samples/device_info_app/res/drawable/ic_launcher_background.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/device_info_app/res/layout/activity_main.xml b/samples/device_info_app/res/layout/activity_main.xml new file mode 100644 index 0000000000000000000000000000000000000000..59689dce22ec7c9bfdc436b017289d9416d8004f --- /dev/null +++ b/samples/device_info_app/res/layout/activity_main.xml @@ -0,0 +1,26 @@ + + + + + + +