Merge "Adding kseniia@ to Game SDK owners"
diff --git a/.gitignore b/.gitignore
index c4c1c51..6dfd570 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
.DS_Store
-**/.idea
-
+# Gradle outputs or settings:
**/.gradle
**/local.properties
@@ -13,4 +12,10 @@
**/gradle/wrapper/
+# IntelliJ/Android Studio outputs or settings:
+**/.idea
**/*.iml
+**/out
+
+# Protocol Buffers:
+third_party/protobuf-3.0.0/python/dist/
\ No newline at end of file
diff --git a/README b/README
deleted file mode 100644
index 5a8c828..0000000
--- a/README
+++ /dev/null
@@ -1,15 +0,0 @@
-
-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:
-cd gamesdk
-ANDROID_HOME=../prebuilts/sdk ./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).
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2f89017
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+# Android Game SDK
+
+## Build the Game SDK
+
+In order to build using prebuild NDK versions, this project must be initialized from a custom repo using:
+
+```bash
+mkdir android-games-sdk
+cd android-games-sdk
+repo init -u https://android.googlesource.com/platform/manifest -b android-games-sdk
+# Or for Googlers:
+# repo init -u persistent-https://googleplex-android.git.corp.google.com/platform/manifest -b android-games-sdk
+repo sync -c -j8
+```
+
+### Build with prebuilt SDKs
+
+```bash
+cd gamesdk
+ANDROID_HOME=../prebuilts/sdk ./gradlew gamesdkZip
+```
+
+will build static and dynamic libraries for several SDK/NDK pairs.
+
+### Build with locally installed SDK/NDK
+
+By default, the gradle script builds target `archiveZip`.
+
+```bash
+./gradlew archiveZip # Without Tuning Fork
+./gradlew archiveTfZip # With Tuning Fork
+```
+
+This 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`).
+
+## Samples
+
+Samples are classic Android projects, using CMake to build the native code. They are also all triggering the build of the Game SDK.
+
+### Using Grade command line:
+
+```bash
+cd samples/bouncyball && ./gradlew assemble
+cd samples/cube && ./gradlew assemble
+cd samples/tuningfork/tftestapp && ./gradlew assemble
+```
+
+The Android SDK/NDK exposed using environment variables (`ANDROID_HOME`) will be used for building both the sample project and the Game SDK.
+
+### Using Android Studio
+
+Open projects using Android Studio:
+
+* `samples/bouncyball`
+* `samples/cube`
+* `samples/tuningfork/tftestapp`
+
+and run them directly (`Shift + F10` on Linux, `Control + R` on macOS). The local Android SDK/NDK (configured in Android Studio) will be used for building both the sample project and the Game SDK.
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b7979f5..fb7bbc2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -367,6 +367,8 @@
def sdkNativeBuild(withTuningFork = true, buildType="Release") {
def threadChecks = false
return defaultAbis().collectMany {
+ [ buildNativeModules(it, "14", "r16", "c++_static", threadChecks, withTuningFork, buildType) ] +
+ [ buildNativeModules(it, "16", "r17", "c++_static", threadChecks, withTuningFork, buildType) ] +
[ buildNativeModules(it, "26", "r16", "gnustl_static", threadChecks, withTuningFork, buildType) ] +
[ buildNativeModules(it, "28", "r17", "gnustl_static", threadChecks, withTuningFork, buildType) ] +
[ buildNativeModules(it, "28", "r17", "c++_static", threadChecks, withTuningFork, buildType) ]
@@ -379,12 +381,12 @@
return new LocalToolchain(project, kLocalMinSdk)
}
-def localNativeBuild(withTuningFork = false, subdir = "src") {
+def localNativeBuild(withTuningFork = false, subdir = "src", buildType="Release") {
def toolchain = getLocalToolchain();
def stl = "c++_static"
def threadChecks = true
return defaultAbis().collect {
- buildNativeModules(it, toolchain , stl, threadChecks, withTuningFork, subdir)
+ buildNativeModules(it, toolchain , stl, threadChecks, withTuningFork, subdir, buildType)
}
}
@@ -451,7 +453,7 @@
ext.withStaticLibs = true;
ext.withFullBuildKey = false;
ext.buildType = "Release";
- ext.nativeBuild = { tf,bt -> localNativeBuild(tf,bt) }
+ ext.nativeBuild = { tf,bt -> localNativeBuild(tf, "src", bt) }
}
// Build using local SDK, with tuning fork
@@ -464,7 +466,7 @@
ext.withStaticLibs = true;
ext.withFullBuildKey = false;
ext.buildType = "Release";
- ext.nativeBuild = { tf,bt -> localNativeBuild(tf,bt) }
+ ext.nativeBuild = { tf,bt -> localNativeBuild(tf, "src", bt) }
}
tasks.withType(BuildTask) {
diff --git a/include/swappy/swappyGL.h b/include/swappy/swappyGL.h
index 1d4c5d7..b777968 100644
--- a/include/swappy/swappyGL.h
+++ b/include/swappy/swappyGL.h
@@ -30,10 +30,11 @@
#endif
// Internal init function. Do not call directly.
-void SwappyGL_init_internal(JNIEnv *env, jobject jactivity);
+bool SwappyGL_init_internal(JNIEnv *env, jobject jactivity);
// Initialize Swappy, getting the required Android parameters from the display subsystem via JNI
-static inline void SwappyGL_init(JNIEnv *env, jobject jactivity) {
+// Returns false if swappy failed to initialize
+static inline bool SwappyGL_init(JNIEnv *env, jobject jactivity) {
// This call ensures that the header and the linked library are from the same version
// (if not, a linker error will be triggered because of an undefined symbolP).
SWAPPY_VERSION_SYMBOL();
@@ -53,7 +54,6 @@
bool SwappyGL_swap(EGLDisplay display, EGLSurface surface);
// Parameter setters
-void SwappyGL_setRefreshPeriod(uint64_t period_ns);
void SwappyGL_setUseAffinity(bool tf);
void SwappyGL_setSwapIntervalNS(uint64_t swap_ns);
void SwappyGL_setFenceTimeoutNS(uint64_t fence_timeout_ns);
diff --git a/include/swappy/swappyGL_extra.h b/include/swappy/swappyGL_extra.h
index 3f52279..345c19f 100644
--- a/include/swappy/swappyGL_extra.h
+++ b/include/swappy/swappyGL_extra.h
@@ -46,6 +46,11 @@
// dynamically, so the swap interval may change.
void SwappyGL_setAutoSwapInterval(bool enabled);
+// Sets the maximal duration for auto-swap interval in milliseconds.
+// If swappy is operating in auto-swap interval and the frame duration is longer than 'max_swap_ns',
+// Swappy will not do any pacing and just submit the frame as soon as possible.
+void SwappyGL_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns);
+
// 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.
diff --git a/include/swappy/swappyVk.h b/include/swappy/swappyVk.h
index a8f6996..2a11b89 100644
--- a/include/swappy/swappyVk.h
+++ b/include/swappy/swappyVk.h
@@ -241,6 +241,19 @@
void SwappyVk_setAutoPipelineMode(bool enabled);
/**
+ * Sets the maximal swap duration for all instances.
+ *
+ * Sets the maximal duration for Auto-Swap-Interval in milliseconds.
+ * If SwappyVk is operating in Auto-Swap-Interval and the frame duration is longer
+ * than the provided duration, SwappyVk will not do any pacing and just submit the
+ * frame as soon as possible.
+ * Parameters:
+ *
+ * (IN) max_swap_ns - maximal swap duration in milliseconds.
+ */
+void SwappyVk_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns);
+
+/**
* The fence timeout parameter can be set for devices with faulty
* drivers. Its default value is 50,000,000.
*/
diff --git a/include/swappy/swappy_common.h b/include/swappy/swappy_common.h
index da26171..7f83f8b 100644
--- a/include/swappy/swappy_common.h
+++ b/include/swappy/swappy_common.h
@@ -27,7 +27,7 @@
// Internal macros to track Swappy version, do not use directly.
#define SWAPPY_MAJOR_VERSION 0
-#define SWAPPY_MINOR_VERSION 1
+#define SWAPPY_MINOR_VERSION 3
#define SWAPPY_PACKED_VERSION ((SWAPPY_MAJOR_VERSION<<16)|(SWAPPY_MINOR_VERSION))
// Internal macros to generate a symbol to track Swappy version, do not use directly.
diff --git a/samples/bouncyball/app/src/main/cpp/Settings.cpp b/samples/bouncyball/app/src/main/cpp/Settings.cpp
index 76823a1..6314b32 100644
--- a/samples/bouncyball/app/src/main/cpp/Settings.cpp
+++ b/samples/bouncyball/app/src/main/cpp/Settings.cpp
@@ -36,10 +36,8 @@
}
void Settings::setPreference(std::string key, std::string value) {
- if (key == "refresh_period") {
- SwappyGL_setRefreshPeriod(std::stoll(value));
- } else if (key == "swap_interval") {
- SwappyGL_setSwapIntervalNS(std::stoi(value) * 1e6);
+ if (key == "swap_interval") {
+ SwappyGL_setSwapIntervalNS(std::stod(value) * 1e6);
} else if (key == "use_affinity") {
SwappyGL_setUseAffinity(value == "true");
} else if (key == "hot_pocket") {
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
index aa663f5..347c744 100644
--- a/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/OrbitActivity.java
+++ b/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/OrbitActivity.java
@@ -229,8 +229,6 @@
// Initialize the native renderer
nInit();
-
- nSetPreference("refresh_period", String.valueOf(refreshPeriodNanos));
}
private void infoOverlayToggle() {
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
index 5fbab36..70620cf 100644
--- a/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/SettingsFragment.java
+++ b/samples/bouncyball/app/src/main/java/com/prefabulated/bouncyball/SettingsFragment.java
@@ -44,10 +44,9 @@
private ListPreference mSwapIntervalPreference;
private String mSwapIntervalKey;
private Display.Mode mCurrentMode;
- private int mDisplayWidth;
@TargetApi(Build.VERSION_CODES.M)
- private boolean isModeValid(Display.Mode mode) {
+ private boolean modeMatchesCurrentResolution(Display.Mode mode) {
return mode.getPhysicalHeight() == mCurrentMode.getPhysicalHeight() &&
mode.getPhysicalWidth() == mCurrentMode.getPhysicalWidth();
}
@@ -76,11 +75,12 @@
Display.Mode[] supportedModes =
getActivity().getWindowManager().getDefaultDisplay().getSupportedModes();
for (Display.Mode mode : supportedModes) {
- if (isModeValid(mode)) {
- float refreshRate = mode.getRefreshRate();
- for (int interval = 1; refreshRate / interval >= 20; interval++) {
- fpsSet.add((int) refreshRate / interval);
- }
+ if (!modeMatchesCurrentResolution(mode)) {
+ continue;
+ }
+ float refreshRate = mode.getRefreshRate();
+ for (int interval = 1; refreshRate / interval >= 20; interval++) {
+ fpsSet.add((int) refreshRate / interval);
}
}
} else {
diff --git a/samples/bouncyball/build.gradle b/samples/bouncyball/build.gradle
index 4699ea7..15b2a1c 100644
--- a/samples/bouncyball/build.gradle
+++ b/samples/bouncyball/build.gradle
@@ -6,7 +6,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.2'
+ classpath 'com.android.tools.build:gradle:3.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
@@ -21,4 +21,4 @@
task clean(type: Delete) {
delete rootProject.buildDir
-}
+}
\ No newline at end of file
diff --git a/samples/bouncyball/gradle b/samples/bouncyball/gradle
new file mode 120000
index 0000000..1ce6c4c
--- /dev/null
+++ b/samples/bouncyball/gradle
@@ -0,0 +1 @@
+../../gradle
\ No newline at end of file
diff --git a/samples/bouncyball/gradle/wrapper/gradle-wrapper.jar b/samples/bouncyball/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 13372ae..0000000
--- a/samples/bouncyball/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/samples/bouncyball/gradle/wrapper/gradle-wrapper.properties b/samples/bouncyball/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 92f5b28..0000000
--- a/samples/bouncyball/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#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/settings.gradle b/samples/bouncyball/settings.gradle
index a0abcdb..36c073e 100644
--- a/samples/bouncyball/settings.gradle
+++ b/samples/bouncyball/settings.gradle
@@ -1,3 +1,3 @@
include ':app'
include ':extras'
-project(':extras').projectDir = new File('../../src/extras')
+project(':extras').projectDir = new File('../../src/extras')
\ No newline at end of file
diff --git a/samples/cube/build.gradle b/samples/cube/build.gradle
index 4699ea7..51b175b 100644
--- a/samples/cube/build.gradle
+++ b/samples/cube/build.gradle
@@ -6,7 +6,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.2'
+ classpath 'com.android.tools.build:gradle:3.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
diff --git a/samples/cube/gradle b/samples/cube/gradle
new file mode 120000
index 0000000..1ce6c4c
--- /dev/null
+++ b/samples/cube/gradle
@@ -0,0 +1 @@
+../../gradle
\ No newline at end of file
diff --git a/samples/cube/settings.gradle b/samples/cube/settings.gradle
index 9dde883..7e6eddb 100644
--- a/samples/cube/settings.gradle
+++ b/samples/cube/settings.gradle
@@ -1,2 +1,4 @@
include ':app'
project(':app').projectDir = new File('../../third_party/cube/app')
+include ':extras'
+project(':extras').projectDir = new File('../../src/extras')
diff --git a/samples/gamesdk.cmake b/samples/gamesdk.cmake
index 7ea6740..ffdfbd9 100644
--- a/samples/gamesdk.cmake
+++ b/samples/gamesdk.cmake
@@ -2,8 +2,9 @@
# This function will create a static library target called 'gamesdk'.
# The location of the library is set according to your ANDROID_NDK_REVISION
-# and ANDROID_PLATFORM, unless you explicitly set GAMESDK_NDK_VERSION
+# and ANDROID_PLATFORM, unless you explicitly set GAMESDK_NDK_VERSION,
# GAMESDK_ANDROID_SDK_VERSION or pass these as 4th and 5th arguments.
+#
# Optional arguments, in order:
# PACKAGE_DIR: where the packaged version of the library is, relative to the gamesdk root dir
# default value: package/localtf
@@ -55,8 +56,11 @@
if (NOT DEFINED GAMESDK_ANDROID_SDK_VERSION)
string(REGEX REPLACE "^android-([^.]+)" "\\1" GAMESDK_ANDROID_SDK_VERSION ${ANDROID_PLATFORM} )
endif()
- set(GAMESDK_PACKAGE_DIR "${_MY_DIR}/../../${GAMESDK_PACKAGE_DIR}")
- set(BUILD_NAME ${ANDROID_ABI}_SDK${GAMESDK_ANDROID_SDK_VERSION}_NDK${GAMESDK_NDK_VERSION}_${ANDROID_STL})
+ string(REPLACE "+" "p" GAMESDK_ANDROID_STL ${ANDROID_STL}) # Game SDK build names use a sanitized STL name (c++ => cpp)
+
+ set(GAMESDK_ROOT_DIR "${_MY_DIR}/..")
+ set(GAMESDK_PACKAGE_DIR "${GAMESDK_ROOT_DIR}/../${GAMESDK_PACKAGE_DIR}")
+ set(BUILD_NAME ${ANDROID_ABI}_SDK${GAMESDK_ANDROID_SDK_VERSION}_NDK${GAMESDK_NDK_VERSION}_${GAMESDK_ANDROID_STL})
set(GAMESDK_LIB_DIR "${GAMESDK_PACKAGE_DIR}/libs/${BUILD_NAME}")
include_directories( "${GAMESDK_PACKAGE_DIR}/include" ) # Games SDK Public Includes
@@ -66,11 +70,27 @@
set(GAMESDK_LIB ${DEP_LIB} PARENT_SCOPE)
endif()
- add_library( gamesdk STATIC IMPORTED GLOBAL)
+ # If building from a project containing local.properties, generated by Android Studio with
+ # the local Android SDK and NDK paths, copy it to gamesdk to allow it to build with the local
+ # toolchain.
+ if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../local.properties")
+ file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../local.properties
+ DESTINATION ${GAMESDK_ROOT_DIR})
+ endif()
+
+ add_library(gamesdk STATIC IMPORTED GLOBAL)
if(GAMESDK_DO_BUILD)
- add_custom_command(OUTPUT ${DEP_LIB}
- COMMAND ./gradlew ${GAMESDK_GEN_TASK} -PGAMESDK_ANDROID_SDK_VERSION=${GAMESDK_ANDROID_SDK_VERSION} VERBATIM
- WORKING_DIRECTORY "${_MY_DIR}/.." )
+ # Build Game SDK (Gradle will use local.properties to find the Android SDK/NDK,
+ # or the environment variables if no local.properties - i.e: if compiling from command line).
+ add_custom_command(
+ OUTPUT
+ ${DEP_LIB}
+ COMMAND
+ ./gradlew ${GAMESDK_GEN_TASK} -PGAMESDK_ANDROID_SDK_VERSION=${GAMESDK_ANDROID_SDK_VERSION}
+ VERBATIM
+ WORKING_DIRECTORY
+ "${GAMESDK_ROOT_DIR}"
+ )
add_custom_target(gamesdk_lib DEPENDS ${DEP_LIB})
add_dependencies(gamesdk gamesdk_lib)
endif()
diff --git a/samples/tuningfork/tftestapp/app/build.gradle b/samples/tuningfork/tftestapp/app/build.gradle
index 39f5e58..8c1711c 100644
--- a/samples/tuningfork/tftestapp/app/build.gradle
+++ b/samples/tuningfork/tftestapp/app/build.gradle
@@ -35,6 +35,8 @@
implementation "com.google.android.gms:play-services-clearcut:16.0.0"
testImplementation 'junit:junit:4.12'
+
+ implementation project(':extras')
}
task createJar(type: GradleBuild) {
diff --git a/samples/tuningfork/tftestapp/build.gradle b/samples/tuningfork/tftestapp/build.gradle
index 798ee14..df75d6a 100644
--- a/samples/tuningfork/tftestapp/build.gradle
+++ b/samples/tuningfork/tftestapp/build.gradle
@@ -7,7 +7,7 @@
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.2'
+ classpath 'com.android.tools.build:gradle:3.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/samples/tuningfork/tftestapp/gradle b/samples/tuningfork/tftestapp/gradle
new file mode 120000
index 0000000..84b694e
--- /dev/null
+++ b/samples/tuningfork/tftestapp/gradle
@@ -0,0 +1 @@
+../../../gradle
\ No newline at end of file
diff --git a/samples/tuningfork/tftestapp/settings.gradle b/samples/tuningfork/tftestapp/settings.gradle
index e7b4def..752f3ad 100644
--- a/samples/tuningfork/tftestapp/settings.gradle
+++ b/samples/tuningfork/tftestapp/settings.gradle
@@ -1 +1,3 @@
include ':app'
+include ':extras'
+project(':extras').projectDir = new File('../../../src/extras')
diff --git a/src/common/Trace.h b/src/common/Trace.h
index 78d6f70..1b0c460 100644
--- a/src/common/Trace.h
+++ b/src/common/Trace.h
@@ -100,7 +100,7 @@
}
void setCounter(const char *name, int64_t value) {
- if (!ATrace_endSection || !isEnabled()) {
+ if (!ATrace_setCounter || !isEnabled()) {
return;
}
@@ -149,3 +149,4 @@
#define PASTE_HELPER(a, b) PASTE_HELPER_HELPER(a, b)
#define TRACE_CALL() gamesdk::ScopedTrace PASTE_HELPER(scopedTrace, __LINE__)(__PRETTY_FUNCTION__)
#define TRACE_INT(name, value) gamesdk::Trace::getInstance()->setCounter(name, value)
+#define TRACE_ENABLED() gamesdk::Trace::getInstance()->isEnabled()
diff --git a/src/extras/src/main/java/com/google/androidgamesdk/SwappyDisplayManager.java b/src/extras/src/main/java/com/google/androidgamesdk/SwappyDisplayManager.java
new file mode 100644
index 0000000..94dba72
--- /dev/null
+++ b/src/extras/src/main/java/com/google/androidgamesdk/SwappyDisplayManager.java
@@ -0,0 +1,161 @@
+package com.google.androidgamesdk;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.os.Build;
+import android.util.Log;
+import android.view.Display;
+import android.view.Window;
+import android.view.WindowManager;
+
+import static android.app.NativeActivity.META_DATA_LIB_NAME;
+
+public class SwappyDisplayManager implements DisplayManager.DisplayListener {
+ final private String LOG_TAG = "SwappyDisplayManager";
+ final private boolean DEBUG = false;
+ final private long ONE_MS_IN_NS = 1000000;
+ final private long ONE_S_IN_NS = ONE_MS_IN_NS * 1000;
+
+ private long mCookie;
+ private Activity mActivity;
+ private WindowManager mWindowManager;
+ private Display.Mode mCurrentMode;
+
+ @TargetApi(Build.VERSION_CODES.M)
+ private boolean modeMatchesCurrentResolution(Display.Mode mode) {
+ return mode.getPhysicalHeight() == mCurrentMode.getPhysicalHeight() &&
+ mode.getPhysicalWidth() == mCurrentMode.getPhysicalWidth();
+
+ }
+
+ public SwappyDisplayManager(long cookie, Activity activity) {
+ // Load the native library for cases where an NDK application is running
+ // without a java componenet
+ try {
+ ActivityInfo ai = activity.getPackageManager().getActivityInfo(
+ activity.getIntent().getComponent(), PackageManager.GET_META_DATA);
+ if (ai.metaData != null) {
+ String nativeLibName = ai.metaData.getString(META_DATA_LIB_NAME);
+ if (nativeLibName != null) {
+ System.loadLibrary(nativeLibName);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, e.getMessage());
+ }
+
+ mCookie = cookie;
+ mActivity = activity;
+
+ mWindowManager = mActivity.getSystemService(WindowManager.class);
+ Display display = mWindowManager.getDefaultDisplay();
+ mCurrentMode = display.getMode();
+ updateSupportedRefreshRates(display);
+
+ // Register display listener callbacks
+ DisplayManager dm = mActivity.getSystemService(DisplayManager.class);
+
+ synchronized(this) {
+ dm.registerDisplayListener(this, null);
+ }
+ }
+
+ private void updateSupportedRefreshRates(Display display) {
+ Display.Mode[] supportedModes = display.getSupportedModes();
+ int totalModes = 0;
+ for (int i = 0; i < supportedModes.length; i++) {
+ if (!modeMatchesCurrentResolution(supportedModes[i])) {
+ continue;
+ }
+ totalModes++;
+ }
+
+ long[] supportedRefreshRates = new long[totalModes];
+ int[] supportedRefreshRatesIds = new int[totalModes];
+ totalModes = 0;
+ for (int i = 0; i < supportedModes.length; i++) {
+ if (!modeMatchesCurrentResolution(supportedModes[i])) {
+ continue;
+ }
+ supportedRefreshRates[totalModes] =
+ (long) (ONE_S_IN_NS / supportedModes[i].getRefreshRate());
+ supportedRefreshRatesIds[totalModes] = supportedModes[i].getModeId();
+ totalModes++;
+
+ }
+ // Call down to native to set the supported refresh rates
+ nSetSupportedRefreshRates(mCookie, supportedRefreshRates, supportedRefreshRatesIds);
+ }
+
+ public void setPreferredRefreshRate(final int modeId) {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Window w = mActivity.getWindow();
+ WindowManager.LayoutParams l = w.getAttributes();
+ if (DEBUG) {
+ Log.v(LOG_TAG, "set preferredDisplayModeId to " + modeId);
+ }
+ l.preferredDisplayModeId = modeId;
+
+
+ w.setAttributes(l);
+ }
+ });
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ synchronized(this) {
+ Display display = mWindowManager.getDefaultDisplay();
+ float newRefreshRate = display.getRefreshRate();
+ Display.Mode newMode = display.getMode();
+ boolean resolutionChanged =
+ (newMode.getPhysicalWidth() != mCurrentMode.getPhysicalWidth()) |
+ (newMode.getPhysicalHeight() != mCurrentMode.getPhysicalHeight());
+ boolean refreshRateChanged = (newRefreshRate != mCurrentMode.getRefreshRate());
+ mCurrentMode = newMode;
+
+ if (resolutionChanged) {
+ updateSupportedRefreshRates(display);
+ }
+
+ if (refreshRateChanged) {
+ final long appVsyncOffsetNanos = display.getAppVsyncOffsetNanos();
+ final long vsyncPresentationDeadlineNanos =
+ mWindowManager.getDefaultDisplay().getPresentationDeadlineNanos();
+
+ final long vsyncPeriodNanos = (long)(ONE_S_IN_NS / newRefreshRate);
+ final long sfVsyncOffsetNanos =
+ vsyncPeriodNanos - (vsyncPresentationDeadlineNanos - ONE_MS_IN_NS);
+
+ nOnRefreshRateChanged(mCookie,
+ vsyncPeriodNanos,
+ appVsyncOffsetNanos,
+ sfVsyncOffsetNanos);
+ }
+ }
+ }
+
+ private native void nSetSupportedRefreshRates(long cookie,
+ long[] refreshRates,
+ int[] modeIds);
+ private native void nOnRefreshRateChanged(long cookie,
+ long refreshPeriod,
+ long appOffset,
+ long sfOffset);
+}
diff --git a/src/swappy/CMakeLists.txt b/src/swappy/CMakeLists.txt
index 958eb79..367ced1 100644
--- a/src/swappy/CMakeLists.txt
+++ b/src/swappy/CMakeLists.txt
@@ -38,6 +38,8 @@
${SOURCE_LOCATION_COMMON}/Thread.cpp
${SOURCE_LOCATION_COMMON}/SwappyCommon.cpp
${SOURCE_LOCATION_COMMON}/swappy_c.cpp
+ ${SOURCE_LOCATION_COMMON}/SwappyDisplayManager.cpp
+ ${SOURCE_LOCATION_COMMON}/CPUTracer.cpp
${SOURCE_LOCATION_OPENGL}/EGL.cpp
${SOURCE_LOCATION_OPENGL}/swappyGL_c.cpp
${SOURCE_LOCATION_OPENGL}/SwappyGL.cpp
diff --git a/src/swappy/common/CPUTracer.cpp b/src/swappy/common/CPUTracer.cpp
new file mode 100644
index 0000000..bffd5b8
--- /dev/null
+++ b/src/swappy/common/CPUTracer.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+
+#include "CPUTracer.h"
+#include "../../common/Trace.h"
+#include "../../common/Log.h"
+
+namespace swappy {
+
+CPUTracer::CPUTracer() {}
+
+CPUTracer::~CPUTracer() {
+ joinThread();
+}
+
+void CPUTracer::joinThread() {
+ bool join = false;
+ if (mThread && mThread->joinable()) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mTrace = false;
+ mRunning = false;
+ mCond.notify_one();
+ join = true;
+ }
+ if (join) {
+ mThread->join();
+ }
+ mThread.reset();
+}
+
+void CPUTracer::startTrace() {
+ if (TRACE_ENABLED()) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (!mThread) {
+ mRunning = true;
+ mThread = std::make_unique<std::thread>(&CPUTracer::threadMain, this);
+ }
+ mTrace = true;
+ mCond.notify_one();
+ } else {
+ joinThread();
+ }
+}
+
+void CPUTracer::endTrace() {
+ if (TRACE_ENABLED()) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mTrace = false;
+ mCond.notify_one();
+ } else {
+ joinThread();
+ }
+}
+
+void CPUTracer::threadMain() NO_THREAD_SAFETY_ANALYSIS {
+ std::unique_lock<std::mutex> lock(mMutex);
+ while (mRunning) {
+ if (mTrace) {
+ gamesdk::ScopedTrace trace("Swappy: CPU frame time");
+ mCond.wait(lock);
+ } else {
+ mCond.wait(lock);
+ }
+ }
+}
+
+} // namespace swappy
\ No newline at end of file
diff --git a/src/swappy/common/CPUTracer.h b/src/swappy/common/CPUTracer.h
new file mode 100644
index 0000000..99d1e98
--- /dev/null
+++ b/src/swappy/common/CPUTracer.h
@@ -0,0 +1,48 @@
+/*
+ * 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 <condition_variable>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include "Thread.h"
+
+namespace swappy {
+
+class CPUTracer {
+public:
+ CPUTracer();
+ ~CPUTracer();
+
+ CPUTracer(CPUTracer&) = delete;
+
+ void startTrace();
+ void endTrace();
+
+private:
+ void threadMain();
+ void joinThread();
+
+ std::mutex mMutex;
+ std::condition_variable_any mCond GUARDED_BY(mMutex);
+ std::unique_ptr<std::thread> mThread;
+ bool mRunning GUARDED_BY(mMutex) = true;
+ bool mTrace GUARDED_BY(mMutex) = false;
+};
+
+} // namespace swappy
\ No newline at end of file
diff --git a/src/swappy/common/ChoreographerFilter.cpp b/src/swappy/common/ChoreographerFilter.cpp
index f10f639..d15a78f 100644
--- a/src/swappy/common/ChoreographerFilter.cpp
+++ b/src/swappy/common/ChoreographerFilter.cpp
@@ -68,7 +68,8 @@
}
// TODO: 0.2 weighting factor for exponential smoothing is completely arbitrary
- mBaseTime += mRefreshPeriod + delta * 2 / 10;
+ mRefreshPeriod += delta * 2 / 10;
+ mBaseTime += mRefreshPeriod;
return true;
}
@@ -88,7 +89,7 @@
}
private:
- const std::chrono::nanoseconds mRefreshPeriod;
+ std::chrono::nanoseconds mRefreshPeriod;
const std::chrono::nanoseconds mAppToSfDelay;
time_point mBaseTime = std::chrono::steady_clock::now();
@@ -119,7 +120,7 @@
}
void ChoreographerFilter::onChoreographer() {
- std::unique_lock<std::mutex> lock(mMutex);
+ std::lock_guard<std::mutex> lock(mMutex);
mLastTimestamp = std::chrono::steady_clock::now();
++mSequenceNumber;
mCondition.notify_all();
@@ -152,13 +153,20 @@
void ChoreographerFilter::onSettingsChanged() {
const bool useAffinity = Settings::getInstance()->getUseAffinity();
+ const Settings::DisplayTimings& displayTimings = Settings::getInstance()->getDisplayTimings();
std::lock_guard<std::mutex> lock(mThreadPoolMutex);
- if (useAffinity == mUseAffinity) {
+ if (useAffinity == mUseAffinity && mRefreshPeriod == displayTimings.refreshPeriod) {
return;
}
terminateThreadsLocked();
mUseAffinity = useAffinity;
+ mRefreshPeriod = displayTimings.refreshPeriod;
+ mAppToSfDelay = displayTimings.sfOffset - displayTimings.appOffset;
+ ALOGV("onSettingsChanged(): refreshPeriod=%lld, appOffset=%lld, sfOffset=%lld",
+ (long long)displayTimings.refreshPeriod.count(),
+ (long long)displayTimings.appOffset.count(),
+ (long long)displayTimings.sfOffset.count());
launchThreadsLocked();
}
diff --git a/src/swappy/common/ChoreographerFilter.h b/src/swappy/common/ChoreographerFilter.h
index 4f6c892..3d95937 100644
--- a/src/swappy/common/ChoreographerFilter.h
+++ b/src/swappy/common/ChoreographerFilter.h
@@ -21,6 +21,7 @@
#include <vector>
#include <mutex>
#include <condition_variable>
+#include "Settings.h"
namespace swappy {
@@ -57,8 +58,8 @@
std::chrono::steady_clock::time_point mLastWorkRun;
std::chrono::nanoseconds mWorkDuration;
- const std::chrono::nanoseconds mRefreshPeriod;
- const std::chrono::nanoseconds mAppToSfDelay;
+ std::chrono::nanoseconds mRefreshPeriod;
+ std::chrono::nanoseconds mAppToSfDelay;
const Worker mDoWork;
};
diff --git a/src/swappy/common/ChoreographerThread.cpp b/src/swappy/common/ChoreographerThread.cpp
index a8f3ede..e413cfb 100644
--- a/src/swappy/common/ChoreographerThread.cpp
+++ b/src/swappy/common/ChoreographerThread.cpp
@@ -54,6 +54,8 @@
class NDKChoreographerThread : public ChoreographerThread {
public:
+ static constexpr int MIN_SDK_VERSION = 24;
+
NDKChoreographerThread(Callback onChoreographer);
~NDKChoreographerThread() override;
@@ -78,7 +80,7 @@
mLibAndroid = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
if (mLibAndroid == nullptr) {
ALOGE("FATAL: cannot open libandroid.so: %s", strerror(errno));
- abort();
+ return;
}
mAChoreographer_getInstance =
@@ -97,7 +99,7 @@
!mAChoreographer_postFrameCallback ||
!mAChoreographer_postFrameCallbackDelayed) {
ALOGE("FATAL: cannot get AChoreographer symbols");
- abort();
+ return;
}
std::unique_lock<std::mutex> lock(mWaitingMutex);
@@ -107,6 +109,8 @@
mWaitingCondition.wait(lock, [&]() REQUIRES(mWaitingMutex) {
return mChoreographer != nullptr;
});
+
+ mInitialized = true;
}
NDKChoreographerThread::~NDKChoreographerThread()
@@ -238,6 +242,8 @@
env->NewObject(choreographerCallbackClass, constructor, reinterpret_cast<jlong>(this));
mJobj = env->NewGlobalRef(choreographerCallback);
+
+ mInitialized = true;
}
JavaChoreographerThread::~JavaChoreographerThread()
@@ -289,7 +295,9 @@
};
NoChoreographerThread::NoChoreographerThread(Callback onChoreographer) :
- ChoreographerThread(onChoreographer) {}
+ ChoreographerThread(onChoreographer) {
+ mInitialized = true;
+}
void NoChoreographerThread::postFrameCallbacks() {
@@ -332,36 +340,6 @@
mCallback();
}
-int ChoreographerThread::getSDKVersion(JavaVM *vm)
-{
- JNIEnv *env;
- vm->AttachCurrentThread(&env, nullptr);
-
- const jclass buildClass = env->FindClass("android/os/Build$VERSION");
- if (env->ExceptionCheck()) {
- env->ExceptionClear();
- ALOGE("Failed to get Build.VERSION class");
- return 0;
- }
-
- const jfieldID sdk_int = env->GetStaticFieldID(buildClass, "SDK_INT", "I");
- if (env->ExceptionCheck()) {
- env->ExceptionClear();
- ALOGE("Failed to get Build.VERSION.SDK_INT field");
- return 0;
- }
-
- const jint sdk = env->GetStaticIntField(buildClass, sdk_int);
- if (env->ExceptionCheck()) {
- env->ExceptionClear();
- ALOGE("Failed to get SDK version");
- return 0;
- }
-
- ALOGI("SDK version = %d", sdk);
- return sdk;
-}
-
bool ChoreographerThread::isChoreographerCallbackClassLoaded(JavaVM *vm)
{
JNIEnv *env;
@@ -378,13 +356,13 @@
std::unique_ptr<ChoreographerThread>
ChoreographerThread::createChoreographerThread(
- Type type, JavaVM *vm, Callback onChoreographer) {
+ Type type, JavaVM *vm, Callback onChoreographer, int sdkVersion) {
if (type == Type::App) {
ALOGI("Using Application's Choreographer");
return std::make_unique<NoChoreographerThread>(onChoreographer);
}
- if (vm == nullptr || getSDKVersion(vm) >= 24) {
+ if (vm == nullptr || sdkVersion >= NDKChoreographerThread::MIN_SDK_VERSION) {
ALOGI("Using NDK Choreographer");
return std::make_unique<NDKChoreographerThread>(onChoreographer);
}
diff --git a/src/swappy/common/ChoreographerThread.h b/src/swappy/common/ChoreographerThread.h
index d06fb08..cf4383d 100644
--- a/src/swappy/common/ChoreographerThread.h
+++ b/src/swappy/common/ChoreographerThread.h
@@ -37,12 +37,14 @@
using Callback = std::function<void()>;
static std::unique_ptr<ChoreographerThread> createChoreographerThread(
- Type type, JavaVM *vm, Callback onChoreographer);
+ Type type, JavaVM *vm, Callback onChoreographer, int sdkVersion);
virtual ~ChoreographerThread() = 0;
virtual void postFrameCallbacks();
+ bool isInitialized() { return mInitialized; }
+
protected:
ChoreographerThread(Callback onChoreographer);
virtual void scheduleNextFrameCallback() REQUIRES(mWaitingMutex) = 0;
@@ -51,11 +53,11 @@
std::mutex mWaitingMutex;
int mCallbacksBeforeIdle GUARDED_BY(mWaitingMutex) = 0;
Callback mCallback;
+ bool mInitialized = false;
static constexpr int MAX_CALLBACKS_BEFORE_IDLE = 10;
private:
- static int getSDKVersion(JavaVM *vm);
static bool isChoreographerCallbackClassLoaded(JavaVM *vm);
};
diff --git a/src/swappy/common/FrameStatistics.cpp b/src/swappy/common/FrameStatistics.cpp
index c3ea474..1017c9d 100644
--- a/src/swappy/common/FrameStatistics.cpp
+++ b/src/swappy/common/FrameStatistics.cpp
@@ -35,7 +35,7 @@
void FrameStatistics::updateFrames(EGLnsecsANDROID start, EGLnsecsANDROID end, uint64_t stat[]) {
const uint64_t deltaTimeNano = end - start;
- uint32_t numFrames = deltaTimeNano / mRefreshPeriod.count();
+ uint32_t numFrames = deltaTimeNano / mSwappyCommon.getRefreshPeriod().count();
numFrames = std::min(numFrames, static_cast<uint32_t>(MAX_FRAME_BUCKETS));
stat[numFrames]++;
}
@@ -73,7 +73,7 @@
const TimePoint frameStartTime = std::chrono::steady_clock::now();
// first get the next frame id
- std::pair<bool,EGLuint64KHR> nextFrameId = mEgl->getNextFrameId(dpy, surface);
+ std::pair<bool,EGLuint64KHR> nextFrameId = mEgl.getNextFrameId(dpy, surface);
if (nextFrameId.first) {
mPendingFrames.push_back({dpy, surface, nextFrameId.second, frameStartTime});
}
@@ -93,7 +93,7 @@
}
std::unique_ptr<EGL::FrameTimestamps> frameStats =
- mEgl->getFrameTimestamps(frame.dpy, frame.surface, frame.id);
+ mEgl.getFrameTimestamps(frame.dpy, frame.surface, frame.id);
if (!frameStats) {
return;
diff --git a/src/swappy/common/FrameStatistics.h b/src/swappy/common/FrameStatistics.h
index 7c5e996..e8f1b05 100644
--- a/src/swappy/common/FrameStatistics.h
+++ b/src/swappy/common/FrameStatistics.h
@@ -17,6 +17,7 @@
#pragma once
#include "EGL.h"
+#include "SwappyCommon.h"
#include "Thread.h"
#include <array>
@@ -33,9 +34,8 @@
class FrameStatistics {
public:
- explicit FrameStatistics(std::shared_ptr<EGL> egl,
- std::chrono::nanoseconds refreshPeriod) :
- mEgl(egl), mRefreshPeriod(refreshPeriod) {};
+ FrameStatistics(const EGL& egl, const SwappyCommon& swappyCommon)
+ : mEgl(egl), mSwappyCommon(swappyCommon) {};
~FrameStatistics() = default;
void capture(EGLDisplay dpy, EGLSurface surface);
@@ -54,8 +54,8 @@
TimePoint frameStartTime) REQUIRES(mMutex);
void logFrames() REQUIRES(mMutex);
- std::shared_ptr<EGL> mEgl;
- const std::chrono::nanoseconds mRefreshPeriod;
+ const EGL& mEgl;
+ const SwappyCommon& mSwappyCommon;
struct EGLFrame {
EGLDisplay dpy;
diff --git a/src/swappy/common/Settings.cpp b/src/swappy/common/Settings.cpp
index 4c5a7ee..7f0f36b 100644
--- a/src/swappy/common/Settings.cpp
+++ b/src/swappy/common/Settings.cpp
@@ -40,10 +40,10 @@
mListeners.emplace_back(std::move(listener));
}
-void Settings::setRefreshPeriod(std::chrono::nanoseconds period) {
+void Settings::setDisplayTimings(const DisplayTimings& displayTimings) {
{
std::lock_guard<std::mutex> lock(mMutex);
- mRefreshPeriod = period;
+ mDisplayTimings = displayTimings;
}
// Notify the listeners without the lock held
notifyListeners();
@@ -66,10 +66,9 @@
notifyListeners();
}
-
-std::chrono::nanoseconds Settings::getRefreshPeriod() const {
+const Settings::DisplayTimings& Settings::getDisplayTimings() const {
std::lock_guard<std::mutex> lock(mMutex);
- return mRefreshPeriod;
+ return mDisplayTimings;
}
uint64_t Settings::getSwapIntervalNS() const {
diff --git a/src/swappy/common/Settings.h b/src/swappy/common/Settings.h
index 857fe53..84a39ea 100644
--- a/src/swappy/common/Settings.h
+++ b/src/swappy/common/Settings.h
@@ -34,6 +34,12 @@
struct ConstructorTag {
};
public:
+ struct DisplayTimings {
+ std::chrono::nanoseconds refreshPeriod{0};
+ std::chrono::nanoseconds appOffset{0};
+ std::chrono::nanoseconds sfOffset{0};
+ };
+
explicit Settings(ConstructorTag) {};
static Settings *getInstance();
@@ -43,11 +49,11 @@
using Listener = std::function<void()>;
void addListener(Listener listener);
- void setRefreshPeriod(std::chrono::nanoseconds period);
+ void setDisplayTimings(const DisplayTimings& displayTimings);
void setSwapIntervalNS(uint64_t swap_ns);
void setUseAffinity(bool);
- std::chrono::nanoseconds getRefreshPeriod() const;
+ const DisplayTimings& getDisplayTimings() const;
uint64_t getSwapIntervalNS() const;
bool getUseAffinity() const;
@@ -59,8 +65,7 @@
mutable std::mutex mMutex;
std::vector<Listener> mListeners GUARDED_BY(mMutex);
- std::chrono::nanoseconds
- mRefreshPeriod GUARDED_BY(mMutex) = std::chrono::nanoseconds{12'345'678};
+ DisplayTimings mDisplayTimings GUARDED_BY(mMutex);
uint64_t mSwapIntervalNS GUARDED_BY(mMutex) = 16666667L;
bool mUseAffinity GUARDED_BY(mMutex) = true;
};
diff --git a/src/swappy/common/SwappyCommon.cpp b/src/swappy/common/SwappyCommon.cpp
index a9a4954..cdde397 100644
--- a/src/swappy/common/SwappyCommon.cpp
+++ b/src/swappy/common/SwappyCommon.cpp
@@ -33,12 +33,14 @@
using std::chrono::nanoseconds;
// NB These are only needed for C++14
-constexpr std::chrono::nanoseconds SwappyCommon::FrameDuration::MAX_DURATION;
-constexpr std::chrono::nanoseconds SwappyCommon::FRAME_HYSTERESIS;
+constexpr nanoseconds SwappyCommon::FrameDuration::MAX_DURATION;
+constexpr nanoseconds SwappyCommon::FRAME_MARGIN;
+constexpr nanoseconds SwappyCommon::EDGE_HYSTERESIS;
+constexpr nanoseconds SwappyCommon::REFRESH_RATE_MARGIN;
SwappyCommon::SwappyCommon(JNIEnv *env, jobject jactivity)
- : mSwapDuration(std::chrono::nanoseconds(0)),
- mSwapInterval(1),
+ : mSdkVersion(getSDKVersion(env)),
+ mSwapDuration(nanoseconds(0)),
mAutoSwapInterval(1),
mValid(false) {
jclass activityClass = env->FindClass("android/app/NativeActivity");
@@ -103,36 +105,56 @@
env->GetJavaVM(&vm);
using std::chrono::nanoseconds;
- mRefreshPeriod = nanoseconds(vsyncPeriodNanos);
- mAppVsyncOffset = nanoseconds(appVsyncOffsetNanos);
- mSfVsyncOffset = nanoseconds(sfVsyncOffsetNanos);
+ mRefreshPeriod = nanoseconds(vsyncPeriodNanos);
+ nanoseconds appVsyncOffset = nanoseconds(appVsyncOffsetNanos);
+ nanoseconds sfVsyncOffset = nanoseconds(sfVsyncOffsetNanos);
mChoreographerFilter = std::make_unique<ChoreographerFilter>(mRefreshPeriod,
- mSfVsyncOffset - mAppVsyncOffset,
+ sfVsyncOffset - appVsyncOffset,
[this]() { return wakeClient(); });
- mChoreographerThread = ChoreographerThread::createChoreographerThread(
+ mChoreographerThread = ChoreographerThread::createChoreographerThread(
ChoreographerThread::Type::Swappy,
vm,
- [this]{ mChoreographerFilter->onChoreographer(); });
+ [this]{ mChoreographerFilter->onChoreographer(); },
+ mSdkVersion);
+ if (!mChoreographerThread->isInitialized()) {
+ ALOGE("failed to initialize ChoreographerThread");
+ return;
+ }
+
+ if (USE_DISPLAY_MANAGER && mSdkVersion >= SwappyDisplayManager::MIN_SDK_VERSION) {
+ mDisplayManager = std::make_unique<SwappyDisplayManager>(vm, jactivity);
+ if (!mDisplayManager->isInitialized()) {
+ ALOGE("failed to initialize DisplayManager");
+ return;
+ }
+ }
Settings::getInstance()->addListener([this]() { onSettingsChanged(); });
+ Settings::getInstance()->setDisplayTimings({mRefreshPeriod, appVsyncOffset, sfVsyncOffset});
- mAutoSwapIntervalThreshold = (1e9f / mRefreshPeriod.count()) / 20; // 20FPS
+ std::lock_guard<std::mutex> lock(mFrameDurationsMutex);
mFrameDurations.reserve(mFrameDurationSamples);
+ ALOGI("Initialized Swappy with vsyncPeriod=%lld, appOffset=%lld, sfOffset=%lld",
+ (long long)vsyncPeriodNanos,
+ (long long)appVsyncOffset.count(),
+ (long long)sfVsyncOffset.count()
+ );
+
mValid = true;
}
SwappyCommon::~SwappyCommon() {
// destroy all threads first before the other members of this class
- mChoreographerFilter.reset();
mChoreographerThread.reset();
+ mChoreographerFilter.reset();
Settings::reset();
}
-std::chrono::nanoseconds SwappyCommon::wakeClient() {
+nanoseconds SwappyCommon::wakeClient() {
std::lock_guard<std::mutex> lock(mWaitingMutex);
++mCurrentFrame;
@@ -153,7 +175,8 @@
ChoreographerThread::createChoreographerThread(
ChoreographerThread::Type::App,
nullptr,
- [this] { mChoreographerFilter->onChoreographer(); });
+ [this] { mChoreographerFilter->onChoreographer(); },
+ mSdkVersion);
}
mChoreographerThread->postFrameCallbacks();
@@ -163,13 +186,14 @@
int lateFrames = 0;
bool presentationTimeIsNeeded;
- const std::chrono::nanoseconds cpuTime = std::chrono::steady_clock::now() - mStartFrameTime;
+ const nanoseconds cpuTime = std::chrono::steady_clock::now() - mStartFrameTime;
+ mCPUTracer.endTrace();
preWaitCallbacks();
// if we are running slower than the threshold there is no point to sleep, just let the
// app run as fast as it can
- if (mAutoSwapInterval <= mAutoSwapIntervalThreshold) {
+ if (mRefreshPeriod * mAutoSwapInterval <= mAutoSwapIntervalThresholdNS.load()) {
waitUntilTargetFrame();
// wait for the previous frame to be rendered
@@ -184,13 +208,62 @@
presentationTimeIsNeeded = false;
}
- const std::chrono::nanoseconds gpuTime = h.getPrevFrameGpuTime();
+ const nanoseconds gpuTime = h.getPrevFrameGpuTime();
addFrameDuration({cpuTime, gpuTime});
postWaitCallbacks();
return presentationTimeIsNeeded;
}
+void SwappyCommon::updateDisplayTimings() {
+ // grab a pointer to the latest supported refresh rates
+ if (mDisplayManager) {
+ mSupportedRefreshRates = mDisplayManager->getSupportedRefreshRates();
+ }
+
+ std::lock_guard<std::mutex> lock(mFrameDurationsMutex);
+ if (!mTimingSettingsNeedUpdate) {
+ return;
+ }
+
+ mTimingSettingsNeedUpdate = false;
+
+ if (mRefreshPeriod == mNextTimingSettings.refreshPeriod &&
+ mSwapIntervalNS == mNextTimingSettings.swapIntervalNS) {
+ return;
+ }
+
+ mAutoSwapInterval = mSwapIntervalForNewRefresh;
+ mPipelineMode = mPipelineModeForNewRefresh;
+ mSwapIntervalForNewRefresh = 0;
+
+ const bool swapIntervalValid = mNextTimingSettings.refreshPeriod * mAutoSwapInterval >=
+ mNextTimingSettings.swapIntervalNS;
+ const bool swapIntervalChangedBySettings = mSwapIntervalNS !=
+ mNextTimingSettings.swapIntervalNS;
+
+ mRefreshPeriod = mNextTimingSettings.refreshPeriod;
+ mSwapIntervalNS = mNextTimingSettings.swapIntervalNS;
+ if (!mAutoSwapIntervalEnabled || swapIntervalChangedBySettings ||
+ mAutoSwapInterval == 0 || !swapIntervalValid) {
+ mAutoSwapInterval = calculateSwapInterval(mSwapIntervalNS, mRefreshPeriod);
+ mPipelineMode = mAutoSwapIntervalEnabled ? PipelineMode::Off : PipelineMode::On;
+ setPreferredRefreshRate(mSwapIntervalNS);
+ }
+
+ if (mNextModeId == -1) {
+ setPreferredRefreshRate(mSwapIntervalNS);
+ }
+
+ mFrameDurations.clear();
+ mFrameDurationsSum = {};
+
+ TRACE_INT("mSwapIntervalNS", int(mSwapIntervalNS.count()));
+ TRACE_INT("mAutoSwapInterval", mAutoSwapInterval);
+ TRACE_INT("mRefreshPeriod", mRefreshPeriod.count());
+ TRACE_INT("mPipelineMode", static_cast<int>(mPipelineMode));
+}
+
void SwappyCommon::onPreSwap(const SwapHandlers& h) {
if (!mUsingExternalChoreographer) {
mChoreographerThread->postFrameCallbacks();
@@ -198,10 +271,11 @@
// for non pipeline mode where both cpu and gpu work is done at the same stage
// wait for next frame will happen after swap
- if (mPipelineMode) {
+ if (mPipelineMode == PipelineMode::On) {
mPresentationTimeNeeded = waitForNextFrame(h);
} else {
- mPresentationTimeNeeded = mAutoSwapInterval <= mAutoSwapIntervalThreshold;
+ mPresentationTimeNeeded =
+ (mRefreshPeriod * mAutoSwapInterval <= mAutoSwapIntervalThresholdNS.load());
}
mSwapTime = std::chrono::steady_clock::now();
@@ -211,22 +285,25 @@
void SwappyCommon::onPostSwap(const SwapHandlers& h) {
postSwapBuffersCallbacks();
- if (updateSwapInterval()) {
- swapIntervalChangedCallbacks();
- TRACE_INT("mPipelineMode", mPipelineMode);
- TRACE_INT("mAutoSwapInterval", mAutoSwapInterval);
- }
updateSwapDuration(std::chrono::steady_clock::now() - mSwapTime);
- if (!mPipelineMode) {
+ if (mPipelineMode == PipelineMode::Off) {
waitForNextFrame(h);
}
+ if (updateSwapInterval()) {
+ swapIntervalChangedCallbacks();
+ TRACE_INT("mPipelineMode", static_cast<int>(mPipelineMode));
+ TRACE_INT("mAutoSwapInterval", mAutoSwapInterval);
+ }
+
+ updateDisplayTimings();
+
startFrame();
}
-void SwappyCommon::updateSwapDuration(std::chrono::nanoseconds duration) {
+void SwappyCommon::updateSwapDuration(nanoseconds duration) {
// TODO: The exponential smoothing factor here is arbitrary
mSwapDuration = (mSwapDuration.load() * 4 / 5) + duration / 5;
@@ -240,7 +317,7 @@
uint64_t SwappyCommon::getSwapIntervalNS() {
std::lock_guard<std::mutex> lock(mFrameDurationsMutex);
- return mAutoSwapInterval.load() * mRefreshPeriod.count();
+ return mAutoSwapInterval * mRefreshPeriod.count();
};
void SwappyCommon::addFrameDuration(FrameDuration duration) {
@@ -258,52 +335,71 @@
mFrameDurationsSum += duration;
}
+bool SwappyCommon::pipelineModeNotNeeded(const nanoseconds& averageFrameTime,
+ const nanoseconds& upperBound) {
+ return (mPipelineModeAutoMode && averageFrameTime < upperBound);
+}
+
void SwappyCommon::swapSlower(const FrameDuration& averageFrameTime,
- const std::chrono::nanoseconds& upperBound,
- const std::chrono::nanoseconds& lowerBound,
+ const nanoseconds& upperBound,
const int32_t& newSwapInterval) {
ALOGV("Rendering takes too much time for the given config");
- if (!mPipelineMode && averageFrameTime.getTime(true) <= upperBound) {
+ if (mPipelineMode == PipelineMode::Off &&
+ averageFrameTime.getTime(PipelineMode::On) <= upperBound) {
ALOGV("turning on pipelining");
- mPipelineMode = true;
+ mPipelineMode = PipelineMode::On;
} else {
mAutoSwapInterval = newSwapInterval;
- ALOGV("Changing Swap interval to %d", mAutoSwapInterval.load());
+ ALOGV("Changing Swap interval to %d", mAutoSwapInterval);
// since we changed the swap interval, we may be able to turn off pipeline mode
- nanoseconds newBound = mRefreshPeriod * mAutoSwapInterval.load();
- newBound -= (FRAME_HYSTERESIS * 2);
- if (mPipelineModeAutoMode && averageFrameTime.getTime(false) < newBound) {
+ const nanoseconds newUpperBound = mRefreshPeriod * mAutoSwapInterval;
+ if (pipelineModeNotNeeded(averageFrameTime.getTime(PipelineMode::Off) + FRAME_MARGIN,
+ newUpperBound)) {
ALOGV("Turning off pipelining");
- mPipelineMode = false;
+ mPipelineMode = PipelineMode::Off;
} else {
ALOGV("Turning on pipelining");
- mPipelineMode = true;
+ mPipelineMode = PipelineMode::On;
}
}
}
void SwappyCommon::swapFaster(const FrameDuration& averageFrameTime,
- const std::chrono::nanoseconds& upperBound,
- const std::chrono::nanoseconds& lowerBound,
+ const nanoseconds& lowerBound,
const int32_t& newSwapInterval) {
+
+
ALOGV("Rendering is much shorter for the given config");
mAutoSwapInterval = newSwapInterval;
- ALOGV("Changing Swap interval to %d", mAutoSwapInterval.load());
+ ALOGV("Changing Swap interval to %d", mAutoSwapInterval);
// since we changed the swap interval, we may need to turn on pipeline mode
- nanoseconds newBound = mRefreshPeriod * mAutoSwapInterval.load();
- newBound -= FRAME_HYSTERESIS;
- if (!mPipelineModeAutoMode || averageFrameTime.getTime(false) > newBound) {
- ALOGV("Turning on pipelining");
- mPipelineMode = true;
- } else {
+ const nanoseconds newUpperBound = mRefreshPeriod * mAutoSwapInterval;
+ if (pipelineModeNotNeeded(averageFrameTime.getTime(PipelineMode::Off) + FRAME_MARGIN,
+ newUpperBound)) {
ALOGV("Turning off pipelining");
- mPipelineMode = false;
+ mPipelineMode = PipelineMode::Off;
+ } else {
+ ALOGV("Turning on pipelining");
+ mPipelineMode = PipelineMode::On;
}
}
+bool SwappyCommon::isSameDuration(std::chrono::nanoseconds period1, int interval1,
+ std::chrono::nanoseconds period2, int interval2) {
+ static constexpr std::chrono::nanoseconds MARGIN = 1ms;
+
+ auto duration1 = period1 * interval1;
+ auto duration2 = period2 * interval2;
+
+ if (std::max(duration1, duration2) - std::min(duration1, duration2) < MARGIN) {
+ return true;
+ }
+ return false;
+}
+
bool SwappyCommon::updateSwapInterval() {
std::lock_guard<std::mutex> lock(mFrameDurationsMutex);
if (!mAutoSwapIntervalEnabled)
@@ -313,43 +409,54 @@
return false;
const auto averageFrameTime = mFrameDurationsSum / mFrameDurations.size();
- // define lower and upper bound based on the swap duration
- nanoseconds upperBound = mRefreshPeriod * mAutoSwapInterval.load();
- nanoseconds lowerBound = mRefreshPeriod * (mAutoSwapInterval - 1);
- // to be on the conservative side, lower bounds by FRAME_HYSTERESIS
- upperBound -= FRAME_HYSTERESIS;
- lowerBound -= FRAME_HYSTERESIS;
+ const auto pipelineFrameTime = averageFrameTime.getTime(PipelineMode::On) + FRAME_MARGIN;
+ const auto nonPipelineFrameTime = averageFrameTime.getTime(PipelineMode::Off) + FRAME_MARGIN;
+ const auto currentConfigFrameTime = mPipelineMode == PipelineMode::On ?
+ pipelineFrameTime :
+ nonPipelineFrameTime;
+
+ // calculate the new swap interval based on average frame time assume we are in pipeline mode
+ // (prefer higher swap interval rather than turning off pipeline mode)
+ const int newSwapInterval = calculateSwapInterval(pipelineFrameTime, mRefreshPeriod);
+
+ // Define upper and lower bounds based on the swap duration
+ nanoseconds upperBoundForThisRefresh = mRefreshPeriod * mAutoSwapInterval;
+ nanoseconds lowerBoundForThisRefresh = mRefreshPeriod * (mAutoSwapInterval - 1);
// add the hysteresis to one of the bounds to avoid going back and forth when frames
// are exactly at the edge.
- lowerBound -= FRAME_HYSTERESIS;
+ lowerBoundForThisRefresh -= EDGE_HYSTERESIS;
- auto div_result = div((averageFrameTime.getTime(true) + FRAME_HYSTERESIS).count(),
- mRefreshPeriod.count());
- auto framesPerRefresh = div_result.quot;
- auto framesPerRefreshRemainder = div_result.rem;
- const int32_t newSwapInterval = framesPerRefresh + (framesPerRefreshRemainder ? 1 : 0);
-
- ALOGV("mPipelineMode = %d", mPipelineMode);
+ ALOGV("mPipelineMode = %d", static_cast<int>(mPipelineMode));
ALOGV("Average cpu frame time = %.2f", (averageFrameTime.getCpuTime().count()) / 1e6f);
ALOGV("Average gpu frame time = %.2f", (averageFrameTime.getGpuTime().count()) / 1e6f);
- ALOGV("upperBound = %.2f", upperBound.count() / 1e6f);
- ALOGV("lowerBound = %.2f", lowerBound.count() / 1e6f);
+ ALOGV("upperBound = %.2f", upperBoundForThisRefresh.count() / 1e6f);
+ ALOGV("lowerBound = %.2f", lowerBoundForThisRefresh.count() / 1e6f);
bool configChanged = false;
- if (averageFrameTime.getTime(mPipelineMode) > upperBound) {
- swapSlower(averageFrameTime, upperBound, lowerBound, newSwapInterval);
+
+ // Make sure the frame time fits in the current config to avoid missing frames
+ if (currentConfigFrameTime > upperBoundForThisRefresh) {
+ swapSlower(averageFrameTime, upperBoundForThisRefresh, newSwapInterval);
configChanged = true;
- } else if (mSwapInterval < mAutoSwapInterval &&
- (averageFrameTime.getTime(true) < lowerBound)) {
- swapFaster(averageFrameTime, upperBound, lowerBound, newSwapInterval);
+ }
+
+ // So we shouldn't miss any frames with this config but maybe we can go faster ?
+ // we check the pipeline frame time here as we prefer lower swap interval than no pipelining
+ else if (mSwapIntervalNS <= mRefreshPeriod * (mAutoSwapInterval - 1) &&
+ pipelineFrameTime < lowerBoundForThisRefresh) {
+ swapFaster(averageFrameTime, lowerBoundForThisRefresh, newSwapInterval);
configChanged = true;
- } else if (mPipelineModeAutoMode && mPipelineMode &&
- averageFrameTime.getTime(false) < upperBound - FRAME_HYSTERESIS) {
+ }
+
+ // If we reached to this condition it means that we fit into the boundaries.
+ // However we might be in pipeline mode and we could turn it off if we still fit.
+ else if (mPipelineMode == PipelineMode::On &&
+ pipelineModeNotNeeded(nonPipelineFrameTime, upperBoundForThisRefresh)) {
ALOGV("Rendering time fits the current swap interval without pipelining");
- mPipelineMode = false;
+ mPipelineMode = PipelineMode::Off;
configChanged = true;
}
@@ -357,6 +464,61 @@
mFrameDurationsSum = {};
mFrameDurations.clear();
}
+
+ // Loop across all supported refresh rate to see if we can find a better refresh rate.
+ // Better refresh rate means:
+ // Shorter swap period that can still accommodate the frame time can be achieved
+ // Or,
+ // Same swap period can be achieved with a lower refresh rate to optimize power
+ // consumption.
+ nanoseconds minSwapPeriod = mRefreshPeriod * mAutoSwapInterval;
+ bool betterRefreshFound = false;
+ std::pair<std::chrono::nanoseconds, int> betterRefreshConfig;
+ int betterRefreshSwapInterval = 0;
+ if (mSupportedRefreshRates) {
+ for (auto i : *mSupportedRefreshRates) {
+ const auto period = i.first;
+ const int swapIntervalForPeriod = calculateSwapInterval(pipelineFrameTime, period);
+ const nanoseconds duration = period * swapIntervalForPeriod;
+ const nanoseconds lowerBound = duration - EDGE_HYSTERESIS;
+ if (pipelineFrameTime < lowerBound && duration < minSwapPeriod && duration >= mSwapIntervalNS) {
+ minSwapPeriod = duration;
+ betterRefreshConfig = i;
+ betterRefreshSwapInterval = swapIntervalForPeriod;
+ betterRefreshFound = true;
+ ALOGV("Found better refresh %.2f", 1e9f / period.count());
+ }
+ }
+
+ if (!betterRefreshFound) {
+ for (auto i : *mSupportedRefreshRates) {
+ const auto period = i.first;
+ const int swapIntervalForPeriod =
+ calculateSwapInterval(pipelineFrameTime, period);
+ const nanoseconds duration = period * swapIntervalForPeriod;
+ if (isSameDuration(period, swapIntervalForPeriod,
+ mRefreshPeriod, mAutoSwapInterval) && period > mRefreshPeriod) {
+ betterRefreshFound = true;
+ betterRefreshConfig = i;
+ betterRefreshSwapInterval = swapIntervalForPeriod;
+ ALOGV("Found better refresh %.2f", 1e9f / period.count());
+ }
+ }
+ }
+ }
+
+ // Check if we there is a potential better refresh rate
+ if (betterRefreshFound) {
+ TRACE_INT("preferredRefreshPeriod", betterRefreshConfig.first.count());
+ setPreferredRefreshRate(betterRefreshConfig.second);
+ mSwapIntervalForNewRefresh = betterRefreshSwapInterval;
+
+ nanoseconds upperBoundForNewRefresh = betterRefreshConfig.first * betterRefreshSwapInterval;
+ mPipelineModeForNewRefresh =
+ pipelineModeNotNeeded(nonPipelineFrameTime, upperBoundForNewRefresh) ?
+ PipelineMode::Off : PipelineMode::On;
+ }
+
return configChanged;
}
@@ -416,8 +578,8 @@
// non pipeline mode is not supported when auto mode is disabled
if (!enabled) {
- mPipelineMode = true;
- TRACE_INT("mPipelineMode", mPipelineMode);
+ mPipelineMode = PipelineMode::On;
+ TRACE_INT("mPipelineMode", static_cast<int>(mPipelineMode));
}
}
@@ -426,23 +588,74 @@
mPipelineModeAutoMode = enabled;
TRACE_INT("mPipelineModeAutoMode", mPipelineModeAutoMode);
if (!enabled) {
- mPipelineMode = true;
- TRACE_INT("mPipelineMode", mPipelineMode);
+ mPipelineMode = PipelineMode::On;
+ TRACE_INT("mPipelineMode", static_cast<int>(mPipelineMode));
}
}
+void SwappyCommon::setPreferredRefreshRate(int modeId) {
+ if (!mDisplayManager || modeId < 0 || mNextModeId == modeId) {
+ return;
+ }
+
+ mNextModeId = modeId;
+ mDisplayManager->setPreferredRefreshRate(modeId);
+}
+
+int SwappyCommon::calculateSwapInterval(nanoseconds frameTime, nanoseconds refreshPeriod) {
+
+ if (frameTime < refreshPeriod) {
+ return 1;
+ }
+
+ auto div_result = div(frameTime.count(), refreshPeriod.count());
+ auto framesPerRefresh = div_result.quot;
+ auto framesPerRefreshRemainder = div_result.rem;
+
+ return (framesPerRefresh + (framesPerRefreshRemainder > REFRESH_RATE_MARGIN.count() ? 1 : 0));
+}
+
+void SwappyCommon::setPreferredRefreshRate(nanoseconds frameTime) {
+ if (!mDisplayManager) {
+ return;
+ }
+
+ int bestModeId = -1;
+ nanoseconds bestPeriod = 0ns;
+ nanoseconds swapIntervalNSMin = 100ms;
+ for (auto i = mSupportedRefreshRates->crbegin(); i != mSupportedRefreshRates->crend(); ++i) {
+ const auto period = i->first;
+ const int modeId = i->second;
+
+ // Make sure we don't cross the swap interval set by the app
+ if (frameTime < mSwapIntervalNS) {
+ frameTime = mSwapIntervalNS;
+ }
+
+ int swapIntervalForPeriod = calculateSwapInterval(frameTime, period);
+ const auto swapIntervalNS = (period * swapIntervalForPeriod);
+ if (swapIntervalNS < swapIntervalNSMin) {
+ swapIntervalNSMin = swapIntervalNS;
+ bestModeId = modeId;
+ bestPeriod = period;
+ }
+ }
+
+ TRACE_INT("preferredRefreshPeriod", bestPeriod.count());
+ setPreferredRefreshRate(bestModeId);
+}
+
void SwappyCommon::onSettingsChanged() {
std::lock_guard<std::mutex> lock(mFrameDurationsMutex);
- int32_t newSwapInterval = round(float(Settings::getInstance()->getSwapIntervalNS()) /
- float(mRefreshPeriod.count()));
- if (mSwapInterval != newSwapInterval || mAutoSwapInterval != newSwapInterval) {
- mSwapInterval = newSwapInterval;
- mAutoSwapInterval = mSwapInterval.load();
- mFrameDurations.clear();
- mFrameDurationsSum = {};
+
+ TimingSettings timingSettings = TimingSettings::from(*Settings::getInstance());
+
+ // If display timings has changed, cache the update and apply them on the next frame
+ if (timingSettings != mNextTimingSettings) {
+ mNextTimingSettings = timingSettings;
+ mTimingSettingsNeedUpdate = true;
}
- TRACE_INT("mSwapInterval", mSwapInterval);
- TRACE_INT("mAutoSwapInterval", mAutoSwapInterval);
+
}
void SwappyCommon::startFrame() {
@@ -460,13 +673,14 @@
mTargetFrame = currentFrame + mAutoSwapInterval;
- const int intervals = (mPipelineMode) ? 2 : 1;
+ const int intervals = (mPipelineMode == PipelineMode::On) ? 2 : 1;
// We compute the target time as now
// + the time the buffer will be on the GPU and in the queue to the compositor (1 swap period)
mPresentationTime = currentFrameTimestamp + (mAutoSwapInterval * intervals) * mRefreshPeriod;
mStartFrameTime = std::chrono::steady_clock::now();
+ mCPUTracer.startTrace();
}
void SwappyCommon::waitUntilTargetFrame() {
@@ -482,4 +696,31 @@
mWaitingCondition.wait(lock, [&]() { return mCurrentFrame >= target; });
}
+int SwappyCommon::getSDKVersion(JNIEnv *env)
+{
+ const jclass buildClass = env->FindClass("android/os/Build$VERSION");
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ ALOGE("Failed to get Build.VERSION class");
+ return 0;
+ }
+
+ const jfieldID sdk_int = env->GetStaticFieldID(buildClass, "SDK_INT", "I");
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ ALOGE("Failed to get Build.VERSION.SDK_INT field");
+ return 0;
+ }
+
+ const jint sdk = env->GetStaticIntField(buildClass, sdk_int);
+ if (env->ExceptionCheck()) {
+ env->ExceptionClear();
+ ALOGE("Failed to get SDK version");
+ return 0;
+ }
+
+ ALOGI("SDK version = %d", sdk);
+ return sdk;
+}
+
} // namespace swappy
diff --git a/src/swappy/common/SwappyCommon.h b/src/swappy/common/SwappyCommon.h
index 0d0f75a..292e59c 100644
--- a/src/swappy/common/SwappyCommon.h
+++ b/src/swappy/common/SwappyCommon.h
@@ -29,6 +29,8 @@
#include "Thread.h"
#include "ChoreographerFilter.h"
#include "ChoreographerThread.h"
+#include "SwappyDisplayManager.h"
+#include "CPUTracer.h"
namespace swappy {
@@ -37,6 +39,8 @@
// Common part between OpenGL and Vulkan implementations.
class SwappyCommon final {
public:
+ enum class PipelineMode { Off, On };
+
// callbacks to be called during pre/post swap
struct SwapHandlers {
std::function<bool()> lastFrameIsComplete;
@@ -56,6 +60,8 @@
void onPostSwap(const SwapHandlers& h);
+ PipelineMode getCurrentPipelineMode() { return mPipelineMode; }
+
template <typename ...T>
using Tracer = std::function<void (T...)>;
void addTracerCallbacks(SwappyTracer tracer);
@@ -63,10 +69,12 @@
void setAutoSwapInterval(bool enabled);
void setAutoPipelineMode(bool enabled);
+ void setMaxAutoSwapIntervalNS(std::chrono::nanoseconds swapIntervalNS) {
+ mAutoSwapIntervalThresholdNS = swapIntervalNS;
+ }
+
std::chrono::steady_clock::time_point getPresentationTime() { return mPresentationTime; }
- std::chrono::nanoseconds getRefreshPeriod() { return mRefreshPeriod; }
- std::chrono::nanoseconds getAppVsyncOffset() { return mAppVsyncOffset; }
- std::chrono::nanoseconds getSfVsyncOffset() { return mSfVsyncOffset; }
+ std::chrono::nanoseconds getRefreshPeriod() const { return mRefreshPeriod; }
bool isValid() { return mValid; }
@@ -85,8 +93,9 @@
std::chrono::nanoseconds getCpuTime() const { return mCpuTime; }
std::chrono::nanoseconds getGpuTime() const { return mGpuTime; }
- std::chrono::nanoseconds getTime(bool pipeline) const {
- if (pipeline) {
+
+ std::chrono::nanoseconds getTime(PipelineMode pipeline) const {
+ if (pipeline == PipelineMode::On) {
return std::max(mCpuTime, mGpuTime);
}
@@ -122,13 +131,11 @@
std::chrono::nanoseconds wakeClient();
void swapFaster(const FrameDuration& averageFrameTime,
- const std::chrono::nanoseconds& upperBound,
const std::chrono::nanoseconds& lowerBound,
const int32_t& newSwapInterval) REQUIRES(mFrameDurationsMutex);
void swapSlower(const FrameDuration& averageFrameTime,
const std::chrono::nanoseconds& upperBound,
- const std::chrono::nanoseconds& lowerBound,
const int32_t& newSwapInterval) REQUIRES(mFrameDurationsMutex);
bool updateSwapInterval();
void preSwapBuffersCallbacks();
@@ -142,10 +149,27 @@
void startFrame();
void waitUntilTargetFrame();
void waitOneFrame();
+ void setPreferredRefreshRate(int index);
+ void setPreferredRefreshRate(std::chrono::nanoseconds frameTime);
+ int calculateSwapInterval(std::chrono::nanoseconds frameTime,
+ std::chrono::nanoseconds refreshPeriod);
+ bool pipelineModeNotNeeded(const std::chrono::nanoseconds& averageFrameTime,
+ const std::chrono::nanoseconds& upperBound)
+ REQUIRES(mFrameDurationsMutex);
+ void updateDisplayTimings();
// Waits for the next frame, considering both Choreographer and the prior frame's completion
bool waitForNextFrame(const SwapHandlers& h);
+ bool isSameDuration(std::chrono::nanoseconds period1,
+ int interval1,
+ std::chrono::nanoseconds period2,
+ int interval2);
+
+ int getSDKVersion(JNIEnv *env);
+
+ const int mSdkVersion;
+
std::unique_ptr<ChoreographerFilter> mChoreographerFilter;
bool mUsingExternalChoreographer = false;
@@ -160,20 +184,23 @@
std::chrono::steady_clock::time_point mSwapTime;
std::chrono::nanoseconds mRefreshPeriod;
- std::chrono::nanoseconds mAppVsyncOffset;
- std::chrono::nanoseconds mSfVsyncOffset;
std::mutex mFrameDurationsMutex;
std::vector<FrameDuration> mFrameDurations GUARDED_BY(mFrameDurationsMutex);
FrameDuration mFrameDurationsSum GUARDED_BY(mFrameDurationsMutex);
- static constexpr int mFrameDurationSamples = 10;
+ static constexpr int mFrameDurationSamples = 300; // 5 Seconds in 60Hz
bool mAutoSwapIntervalEnabled GUARDED_BY(mFrameDurationsMutex) = true;
bool mPipelineModeAutoMode GUARDED_BY(mFrameDurationsMutex) = true;
- static constexpr std::chrono::nanoseconds FRAME_HYSTERESIS = 3ms;
- std::atomic<int32_t> mSwapInterval;
- std::atomic<int32_t> mAutoSwapInterval;
- int mAutoSwapIntervalThreshold = 0;
+ static constexpr std::chrono::nanoseconds FRAME_MARGIN = 3ms;
+ static constexpr std::chrono::nanoseconds EDGE_HYSTERESIS = 4ms;
+
+ std::chrono::nanoseconds mSwapIntervalNS;
+ int32_t mAutoSwapInterval;
+ std::atomic<std::chrono::nanoseconds> mAutoSwapIntervalThresholdNS = {50ms}; // 20FPS
+ int mSwapIntervalForNewRefresh = 0;
+ PipelineMode mPipelineModeForNewRefresh;
+ static constexpr std::chrono::nanoseconds REFRESH_RATE_MARGIN = 500ns;
std::chrono::steady_clock::time_point mStartFrameTime;
@@ -191,11 +218,44 @@
int32_t mTargetFrame = 0;
std::chrono::steady_clock::time_point mPresentationTime = std::chrono::steady_clock::now();
bool mPresentationTimeNeeded;
- bool mPipelineMode = false;
+ PipelineMode mPipelineMode = PipelineMode::Off;
bool mValid;
std::chrono::nanoseconds mFenceTimeout = std::chrono::nanoseconds(50ms);
+
+ constexpr static bool USE_DISPLAY_MANAGER = true;
+ std::unique_ptr<SwappyDisplayManager> mDisplayManager;
+ int mNextModeId = -1;
+
+ std::shared_ptr<SwappyDisplayManager::RefreshRateMap> mSupportedRefreshRates;
+
+ struct TimingSettings {
+ std::chrono::nanoseconds refreshPeriod = {};
+ std::chrono::nanoseconds swapIntervalNS = {};
+
+ static TimingSettings from(const Settings& settings) {
+ TimingSettings timingSettings;
+
+ timingSettings.refreshPeriod = settings.getDisplayTimings().refreshPeriod;
+ timingSettings.swapIntervalNS =
+ std::chrono::nanoseconds(settings.getSwapIntervalNS());
+ return timingSettings;
+ }
+
+ bool operator != (const TimingSettings& other) const {
+ return (refreshPeriod != other.refreshPeriod) ||
+ (swapIntervalNS != other.swapIntervalNS);
+ }
+
+ bool operator == (const TimingSettings& other) const {
+ return !(*this != other);
+ }
+ };
+ TimingSettings mNextTimingSettings GUARDED_BY(mFrameDurationsMutex) = {};
+ bool mTimingSettingsNeedUpdate GUARDED_BY(mFrameDurationsMutex) = false;
+
+ CPUTracer mCPUTracer;
};
} //namespace swappy
diff --git a/src/swappy/common/SwappyDisplayManager.cpp b/src/swappy/common/SwappyDisplayManager.cpp
new file mode 100644
index 0000000..19f374e
--- /dev/null
+++ b/src/swappy/common/SwappyDisplayManager.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <Log.h>
+#include <map>
+#include "SwappyDisplayManager.h"
+#include "Settings.h"
+
+#define LOG_TAG "SwappyDisplayManager"
+
+namespace swappy {
+
+SwappyDisplayManager::SwappyDisplayManager(JavaVM* vm, jobject mainActivity) : mJVM(vm) {
+ JNIEnv *env;
+ mJVM->AttachCurrentThread(&env, nullptr);
+
+ // Since we may call this from ANativeActivity we cannot use env->FindClass as the classpath
+ // will be empty. Instead we need to work a bit harder
+ jclass activity = env->GetObjectClass(mainActivity);
+ jclass classLoader = env->FindClass("java/lang/ClassLoader");
+ jmethodID getClassLoader = env->GetMethodID(activity,
+ "getClassLoader",
+ "()Ljava/lang/ClassLoader;");
+ jmethodID loadClass = env->GetMethodID(classLoader,
+ "loadClass",
+ "(Ljava/lang/String;)Ljava/lang/Class;");
+ jobject classLoaderObj = env->CallObjectMethod(mainActivity, getClassLoader);
+ jstring swappyDisplayManagerName =
+ env->NewStringUTF("com/google/androidgamesdk/SwappyDisplayManager");
+
+ jclass swappyDisplayManagerClass = static_cast<jclass>(
+ env->CallObjectMethod(classLoaderObj, loadClass, swappyDisplayManagerName));
+ env->DeleteLocalRef(swappyDisplayManagerName);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ ALOGE("Unable to find com.google.androidgamesdk.SwappyDisplayManager class");
+ ALOGE("Did you integrate extras ?");
+ return;
+ }
+
+ jmethodID constructor = env->GetMethodID(
+ swappyDisplayManagerClass,
+ "<init>",
+ "(JLandroid/app/Activity;)V");
+ mSetPreferredRefreshRate = env->GetMethodID(
+ swappyDisplayManagerClass,
+ "setPreferredRefreshRate",
+ "(I)V");
+ jobject swappyDisplayManager = env->NewObject(swappyDisplayManagerClass,
+ constructor,
+ (jlong)this,
+ mainActivity);
+ mJthis = env->NewGlobalRef(swappyDisplayManager);
+
+ mInitialized = true;
+}
+
+SwappyDisplayManager::~SwappyDisplayManager() {
+ JNIEnv *env;
+ mJVM->AttachCurrentThread(&env, nullptr);
+
+ env->DeleteGlobalRef(mJthis);
+}
+
+std::shared_ptr<SwappyDisplayManager::RefreshRateMap>
+SwappyDisplayManager::getSupportedRefreshRates() {
+ std::unique_lock<std::mutex> lock(mMutex);
+
+ mCondition.wait(lock, [&]() { return mSupportedRefreshRates.get() != nullptr; });
+ return mSupportedRefreshRates;
+}
+
+void SwappyDisplayManager::setPreferredRefreshRate(int index) {
+ JNIEnv *env;
+ mJVM->AttachCurrentThread(&env, nullptr);
+
+ env->CallVoidMethod(mJthis, mSetPreferredRefreshRate, index);
+}
+
+// Helper class to wrap JNI entry points to SwappyDisplayManager
+class SwappyDisplayManagerJNI {
+public:
+ static void onSetSupportedRefreshRates(jlong,
+ std::shared_ptr<SwappyDisplayManager::RefreshRateMap>);
+ static void onRefreshRateChanged(jlong, long, long, long);
+};
+
+void SwappyDisplayManagerJNI::onSetSupportedRefreshRates(jlong cookie,
+ std::shared_ptr<SwappyDisplayManager::RefreshRateMap> refreshRates) {
+ auto *sDM = reinterpret_cast<SwappyDisplayManager*>(cookie);
+
+ std::lock_guard<std::mutex> lock(sDM->mMutex);
+ sDM->mSupportedRefreshRates = std::move(refreshRates);
+ sDM->mCondition.notify_one();
+}
+
+void SwappyDisplayManagerJNI::onRefreshRateChanged(jlong /*cookie*/,
+ long refreshPeriod,
+ long appOffset,
+ long sfOffset) {
+ using std::chrono::nanoseconds;
+ Settings::DisplayTimings displayTimings;
+ displayTimings.refreshPeriod = nanoseconds(refreshPeriod);
+ displayTimings.appOffset = nanoseconds(appOffset);
+ displayTimings.sfOffset = nanoseconds(sfOffset);
+ Settings::getInstance()->setDisplayTimings(displayTimings);
+}
+
+extern "C" {
+
+JNIEXPORT void JNICALL
+Java_com_google_androidgamesdk_SwappyDisplayManager_nSetSupportedRefreshRates(
+ JNIEnv *env,
+ jobject /* this */,
+ jlong cookie,
+ jlongArray refreshRates,
+ jintArray modeIds) {
+ int length = env->GetArrayLength(refreshRates);
+ auto refreshRatesMap =
+ std::make_shared<SwappyDisplayManager::RefreshRateMap>();
+
+ jlong *refreshRatesArr = env->GetLongArrayElements(refreshRates, 0);
+ jint *modeIdsArr = env->GetIntArrayElements(modeIds, 0);
+ for (int i = 0; i < length; i++) {
+ (*refreshRatesMap)[std::chrono::nanoseconds(refreshRatesArr[i])] = modeIdsArr[i];
+ }
+ env->ReleaseLongArrayElements(refreshRates, refreshRatesArr, 0);
+ env->ReleaseIntArrayElements(modeIds, modeIdsArr, 0);
+
+ SwappyDisplayManagerJNI::onSetSupportedRefreshRates(cookie, refreshRatesMap);
+}
+
+JNIEXPORT void JNICALL
+Java_com_google_androidgamesdk_SwappyDisplayManager_nOnRefreshRateChanged(JNIEnv *env,
+ jobject /* this */,
+ jlong cookie,
+ jlong refreshPeriod,
+ jlong appOffset,
+ jlong sfOffset) {
+ SwappyDisplayManagerJNI::onRefreshRateChanged(cookie, refreshPeriod, appOffset, sfOffset);
+}
+
+} // extern "C"
+
+} // namespace swappy
diff --git a/src/swappy/common/SwappyDisplayManager.h b/src/swappy/common/SwappyDisplayManager.h
new file mode 100644
index 0000000..bec9736
--- /dev/null
+++ b/src/swappy/common/SwappyDisplayManager.h
@@ -0,0 +1,55 @@
+/*
+ * 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 <condition_variable>
+#include <functional>
+#include <map>
+#include <memory>
+#include <mutex>
+
+
+namespace swappy {
+
+class SwappyDisplayManager {
+public:
+ static constexpr int MIN_SDK_VERSION = 23;
+
+ SwappyDisplayManager(JavaVM*, jobject);
+ ~SwappyDisplayManager();
+
+ bool isInitialized() { return mInitialized; }
+
+ using RefreshRateMap = std::map<std::chrono::nanoseconds, int>;
+
+ std::shared_ptr<RefreshRateMap> getSupportedRefreshRates();
+
+ void setPreferredRefreshRate(int index);
+
+private:
+ JavaVM* mJVM;
+ std::mutex mMutex;
+ std::condition_variable mCondition;
+ std::shared_ptr<RefreshRateMap> mSupportedRefreshRates;
+ jobject mJthis;
+ jmethodID mSetPreferredRefreshRate;
+ bool mInitialized = false;
+
+ friend class SwappyDisplayManagerJNI;
+};
+
+} // namespace swappy
\ No newline at end of file
diff --git a/src/swappy/common/Thread.h b/src/swappy/common/Thread.h
index 18557b8..27a7e4e 100644
--- a/src/swappy/common/Thread.h
+++ b/src/swappy/common/Thread.h
@@ -36,9 +36,13 @@
#define REQUIRES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
+
+ #define NO_THREAD_SAFETY_ANALYSIS \
+ THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
#else
#define GUARDED_BY(x)
#define REQUIRES(...)
+ #define NO_THREAD_SAFETY_ANALYSIS
#endif
namespace swappy {
diff --git a/src/swappy/opengl/EGL.cpp b/src/swappy/opengl/EGL.cpp
index 7ecc9fc..198a6c0 100644
--- a/src/swappy/opengl/EGL.cpp
+++ b/src/swappy/opengl/EGL.cpp
@@ -17,6 +17,7 @@
#include "EGL.h"
#include <vector>
+#include <Trace.h>
#define LOG_TAG "Swappy::EGL"
@@ -26,8 +27,7 @@
namespace swappy {
-std::unique_ptr<EGL> EGL::create(std::chrono::nanoseconds refreshPeriod,
- std::chrono::nanoseconds fenceTimeout) {
+std::unique_ptr<EGL> EGL::create(std::chrono::nanoseconds fenceTimeout) {
auto eglPresentationTimeANDROID = reinterpret_cast<eglPresentationTimeANDROID_type>(
eglGetProcAddress("eglPresentationTimeANDROID"));
if (eglPresentationTimeANDROID == nullptr) {
@@ -83,7 +83,7 @@
ALOGI("Failed to load eglGetFrameTimestampsANDROID");
}
- auto egl = std::make_unique<EGL>(refreshPeriod, fenceTimeout, ConstructorTag{});
+ auto egl = std::make_unique<EGL>(fenceTimeout, ConstructorTag{});
egl->eglPresentationTimeANDROID = eglPresentationTimeANDROID;
egl->eglCreateSyncKHR = eglCreateSyncKHR;
egl->eglDestroySyncKHR = eglDestroySyncKHR;
@@ -148,7 +148,7 @@
return (eglGetNextFrameIdANDROID != nullptr && eglGetFrameTimestampsANDROID != nullptr);
}
-std::pair<bool,EGLuint64KHR> EGL::getNextFrameId(EGLDisplay dpy, EGLSurface surface) {
+std::pair<bool,EGLuint64KHR> EGL::getNextFrameId(EGLDisplay dpy, EGLSurface surface) const {
if (eglGetNextFrameIdANDROID == nullptr) {
ALOGE("stats are not supported on this platform");
return {false, 0};
@@ -166,7 +166,7 @@
std::unique_ptr<EGL::FrameTimestamps> EGL::getFrameTimestamps(EGLDisplay dpy,
EGLSurface surface,
- EGLuint64KHR frameId) {
+ EGLuint64KHR frameId) const {
if (eglGetFrameTimestampsANDROID == nullptr) {
ALOGE("stats are not supported on this platform");
return nullptr;
@@ -259,6 +259,7 @@
break;
}
+ gamesdk::ScopedTrace tracer("Swappy: GPU frame time");
const auto startTime = std::chrono::steady_clock::now();
EGLBoolean result = eglClientWaitSyncKHR(mDisplay, mSyncFence, 0,
mFenceTimeout.count());
@@ -282,7 +283,7 @@
}
}
-std::chrono::nanoseconds EGL::FenceWaiter::getFencePendingTime() {
+std::chrono::nanoseconds EGL::FenceWaiter::getFencePendingTime() const {
// return mFencePendingTime without a lock to avoid blocking the main thread
// worst case, the time will be of some previous frame
return mFencePendingTime.load();
diff --git a/src/swappy/opengl/EGL.h b/src/swappy/opengl/EGL.h
index de05a4f..4450bd3 100644
--- a/src/swappy/opengl/EGL.h
+++ b/src/swappy/opengl/EGL.h
@@ -45,31 +45,28 @@
EGLnsecsANDROID presented;
};
- explicit EGL(std::chrono::nanoseconds refreshPeriod,
- std::chrono::nanoseconds fenceTimeout, ConstructorTag)
- : mRefreshPeriod(refreshPeriod), mFenceWaiter(fenceTimeout) {}
+ explicit EGL(std::chrono::nanoseconds fenceTimeout, ConstructorTag)
+ : mFenceWaiter(fenceTimeout) {}
- static std::unique_ptr<EGL> create(std::chrono::nanoseconds refreshPeriod,
- std::chrono::nanoseconds fenceTimeout);
+ static std::unique_ptr<EGL> create(std::chrono::nanoseconds fenceTimeout);
void resetSyncFence(EGLDisplay display);
bool lastFrameIsComplete(EGLDisplay display);
bool setPresentationTime(EGLDisplay display,
EGLSurface surface,
std::chrono::steady_clock::time_point time);
- std::chrono::nanoseconds getFencePendingTime() { return mFenceWaiter.getFencePendingTime(); }
+ std::chrono::nanoseconds getFencePendingTime() const
+ { return mFenceWaiter.getFencePendingTime(); }
// for stats
bool statsSupported();
std::pair<bool,EGLuint64KHR> getNextFrameId(EGLDisplay dpy,
- EGLSurface surface);
+ EGLSurface surface) const;
std::unique_ptr<FrameTimestamps> getFrameTimestamps(EGLDisplay dpy,
EGLSurface surface,
- EGLuint64KHR frameId);
+ EGLuint64KHR frameId) const;
private:
- const std::chrono::nanoseconds mRefreshPeriod;
-
using eglPresentationTimeANDROID_type = EGLBoolean (*)(EGLDisplay, EGLSurface, EGLnsecsANDROID);
eglPresentationTimeANDROID_type eglPresentationTimeANDROID = nullptr;
using eglCreateSyncKHR_type = EGLSyncKHR (*)(EGLDisplay, EGLenum, const EGLint *);
@@ -99,7 +96,7 @@
void onFenceCreation(EGLDisplay display, EGLSyncKHR syncFence);
void waitForIdle();
- std::chrono::nanoseconds getFencePendingTime();
+ std::chrono::nanoseconds getFencePendingTime() const;
private:
using eglClientWaitSyncKHR_type = EGLBoolean (*)(EGLDisplay, EGLSyncKHR, EGLint, EGLTimeKHR);
diff --git a/src/swappy/opengl/SwappyGL.cpp b/src/swappy/opengl/SwappyGL.cpp
index fe6aac9..7c76f49 100644
--- a/src/swappy/opengl/SwappyGL.cpp
+++ b/src/swappy/opengl/SwappyGL.cpp
@@ -37,13 +37,20 @@
std::mutex SwappyGL::sInstanceMutex;
std::unique_ptr<SwappyGL> SwappyGL::sInstance;
-void SwappyGL::init(JNIEnv *env, jobject jactivity) {
+bool SwappyGL::init(JNIEnv *env, jobject jactivity) {
std::lock_guard<std::mutex> lock(sInstanceMutex);
if (sInstance) {
ALOGE("Attempted to initialize SwappyGL twice");
- return;
+ return false;
}
sInstance = std::make_unique<SwappyGL>(env, jactivity, ConstructorTag{});
+ if (!sInstance->mEnableSwappy) {
+ ALOGE("Failed to initialize SwappyGL");
+ sInstance = nullptr;
+ return false;
+ }
+
+ return true;
}
void SwappyGL::onChoreographer(int64_t frameTimeNanos) {
@@ -145,6 +152,15 @@
swappy->mCommonBase.setAutoPipelineMode(enabled);
}
+void SwappyGL::setMaxAutoSwapIntervalNS(std::chrono::nanoseconds maxSwapNS) {
+ SwappyGL *swappy = getInstance();
+ if (!swappy) {
+ ALOGE("Failed to get SwappyGL instance in setMaxAutoSwapIntervalNS");
+ return;
+ }
+ swappy->mCommonBase.setMaxAutoSwapIntervalNS(maxSwapNS);
+}
+
void SwappyGL::enableStats(bool enabled) {
SwappyGL *swappy = getInstance();
if (!swappy) {
@@ -163,7 +179,7 @@
if (enabled && swappy->mFrameStatistics == nullptr) {
swappy->mFrameStatistics = std::make_unique<FrameStatistics>(
- swappy->mEgl, swappy->mCommonBase.getRefreshPeriod());
+ *swappy->mEgl, swappy->mCommonBase);
ALOGI("Enabling stats");
} else {
swappy->mFrameStatistics = nullptr;
@@ -259,18 +275,14 @@
}
std::lock_guard<std::mutex> lock(mEglMutex);
- mEgl = EGL::create(mCommonBase.getRefreshPeriod(), mCommonBase.getFenceTimeout());
+ mEgl = EGL::create(mCommonBase.getFenceTimeout());
if (!mEgl) {
ALOGE("Failed to load EGL functions");
mEnableSwappy = false;
return;
}
- ALOGI("Initialized Swappy with vsyncPeriod=%lld, appOffset=%lld, sfOffset=%lld",
- (long long)mCommonBase.getRefreshPeriod().count(),
- (long long)mCommonBase.getAppVsyncOffset().count(),
- (long long)mCommonBase.getSfVsyncOffset().count()
- );
+ ALOGI("SwappyGL initialized successfully");
}
void SwappyGL::resetSyncFence(EGLDisplay display) {
@@ -280,9 +292,11 @@
bool SwappyGL::setPresentationTime(EGLDisplay display, EGLSurface surface) {
TRACE_CALL();
+ auto displayTimings = Settings::getInstance()->getDisplayTimings();
+
// if we are too close to the vsync, there is no need to set presentation time
if ((mCommonBase.getPresentationTime() - std::chrono::steady_clock::now()) <
- (mCommonBase.getRefreshPeriod() - mCommonBase.getSfVsyncOffset())) {
+ (mCommonBase.getRefreshPeriod() - displayTimings.sfOffset)) {
return EGL_TRUE;
}
diff --git a/src/swappy/opengl/SwappyGL.h b/src/swappy/opengl/SwappyGL.h
index 253e6cd..c185a0a 100644
--- a/src/swappy/opengl/SwappyGL.h
+++ b/src/swappy/opengl/SwappyGL.h
@@ -41,7 +41,7 @@
struct ConstructorTag {};
public:
SwappyGL(JNIEnv *env, jobject jactivity, ConstructorTag);
- static void init(JNIEnv *env, jobject jactivity);
+ static bool init(JNIEnv *env, jobject jactivity);
static void onChoreographer(int64_t frameTimeNanos);
@@ -56,6 +56,8 @@
static void setAutoPipelineMode(bool enabled);
+ static void setMaxAutoSwapIntervalNS(std::chrono::nanoseconds maxSwapNS);
+
static void enableStats(bool enabled);
static void recordFrameStart(EGLDisplay display, EGLSurface surface);
static void getStats(SwappyStats *stats);
@@ -86,10 +88,10 @@
bool mEnableSwappy = true;
static std::mutex sInstanceMutex;
- static std::unique_ptr<SwappyGL> sInstance;
+ static std::unique_ptr<SwappyGL> sInstance GUARDED_BY(sInstanceMutex);
std::mutex mEglMutex;
- std::shared_ptr<EGL> mEgl;
+ std::unique_ptr<EGL> mEgl;
std::unique_ptr<FrameStatistics> mFrameStatistics;
diff --git a/src/swappy/opengl/swappyGL_c.cpp b/src/swappy/opengl/swappyGL_c.cpp
index db16911..8064c95 100644
--- a/src/swappy/opengl/swappyGL_c.cpp
+++ b/src/swappy/opengl/swappyGL_c.cpp
@@ -28,8 +28,8 @@
extern "C" {
-void SwappyGL_init_internal(JNIEnv *env, jobject jactivity) {
- SwappyGL::init(env, jactivity);
+bool SwappyGL_init_internal(JNIEnv *env, jobject jactivity) {
+ return SwappyGL::init(env, jactivity);
}
void SwappyGL_destroy() {
@@ -44,10 +44,6 @@
return SwappyGL::swap(display, surface);
}
-void SwappyGL_setRefreshPeriod(uint64_t period_ns) {
- Settings::getInstance()->setRefreshPeriod(std::chrono::nanoseconds(period_ns));
-}
-
void SwappyGL_setUseAffinity(bool tf) {
Settings::getInstance()->setUseAffinity(tf);
}
@@ -57,7 +53,7 @@
}
uint64_t SwappyGL_getRefreshPeriodNanos() {
- return Settings::getInstance()->getRefreshPeriod().count();
+ return Settings::getInstance()->getDisplayTimings().refreshPeriod.count();
}
bool SwappyGL_getUseAffinity() {
@@ -76,6 +72,10 @@
SwappyGL::setAutoSwapInterval(enabled);
}
+void SwappyGL_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns) {
+ SwappyGL::setMaxAutoSwapIntervalNS(std::chrono::nanoseconds(max_swap_ns));
+}
+
void SwappyGL_setAutoPipelineMode(bool enabled) {
SwappyGL::setAutoPipelineMode(enabled);
}
diff --git a/src/swappy/vulkan/SwappyVk.cpp b/src/swappy/vulkan/SwappyVk.cpp
index e733237..60760d8 100644
--- a/src/swappy/vulkan/SwappyVk.cpp
+++ b/src/swappy/vulkan/SwappyVk.cpp
@@ -184,6 +184,12 @@
}
}
+void SwappyVk::SetMaxAutoSwapIntervalNS(std::chrono::nanoseconds maxSwapNS) {
+ for (auto i : perSwapchainImplementation) {
+ i.second->setMaxAutoSwapIntervalNS(maxSwapNS);
+ }
+}
+
void SwappyVk::SetFenceTimeout(std::chrono::nanoseconds t) {
for(auto i : perDeviceImplementation) {
i.second->setFenceTimeout(t);
diff --git a/src/swappy/vulkan/SwappyVk.h b/src/swappy/vulkan/SwappyVk.h
index 9f6ca19..facd735 100644
--- a/src/swappy/vulkan/SwappyVk.h
+++ b/src/swappy/vulkan/SwappyVk.h
@@ -77,6 +77,7 @@
void SetAutoSwapInterval(bool enabled);
void SetAutoPipelineMode(bool enabled);
+ void SetMaxAutoSwapIntervalNS(std::chrono::nanoseconds maxSwapNS);
void SetFenceTimeout(std::chrono::nanoseconds duration);
std::chrono::nanoseconds GetFenceTimeout() const;
diff --git a/src/swappy/vulkan/SwappyVkBase.cpp b/src/swappy/vulkan/SwappyVkBase.cpp
index b19bb3a..0b4025c 100644
--- a/src/swappy/vulkan/SwappyVkBase.cpp
+++ b/src/swappy/vulkan/SwappyVkBase.cpp
@@ -154,7 +154,7 @@
return res;
}
- mFreeSync[queue].push_back(sync);
+ mFreeSyncPool[queue].push_back(sync);
}
// Create a thread that will wait for the fences
@@ -165,6 +165,7 @@
}
void SwappyVkBase::destroyVkSyncObjects() {
+ // Stop all waiters threads
for (auto it = mThreads.begin(); it != mThreads.end(); it++) {
{
std::lock_guard<std::mutex> lock(it->second->lock);
@@ -174,60 +175,83 @@
it->second->thread.join();
}
- for (auto it = mPendingSync.begin(); it != mPendingSync.end(); it++) {
- while (mPendingSync[it->first].size() > 0) {
- VkSync sync = mPendingSync[it->first].front();
- mPendingSync[it->first].pop_front();
- if (!sync.fenceSignaled) {
- vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE,
- mCommonBase.getFenceTimeout().count());
- vkResetFences(mDevice, 1, &sync.fence);
- }
- mFreeSync[it->first].push_back(sync);
+ // Wait for all unsignaled fences to get signlaed
+ for (auto it = mWaitingSyncs.begin(); it != mWaitingSyncs.end(); it++) {
+ auto queue = it->first;
+ auto syncList = it->second;
+ while (syncList.size() > 0) {
+ VkSync sync = syncList.front();
+ syncList.pop_front();
+ vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE,
+ mCommonBase.getFenceTimeout().count());
+ vkResetFences(mDevice, 1, &sync.fence);
+ mSignaledSyncs[queue].push_back(sync);
}
+ }
- while (mFreeSync[it->first].size() > 0) {
- VkSync sync = mFreeSync[it->first].front();
- mFreeSync[it->first].pop_front();
+ // Move all signaled fences to the free pool
+ for (auto it = mSignaledSyncs.begin(); it != mSignaledSyncs.end(); it++) {
+ auto queue = it->first;
+ reclaimSignaledFences(queue);
+ }
+
+ // Free all sync objects
+ for (auto it = mFreeSyncPool.begin(); it != mFreeSyncPool.end(); it++) {
+ auto queue = it->first;
+ auto syncList = it->second;
+ while (syncList.size() > 0) {
+ VkSync sync = syncList.front();
+ syncList.pop_front();
vkFreeCommandBuffers(mDevice, mCommandPool[it->first], 1, &sync.command);
vkDestroyEvent(mDevice, sync.event, NULL);
vkDestroySemaphore(mDevice, sync.semaphore, NULL);
vkDestroyFence(mDevice, sync.fence, NULL);
}
+ }
- vkDestroyCommandPool(mDevice, mCommandPool[it->first], NULL);
+ // Free destroy the command pools
+ for (auto it = mCommandPool.begin(); it != mCommandPool.end(); it++) {
+ auto commandPool = it->second;
+ vkDestroyCommandPool(mDevice, commandPool, NULL);
+ }
+}
+
+void SwappyVkBase::reclaimSignaledFences(VkQueue queue) {
+ std::lock_guard<std::mutex> lock(mThreads[queue]->lock);
+ while (!mSignaledSyncs[queue].empty()) {
+ VkSync sync = mSignaledSyncs[queue].front();
+ mSignaledSyncs[queue].pop_front();
+ mFreeSyncPool[queue].push_back(sync);
}
}
bool SwappyVkBase::lastFrameIsCompleted(VkQueue queue) {
+ auto pipelineMode = mCommonBase.getCurrentPipelineMode();
std::lock_guard<std::mutex> lock(mThreads[queue]->lock);
- if (mPendingSync[queue].size() < MAX_PENDING_FENCES) {
- return true;
+ if (pipelineMode == SwappyCommon::PipelineMode::On) {
+ // We are in pipeline mode so we need to check the fence of frame N-1
+ return mWaitingSyncs[queue].size() < 2;
}
- VkSync& sync = mPendingSync[queue].front();
+ // We are not in pipeline mode so we need to check the fence the current frame. i.e. there
+ // are not unsignaled frames
+ return mWaitingSyncs[queue].empty();
- // Waiter thread updates the pending time when the fence has signaled.
- if (!sync.fenceSignaled) {
- return false;
- }
-
- mPendingSync[queue].pop_front();
- mFreeSync[queue].push_back(sync);
- return true;
}
VkResult SwappyVkBase::injectFence(VkQueue queue,
const VkPresentInfoKHR* pPresentInfo,
VkSemaphore* pSemaphore) {
+ reclaimSignaledFences(queue);
+
// If we cross the swap interval threshold, we don't pace at all.
// In this case we might not have a free fence, so just don't use the fence.
- if (mFreeSync[queue].size() == 0) {
+ if (mFreeSyncPool[queue].size() == 0) {
return VK_SUCCESS;
}
- VkSync sync = mFreeSync[queue].front();
- mFreeSync[queue].pop_front();
+ VkSync sync = mFreeSyncPool[queue].front();
+ mFreeSyncPool[queue].pop_front();
VkPipelineStageFlags pipe_stage_flags;
VkSubmitInfo submit_info;
@@ -245,8 +269,7 @@
*pSemaphore = sync.semaphore;
std::lock_guard<std::mutex> lock(mThreads[queue]->lock);
- sync.fenceSignaled = false;
- mPendingSync[queue].push_back(sync);
+ mWaitingSyncs[queue].push_back(sync);
mThreads[queue]->hasPendingWork = true;
mThreads[queue]->condition.notify_all();
@@ -257,6 +280,10 @@
mCommonBase.setAutoSwapInterval(enabled);
}
+void SwappyVkBase::setMaxAutoSwapIntervalNS(std::chrono::nanoseconds swapMaxNS) {
+ mCommonBase.setMaxAutoSwapIntervalNS(swapMaxNS);
+}
+
void SwappyVkBase::setAutoPipelineMode(bool enabled) {
mCommonBase.setAutoPipelineMode(enabled);
}
@@ -265,8 +292,7 @@
ThreadContext& thread = *mThreads[queue];
while (true) {
- std::list<VkSync>::iterator pendingSyncIterator;
- bool remainingSyncs = true;
+ bool waitingSyncsEmpty;
{
std::lock_guard<std::mutex> lock(thread.lock);
// Wait for new fence object
@@ -280,48 +306,40 @@
break;
}
- pendingSyncIterator = mPendingSync[queue].begin();
- while (pendingSyncIterator != mPendingSync[queue].end() &&
- pendingSyncIterator->fenceSignaled) {
- ++pendingSyncIterator;
- }
- remainingSyncs = pendingSyncIterator != mPendingSync[queue].end();
+ waitingSyncsEmpty = mWaitingSyncs[queue].empty();
}
- while (remainingSyncs) {
- VkSync *sync;
+ while (!waitingSyncsEmpty) {
+ VkSync sync;
{ // Get the sync object with a lock
std::lock_guard<std::mutex> lock(thread.lock);
- sync = &(*pendingSyncIterator);
+ sync = mWaitingSyncs[queue].front();
+ mWaitingSyncs[queue].pop_front();
}
+ gamesdk::ScopedTrace tracer("Swappy: GPU frame time");
const auto startTime = std::chrono::steady_clock::now();
- VkResult result = vkWaitForFences(mDevice, 1, &sync->fence, VK_TRUE, UINT64_MAX);
+ VkResult result = vkWaitForFences(mDevice, 1, &sync.fence, VK_TRUE,
+ mCommonBase.getFenceTimeout().count());
if (result) {
ALOGE("Failed to wait for fence %d", result);
}
- auto pendingTime = std::chrono::steady_clock::now() - startTime;
+ vkResetFences(mDevice, 1, &sync.fence);
+ mLastFenceTime = std::chrono::steady_clock::now() - startTime;
- vkResetFences(mDevice, 1, &sync->fence);
-
- { // Advance the iterator
+ // Move the sync object to the signaled list
+ {
std::lock_guard<std::mutex> lock(thread.lock);
- sync->pendingTime = pendingTime;
- sync->fenceSignaled = true;
- ++pendingSyncIterator;
- remainingSyncs = pendingSyncIterator != mPendingSync[queue].end();
+
+ mSignaledSyncs[queue].push_back(sync);
+ waitingSyncsEmpty = mWaitingSyncs[queue].empty();
}
}
}
}
std::chrono::nanoseconds SwappyVkBase::getLastFenceTime(VkQueue queue) {
- std::lock_guard<std::mutex> lock(mThreads[queue]->lock);
- // Last fence is either the first one pending or the last one that was free.
- if (mPendingSync[queue].size() && mPendingSync[queue].front().pendingTime != 0ns) {
- return mPendingSync[queue].begin()->pendingTime;
- }
- return mFreeSync[queue].back().pendingTime;
+ return mLastFenceTime;
}
void SwappyVkBase::setFenceTimeout(std::chrono::nanoseconds duration) {
diff --git a/src/swappy/vulkan/SwappyVkBase.h b/src/swappy/vulkan/SwappyVkBase.h
index cf1c109..cb6f43a 100644
--- a/src/swappy/vulkan/SwappyVkBase.h
+++ b/src/swappy/vulkan/SwappyVkBase.h
@@ -119,6 +119,8 @@
void setAutoSwapInterval(bool enabled);
void setAutoPipelineMode(bool enabled);
+ void setMaxAutoSwapIntervalNS(std::chrono::nanoseconds swapMaxNS);
+
void setFenceTimeout(std::chrono::nanoseconds duration);
std::chrono::nanoseconds getFenceTimeout() const;
@@ -130,8 +132,6 @@
VkSemaphore semaphore;
VkCommandBuffer command;
VkEvent event;
- bool fenceSignaled = false;
- std::chrono::nanoseconds pendingTime = {};
};
struct ThreadContext {
@@ -159,16 +159,26 @@
PFN_vkGetRefreshCycleDurationGOOGLE mpfnGetRefreshCycleDurationGOOGLE = nullptr;
PFN_vkGetPastPresentationTimingGOOGLE mpfnGetPastPresentationTimingGOOGLE = nullptr;
- std::map<VkQueue, std::list<VkSync>> mFreeSync;
- std::map<VkQueue, std::list<VkSync>> mPendingSync;
+ // Holds VKSync objects ready to be used
+ std::map<VkQueue, std::list<VkSync>> mFreeSyncPool;
+
+ // Holds VKSync objects queued and but signaled yet
+ std::map<VkQueue, std::list<VkSync>> mWaitingSyncs;
+
+ // Holds VKSync objects that were signaled
+ std::map<VkQueue, std::list<VkSync>> mSignaledSyncs;
+
std::map<VkQueue, VkCommandPool> mCommandPool;
std::map<VkQueue, std::unique_ptr<ThreadContext>> mThreads;
- static constexpr int MAX_PENDING_FENCES = 1;
+ static constexpr int MAX_PENDING_FENCES = 2;
+
+ std::atomic<std::chrono::nanoseconds> mLastFenceTime = {};
void initGoogExtension();
VkResult initializeVkSyncObjects(VkQueue queue, uint32_t queueFamilyIndex);
void destroyVkSyncObjects();
+ void reclaimSignaledFences(VkQueue queue);
bool lastFrameIsCompleted(VkQueue queue);
std::chrono::nanoseconds getLastFenceTime(VkQueue queue);
void waitForFenceThreadMain(VkQueue queue);
diff --git a/src/swappy/vulkan/SwappyVkGoogleDisplayTiming.cpp b/src/swappy/vulkan/SwappyVkGoogleDisplayTiming.cpp
index 321e451..c907977 100644
--- a/src/swappy/vulkan/SwappyVkGoogleDisplayTiming.cpp
+++ b/src/swappy/vulkan/SwappyVkGoogleDisplayTiming.cpp
@@ -75,7 +75,6 @@
.getPrevFrameGpuTime =
std::bind(&SwappyVkGoogleDisplayTiming::getLastFenceTime, this, queue),
};
- mCommonBase.onPreSwap(handlers);
VkSemaphore semaphore;
res = injectFence(queue, pPresentInfo, &semaphore);
@@ -84,6 +83,8 @@
return res;
}
+ mCommonBase.onPreSwap(handlers);
+
VkPresentTimeGOOGLE pPresentTimes[pPresentInfo->swapchainCount];
VkPresentInfoKHR replacementPresentInfo;
VkPresentTimesInfoGOOGLE presentTimesInfo;
diff --git a/src/swappy/vulkan/swappyVk_c.cpp b/src/swappy/vulkan/swappyVk_c.cpp
index b3c37fd..2aa365a 100644
--- a/src/swappy/vulkan/swappyVk_c.cpp
+++ b/src/swappy/vulkan/swappyVk_c.cpp
@@ -107,6 +107,12 @@
swappy.SetFenceTimeout(std::chrono::nanoseconds(fence_timeout_ns));
}
+void SwappyVk_setMaxAutoSwapIntervalNS(uint64_t max_swap_ns) {
+ TRACE_CALL();
+ swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
+ swappy.SetMaxAutoSwapIntervalNS(std::chrono::nanoseconds(max_swap_ns));
+}
+
uint64_t SwappyVk_getFenceTimeoutNS() {
TRACE_CALL();
swappy::SwappyVk& swappy = swappy::SwappyVk::getInstance();
diff --git a/src/tuningfork/crash_handler.cpp b/src/tuningfork/crash_handler.cpp
index 1b5e48b..7d54b90 100644
--- a/src/tuningfork/crash_handler.cpp
+++ b/src/tuningfork/crash_handler.cpp
@@ -14,6 +14,16 @@
* limitations under the License.
*/
+#include "crash_handler.h"
+
+#if __ANDROID_API__ < 16
+namespace tuningfork {
+ CrashHandler::CrashHandler() { }
+ CrashHandler::~CrashHandler() { }
+ void CrashHandler::Init(std::function<bool(void)> callback) { }
+}
+#else
+
#include <vector>
#include <pthread.h>
#include <sys/syscall.h>
@@ -23,7 +33,6 @@
#include <cstdlib>
#include <algorithm>
-#include "crash_handler.h"
#include "Log.h"
#define LOG_TAG "TFCrashHandler"
@@ -249,3 +258,4 @@
return true;
}
} // namespace tuningfork
+#endif
diff --git a/third_party/cube/app/CMakeLists.txt b/third_party/cube/app/CMakeLists.txt
index ca2777c..467b15a 100644
--- a/third_party/cube/app/CMakeLists.txt
+++ b/third_party/cube/app/CMakeLists.txt
@@ -34,11 +34,6 @@
)
endforeach()
-# Force export ANativeActivity_onCreate(),
-# Refer to: https://github.com/android-ndk/ndk/issues/381.
-set(CMAKE_SHARED_LINKER_FLAGS
- "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
-
include_directories( src/main/cpp )
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Werror -DVK_USE_PLATFORM_ANDROID_KHR")
@@ -50,9 +45,8 @@
cube.vert.inc
cube.frag.inc
- ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c
src/main/cpp/cube.c
- src/main/cpp/common/android_util.cpp
+ src/main/cpp/native-lib.c
src/main/cpp/common/vulkan_wrapper.cpp
)
@@ -63,8 +57,7 @@
${SHADERC_SRC}/third_party/spirv-tools/include
${SHADERC_SRC}/third_party/spirv-tools/include/spirv-tools
src/main/cpp/include
- src/main/cpp/common
- ${ANDROID_NDK}/sources/android/native_app_glue)
+ src/main/cpp/common)
target_link_libraries( native-lib
diff --git a/third_party/cube/app/build.gradle b/third_party/cube/app/build.gradle
index 248ac4a..a4c1801 100644
--- a/third_party/cube/app/build.gradle
+++ b/third_party/cube/app/build.gradle
@@ -68,4 +68,5 @@
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation project(':extras')
}
diff --git a/third_party/cube/app/src/main/AndroidManifest.xml b/third_party/cube/app/src/main/AndroidManifest.xml
index 9a311d9..ea51328 100644
--- a/third_party/cube/app/src/main/AndroidManifest.xml
+++ b/third_party/cube/app/src/main/AndroidManifest.xml
@@ -1,24 +1,22 @@
-<?xml version="1.0"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.Cube" android:versionCode="1" android:versionName="1.0">
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.samples.cube">
- <!-- Allow this app to read and write files (for use by tracing libraries). -->
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
-
- <!-- This .apk has no Java code itself, so set hasCode to false. -->
- <application android:label="@string/app_name" android:hasCode="false">
-
- <!-- Our activity is the built-in NativeActivity framework class.
- This will take care of integrating with our NDK code. -->
- <activity android:name="android.app.NativeActivity" android:label="@string/app_name" android:exported="true">
- <!-- Tell NativeActivity the name of or .so -->
- <meta-data android:name="android.app.lib_name" android:value="native-lib"/>
+ <application android:label="@string/app_name">
+ <activity
+ android:name=".CubeActivity"
+ android:launchMode="singleTop"
+ android:configChanges="orientation|screenSize"
+ android:screenOrientation="portrait">
<intent-filter>
- <action android:name="android.intent.action.MAIN"/>
- <category android:name="android.intent.category.LAUNCHER"/>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.SEND" />
+ <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/third_party/cube/app/src/main/cpp/cube.c b/third_party/cube/app/src/main/cpp/cube.c
index 7b7b250..3636fb5 100644
--- a/third_party/cube/app/src/main/cpp/cube.c
+++ b/third_party/cube/app/src/main/cpp/cube.c
@@ -255,6 +255,7 @@
float mvp[4][4];
float position[12 * 3][4];
float attr[12 * 3][4];
+ uint32_t gpu_workload;
};
//--------------------------------------------------------------------------------------
@@ -496,10 +497,19 @@
mat4x4 view_matrix;
mat4x4 model_matrix;
+ uint32_t gpu_workload;
+ uint64_t cpu_workload;
+
+ float scale;
float spin_angle;
float spin_increment;
+ float spin_speed;
bool pause;
+ bool draw_cmd_dirty;
+
+ struct vktexcube_vs_uniform uniform;
+
VkShaderModule vert_shader_module;
VkShaderModule frag_shader_module;
@@ -529,6 +539,8 @@
JavaVM* vm;
jobject jactivity;
} swappy_init_data;
+
+ bool tracer_injected;
};
VKAPI_ATTR VkBool32 VKAPI_CALL debug_messenger_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
@@ -944,23 +956,33 @@
}
void demo_update_data_buffer(struct demo *demo) {
- mat4x4 MVP, Model, VP;
- int matrixSize = sizeof(MVP);
+ mat4x4 Model, VP;
uint8_t *pData;
VkResult U_ASSERT_ONLY err;
mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix);
- // Rotate around the Y axis
+ // Set scale.
+ mat4x4_identity(demo->model_matrix);
+ demo->model_matrix[0][0] = demo->scale;
+ demo->model_matrix[1][1] = demo->scale;
+ demo->model_matrix[2][2] = demo->scale;
+
+ uint64_t long_time = getTimeInNanoseconds();
+ long_time = ((long_time << 16) >> 16) >> 18; // Keep only middle bits.
+ demo->spin_angle = long_time * demo->spin_speed;
+
+ // Rotate around the Y axis.
mat4x4_dup(Model, demo->model_matrix);
- mat4x4_rotate(demo->model_matrix, Model, 0.0f, 1.0f, 0.0f, (float)degreesToRadians(demo->spin_angle));
- mat4x4_mul(MVP, VP, demo->model_matrix);
+ mat4x4_rotate(demo->model_matrix, Model, 0.0f, 1.0f, 0.0f, demo->spin_angle);
+ mat4x4_mul(demo->uniform.mvp, VP, demo->model_matrix);
+
+ demo->uniform.gpu_workload = demo->gpu_workload;
err = vkMapMemory(demo->device, demo->swapchain_image_resources[demo->current_buffer].uniform_memory, 0, VK_WHOLE_SIZE, 0,
(void **)&pData);
assert(!err);
-
- memcpy(pData, (const void *)&MVP[0][0], matrixSize);
+ memcpy(pData, &demo->uniform, sizeof(demo->uniform));
vkUnmapMemory(demo->device, demo->swapchain_image_resources[demo->current_buffer].uniform_memory);
}
@@ -1081,6 +1103,17 @@
}
}
+static void update_draw_cmd(struct demo *demo) {
+ // Rerecord draw cmds.
+ vkDeviceWaitIdle(demo->device);
+ uint32_t current_buffer = demo->current_buffer;
+ for (uint32_t i = 0; i < demo->swapchainImageCount; i++) {
+ demo->current_buffer = i;
+ demo_draw_build_cmd(demo, demo->swapchain_image_resources[i].cmd);
+ }
+ demo->current_buffer = current_buffer;
+}
+
static void demo_draw(struct demo *demo) {
VkResult U_ASSERT_ONLY err;
@@ -1093,6 +1126,11 @@
vkResetFences(demo->device, 1, &demo->fences[demo->frame_index]);
+ if (demo->draw_cmd_dirty) {
+ update_draw_cmd(demo);
+ demo->draw_cmd_dirty = false;
+ }
+
do {
// Get the index of the next available swapchain image:
logEvent(EVENT_CALLING_ANI);
@@ -1514,14 +1552,17 @@
// Refresh rate of this demo is locked to 30 FPS.
SwappyVk_setSwapIntervalNS(demo->device, demo->swapchain, SWAPPY_SWAP_30FPS);
- SwappyTracer tracer;
- tracer.preWait = swappy_trace_test_preWait;
- tracer.postWait = swappy_trace_test_postWait;
- tracer.preSwapBuffers = swappy_trace_test_preSwapBuffers;
- tracer.postSwapBuffers = swappy_trace_test_postSwapBuffers;
- tracer.startFrame = swappy_trace_test_startFrame;
- tracer.swapIntervalChanged = swappy_trace_test_swapIntervalChanged;
- SwappyVk_injectTracer(&tracer);
+ if (!demo->tracer_injected) {
+ SwappyTracer tracer;
+ tracer.preWait = swappy_trace_test_preWait;
+ tracer.postWait = swappy_trace_test_postWait;
+ tracer.preSwapBuffers = swappy_trace_test_preSwapBuffers;
+ tracer.postSwapBuffers = swappy_trace_test_postSwapBuffers;
+ tracer.startFrame = swappy_trace_test_startFrame;
+ tracer.swapIntervalChanged = swappy_trace_test_swapIntervalChanged;
+ SwappyVk_injectTracer(&tracer);
+ demo->tracer_injected = true;
+ }
if (NULL != presentModes) {
free(presentModes);
@@ -1876,31 +1917,29 @@
VkMemoryRequirements mem_reqs;
VkMemoryAllocateInfo mem_alloc;
uint8_t *pData;
- mat4x4 MVP, VP;
+ mat4x4 VP;
VkResult U_ASSERT_ONLY err;
bool U_ASSERT_ONLY pass;
- struct vktexcube_vs_uniform data;
mat4x4_mul(VP, demo->projection_matrix, demo->view_matrix);
- mat4x4_mul(MVP, VP, demo->model_matrix);
- memcpy(data.mvp, MVP, sizeof(MVP));
- // dumpMatrix("MVP", MVP);
+ mat4x4_mul(demo->uniform.mvp, VP, demo->model_matrix);
+ // dumpMatrix("MVP", demo->uniform.mvp);
for (unsigned int i = 0; i < 12 * 3; i++) {
- data.position[i][0] = g_vertex_buffer_data[i * 3];
- data.position[i][1] = g_vertex_buffer_data[i * 3 + 1];
- data.position[i][2] = g_vertex_buffer_data[i * 3 + 2];
- data.position[i][3] = 1.0f;
- data.attr[i][0] = g_uv_buffer_data[2 * i];
- data.attr[i][1] = g_uv_buffer_data[2 * i + 1];
- data.attr[i][2] = 0;
- data.attr[i][3] = 0;
+ demo->uniform.position[i][0] = g_vertex_buffer_data[i * 3];
+ demo->uniform.position[i][1] = g_vertex_buffer_data[i * 3 + 1];
+ demo->uniform.position[i][2] = g_vertex_buffer_data[i * 3 + 2];
+ demo->uniform.position[i][3] = 1.0f;
+ demo->uniform.attr[i][0] = g_uv_buffer_data[2 * i];
+ demo->uniform.attr[i][1] = g_uv_buffer_data[2 * i + 1];
+ demo->uniform.attr[i][2] = 0;
+ demo->uniform.attr[i][3] = 0;
}
memset(&buf_info, 0, sizeof(buf_info));
buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buf_info.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
- buf_info.size = sizeof(data);
+ buf_info.size = sizeof(demo->uniform);
for (unsigned int i = 0; i < demo->swapchainImageCount; i++) {
err = vkCreateBuffer(demo->device, &buf_info, NULL, &demo->swapchain_image_resources[i].uniform_buffer);
@@ -1924,7 +1963,7 @@
err = vkMapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, 0, VK_WHOLE_SIZE, 0, (void **)&pData);
assert(!err);
- memcpy(pData, &data, sizeof data);
+ memcpy(pData, &demo->uniform, sizeof(demo->uniform));
vkUnmapMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory);
@@ -1941,7 +1980,7 @@
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
- .stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
+ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
.pImmutableSamplers = NULL,
},
[1] =
@@ -2413,7 +2452,6 @@
vkDestroyFramebuffer(demo->device, demo->swapchain_image_resources[i].framebuffer, NULL);
}
vkDestroyDescriptorPool(demo->device, demo->desc_pool, NULL);
-
vkDestroyPipeline(demo->device, demo->pipeline, NULL);
vkDestroyPipelineCache(demo->device, demo->pipelineCache, NULL);
vkDestroyRenderPass(demo->device, demo->render_pass, NULL);
@@ -2426,8 +2464,10 @@
vkFreeMemory(demo->device, demo->textures[i].mem, NULL);
vkDestroySampler(demo->device, demo->textures[i].sampler, NULL);
}
+
SwappyVk_destroySwapchain(demo->device, demo->swapchain);
demo->fpDestroySwapchainKHR(demo->device, demo->swapchain, NULL);
+ demo->swapchain = VK_NULL_HANDLE;
vkDestroyImageView(demo->device, demo->depth.view, NULL);
vkDestroyImage(demo->device, demo->depth.image, NULL);
@@ -2439,6 +2479,7 @@
vkDestroyBuffer(demo->device, demo->swapchain_image_resources[i].uniform_buffer, NULL);
vkFreeMemory(demo->device, demo->swapchain_image_resources[i].uniform_memory, NULL);
}
+
free(demo->swapchain_image_resources);
free(demo->queue_props);
vkDestroyCommandPool(demo->device, demo->cmd_pool, NULL);
@@ -2447,6 +2488,7 @@
vkDestroyCommandPool(demo->device, demo->present_cmd_pool, NULL);
}
}
+
vkDeviceWaitIdle(demo->device);
vkDestroyDevice(demo->device, NULL);
if (demo->validate) {
@@ -2533,10 +2575,11 @@
demo_prepare(demo);
}
+#if defined(VK_USE_PLATFORM_WIN32_KHR)
+
// On MS-Windows, make this a global, so it's available to WndProc()
struct demo demo;
-#if defined(VK_USE_PLATFORM_WIN32_KHR)
static void demo_run(struct demo *demo) {
if (!demo->prepared) return;
@@ -3821,7 +3864,6 @@
vec3 origin = {0, 0, 0};
vec3 up = {0.0f, 1.0f, 0.0};
- memset(demo, 0, sizeof(*demo));
demo->presentMode = VK_PRESENT_MODE_FIFO_KHR;
demo->frameCount = INT32_MAX;
@@ -3895,10 +3937,15 @@
demo->width = 500;
demo->height = 500;
+ demo->scale = 1.0f;
+
demo->spin_angle = 4.0f;
demo->spin_increment = 0.2f;
+ demo->spin_speed = 0.0005f;
demo->pause = false;
+ demo->draw_cmd_dirty = true;
+
mat4x4_perspective(demo->projection_matrix, (float)degreesToRadians(45.0f), 1.0f, 0.1f, 100.0f);
mat4x4_look_at(demo->view_matrix, eye, origin, up);
mat4x4_identity(demo->model_matrix);
@@ -4000,99 +4047,60 @@
}
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
+#include "cube.h"
#include <android/log.h>
-#include <android_native_app_glue.h>
-#include "android_util.h"
static bool initialized = false;
static bool active = false;
-struct demo demo;
+struct demo demo_;
-static int32_t processInput(struct android_app *app, AInputEvent *event) { return 0; }
-
-static void processCommand(struct android_app *app, int32_t cmd) {
- switch (cmd) {
- case APP_CMD_INIT_WINDOW: {
- if (app->window) {
- // We're getting a new window. If the app is starting up, we
- // need to initialize. If the app has already been
- // initialized, that means that we lost our previous window,
- // which means that we have a lot of work to do. At a minimum,
- // we need to destroy the swapchain and surface associated with
- // the old window, and create a new surface and swapchain.
- // However, since there are a lot of other objects/state that
- // is tied to the swapchain, it's easiest to simply cleanup and
- // start over (i.e. use a brute-force approach of re-starting
- // the app)
- if (demo.prepared) {
- demo_cleanup(&demo);
- }
-
- // Parse Intents into argc, argv
- // Use the following key to send arguments, i.e.
- // --es args "--validate"
- const char key[] = "args";
- char *appTag = (char *)APP_SHORT_NAME;
- int argc = 0;
- char **argv = get_args(app, key, appTag, &argc);
-
- __android_log_print(ANDROID_LOG_INFO, appTag, "argc = %i", argc);
- for (int i = 0; i < argc; i++) __android_log_print(ANDROID_LOG_INFO, appTag, "argv[%i] = %s", i, argv[i]);
-
- demo_init(&demo, argc, argv);
- demo.swappy_init_data.vm = app->activity->vm;
- demo.swappy_init_data.jactivity = app->activity->clazz;
-
- // Free the argv malloc'd by get_args
- for (int i = 0; i < argc; i++) free(argv[i]);
-
- demo.window = (void *)app->window;
- demo_init_vk_swapchain(&demo);
- demo_prepare(&demo);
- initialized = true;
- }
- break;
- }
- case APP_CMD_GAINED_FOCUS: {
- active = true;
- break;
- }
- case APP_CMD_LOST_FOCUS: {
- active = false;
- break;
- }
- }
+void update_gpu_workload(int32_t new_workload) {
+ demo_.gpu_workload = new_workload;
+ demo_.draw_cmd_dirty = true;
}
-void android_main(struct android_app *app) {
-#ifdef ANDROID
+void update_cpu_workload(int32_t new_workload) {
+ demo_.cpu_workload = (uint64_t)100*(uint64_t)new_workload;
+}
+
+void main_loop(struct android_app_state *app) {
int vulkanSupport = InitVulkan();
if (vulkanSupport == 0) return;
-#endif
- demo.prepared = false;
+ demo_.prepared = false;
- app->onAppCmd = processCommand;
- app->onInputEvent = processInput;
-
- while (1) {
- int events;
- struct android_poll_source *source;
- while (ALooper_pollAll(active ? 0 : -1, NULL, &events, (void **)&source) >= 0) {
- if (source) {
- source->process(app, source);
- }
-
- if (app->destroyRequested != 0) {
- demo_cleanup(&demo);
- return;
- }
+ while(true) {
+ if (!initialized) {
+ demo_.window = app->window;
+ demo_.swappy_init_data.vm = app->vm;
+ demo_.swappy_init_data.jactivity = app->clazz;
+ demo_init(&demo_, 0, NULL);
+ demo_init_vk_swapchain(&demo_);
+ demo_prepare(&demo_);
+ initialized = true;
+ active = true;
}
+
+ if (app->destroyRequested != 0) {
+ JavaVM* vm = app->vm;
+ (*vm)->DetachCurrentThread(vm);
+
+ demo_cleanup(&demo_);
+ initialized = false;
+ active = false;
+
+ return;
+ }
+
if (initialized && active) {
- demo_run(&demo);
+ demo_run(&demo_);
}
- }
+
+ uint64_t count = 0;
+ while(count++ < demo_.cpu_workload) {}
+ }
}
+
#else
int main(int argc, char **argv) {
struct demo demo;
diff --git a/third_party/cube/app/src/main/cpp/cube.frag b/third_party/cube/app/src/main/cpp/cube.frag
index 5bf6507..edaaecc 100644
--- a/third_party/cube/app/src/main/cpp/cube.frag
+++ b/third_party/cube/app/src/main/cpp/cube.frag
@@ -21,6 +21,13 @@
#version 400
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
+layout(std140, binding = 0) uniform buf {
+ mat4 MVP;
+ vec4 position[12*3];
+ vec4 attr[12*3];
+ highp int gpu_workload;
+} ubuf;
+
layout (binding = 1) uniform sampler2D tex;
layout (location = 0) in vec4 texcoord;
@@ -30,9 +37,16 @@
const vec3 lightDir= vec3(0.424, 0.566, 0.707);
void main() {
+ float tint = 0;
+
+ for(int i = 0; i < ubuf.gpu_workload; ++i) {
+ tint += 0.0001;
+ }
+
vec3 dX = dFdx(frag_pos);
vec3 dY = dFdy(frag_pos);
vec3 normal = normalize(cross(dX,dY));
float light = max(0.0, dot(lightDir, normal));
uFragColor = light * texture(tex, texcoord.xy);
+ uFragColor.x += tint;
}
diff --git a/third_party/cube/app/src/main/cpp/cube.vert b/third_party/cube/app/src/main/cpp/cube.vert
index 6338032..1c89080 100644
--- a/third_party/cube/app/src/main/cpp/cube.vert
+++ b/third_party/cube/app/src/main/cpp/cube.vert
@@ -30,7 +30,7 @@
layout (location = 0) out vec4 texcoord;
layout (location = 1) out vec3 frag_pos;
-void main()
+void main()
{
texcoord = ubuf.attr[gl_VertexIndex];
gl_Position = ubuf.MVP * ubuf.position[gl_VertexIndex];
diff --git a/third_party/cube/app/src/main/cpp/include/cube.frag.inc b/third_party/cube/app/src/main/cpp/include/cube.frag.inc
index cbfbf5a..7c95590 100644
--- a/third_party/cube/app/src/main/cpp/include/cube.frag.inc
+++ b/third_party/cube/app/src/main/cpp/include/cube.frag.inc
@@ -1,9 +1,9 @@
-0x07230203,0x00010000,0x000d0006,0x00000030,
+0x07230203,0x00010000,0x000d0007,0x00000056,
0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,
0x0008000f,0x00000004,0x00000004,0x6e69616d,
-0x00000000,0x0000000b,0x00000022,0x0000002a,
+0x00000000,0x0000002d,0x00000041,0x00000049,
0x00030010,0x00000004,0x00000007,0x00030003,
0x00000002,0x00000190,0x00090004,0x415f4c47,
0x735f4252,0x72617065,0x5f657461,0x64616873,
@@ -15,71 +15,137 @@
0x69746365,0x00006576,0x00080004,0x475f4c47,
0x4c474f4f,0x6e695f45,0x64756c63,0x69645f65,
0x74636572,0x00657669,0x00040005,0x00000004,
-0x6e69616d,0x00000000,0x00030005,0x00000009,
-0x00005864,0x00050005,0x0000000b,0x67617266,
-0x736f705f,0x00000000,0x00030005,0x0000000e,
-0x00005964,0x00040005,0x00000011,0x6d726f6e,
-0x00006c61,0x00040005,0x00000017,0x6867696c,
-0x00000074,0x00050005,0x00000022,0x61724675,
-0x6c6f4367,0x0000726f,0x00030005,0x00000027,
-0x00786574,0x00050005,0x0000002a,0x63786574,
-0x64726f6f,0x00000000,0x00040047,0x0000000b,
-0x0000001e,0x00000001,0x00040047,0x00000022,
-0x0000001e,0x00000000,0x00040047,0x00000027,
-0x00000022,0x00000000,0x00040047,0x00000027,
-0x00000021,0x00000001,0x00040047,0x0000002a,
+0x6e69616d,0x00000000,0x00040005,0x00000008,
+0x746e6974,0x00000000,0x00030005,0x0000000c,
+0x00000069,0x00030005,0x0000001a,0x00667562,
+0x00040006,0x0000001a,0x00000000,0x0050564d,
+0x00060006,0x0000001a,0x00000001,0x69736f70,
+0x6e6f6974,0x00000000,0x00050006,0x0000001a,
+0x00000002,0x72747461,0x00000000,0x00070006,
+0x0000001a,0x00000003,0x5f757067,0x6b726f77,
+0x64616f6c,0x00000000,0x00040005,0x0000001c,
+0x66756275,0x00000000,0x00030005,0x0000002b,
+0x00005864,0x00050005,0x0000002d,0x67617266,
+0x736f705f,0x00000000,0x00030005,0x00000030,
+0x00005964,0x00040005,0x00000033,0x6d726f6e,
+0x00006c61,0x00040005,0x00000038,0x6867696c,
+0x00000074,0x00050005,0x00000041,0x61724675,
+0x6c6f4367,0x0000726f,0x00030005,0x00000046,
+0x00786574,0x00050005,0x00000049,0x63786574,
+0x64726f6f,0x00000000,0x00040047,0x00000018,
+0x00000006,0x00000010,0x00040047,0x00000019,
+0x00000006,0x00000010,0x00040048,0x0000001a,
+0x00000000,0x00000005,0x00050048,0x0000001a,
+0x00000000,0x00000023,0x00000000,0x00050048,
+0x0000001a,0x00000000,0x00000007,0x00000010,
+0x00050048,0x0000001a,0x00000001,0x00000023,
+0x00000040,0x00050048,0x0000001a,0x00000002,
+0x00000023,0x00000280,0x00050048,0x0000001a,
+0x00000003,0x00000023,0x000004c0,0x00030047,
+0x0000001a,0x00000002,0x00040047,0x0000001c,
+0x00000022,0x00000000,0x00040047,0x0000001c,
+0x00000021,0x00000000,0x00040047,0x0000002d,
+0x0000001e,0x00000001,0x00040047,0x00000041,
+0x0000001e,0x00000000,0x00040047,0x00000046,
+0x00000022,0x00000000,0x00040047,0x00000046,
+0x00000021,0x00000001,0x00040047,0x00000049,
0x0000001e,0x00000000,0x00020013,0x00000002,
0x00030021,0x00000003,0x00000002,0x00030016,
-0x00000006,0x00000020,0x00040017,0x00000007,
-0x00000006,0x00000003,0x00040020,0x00000008,
-0x00000007,0x00000007,0x00040020,0x0000000a,
-0x00000001,0x00000007,0x0004003b,0x0000000a,
-0x0000000b,0x00000001,0x00040020,0x00000016,
+0x00000006,0x00000020,0x00040020,0x00000007,
0x00000007,0x00000006,0x0004002b,0x00000006,
-0x00000018,0x00000000,0x0004002b,0x00000006,
-0x00000019,0x3ed91687,0x0004002b,0x00000006,
-0x0000001a,0x3f10e560,0x0004002b,0x00000006,
-0x0000001b,0x3f34fdf4,0x0006002c,0x00000007,
-0x0000001c,0x00000019,0x0000001a,0x0000001b,
-0x00040017,0x00000020,0x00000006,0x00000004,
-0x00040020,0x00000021,0x00000003,0x00000020,
-0x0004003b,0x00000021,0x00000022,0x00000003,
-0x00090019,0x00000024,0x00000006,0x00000001,
+0x00000009,0x00000000,0x00040015,0x0000000a,
+0x00000020,0x00000001,0x00040020,0x0000000b,
+0x00000007,0x0000000a,0x0004002b,0x0000000a,
+0x0000000d,0x00000000,0x00040017,0x00000014,
+0x00000006,0x00000004,0x00040018,0x00000015,
+0x00000014,0x00000004,0x00040015,0x00000016,
+0x00000020,0x00000000,0x0004002b,0x00000016,
+0x00000017,0x00000024,0x0004001c,0x00000018,
+0x00000014,0x00000017,0x0004001c,0x00000019,
+0x00000014,0x00000017,0x0006001e,0x0000001a,
+0x00000015,0x00000018,0x00000019,0x0000000a,
+0x00040020,0x0000001b,0x00000002,0x0000001a,
+0x0004003b,0x0000001b,0x0000001c,0x00000002,
+0x0004002b,0x0000000a,0x0000001d,0x00000003,
+0x00040020,0x0000001e,0x00000002,0x0000000a,
+0x00020014,0x00000021,0x0004002b,0x00000006,
+0x00000023,0x38d1b717,0x0004002b,0x0000000a,
+0x00000027,0x00000001,0x00040017,0x00000029,
+0x00000006,0x00000003,0x00040020,0x0000002a,
+0x00000007,0x00000029,0x00040020,0x0000002c,
+0x00000001,0x00000029,0x0004003b,0x0000002c,
+0x0000002d,0x00000001,0x0004002b,0x00000006,
+0x00000039,0x3ed91687,0x0004002b,0x00000006,
+0x0000003a,0x3f10e560,0x0004002b,0x00000006,
+0x0000003b,0x3f34fdf4,0x0006002c,0x00000029,
+0x0000003c,0x00000039,0x0000003a,0x0000003b,
+0x00040020,0x00000040,0x00000003,0x00000014,
+0x0004003b,0x00000040,0x00000041,0x00000003,
+0x00090019,0x00000043,0x00000006,0x00000001,
0x00000000,0x00000000,0x00000000,0x00000001,
-0x00000000,0x0003001b,0x00000025,0x00000024,
-0x00040020,0x00000026,0x00000000,0x00000025,
-0x0004003b,0x00000026,0x00000027,0x00000000,
-0x00040020,0x00000029,0x00000001,0x00000020,
-0x0004003b,0x00000029,0x0000002a,0x00000001,
-0x00040017,0x0000002b,0x00000006,0x00000002,
+0x00000000,0x0003001b,0x00000044,0x00000043,
+0x00040020,0x00000045,0x00000000,0x00000044,
+0x0004003b,0x00000045,0x00000046,0x00000000,
+0x00040020,0x00000048,0x00000001,0x00000014,
+0x0004003b,0x00000048,0x00000049,0x00000001,
+0x00040017,0x0000004a,0x00000006,0x00000002,
+0x0004002b,0x00000016,0x00000050,0x00000000,
+0x00040020,0x00000051,0x00000003,0x00000006,
0x00050036,0x00000002,0x00000004,0x00000000,
0x00000003,0x000200f8,0x00000005,0x0004003b,
-0x00000008,0x00000009,0x00000007,0x0004003b,
-0x00000008,0x0000000e,0x00000007,0x0004003b,
-0x00000008,0x00000011,0x00000007,0x0004003b,
-0x00000016,0x00000017,0x00000007,0x0004003d,
-0x00000007,0x0000000c,0x0000000b,0x000400cf,
-0x00000007,0x0000000d,0x0000000c,0x0003003e,
-0x00000009,0x0000000d,0x0004003d,0x00000007,
-0x0000000f,0x0000000b,0x000400d0,0x00000007,
-0x00000010,0x0000000f,0x0003003e,0x0000000e,
-0x00000010,0x0004003d,0x00000007,0x00000012,
-0x00000009,0x0004003d,0x00000007,0x00000013,
-0x0000000e,0x0007000c,0x00000007,0x00000014,
-0x00000001,0x00000044,0x00000012,0x00000013,
-0x0006000c,0x00000007,0x00000015,0x00000001,
-0x00000045,0x00000014,0x0003003e,0x00000011,
-0x00000015,0x0004003d,0x00000007,0x0000001d,
-0x00000011,0x00050094,0x00000006,0x0000001e,
-0x0000001c,0x0000001d,0x0007000c,0x00000006,
-0x0000001f,0x00000001,0x00000028,0x00000018,
-0x0000001e,0x0003003e,0x00000017,0x0000001f,
-0x0004003d,0x00000006,0x00000023,0x00000017,
-0x0004003d,0x00000025,0x00000028,0x00000027,
-0x0004003d,0x00000020,0x0000002c,0x0000002a,
-0x0007004f,0x0000002b,0x0000002d,0x0000002c,
-0x0000002c,0x00000000,0x00000001,0x00050057,
-0x00000020,0x0000002e,0x00000028,0x0000002d,
-0x0005008e,0x00000020,0x0000002f,0x0000002e,
-0x00000023,0x0003003e,0x00000022,0x0000002f,
+0x00000007,0x00000008,0x00000007,0x0004003b,
+0x0000000b,0x0000000c,0x00000007,0x0004003b,
+0x0000002a,0x0000002b,0x00000007,0x0004003b,
+0x0000002a,0x00000030,0x00000007,0x0004003b,
+0x0000002a,0x00000033,0x00000007,0x0004003b,
+0x00000007,0x00000038,0x00000007,0x0003003e,
+0x00000008,0x00000009,0x0003003e,0x0000000c,
+0x0000000d,0x000200f9,0x0000000e,0x000200f8,
+0x0000000e,0x000400f6,0x00000010,0x00000011,
+0x00000000,0x000200f9,0x00000012,0x000200f8,
+0x00000012,0x0004003d,0x0000000a,0x00000013,
+0x0000000c,0x00050041,0x0000001e,0x0000001f,
+0x0000001c,0x0000001d,0x0004003d,0x0000000a,
+0x00000020,0x0000001f,0x000500b1,0x00000021,
+0x00000022,0x00000013,0x00000020,0x000400fa,
+0x00000022,0x0000000f,0x00000010,0x000200f8,
+0x0000000f,0x0004003d,0x00000006,0x00000024,
+0x00000008,0x00050081,0x00000006,0x00000025,
+0x00000024,0x00000023,0x0003003e,0x00000008,
+0x00000025,0x000200f9,0x00000011,0x000200f8,
+0x00000011,0x0004003d,0x0000000a,0x00000026,
+0x0000000c,0x00050080,0x0000000a,0x00000028,
+0x00000026,0x00000027,0x0003003e,0x0000000c,
+0x00000028,0x000200f9,0x0000000e,0x000200f8,
+0x00000010,0x0004003d,0x00000029,0x0000002e,
+0x0000002d,0x000400cf,0x00000029,0x0000002f,
+0x0000002e,0x0003003e,0x0000002b,0x0000002f,
+0x0004003d,0x00000029,0x00000031,0x0000002d,
+0x000400d0,0x00000029,0x00000032,0x00000031,
+0x0003003e,0x00000030,0x00000032,0x0004003d,
+0x00000029,0x00000034,0x0000002b,0x0004003d,
+0x00000029,0x00000035,0x00000030,0x0007000c,
+0x00000029,0x00000036,0x00000001,0x00000044,
+0x00000034,0x00000035,0x0006000c,0x00000029,
+0x00000037,0x00000001,0x00000045,0x00000036,
+0x0003003e,0x00000033,0x00000037,0x0004003d,
+0x00000029,0x0000003d,0x00000033,0x00050094,
+0x00000006,0x0000003e,0x0000003c,0x0000003d,
+0x0007000c,0x00000006,0x0000003f,0x00000001,
+0x00000028,0x00000009,0x0000003e,0x0003003e,
+0x00000038,0x0000003f,0x0004003d,0x00000006,
+0x00000042,0x00000038,0x0004003d,0x00000044,
+0x00000047,0x00000046,0x0004003d,0x00000014,
+0x0000004b,0x00000049,0x0007004f,0x0000004a,
+0x0000004c,0x0000004b,0x0000004b,0x00000000,
+0x00000001,0x00050057,0x00000014,0x0000004d,
+0x00000047,0x0000004c,0x0005008e,0x00000014,
+0x0000004e,0x0000004d,0x00000042,0x0003003e,
+0x00000041,0x0000004e,0x0004003d,0x00000006,
+0x0000004f,0x00000008,0x00050041,0x00000051,
+0x00000052,0x00000041,0x00000050,0x0004003d,
+0x00000006,0x00000053,0x00000052,0x00050081,
+0x00000006,0x00000054,0x00000053,0x0000004f,
+0x00050041,0x00000051,0x00000055,0x00000041,
+0x00000050,0x0003003e,0x00000055,0x00000054,
0x000100fd,0x00010038
diff --git a/third_party/cube/app/src/main/cpp/include/cube.h b/third_party/cube/app/src/main/cpp/include/cube.h
new file mode 100644
index 0000000..58e4900
--- /dev/null
+++ b/third_party/cube/app/src/main/cpp/include/cube.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include <android/log.h>
+#include <android/looper.h>
+#include <android/native_activity.h>
+#include <stdbool.h>
+
+// Struct for passing state from java app to native app.
+struct android_app_state {
+ ANativeWindow* window;
+ JavaVM* vm;
+ jobject clazz;
+ bool running;
+ bool destroyRequested;
+};
+
+// Start the cube application's render loop.
+void main_loop(struct android_app_state*);
+
+// Update the amount of GPU work done each frame.
+void update_gpu_workload(int32_t new_workload);
+
+// Update the amount of CPU work done each frame.
+void update_cpu_workload(int32_t new_workload);
diff --git a/third_party/cube/app/src/main/cpp/include/cube.vert.inc b/third_party/cube/app/src/main/cpp/include/cube.vert.inc
index 3828d39..55c5fae 100644
--- a/third_party/cube/app/src/main/cpp/include/cube.vert.inc
+++ b/third_party/cube/app/src/main/cpp/include/cube.vert.inc
@@ -1,4 +1,4 @@
-0x07230203,0x00010000,0x000d0006,0x0000002f,
+0x07230203,0x00010000,0x000d0007,0x0000002f,
0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,
0x00000000,0x0003000e,0x00000000,0x00000001,
diff --git a/third_party/cube/app/src/main/cpp/native-lib.c b/third_party/cube/app/src/main/cpp/native-lib.c
new file mode 100644
index 0000000..e5c2737
--- /dev/null
+++ b/third_party/cube/app/src/main/cpp/native-lib.c
@@ -0,0 +1,49 @@
+#include <jni.h>
+#include <android/native_window_jni.h>
+#include <pthread.h>
+
+#include "cube.h"
+
+static struct android_app_state state;
+static pthread_t thread;
+
+static void *startCubes(void *state_void_ptr)
+{
+ struct android_app_state* state = (struct android_app_state*)state_void_ptr;
+ state->running = true;
+ main_loop(state);
+ state->running = false;
+ state->destroyRequested = false;
+ return NULL;
+}
+
+
+JNIEXPORT void JNICALL
+Java_com_samples_cube_CubeActivity_nStartCube(JNIEnv *env, jobject clazz, jobject surface) {
+ if (!surface || state.running) {
+ return;
+ }
+ state.window = ANativeWindow_fromSurface(env, surface);
+ state.clazz = (jobject) (*env)->NewGlobalRef(env, clazz);
+ (*env)->GetJavaVM(env, &state.vm);
+
+ pthread_create(&thread, NULL, startCubes, &state);
+}
+
+JNIEXPORT void JNICALL
+Java_com_samples_cube_CubeActivity_nStopCube(JNIEnv *env, jobject clazz) {
+ if (state.running) {
+ state.destroyRequested = true;
+ pthread_join(thread, NULL);
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_com_samples_cube_CubeActivity_nUpdateGpuWorkload(JNIEnv *env, jobject clazz, jint new_workload) {
+ update_gpu_workload(new_workload);
+}
+
+JNIEXPORT void JNICALL
+Java_com_samples_cube_CubeActivity_nUpdateCpuWorkload(JNIEnv *env, jobject clazz, jint new_workload) {
+ update_cpu_workload(new_workload);
+}
diff --git a/third_party/cube/app/src/main/java/com/samples/cube/CubeActivity.java b/third_party/cube/app/src/main/java/com/samples/cube/CubeActivity.java
new file mode 100644
index 0000000..ca1244d
--- /dev/null
+++ b/third_party/cube/app/src/main/java/com/samples/cube/CubeActivity.java
@@ -0,0 +1,208 @@
+package com.samples.cube;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.View.OnTouchListener;
+import android.view.Window;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+public class CubeActivity extends Activity implements SurfaceHolder.Callback {
+
+ private static final String GPU_WORKLOAD = "com.samples.GPU_WORKLOAD";
+ private static final String CPU_WORKLOAD = "com.samples.CPU_WORKLOAD";
+ private static final String APP_NAME = "CubeActivity";
+
+ private LinearLayout settingsLayout;
+
+ private SeekBar gpuWorkSeekBar;
+ private TextView gpuWorkText;
+ private SeekBar cpuWorkSeekBar;
+ private TextView cpuWorkText;
+
+ // Used to load the 'native-lib' library on application startup.
+ static {
+ try {
+ System.loadLibrary("native-lib");
+ } catch (Exception e) {
+ Log.e(APP_NAME, "Native code library failed to load.\n" + e);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(R.layout.activity_cube);
+ settingsLayout = findViewById(R.id.settingsLayout);
+
+ setupGpuWorkSeekBar();
+ setupCpuWorkSeekBar();
+
+ SurfaceView surfaceView = findViewById(R.id.surface_view);
+ surfaceView.getHolder().addCallback(this);
+ surfaceView.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View view, MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+ toggleOptionsPanel();
+ }
+ return true;
+ }
+ });
+
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ String type = intent.getType();
+ switch (action) {
+ case Intent.ACTION_MAIN:
+ updateGpuWork(0);
+ updateCpuWork(0);
+ break;
+ case Intent.ACTION_SEND:
+ handleSendIntent(intent);
+ break;
+ default:
+ Log.e(APP_NAME, "Unknown intent received: " + action);
+ break;
+ }
+ }
+
+ private void toggleOptionsPanel() {
+ if (settingsLayout.getVisibility() == View.GONE) {
+ settingsLayout.setVisibility(View.VISIBLE);
+ } else {
+ settingsLayout.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_SEND.equals(action)) {
+ handleSendIntent(intent);
+ }
+ }
+
+ private void handleSendIntent(Intent intent) {
+ String gpuWorkStr = intent.getStringExtra(GPU_WORKLOAD);
+ if (gpuWorkStr != null) {
+ updateGpuWork(Integer.parseInt(gpuWorkStr));
+ Log.d(APP_NAME, "GPU work changed by intent. gpu_workload: " + gpuWorkStr);
+ }
+ String cpuWorkStr = intent.getStringExtra(CPU_WORKLOAD);
+ if (cpuWorkStr != null) {
+ updateCpuWork(Integer.parseInt(cpuWorkStr));
+ Log.d(APP_NAME, "CPU work changed by intent. cpu_workload: " + cpuWorkStr);
+ }
+ }
+
+ private void setupGpuWorkSeekBar() {
+ gpuWorkSeekBar = findViewById(R.id.seekBarGpuWork);
+ gpuWorkText = findViewById(R.id.textViewGpuWork);
+
+ gpuWorkSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!fromUser) {
+ return;
+ }
+
+ int newGpuWork = linearToExponential(progress);
+ updateGpuWork(newGpuWork, true);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {}
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {}
+ });
+ }
+
+ private void updateGpuWork(int newGpuWork) {
+ updateGpuWork(newGpuWork, false);
+ }
+
+ private void updateGpuWork(int newGpuWork, boolean fromSeekBar) {
+ gpuWorkText.setText(String.format("GPU Work: %,d", newGpuWork));
+ if (!fromSeekBar) {
+ gpuWorkSeekBar.setProgress(exponentialToLinear(newGpuWork));
+ }
+ nUpdateGpuWorkload(newGpuWork);
+ }
+
+ private void setupCpuWorkSeekBar() {
+ cpuWorkSeekBar = findViewById(R.id.seekBarCpuWork);
+ cpuWorkText = findViewById(R.id.textViewCpuWork);
+
+ cpuWorkSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!fromUser) {
+ return;
+ }
+
+ int newCpuWork = linearToExponential(progress);
+ updateCpuWork(newCpuWork, true);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {}
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {}
+ });
+ }
+
+ private void updateCpuWork(int newCpuWork) {
+ updateCpuWork(newCpuWork, false);
+ }
+
+ private void updateCpuWork(int newCpuWork, boolean fromSeekBar) {
+ cpuWorkText.setText(String.format("CPU Work: %,d", newCpuWork));
+ if (!fromSeekBar) {
+ cpuWorkSeekBar.setProgress(exponentialToLinear(newCpuWork));
+ }
+ nUpdateCpuWorkload(newCpuWork);
+ }
+
+ static private int linearToExponential(int linearValue) {
+ return linearValue == 0 ? 0 : (int)Math.pow(10, (double)linearValue / 1000.0);
+ }
+
+ static private int exponentialToLinear(int exponentialValue) {
+ return exponentialValue == 0 ? 0 : (int)Math.log10(exponentialValue) * 1000;
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ Log.d(APP_NAME, "Surface created.");
+ Surface surface = holder.getSurface();
+ nStartCube(surface);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.d(APP_NAME, "Surface destroyed.");
+ nStopCube();
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+ /**
+ * Native methods that are implemented by the 'native-lib' native library,
+ * which is packaged with this application.
+ */
+ public native void nStartCube(Surface holder);
+ public native void nStopCube();
+ public native void nUpdateGpuWorkload(int newWorkload);
+ public native void nUpdateCpuWorkload(int newWorkload);
+}
diff --git a/third_party/cube/app/src/main/res/layout/activity_cube.xml b/third_party/cube/app/src/main/res/layout/activity_cube.xml
new file mode 100644
index 0000000..e44f3ab
--- /dev/null
+++ b/third_party/cube/app/src/main/res/layout/activity_cube.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ tools:context="com.samples.cube.CubeActivity">
+
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/settingsLayout"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/textViewGpuWork"
+ android:layout_margin="5dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <SeekBar
+ android:id="@+id/seekBarGpuWork"
+ android:max="8000"
+ android:progress="0"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ <TextView
+ android:id="@+id/textViewCpuWork"
+ android:layout_margin="5dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ <SeekBar
+ android:id="@+id/seekBarCpuWork"
+ android:max="8000"
+ android:progress="0"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </LinearLayout>
+
+ <SurfaceView
+ android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+</LinearLayout>