From 6430e5e0035bf43695662784b9a2aec726f25cd7 Mon Sep 17 00:00:00 2001 From: Cody Northrop Date: Wed, 20 Nov 2019 19:56:10 -0700 Subject: [PATCH] Enable frame capture on Android This CL gets capture/replay working on Android again. * Updates where Android frame captures are written * Uses debug system properties to prime Android environment variables * Adds a configurable target Context to the capture_replay sample * Updates capture/replay documentation for Android Bug: angleproject:4036 Test: Captured TRex on Android, replayed on Linux Change-Id: I94b4f6dc77468cd179b9d884b4dcd4afa56bd28c Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1928056 Reviewed-by: Courtney Goeltzenleuchter Reviewed-by: Jamie Madill Commit-Queue: Cody Northrop --- doc/CaptureAndReplay.md | 63 ++++++++++++++++ samples/BUILD.gn | 22 ++++-- samples/capture_replay/CaptureReplay.cpp | 23 +++++- src/libANGLE/FrameCapture.cpp | 93 ++++++++++++++++++++++-- 4 files changed, 187 insertions(+), 14 deletions(-) diff --git a/doc/CaptureAndReplay.md b/doc/CaptureAndReplay.md index 1f8479145..25eae754e 100644 --- a/doc/CaptureAndReplay.md +++ b/doc/CaptureAndReplay.md @@ -68,3 +68,66 @@ $ ANGLE_CAPTURE_ENABLED=0 out/Debug/capture_replay_sample ``` Note that we specify `ANGLE_CAPTURE_ENABLED=0` to prevent re-capturing when running the replay. + +## Capturing an Android application + +In order to capture on Android, the following additional steps must be taken. These steps +presume you've built and installed the ANGLE APK with capture enabled, and selected ANGLE +as the GLES driver for your application. + +1. Create the output directory + + Determine your package name: + ``` + export PACKAGE_NAME com.android.gl2jni + ``` + Then create an output directory that it can write to: + ``` + $ adb shell mkdir -p /sdcard/Android/data/$PACKAGE_NAME/angle_capture + ``` + +2. Set properties to use for environment variable + + On Android, it is difficult to set an environment variable before starting native code. + To work around this, ANGLE will read debug system properties before starting the capture + and use them to prime environment variables used by the capture code. + + Note: Mid-execution capture doesn't work for Android just yet, so frame_start must be + zero, which is the default. This it is sufficient to only set the end frame. + ``` + $ adb shell setprop debug.angle.capture.frame_end 200 + ``` + + There are other properties that can be set that match 1:1 with the env vars, but + they are not required for capture: + ``` + # Optional + $ adb shell setprop debug.angle.capture.enabled 0 + $ adb shell setprop debug.angle.capture.out_dir foo + $ adb shell setprop debug.angle.capture.frame_start 0 + ``` + +3. Run the application, then pull the files to the capture_replay directory + ``` + $ cd samples/capture_replay + $ adb pull /sdcard/Android/data/$PACKAGE_NAME/angle_capture replay_files + $ cp replay_files/* . + ``` + +4. Update your GN args to specifiy which context will be replayed. + + By default Context ID 1 will be replayed. On Android, Context ID 2 is more typical, some apps + we've run go as high as ID 6. + Note: this solution is temporary until EGL capture is in place. + ``` + angle_capture_replay_sample_context_id = 2 + ``` + +5. Replay the capture on desktop + + Until we have samples building for Android, the replay sample must be run on desktop. + We will also be plumbing replay files into perf and correctness tests which will run on Android. + ``` + $ autoninja -C out/Release capture_replay_sample + $ out/Release/capture_replay_sample + ``` \ No newline at end of file diff --git a/samples/BUILD.gn b/samples/BUILD.gn index f394d553d..3e65a0983 100644 --- a/samples/BUILD.gn +++ b/samples/BUILD.gn @@ -7,6 +7,9 @@ import("../gni/angle.gni") declare_args() { # Determines if we build the capture_replay sample. Off by default. angle_build_capture_replay_sample = false + + # Decide which context to replay, starting with desktop default + angle_capture_replay_sample_context_id = 1 } angle_executable("shader_translator") { @@ -228,17 +231,26 @@ if (angle_build_capture_replay_sample) { # To use the capture replay sample first move your capture sources into # the capture_replay folder and enable the gn arg above. angle_sample("capture_replay_sample") { + _contextid = angle_capture_replay_sample_context_id sources = - rebase_path(read_file("capture_replay/angle_capture_context1_files.txt", - "list lines"), ".", "capture_replay") + + rebase_path( + read_file( + "capture_replay/angle_capture_context${_contextid}_files.txt", + "list lines"), + ".", + "capture_replay") + [ "capture_replay/CaptureReplay.cpp", - "capture_replay/angle_capture_context1.cpp", - "capture_replay/angle_capture_context1.h", + "capture_replay/angle_capture_context${_contextid}.cpp", + "capture_replay/angle_capture_context${_contextid}.h", ] _data_path = rebase_path("capture_replay", root_out_dir) - defines = [ "ANGLE_CAPTURE_REPLAY_SAMPLE_DATA_DIR=\"${_data_path}\"" ] + defines = [ + "ANGLE_CAPTURE_REPLAY_SAMPLE_DATA_DIR=\"${_data_path}\"", + "ANGLE_CAPTURE_REPLAY_SAMPLE_CONTEXT_ID=${_contextid}", + "ANGLE_CAPTURE_REPLAY_SAMPLE_HEADER=angle_capture_context${_contextid}.h", + ] suppressed_configs = [ "$angle_root:constructor_and_destructor_warnings" ] } } diff --git a/samples/capture_replay/CaptureReplay.cpp b/samples/capture_replay/CaptureReplay.cpp index be6de16db..c28247662 100644 --- a/samples/capture_replay/CaptureReplay.cpp +++ b/samples/capture_replay/CaptureReplay.cpp @@ -7,7 +7,24 @@ #include "SampleApplication.h" -#include "angle_capture_context1.h" +#include + +#define ANGLE_MACRO_STRINGIZE_AUX(a) #a +#define ANGLE_MACRO_STRINGIZE(a) ANGLE_MACRO_STRINGIZE_AUX(a) +#define ANGLE_MACRO_CONCAT_AUX(a, b) a##b +#define ANGLE_MACRO_CONCAT(a, b) ANGLE_MACRO_CONCAT_AUX(a, b) + +// Build the right context header based on replay ID +// This will expand to "angle_capture_context<#>.h" +#include ANGLE_MACRO_STRINGIZE(ANGLE_CAPTURE_REPLAY_SAMPLE_HEADER) + +// Assign the context numbered functions based on GN arg selecting replay ID +std::function SetupContextReplay = reinterpret_cast( + ANGLE_MACRO_CONCAT(SetupContext, + ANGLE_MACRO_CONCAT(ANGLE_CAPTURE_REPLAY_SAMPLE_CONTEXT_ID, Replay))); +std::function ReplayContextFrame = reinterpret_cast( + ANGLE_MACRO_CONCAT(ReplayContext, + ANGLE_MACRO_CONCAT(ANGLE_CAPTURE_REPLAY_SAMPLE_CONTEXT_ID, Frame))); class CaptureReplaySample : public SampleApplication { @@ -23,7 +40,7 @@ class CaptureReplaySample : public SampleApplication if (!angle::SetCWD(exeDir.c_str())) return false; SetBinaryDataDir(ANGLE_CAPTURE_REPLAY_SAMPLE_DATA_DIR); - SetupContext1Replay(); + SetupContextReplay(); eglSwapInterval(getDisplay(), 1); return true; @@ -36,7 +53,7 @@ class CaptureReplaySample : public SampleApplication // Compute the current frame, looping from kReplayFrameStart to kReplayFrameEnd. uint32_t frame = kReplayFrameStart + (mCurrentFrame % (kReplayFrameEnd - kReplayFrameStart)); - ReplayContext1Frame(frame); + ReplayContextFrame(frame); mCurrentFrame++; } diff --git a/src/libANGLE/FrameCapture.cpp b/src/libANGLE/FrameCapture.cpp index 44780da4e..3da9bf3ae 100644 --- a/src/libANGLE/FrameCapture.cpp +++ b/src/libANGLE/FrameCapture.cpp @@ -14,6 +14,8 @@ #include #include +#include "sys/stat.h" + #include "common/system_utils.h" #include "libANGLE/Context.h" #include "libANGLE/Framebuffer.h" @@ -33,6 +35,76 @@ namespace angle { namespace { + +constexpr char kEnabledVarName[] = "ANGLE_CAPTURE_ENABLED"; +constexpr char kOutDirectoryVarName[] = "ANGLE_CAPTURE_OUT_DIR"; +constexpr char kFrameStartVarName[] = "ANGLE_CAPTURE_FRAME_START"; +constexpr char kFrameEndVarName[] = "ANGLE_CAPTURE_FRAME_END"; + +#if defined(ANGLE_PLATFORM_ANDROID) + +constexpr char kAndroidCaptureEnabled[] = "debug.angle.capture.enabled"; +constexpr char kAndroidOutDir[] = "debug.angle.capture.out_dir"; +constexpr char kAndroidFrameStart[] = "debug.angle.capture.frame_start"; +constexpr char kAndroidFrameEnd[] = "debug.angle.capture.frame_end"; + +constexpr int kStreamSize = 64; + +constexpr char kAndroidOutputSubdir[] = "/angle_capture/"; + +// Call out to 'getprop' on a shell and return a string if the value was set +std::string AndroidGetEnvFromProp(const char *key) +{ + std::string command("getprop "); + command += key; + + // Run the command and open a I/O stream to read results + char stream[kStreamSize] = {}; + FILE *pipe = popen(command.c_str(), "r"); + if (pipe != nullptr) + { + fgets(stream, kStreamSize, pipe); + pclose(pipe); + } + + // Right strip white space + std::string result(stream); + result.erase(result.find_last_not_of(" \n\r\t") + 1); + return result; +} + +void PrimeAndroidEnvironmentVariables() +{ + std::string enabled = AndroidGetEnvFromProp(kAndroidCaptureEnabled); + if (!enabled.empty()) + { + INFO() << "Frame capture read " << enabled << " from " << kAndroidCaptureEnabled; + setenv(kEnabledVarName, enabled.c_str(), 1); + } + + std::string outDir = AndroidGetEnvFromProp(kAndroidOutDir); + if (!outDir.empty()) + { + INFO() << "Frame capture read " << outDir << " from " << kAndroidOutDir; + setenv(kOutDirectoryVarName, outDir.c_str(), 1); + } + + std::string frameStart = AndroidGetEnvFromProp(kAndroidFrameStart); + if (!frameStart.empty()) + { + INFO() << "Frame capture read " << frameStart << " from " << kAndroidFrameStart; + setenv(kFrameStartVarName, frameStart.c_str(), 1); + } + + std::string frameEnd = AndroidGetEnvFromProp(kAndroidFrameEnd); + if (!frameEnd.empty()) + { + INFO() << "Frame capture read " << frameEnd << " from " << kAndroidFrameEnd; + setenv(kFrameEndVarName, frameEnd.c_str(), 1); + } +} +#endif + std::string GetDefaultOutDirectory() { #if defined(ANGLE_PLATFORM_ANDROID) @@ -57,18 +129,23 @@ std::string GetDefaultOutDirectory() { ERR() << "not able to lookup application id"; } - path += std::string(applicationId) + "/"; + + path += std::string(applicationId) + kAndroidOutputSubdir; + + // Check for existance of output path + struct stat dir_stat; + if (stat(path.c_str(), &dir_stat) == -1) + { + ERR() << "Output directory '" << path + << "' does not exist. Create it over adb using mkdir."; + } + return path; #else return std::string("./"); #endif // defined(ANGLE_PLATFORM_ANDROID) } -constexpr char kEnabledVarName[] = "ANGLE_CAPTURE_ENABLED"; -constexpr char kOutDirectoryVarName[] = "ANGLE_CAPTURE_OUT_DIR"; -constexpr char kFrameStartVarName[] = "ANGLE_CAPTURE_FRAME_START"; -constexpr char kFrameEndVarName[] = "ANGLE_CAPTURE_FRAME_END"; - struct FmtCapturePrefix { FmtCapturePrefix(int contextIdIn) : contextId(contextIdIn) {} @@ -1751,6 +1828,10 @@ FrameCapture::FrameCapture() { reset(); +#if defined(ANGLE_PLATFORM_ANDROID) + PrimeAndroidEnvironmentVariables(); +#endif + std::string enabledFromEnv = angle::GetEnvironmentVar(kEnabledVarName); if (enabledFromEnv == "0") {