Merge "Add wrapper for IVibratorManager.aidl to native vibrator service"
diff --git a/cmds/bugreportz/bugreportz.cpp b/cmds/bugreportz/bugreportz.cpp
index ded0ed3..203d748 100644
--- a/cmds/bugreportz/bugreportz.cpp
+++ b/cmds/bugreportz/bugreportz.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#include "bugreportz.h"
+
+#include <android-base/file.h>
+#include <android-base/strings.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
@@ -22,11 +26,6 @@
#include <string>
-#include <android-base/file.h>
-#include <android-base/strings.h>
-
-#include "bugreportz.h"
-
static constexpr char BEGIN_PREFIX[] = "BEGIN:";
static constexpr char PROGRESS_PREFIX[] = "PROGRESS:";
@@ -70,6 +69,30 @@
}
// Process final line, in case it didn't finish with newline
write_line(line, show_progress);
+ return EXIT_SUCCESS;
+}
+int bugreportz_stream(int s) {
+ while (1) {
+ char buffer[65536];
+ ssize_t bytes_read = TEMP_FAILURE_RETRY(read(s, buffer, sizeof(buffer)));
+ if (bytes_read == 0) {
+ break;
+ } else if (bytes_read == -1) {
+ // EAGAIN really means time out, so change the errno.
+ if (errno == EAGAIN) {
+ errno = ETIMEDOUT;
+ }
+ printf("FAIL:Bugreport read terminated abnormally (%s)\n", strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if (!android::base::WriteFully(android::base::borrowed_fd(STDOUT_FILENO), buffer,
+ bytes_read)) {
+ printf("Failed to write data to stdout: trying to send %zd bytes (%s)\n", bytes_read,
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+ }
return EXIT_SUCCESS;
}
diff --git a/cmds/bugreportz/bugreportz.h b/cmds/bugreportz/bugreportz.h
index 7af289b..cdeceae 100644
--- a/cmds/bugreportz/bugreportz.h
+++ b/cmds/bugreportz/bugreportz.h
@@ -19,4 +19,8 @@
// Ownership of the socket is not transferred.
int bugreportz(int s, bool show_progress);
+// Calls dumpstate using the given socket and write the file content to stdout
+// instead of file location. Ownership of the socket is not transferred.
+int bugreportz_stream(int s);
+
#endif // BUGREPORTZ_H
diff --git a/cmds/bugreportz/main.cpp b/cmds/bugreportz/main.cpp
index 1d48e08..cd2652c 100644
--- a/cmds/bugreportz/main.cpp
+++ b/cmds/bugreportz/main.cpp
@@ -26,13 +26,14 @@
#include "bugreportz.h"
-static constexpr char VERSION[] = "1.1";
+static constexpr char VERSION[] = "1.2";
static void show_usage() {
fprintf(stderr,
- "usage: bugreportz [-hpv]\n"
+ "usage: bugreportz [-hpsv]\n"
" -h: to display this help message\n"
" -p: display progress\n"
+ " -s: stream content to standard output\n"
" -v: to display the version\n"
" or no arguments to generate a zipped bugreport\n");
}
@@ -43,10 +44,11 @@
int main(int argc, char* argv[]) {
bool show_progress = false;
+ bool stream_data = false;
if (argc > 1) {
/* parse arguments */
int c;
- while ((c = getopt(argc, argv, "hpv")) != -1) {
+ while ((c = getopt(argc, argv, "hpsv")) != -1) {
switch (c) {
case 'h':
show_usage();
@@ -54,6 +56,9 @@
case 'p':
show_progress = true;
break;
+ case 's':
+ stream_data = true;
+ break;
case 'v':
show_version();
return EXIT_SUCCESS;
@@ -75,7 +80,11 @@
// should be reused instead.
// Start the dumpstatez service.
- property_set("ctl.start", "dumpstatez");
+ if (stream_data) {
+ property_set("ctl.start", "dumpstate");
+ } else {
+ property_set("ctl.start", "dumpstatez");
+ }
// Socket will not be available until service starts.
int s = -1;
@@ -103,7 +112,12 @@
strerror(errno));
}
- int ret = bugreportz(s, show_progress);
+ int ret;
+ if (stream_data) {
+ ret = bugreportz_stream(s);
+ } else {
+ ret = bugreportz(s, show_progress);
+ }
if (close(s) == -1) {
fprintf(stderr, "WARNING: error closing socket: %s\n", strerror(errno));
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 8bdde62..cbb74b3 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -719,6 +719,9 @@
return timeout_ms > MINIMUM_LOGCAT_TIMEOUT_MS ? timeout_ms : MINIMUM_LOGCAT_TIMEOUT_MS;
}
+// Opens a socket and returns its file descriptor.
+static int open_socket(const char* service);
+
Dumpstate::ConsentCallback::ConsentCallback() : result_(UNAVAILABLE), start_time_(Nanotime()) {
}
@@ -2287,20 +2290,18 @@
static void ShowUsage() {
fprintf(stderr,
- "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-d] [-p] "
- "[-z] [-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n"
+ "usage: dumpstate [-h] [-b soundfile] [-e soundfile] [-o directory] [-p] "
+ "[-s] [-S] [-q] [-P] [-R] [-L] [-V version]\n"
" -h: display this help message\n"
" -b: play sound file instead of vibrate, at beginning of job\n"
" -e: play sound file instead of vibrate, at end of job\n"
" -o: write to custom directory (only in limited mode)\n"
- " -d: append date to filename\n"
" -p: capture screenshot to filename.png\n"
- " -z: generate zipped file\n"
- " -s: write output to control socket (for init)\n"
- " -S: write file location to control socket (for init; requires -z)\n"
+ " -s: write zipped file to control socket (for init)\n"
+ " -S: write file location to control socket (for init)\n"
" -q: disable vibrate\n"
" -P: send broadcast when started and do progress updates\n"
- " -R: take bugreport in remote mode (requires -z and -d, shouldn't be used with -P)\n"
+ " -R: take bugreport in remote mode (shouldn't be used with -P)\n"
" -w: start binder service and make it wait for a call to startBugreport\n"
" -L: output limited information that is safe for submission in feedback reports\n"
" -v: prints the dumpstate header and exit\n");
@@ -2399,21 +2400,17 @@
/*
* Prepares state like filename, screenshot path, etc in Dumpstate. Also initializes ZipWriter
- * if we are writing zip files and adds the version file.
+ * and adds the version file. Return false if zip_file could not be open to write.
*/
-static void PrepareToWriteToFile() {
+static bool PrepareToWriteToFile() {
MaybeResolveSymlink(&ds.bugreport_internal_dir_);
std::string build_id = android::base::GetProperty("ro.build.id", "UNKNOWN_BUILD");
std::string device_name = android::base::GetProperty("ro.product.name", "UNKNOWN_DEVICE");
ds.base_name_ = StringPrintf("bugreport-%s-%s", device_name.c_str(), build_id.c_str());
- if (ds.options_->do_add_date) {
- char date[80];
- strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_));
- ds.name_ = date;
- } else {
- ds.name_ = "undated";
- }
+ char date[80];
+ strftime(date, sizeof(date), "%Y-%m-%d-%H-%M-%S", localtime(&ds.now_));
+ ds.name_ = date;
if (ds.options_->telephony_only) {
ds.base_name_ += "-telephony";
@@ -2440,18 +2437,17 @@
destination.c_str(), ds.base_name_.c_str(), ds.name_.c_str(), ds.log_path_.c_str(),
ds.tmp_path_.c_str(), ds.screenshot_path_.c_str());
- if (ds.options_->do_zip_file) {
- ds.path_ = ds.GetPath(ds.CalledByApi() ? "-zip.tmp" : ".zip");
- MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str());
- create_parent_dirs(ds.path_.c_str());
- ds.zip_file.reset(fopen(ds.path_.c_str(), "wb"));
- if (ds.zip_file == nullptr) {
- MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno));
- } else {
- ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get()));
- }
- ds.AddTextZipEntry("version.txt", ds.version_);
+ ds.path_ = ds.GetPath(ds.CalledByApi() ? "-zip.tmp" : ".zip");
+ MYLOGD("Creating initial .zip file (%s)\n", ds.path_.c_str());
+ create_parent_dirs(ds.path_.c_str());
+ ds.zip_file.reset(fopen(ds.path_.c_str(), "wb"));
+ if (ds.zip_file == nullptr) {
+ MYLOGE("fopen(%s, 'wb'): %s\n", ds.path_.c_str(), strerror(errno));
+ return false;
}
+ ds.zip_writer_.reset(new ZipWriter(ds.zip_file.get()));
+ ds.AddTextZipEntry("version.txt", ds.version_);
+ return true;
}
/*
@@ -2459,14 +2455,9 @@
* printing zipped file status, etc.
*/
static void FinalizeFile() {
- bool do_text_file = true;
- if (ds.options_->do_zip_file) {
- if (!ds.FinishZipFile()) {
- MYLOGE("Failed to finish zip file; sending text bugreport instead\n");
- do_text_file = true;
- } else {
- do_text_file = false;
- }
+ bool do_text_file = !ds.FinishZipFile();
+ if (do_text_file) {
+ MYLOGE("Failed to finish zip file; sending text bugreport instead\n");
}
std::string final_path = ds.path_;
@@ -2475,7 +2466,9 @@
android::os::CopyFileToFile(ds.path_, final_path);
}
- if (ds.options_->use_control_socket) {
+ if (ds.options_->stream_to_socket) {
+ android::os::CopyFileToFd(ds.path_, ds.control_socket_fd_);
+ } else if (ds.options_->progress_updates_to_socket) {
if (do_text_file) {
dprintf(ds.control_socket_fd_,
"FAIL:could not create zip file, check %s "
@@ -2531,7 +2524,6 @@
break;
case Dumpstate::BugreportMode::BUGREPORT_WEAR:
options->do_progress_updates = true;
- options->do_zip_file = true;
options->do_screenshot = is_screenshot_requested;
options->dumpstate_hal_mode = DumpstateMode::WEAR;
break;
@@ -2544,7 +2536,6 @@
break;
case Dumpstate::BugreportMode::BUGREPORT_WIFI:
options->wifi_only = true;
- options->do_zip_file = true;
options->do_screenshot = false;
options->dumpstate_hal_mode = DumpstateMode::WIFI;
break;
@@ -2555,11 +2546,11 @@
static void LogDumpOptions(const Dumpstate::DumpOptions& options) {
MYLOGI(
- "do_zip_file: %d do_vibrate: %d use_socket: %d use_control_socket: %d do_screenshot: %d "
+ "do_vibrate: %d stream_to_socket: %d progress_updates_to_socket: %d do_screenshot: %d "
"is_remote_mode: %d show_header_only: %d telephony_only: %d "
"wifi_only: %d do_progress_updates: %d fd: %d bugreport_mode: %s dumpstate_hal_mode: %s "
"limited_only: %d args: %s\n",
- options.do_zip_file, options.do_vibrate, options.use_socket, options.use_control_socket,
+ options.do_vibrate, options.stream_to_socket, options.progress_updates_to_socket,
options.do_screenshot, options.is_remote_mode, options.show_header_only,
options.telephony_only, options.wifi_only,
options.do_progress_updates, options.bugreport_fd.get(), options.bugreport_mode.c_str(),
@@ -2570,11 +2561,6 @@
const android::base::unique_fd& bugreport_fd_in,
const android::base::unique_fd& screenshot_fd_in,
bool is_screenshot_requested) {
- // In the new API world, date is always added; output is always a zip file.
- // TODO(111441001): remove these options once they are obsolete.
- do_add_date = true;
- do_zip_file = true;
-
// Duplicate the fds because the passed in fds don't outlive the binder transaction.
bugreport_fd.reset(dup(bugreport_fd_in.get()));
screenshot_fd.reset(dup(screenshot_fd_in.get()));
@@ -2588,18 +2574,20 @@
while ((c = getopt(argc, argv, "dho:svqzpLPBRSV:w")) != -1) {
switch (c) {
// clang-format off
- case 'd': do_add_date = true; break;
- case 'z': do_zip_file = true; break;
case 'o': out_dir = optarg; break;
- case 's': use_socket = true; break;
- case 'S': use_control_socket = true; break;
+ case 's': stream_to_socket = true; break;
+ case 'S': progress_updates_to_socket = true; break;
case 'v': show_header_only = true; break;
case 'q': do_vibrate = false; break;
case 'p': do_screenshot = true; break;
case 'P': do_progress_updates = true; break;
case 'R': is_remote_mode = true; break;
case 'L': limited_only = true; break;
- case 'V': break; // compatibility no-op
+ case 'V':
+ case 'd':
+ case 'z':
+ // compatibility no-op
+ break;
case 'w':
// This was already processed
break;
@@ -2628,19 +2616,15 @@
}
bool Dumpstate::DumpOptions::ValidateOptions() const {
- if (bugreport_fd.get() != -1 && !do_zip_file) {
+ if (bugreport_fd.get() != -1 && stream_to_socket) {
return false;
}
- if ((do_zip_file || do_add_date || do_progress_updates) && !OutputToFile()) {
+ if ((progress_updates_to_socket || do_progress_updates) && stream_to_socket) {
return false;
}
- if (use_control_socket && !do_zip_file) {
- return false;
- }
-
- if (is_remote_mode && (do_progress_updates || !do_zip_file || !do_add_date)) {
+ if (is_remote_mode && (do_progress_updates || stream_to_socket)) {
return false;
}
return true;
@@ -2715,11 +2699,9 @@
* The temporary bugreport is then populated via printfs, dumping contents of files and
* output of commands to stdout.
*
- * If zipping, the temporary bugreport file is added to the zip archive. Else it's renamed to final
- * text file.
+ * A bunch of other files and dumps are added to the zip archive.
*
- * If zipping, a bunch of other files and dumps also get added to the zip archive. The log file also
- * gets added to the archive.
+ * The temporary bugreport file and the log file also get added to the archive.
*
* Bugreports are first generated in a local directory and later copied to the caller's fd
* or directory if supplied.
@@ -2767,14 +2749,9 @@
MYLOGD("dumpstate calling_uid = %d ; calling package = %s \n",
calling_uid, calling_package.c_str());
- // Redirect output if needed
- bool is_redirecting = options_->OutputToFile();
-
// TODO: temporarily set progress until it's part of the Dumpstate constructor
std::string stats_path =
- is_redirecting
- ? android::base::StringPrintf("%s/dumpstate-stats.txt", bugreport_internal_dir_.c_str())
- : "";
+ android::base::StringPrintf("%s/dumpstate-stats.txt", bugreport_internal_dir_.c_str());
progress_.reset(new Progress(stats_path));
if (acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME) < 0) {
@@ -2797,35 +2774,33 @@
// If we are going to use a socket, do it as early as possible
// to avoid timeouts from bugreport.
- if (options_->use_socket) {
- if (!redirect_to_socket(stdout, "dumpstate")) {
- return ERROR;
- }
- }
-
- if (options_->use_control_socket) {
+ if (options_->stream_to_socket || options_->progress_updates_to_socket) {
MYLOGD("Opening control socket\n");
- control_socket_fd_ = open_socket("dumpstate");
+ control_socket_fd_ = open_socket_fn_("dumpstate");
if (control_socket_fd_ == -1) {
return ERROR;
}
- options_->do_progress_updates = 1;
+ if (options_->progress_updates_to_socket) {
+ options_->do_progress_updates = 1;
+ }
}
- if (is_redirecting) {
- PrepareToWriteToFile();
+ if (!PrepareToWriteToFile()) {
+ return ERROR;
+ }
- if (options_->do_progress_updates) {
- // clang-format off
- std::vector<std::string> am_args = {
- "--receiver-permission", "android.permission.DUMP",
- };
- // clang-format on
- // Send STARTED broadcast for apps that listen to bugreport generation events
- SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args);
- if (options_->use_control_socket) {
- dprintf(control_socket_fd_, "BEGIN:%s\n", path_.c_str());
- }
+ // Interactive, wear & telephony modes are default to true.
+ // and may enable from cli option or when using control socket
+ if (options_->do_progress_updates) {
+ // clang-format off
+ std::vector<std::string> am_args = {
+ "--receiver-permission", "android.permission.DUMP",
+ };
+ // clang-format on
+ // Send STARTED broadcast for apps that listen to bugreport generation events
+ SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args);
+ if (options_->progress_updates_to_socket) {
+ dprintf(control_socket_fd_, "BEGIN:%s\n", path_.c_str());
}
}
@@ -2840,40 +2815,38 @@
Vibrate(150);
}
- if (options_->do_zip_file && zip_file != nullptr) {
+ if (zip_file != nullptr) {
if (chown(path_.c_str(), AID_SHELL, AID_SHELL)) {
MYLOGE("Unable to change ownership of zip file %s: %s\n", path_.c_str(),
- strerror(errno));
+ strerror(errno));
}
}
int dup_stdout_fd;
int dup_stderr_fd;
- if (is_redirecting) {
- // Redirect stderr to log_path_ for debugging.
- TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr)));
- if (!redirect_to_file(stderr, const_cast<char*>(log_path_.c_str()))) {
- return ERROR;
- }
- if (chown(log_path_.c_str(), AID_SHELL, AID_SHELL)) {
- MYLOGE("Unable to change ownership of dumpstate log file %s: %s\n", log_path_.c_str(),
- strerror(errno));
- }
+ // Redirect stderr to log_path_ for debugging.
+ TEMP_FAILURE_RETRY(dup_stderr_fd = dup(fileno(stderr)));
+ if (!redirect_to_file(stderr, const_cast<char*>(log_path_.c_str()))) {
+ return ERROR;
+ }
+ if (chown(log_path_.c_str(), AID_SHELL, AID_SHELL)) {
+ MYLOGE("Unable to change ownership of dumpstate log file %s: %s\n", log_path_.c_str(),
+ strerror(errno));
+ }
- // Redirect stdout to tmp_path_. This is the main bugreport entry and will be
- // moved into zip file later, if zipping.
- TEMP_FAILURE_RETRY(dup_stdout_fd = dup(fileno(stdout)));
- // TODO: why not write to a file instead of stdout to overcome this problem?
- /* TODO: rather than generating a text file now and zipping it later,
- it would be more efficient to redirect stdout to the zip entry
- directly, but the libziparchive doesn't support that option yet. */
- if (!redirect_to_file(stdout, const_cast<char*>(tmp_path_.c_str()))) {
- return ERROR;
- }
- if (chown(tmp_path_.c_str(), AID_SHELL, AID_SHELL)) {
- MYLOGE("Unable to change ownership of temporary bugreport file %s: %s\n",
- tmp_path_.c_str(), strerror(errno));
- }
+ // Redirect stdout to tmp_path_. This is the main bugreport entry and will be
+ // moved into zip file later, if zipping.
+ TEMP_FAILURE_RETRY(dup_stdout_fd = dup(fileno(stdout)));
+ // TODO: why not write to a file instead of stdout to overcome this problem?
+ /* TODO: rather than generating a text file now and zipping it later,
+ it would be more efficient to redirect stdout to the zip entry
+ directly, but the libziparchive doesn't support that option yet. */
+ if (!redirect_to_file(stdout, const_cast<char*>(tmp_path_.c_str()))) {
+ return ERROR;
+ }
+ if (chown(tmp_path_.c_str(), AID_SHELL, AID_SHELL)) {
+ MYLOGE("Unable to change ownership of temporary bugreport file %s: %s\n",
+ tmp_path_.c_str(), strerror(errno));
}
// Don't buffer stdout
@@ -2927,14 +2900,10 @@
}
/* close output if needed */
- if (is_redirecting) {
- TEMP_FAILURE_RETRY(dup2(dup_stdout_fd, fileno(stdout)));
- }
+ TEMP_FAILURE_RETRY(dup2(dup_stdout_fd, fileno(stdout)));
// Zip the (now complete) .tmp file within the internal directory.
- if (options_->OutputToFile()) {
- FinalizeFile();
- }
+ FinalizeFile();
// Share the final file with the caller if the user has consented or Shell is the caller.
Dumpstate::RunStatus status = Dumpstate::RunStatus::OK;
@@ -2977,11 +2946,9 @@
progress_->Save();
MYLOGI("done (id %d)\n", id_);
- if (is_redirecting) {
- TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr)));
- }
+ TEMP_FAILURE_RETRY(dup2(dup_stderr_fd, fileno(stderr)));
- if (options_->use_control_socket && control_socket_fd_ != -1) {
+ if (control_socket_fd_ != -1) {
MYLOGD("Closing control socket\n");
close(control_socket_fd_);
}
@@ -3050,10 +3017,7 @@
}
void Dumpstate::EnableParallelRunIfNeeded() {
- // The thread pool needs to create temporary files to receive dump results.
- // That's why we only enable it when the bugreport client chooses to output
- // to a file.
- if (!PropertiesHelper::IsParallelRun() || !options_->OutputToFile()) {
+ if (!PropertiesHelper::IsParallelRun()) {
return;
}
dump_pool_ = std::make_unique<DumpPool>(bugreport_internal_dir_);
@@ -3198,7 +3162,8 @@
options_(new Dumpstate::DumpOptions()),
last_reported_percent_progress_(0),
version_(version),
- now_(time(nullptr)) {
+ now_(time(nullptr)),
+ open_socket_fn_(open_socket) {
}
Dumpstate& Dumpstate::GetInstance() {
@@ -3832,7 +3797,7 @@
RunCommand(title, dumpsys, options, false, out_fd);
}
-int open_socket(const char *service) {
+static int open_socket(const char* service) {
int s = android_get_control_socket(service);
if (s < 0) {
MYLOGE("android_get_control_socket(%s): %s\n", service, strerror(errno));
@@ -3867,19 +3832,6 @@
return fd;
}
-/* redirect output to a service control socket */
-bool redirect_to_socket(FILE* redirect, const char* service) {
- int fd = open_socket(service);
- if (fd == -1) {
- return false;
- }
- fflush(redirect);
- // TODO: handle dup2 failure
- TEMP_FAILURE_RETRY(dup2(fd, fileno(redirect)));
- close(fd);
- return true;
-}
-
// TODO: should call is_valid_output_file and/or be merged into it.
void create_parent_dirs(const char *path) {
char *chp = const_cast<char *> (path);
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 3b9b1b7..255243f 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -54,6 +54,7 @@
class DumpstateTest;
class ProgressTest;
+class ZippedBugReportStreamTest;
} // namespace dumpstate
} // namespace os
@@ -197,6 +198,7 @@
*/
class Dumpstate {
friend class android::os::dumpstate::DumpstateTest;
+ friend class android::os::dumpstate::ZippedBugReportStreamTest;
public:
enum RunStatus { OK, HELP, INVALID_INPUT, ERROR, USER_CONSENT_DENIED, USER_CONSENT_TIMED_OUT };
@@ -388,12 +390,11 @@
* Structure to hold options that determine the behavior of dumpstate.
*/
struct DumpOptions {
- bool do_add_date = false;
- bool do_zip_file = false;
bool do_vibrate = true;
- // Writes bugreport content to a socket; only flatfile format is supported.
- bool use_socket = false;
- bool use_control_socket = false;
+ // Writes bugreport zipped file to a socket.
+ bool stream_to_socket = false;
+ // Writes generation progress updates to a socket.
+ bool progress_updates_to_socket = false;
bool do_screenshot = false;
bool is_screenshot_copied = false;
bool is_remote_mode = false;
@@ -434,13 +435,6 @@
/* Returns true if the options set so far are consistent. */
bool ValidateOptions() const;
- /* Returns if options specified require writing bugreport to a file */
- bool OutputToFile() const {
- // If we are not writing to socket, we will write to a file. If bugreport_fd is
- // specified, it is preferred. If not bugreport is written to /bugreports.
- return !use_socket;
- }
-
/* Returns if options specified require writing to custom file location */
bool OutputToCustomFile() {
// Custom location is only honored in limited mode.
@@ -466,7 +460,8 @@
std::unique_ptr<Progress> progress_;
- // When set, defines a socket file-descriptor use to report progress to bugreportz.
+ // When set, defines a socket file-descriptor use to report progress to bugreportz
+ // or to stream the zipped file to.
int control_socket_fd_ = -1;
// Bugreport format version;
@@ -478,8 +473,8 @@
// `bugreport-BUILD_ID`.
std::string base_name_;
- // Name is the suffix part of the bugreport files - it's typically the date (when invoked with
- // `-d`), but it could be changed by the user..
+ // Name is the suffix part of the bugreport files - it's typically the date,
+ // but it could be changed by the user..
std::string name_;
std::string bugreport_internal_dir_ = DUMPSTATE_DIRECTORY;
@@ -567,6 +562,8 @@
// called by Shell.
RunStatus CopyBugreportIfUserConsented(int32_t calling_uid);
+ std::function<int(const char *)> open_socket_fn_;
+
// Used by GetInstance() only.
explicit Dumpstate(const std::string& version = VERSION_CURRENT);
@@ -600,16 +597,6 @@
int dump_files(const std::string& title, const char* dir, bool (*skip)(const char* path),
int (*dump_from_fd)(const char* title, const char* path, int fd));
-/** opens a socket and returns its file descriptor */
-int open_socket(const char *service);
-
-/*
- * Redirects 'redirect' to a service control socket.
- *
- * Returns true if redirect succeeds.
- */
-bool redirect_to_socket(FILE* redirect, const char* service);
-
/*
* Redirects 'redirect' to a file indicated by 'path', truncating it.
*
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index e491a4b..a80da4e 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -11,7 +11,7 @@
# dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
# it is finished.
-service dumpstatez /system/bin/dumpstate -S -d -z
+service dumpstatez /system/bin/dumpstate -S
socket dumpstate stream 0660 shell log
class main
disabled
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index 70bdbcc..fe6a34a 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -209,9 +209,7 @@
static void GenerateBugreport() {
// clang-format off
char* argv[] = {
- (char*)"dumpstate",
- (char*)"-d",
- (char*)"-z"
+ (char*)"dumpstate"
};
// clang-format on
sp<DumpstateListener> listener(new DumpstateListener(dup(fileno(stdout)), sections));
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index fdeea24..b2518ad 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "dumpstate"
-#include <cutils/log.h>
+#define LOG_TAG "dumpstate_test"
#include "DumpstateInternal.h"
#include "DumpstateService.h"
@@ -24,6 +23,7 @@
#include "DumpPool.h"
#include <gmock/gmock.h>
+#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include <fcntl.h>
@@ -39,7 +39,9 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <android/hardware/dumpstate/1.1/types.h>
+#include <cutils/log.h>
#include <cutils/properties.h>
+#include <ziparchive/zip_archive.h>
namespace android {
namespace os {
@@ -175,11 +177,9 @@
EXPECT_EQ(status, Dumpstate::RunStatus::OK);
- EXPECT_FALSE(options_.do_add_date);
- EXPECT_FALSE(options_.do_zip_file);
EXPECT_EQ("", options_.out_dir);
- EXPECT_FALSE(options_.use_socket);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_TRUE(options_.do_vibrate);
EXPECT_FALSE(options_.do_screenshot);
@@ -194,17 +194,13 @@
char* argv[] = {
const_cast<char*>("dumpstatez"),
const_cast<char*>("-S"),
- const_cast<char*>("-d"),
- const_cast<char*>("-z"),
};
// clang-format on
Dumpstate::RunStatus status = options_.Initialize(ARRAY_SIZE(argv), argv);
EXPECT_EQ(status, Dumpstate::RunStatus::OK);
- EXPECT_TRUE(options_.do_add_date);
- EXPECT_TRUE(options_.do_zip_file);
- EXPECT_TRUE(options_.use_control_socket);
+ EXPECT_TRUE(options_.progress_updates_to_socket);
// Other options retain default values
EXPECT_TRUE(options_.do_vibrate);
@@ -212,7 +208,7 @@
EXPECT_FALSE(options_.do_screenshot);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_FALSE(options_.limited_only);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
@@ -228,13 +224,11 @@
Dumpstate::RunStatus status = options_.Initialize(ARRAY_SIZE(argv), argv);
EXPECT_EQ(status, Dumpstate::RunStatus::OK);
- EXPECT_TRUE(options_.use_socket);
+ EXPECT_TRUE(options_.stream_to_socket);
// Other options retain default values
EXPECT_TRUE(options_.do_vibrate);
- EXPECT_FALSE(options_.do_add_date);
- EXPECT_FALSE(options_.do_zip_file);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.do_screenshot);
EXPECT_FALSE(options_.do_progress_updates);
@@ -245,105 +239,93 @@
TEST_F(DumpOptionsTest, InitializeFullBugReport) {
options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, fd, fd, true);
- EXPECT_TRUE(options_.do_add_date);
EXPECT_TRUE(options_.do_screenshot);
- EXPECT_TRUE(options_.do_zip_file);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::FULL);
// Other options retain default values
EXPECT_TRUE(options_.do_vibrate);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) {
options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, fd, fd, true);
- EXPECT_TRUE(options_.do_add_date);
- EXPECT_TRUE(options_.do_zip_file);
EXPECT_TRUE(options_.do_progress_updates);
EXPECT_TRUE(options_.do_screenshot);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::INTERACTIVE);
// Other options retain default values
EXPECT_TRUE(options_.do_vibrate);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.is_remote_mode);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeRemoteBugReport) {
options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, fd, fd, false);
- EXPECT_TRUE(options_.do_add_date);
- EXPECT_TRUE(options_.do_zip_file);
EXPECT_TRUE(options_.is_remote_mode);
EXPECT_FALSE(options_.do_vibrate);
EXPECT_FALSE(options_.do_screenshot);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::REMOTE);
// Other options retain default values
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.do_progress_updates);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeWearBugReport) {
options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, fd, fd, true);
- EXPECT_TRUE(options_.do_add_date);
EXPECT_TRUE(options_.do_screenshot);
- EXPECT_TRUE(options_.do_zip_file);
EXPECT_TRUE(options_.do_progress_updates);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::WEAR);
// Other options retain default values
EXPECT_TRUE(options_.do_vibrate);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.is_remote_mode);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) {
options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, fd, fd, false);
- EXPECT_TRUE(options_.do_add_date);
EXPECT_FALSE(options_.do_screenshot);
- EXPECT_TRUE(options_.do_zip_file);
EXPECT_TRUE(options_.telephony_only);
EXPECT_TRUE(options_.do_progress_updates);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::CONNECTIVITY);
// Other options retain default values
EXPECT_TRUE(options_.do_vibrate);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.is_remote_mode);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_FALSE(options_.limited_only);
}
TEST_F(DumpOptionsTest, InitializeWifiBugReport) {
options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, fd, fd, false);
- EXPECT_TRUE(options_.do_add_date);
EXPECT_FALSE(options_.do_screenshot);
- EXPECT_TRUE(options_.do_zip_file);
EXPECT_TRUE(options_.wifi_only);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::WIFI);
// Other options retain default values
EXPECT_TRUE(options_.do_vibrate);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_FALSE(options_.limited_only);
}
@@ -352,8 +334,6 @@
char* argv[] = {
const_cast<char*>("dumpstatez"),
const_cast<char*>("-S"),
- const_cast<char*>("-d"),
- const_cast<char*>("-z"),
const_cast<char*>("-q"),
const_cast<char*>("-L"),
const_cast<char*>("-o abc")
@@ -363,9 +343,7 @@
Dumpstate::RunStatus status = options_.Initialize(ARRAY_SIZE(argv), argv);
EXPECT_EQ(status, Dumpstate::RunStatus::OK);
- EXPECT_TRUE(options_.do_add_date);
- EXPECT_TRUE(options_.do_zip_file);
- EXPECT_TRUE(options_.use_control_socket);
+ EXPECT_TRUE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.do_vibrate);
EXPECT_TRUE(options_.limited_only);
EXPECT_EQ(" abc", std::string(options_.out_dir));
@@ -375,7 +353,7 @@
EXPECT_FALSE(options_.do_screenshot);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
@@ -392,18 +370,16 @@
Dumpstate::RunStatus status = options_.Initialize(ARRAY_SIZE(argv), argv);
EXPECT_EQ(status, Dumpstate::RunStatus::OK);
- EXPECT_TRUE(options_.do_add_date);
EXPECT_TRUE(options_.do_screenshot);
- EXPECT_TRUE(options_.do_zip_file);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
// Other options retain default values
EXPECT_TRUE(options_.do_vibrate);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.do_progress_updates);
EXPECT_FALSE(options_.is_remote_mode);
- EXPECT_FALSE(options_.use_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
EXPECT_FALSE(options_.wifi_only);
EXPECT_FALSE(options_.limited_only);
}
@@ -412,8 +388,6 @@
// clang-format off
char* argv[] = {
const_cast<char*>("dumpstate"),
- const_cast<char*>("-d"),
- const_cast<char*>("-z"),
const_cast<char*>("-s"),
const_cast<char*>("-S"),
@@ -423,11 +397,9 @@
Dumpstate::RunStatus status = options_.Initialize(ARRAY_SIZE(argv), argv);
EXPECT_EQ(status, Dumpstate::RunStatus::OK);
- EXPECT_TRUE(options_.do_add_date);
- EXPECT_TRUE(options_.do_zip_file);
// TODO: Maybe we should trim the filename
- EXPECT_TRUE(options_.use_socket);
- EXPECT_TRUE(options_.use_control_socket);
+ EXPECT_TRUE(options_.stream_to_socket);
+ EXPECT_TRUE(options_.progress_updates_to_socket);
// Other options retain default values
EXPECT_FALSE(options_.show_header_only);
@@ -461,10 +433,8 @@
EXPECT_TRUE(options_.is_remote_mode);
// Other options retain default values
- EXPECT_FALSE(options_.do_add_date);
- EXPECT_FALSE(options_.do_zip_file);
- EXPECT_FALSE(options_.use_socket);
- EXPECT_FALSE(options_.use_control_socket);
+ EXPECT_FALSE(options_.stream_to_socket);
+ EXPECT_FALSE(options_.progress_updates_to_socket);
EXPECT_FALSE(options_.limited_only);
EXPECT_EQ(options_.dumpstate_hal_mode, DumpstateMode::DEFAULT);
}
@@ -497,40 +467,31 @@
EXPECT_EQ(status, Dumpstate::RunStatus::INVALID_INPUT);
}
-TEST_F(DumpOptionsTest, ValidateOptionsNeedOutfile1) {
- options_.do_zip_file = true;
- // Writing to socket = !writing to file.
- options_.use_socket = true;
+TEST_F(DumpOptionsTest, ValidateOptionsSocketUsage1) {
+ options_.progress_updates_to_socket = true;
+ options_.stream_to_socket = true;
EXPECT_FALSE(options_.ValidateOptions());
- options_.use_socket = false;
+ options_.stream_to_socket = false;
EXPECT_TRUE(options_.ValidateOptions());
}
-TEST_F(DumpOptionsTest, ValidateOptionsNeedOutfile2) {
+TEST_F(DumpOptionsTest, ValidateOptionsSocketUsage2) {
options_.do_progress_updates = true;
// Writing to socket = !writing to file.
- options_.use_socket = true;
+ options_.stream_to_socket = true;
EXPECT_FALSE(options_.ValidateOptions());
- options_.use_socket = false;
- EXPECT_TRUE(options_.ValidateOptions());
-}
-
-TEST_F(DumpOptionsTest, ValidateOptionsNeedZipfile) {
- options_.use_control_socket = true;
- EXPECT_FALSE(options_.ValidateOptions());
-
- options_.do_zip_file = true;
+ options_.stream_to_socket = false;
EXPECT_TRUE(options_.ValidateOptions());
}
TEST_F(DumpOptionsTest, ValidateOptionsRemoteMode) {
+ options_.do_progress_updates = true;
options_.is_remote_mode = true;
EXPECT_FALSE(options_.ValidateOptions());
- options_.do_zip_file = true;
- options_.do_add_date = true;
+ options_.do_progress_updates = false;
EXPECT_TRUE(options_.ValidateOptions());
}
@@ -1017,23 +978,13 @@
ds.listener_.clear();
}
-TEST_F(DumpstateTest, DumpPool_withOutputToFileAndParallelRunEnabled_notNull) {
- ds.options_->use_socket = false;
+TEST_F(DumpstateTest, DumpPool_withParallelRunEnabled_notNull) {
SetParallelRun(true);
EnableParallelRunIfNeeded();
- EXPECT_TRUE(ds.options_->OutputToFile());
EXPECT_TRUE(ds.zip_entry_tasks_);
EXPECT_TRUE(ds.dump_pool_);
}
-TEST_F(DumpstateTest, DumpPool_withNotOutputToFile_isNull) {
- ds.options_->use_socket = true;
- EnableParallelRunIfNeeded();
- EXPECT_FALSE(ds.options_->OutputToFile());
- EXPECT_FALSE(ds.zip_entry_tasks_);
- EXPECT_FALSE(ds.dump_pool_);
-}
-
TEST_F(DumpstateTest, DumpPool_withParallelRunDisabled_isNull) {
SetParallelRun(false);
EnableParallelRunIfNeeded();
@@ -1041,6 +992,69 @@
EXPECT_FALSE(ds.dump_pool_);
}
+class ZippedBugReportStreamTest : public DumpstateBaseTest {
+ public:
+ void SetUp() {
+ DumpstateBaseTest::SetUp();
+ ds_.options_.reset(new Dumpstate::DumpOptions());
+ }
+ void TearDown() {
+ CloseArchive(handle_);
+ }
+
+ // Set bugreport mode and options before here.
+ void GenerateBugreport() {
+ ds_.Initialize();
+ EXPECT_EQ(Dumpstate::RunStatus::OK, ds_.Run(/*calling_uid=*/-1, /*calling_package=*/""));
+ }
+
+ // Most bugreports droproot, ensure the file can be opened by shell to verify file content.
+ void CreateFd(const std::string& path, android::base::unique_fd* out_fd) {
+ out_fd->reset(TEMP_FAILURE_RETRY(open(path.c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)));
+ ASSERT_GE(out_fd->get(), 0) << "could not create FD for path " << path;
+ }
+
+ void VerifyEntry(const ZipArchiveHandle archive, const std::string_view entry_name,
+ ZipEntry* data) {
+ int32_t e = FindEntry(archive, entry_name, data);
+ EXPECT_EQ(0, e) << ErrorCodeString(e) << " entry name: " << entry_name;
+ }
+
+ // While testing dumpstate in process, using STDOUT may get confused about
+ // the internal fd redirection. Redirect to a dedicate fd to save content.
+ void RedirectOutputToFd(android::base::unique_fd& ufd) {
+ ds_.open_socket_fn_ = [&](const char*) -> int { return ufd.release(); };
+ };
+
+ Dumpstate& ds_ = Dumpstate::GetInstance();
+ ZipArchiveHandle handle_;
+};
+
+// Generate a quick wifi report redirected to a file, open it and verify entry exist.
+TEST_F(ZippedBugReportStreamTest, StreamWifiReport) {
+ std::string out_path = kTestDataPath + "out.zip";
+ android::base::unique_fd out_fd;
+ CreateFd(out_path, &out_fd);
+ ds_.options_->wifi_only = true;
+ ds_.options_->stream_to_socket = true;
+ RedirectOutputToFd(out_fd);
+
+ GenerateBugreport();
+ OpenArchive(out_path.c_str(), &handle_);
+
+ ZipEntry entry;
+ VerifyEntry(handle_, "main_entry.txt", &entry);
+ std::string bugreport_txt_name;
+ bugreport_txt_name.resize(entry.uncompressed_length);
+ ExtractToMemory(handle_, &entry, reinterpret_cast<uint8_t*>(bugreport_txt_name.data()),
+ entry.uncompressed_length);
+ EXPECT_THAT(bugreport_txt_name,
+ testing::ContainsRegex("(bugreport-.+-wifi(-[[:digit:]]+){6}\\.txt)"));
+ VerifyEntry(handle_, bugreport_txt_name, &entry);
+}
+
class DumpstateServiceTest : public DumpstateBaseTest {
public:
DumpstateService dss;
diff --git a/cmds/installd/QuotaUtils.cpp b/cmds/installd/QuotaUtils.cpp
index e080291..6027139 100644
--- a/cmds/installd/QuotaUtils.cpp
+++ b/cmds/installd/QuotaUtils.cpp
@@ -35,7 +35,7 @@
/* Map of all quota mounts from target to source */
std::unordered_map<std::string, std::string> mQuotaReverseMounts;
-std::string& FindQuotaDeviceForUuid(const std::string& uuid) {
+std::string FindQuotaDeviceForUuid(const std::string& uuid) {
std::lock_guard<std::recursive_mutex> lock(mMountsLock);
auto path = create_data_path(uuid.empty() ? nullptr : uuid.c_str());
return mQuotaReverseMounts[path];
diff --git a/data/etc/android.hardware.uwb.xml b/data/etc/android.hardware.uwb.xml
new file mode 100644
index 0000000..794ba27
--- /dev/null
+++ b/data/etc/android.hardware.uwb.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<!-- Adds the feature indicating support for the Ultra Wideband API -->
+<permissions>
+ <feature name="android.hardware.uwb" />
+</permissions>
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index 015954d..90feedd 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -145,6 +145,7 @@
cflags: [
"-Wall",
"-Wextra",
+ "-Wextra-semi",
"-Werror",
"-Wzero-as-null-pointer-constant",
"-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index cf9bb46..08169f5 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -135,6 +135,6 @@
// ------------------------------------------------------------------------------------
-IMPLEMENT_META_INTERFACE(ActivityManager, "android.app.IActivityManager");
+IMPLEMENT_META_INTERFACE(ActivityManager, "android.app.IActivityManager")
} // namespace android
diff --git a/libs/binder/IAppOpsCallback.cpp b/libs/binder/IAppOpsCallback.cpp
index b9eb281..2b3f462 100644
--- a/libs/binder/IAppOpsCallback.cpp
+++ b/libs/binder/IAppOpsCallback.cpp
@@ -43,7 +43,7 @@
}
};
-IMPLEMENT_META_INTERFACE(AppOpsCallback, "com.android.internal.app.IAppOpsCallback");
+IMPLEMENT_META_INTERFACE(AppOpsCallback, "com.android.internal.app.IAppOpsCallback")
// ----------------------------------------------------------------------
diff --git a/libs/binder/IAppOpsService.cpp b/libs/binder/IAppOpsService.cpp
index ee0cd62..1897969 100644
--- a/libs/binder/IAppOpsService.cpp
+++ b/libs/binder/IAppOpsService.cpp
@@ -162,7 +162,7 @@
}
};
-IMPLEMENT_META_INTERFACE(AppOpsService, "com.android.internal.app.IAppOpsService");
+IMPLEMENT_META_INTERFACE(AppOpsService, "com.android.internal.app.IAppOpsService")
// ----------------------------------------------------------------------
diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp
index a47dbac..d0085df 100644
--- a/libs/binder/IBatteryStats.cpp
+++ b/libs/binder/IBatteryStats.cpp
@@ -130,7 +130,7 @@
};
-IMPLEMENT_META_INTERFACE(BatteryStats, "com.android.internal.app.IBatteryStats");
+IMPLEMENT_META_INTERFACE(BatteryStats, "com.android.internal.app.IBatteryStats")
// ----------------------------------------------------------------------
diff --git a/libs/binder/IMediaResourceMonitor.cpp b/libs/binder/IMediaResourceMonitor.cpp
index 5f3d670..f5fa817 100644
--- a/libs/binder/IMediaResourceMonitor.cpp
+++ b/libs/binder/IMediaResourceMonitor.cpp
@@ -38,7 +38,7 @@
}
};
-IMPLEMENT_META_INTERFACE(MediaResourceMonitor, "android.media.IMediaResourceMonitor");
+IMPLEMENT_META_INTERFACE(MediaResourceMonitor, "android.media.IMediaResourceMonitor")
// ----------------------------------------------------------------------
diff --git a/libs/binder/IMemory.cpp b/libs/binder/IMemory.cpp
index d8b44f9..cca8f81 100644
--- a/libs/binder/IMemory.cpp
+++ b/libs/binder/IMemory.cpp
@@ -223,7 +223,7 @@
// ---------------------------------------------------------------------------
-IMPLEMENT_META_INTERFACE(Memory, "android.utils.IMemory");
+IMPLEMENT_META_INTERFACE(Memory, "android.utils.IMemory")
BnMemory::BnMemory() {
}
@@ -388,7 +388,7 @@
// ---------------------------------------------------------------------------
-IMPLEMENT_META_INTERFACE(MemoryHeap, "android.utils.IMemoryHeap");
+IMPLEMENT_META_INTERFACE(MemoryHeap, "android.utils.IMemoryHeap")
BnMemoryHeap::BnMemoryHeap() {
}
diff --git a/libs/binder/IPermissionController.cpp b/libs/binder/IPermissionController.cpp
index d9bf3cc..f94f413 100644
--- a/libs/binder/IPermissionController.cpp
+++ b/libs/binder/IPermissionController.cpp
@@ -103,7 +103,7 @@
}
};
-IMPLEMENT_META_INTERFACE(PermissionController, "android.os.IPermissionController");
+IMPLEMENT_META_INTERFACE(PermissionController, "android.os.IPermissionController")
// ----------------------------------------------------------------------
diff --git a/libs/binder/IProcessInfoService.cpp b/libs/binder/IProcessInfoService.cpp
index a38a27a..570edb9 100644
--- a/libs/binder/IProcessInfoService.cpp
+++ b/libs/binder/IProcessInfoService.cpp
@@ -84,7 +84,7 @@
}
};
-IMPLEMENT_META_INTERFACE(ProcessInfoService, "android.os.IProcessInfoService");
+IMPLEMENT_META_INTERFACE(ProcessInfoService, "android.os.IProcessInfoService")
// ----------------------------------------------------------------------
diff --git a/libs/binder/IResultReceiver.cpp b/libs/binder/IResultReceiver.cpp
index 556288c..cd92217 100644
--- a/libs/binder/IResultReceiver.cpp
+++ b/libs/binder/IResultReceiver.cpp
@@ -42,7 +42,7 @@
}
};
-IMPLEMENT_META_INTERFACE(ResultReceiver, "com.android.internal.os.IResultReceiver");
+IMPLEMENT_META_INTERFACE(ResultReceiver, "com.android.internal.os.IResultReceiver")
// ----------------------------------------------------------------------
diff --git a/libs/binder/IShellCallback.cpp b/libs/binder/IShellCallback.cpp
index a3e2b67..86dd5c4 100644
--- a/libs/binder/IShellCallback.cpp
+++ b/libs/binder/IShellCallback.cpp
@@ -52,7 +52,7 @@
}
};
-IMPLEMENT_META_INTERFACE(ShellCallback, "com.android.internal.os.IShellCallback");
+IMPLEMENT_META_INTERFACE(ShellCallback, "com.android.internal.os.IShellCallback")
// ----------------------------------------------------------------------
diff --git a/libs/binder/IUidObserver.cpp b/libs/binder/IUidObserver.cpp
index 4714234..a1b08db 100644
--- a/libs/binder/IUidObserver.cpp
+++ b/libs/binder/IUidObserver.cpp
@@ -71,7 +71,7 @@
// ----------------------------------------------------------------------
-IMPLEMENT_META_INTERFACE(UidObserver, "android.app.IUidObserver");
+IMPLEMENT_META_INTERFACE(UidObserver, "android.app.IUidObserver")
// ----------------------------------------------------------------------
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 0377075..88a631a 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -164,12 +164,8 @@
ALOGE("Invalid object type 0x%08x", obj.hdr.type);
}
-status_t Parcel::finishFlattenBinder(
- const sp<IBinder>& binder, const flat_binder_object& flat)
+status_t Parcel::finishFlattenBinder(const sp<IBinder>& binder)
{
- status_t status = writeObject(flat, false);
- if (status != OK) return status;
-
internal::Stability::tryMarkCompilationUnit(binder.get());
auto category = internal::Stability::getCategory(binder.get());
return writeInt32(category.repr());
@@ -238,7 +234,10 @@
obj.flags |= schedBits;
- return finishFlattenBinder(binder, obj);
+ status_t status = writeObject(obj, false);
+ if (status != OK) return status;
+
+ return finishFlattenBinder(binder);
}
status_t Parcel::unflattenBinder(sp<IBinder>* out) const
diff --git a/libs/binder/PermissionCache.cpp b/libs/binder/PermissionCache.cpp
index 75a6d22..6eae5ef 100644
--- a/libs/binder/PermissionCache.cpp
+++ b/libs/binder/PermissionCache.cpp
@@ -27,7 +27,7 @@
// ----------------------------------------------------------------------------
-ANDROID_SINGLETON_STATIC_INSTANCE(PermissionCache) ;
+ANDROID_SINGLETON_STATIC_INSTANCE(PermissionCache)
// ----------------------------------------------------------------------------
diff --git a/libs/binder/ProcessInfoService.cpp b/libs/binder/ProcessInfoService.cpp
index 00d6eef..f75141e 100644
--- a/libs/binder/ProcessInfoService.cpp
+++ b/libs/binder/ProcessInfoService.cpp
@@ -99,6 +99,6 @@
}
}
-ANDROID_SINGLETON_STATIC_INSTANCE(ProcessInfoService);
+ANDROID_SINGLETON_STATIC_INSTANCE(ProcessInfoService)
} // namespace android
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 83ca687..9aedf28 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -130,7 +130,7 @@
// The root object is special since we get it directly from the driver, it is never
// written by Parcell::writeStrongBinder.
- internal::Stability::tryMarkCompilationUnit(context.get());
+ internal::Stability::markCompilationUnit(context.get());
return context;
}
diff --git a/libs/binder/Static.cpp b/libs/binder/Static.cpp
index db0f1c7..565f2e2 100644
--- a/libs/binder/Static.cpp
+++ b/libs/binder/Static.cpp
@@ -33,7 +33,7 @@
{
public:
LogTextOutput() : BufferedTextOutput(MULTITHREADED) { }
- virtual ~LogTextOutput() { };
+ virtual ~LogTextOutput() { }
protected:
virtual status_t writeLines(const struct iovec& vec, size_t N)
@@ -49,7 +49,7 @@
{
public:
explicit FdTextOutput(int fd) : BufferedTextOutput(MULTITHREADED), mFD(fd) { }
- virtual ~FdTextOutput() { };
+ virtual ~FdTextOutput() { }
protected:
virtual status_t writeLines(const struct iovec& vec, size_t N)
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 1cfb560..4375818 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -56,6 +56,9 @@
},
{
"name": "rustBinderTest"
+ },
+ {
+ "name": "binderRustNdkInteropTest"
}
]
}
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index a0e28d2..2735315 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -28,7 +28,7 @@
namespace internal {
class Stability;
-};
+}
using binder_proxy_limit_callback = void(*)(int);
diff --git a/libs/binder/include/binder/IShellCallback.h b/libs/binder/include/binder/IShellCallback.h
index 17e34db..6d3fe4a 100644
--- a/libs/binder/include/binder/IShellCallback.h
+++ b/libs/binder/include/binder/IShellCallback.h
@@ -25,7 +25,7 @@
class IShellCallback : public IInterface
{
public:
- DECLARE_META_INTERFACE(ShellCallback);
+ DECLARE_META_INTERFACE(ShellCallback)
virtual int openFile(const String16& path, const String16& seLinuxContext,
const String16& mode) = 0;
diff --git a/libs/binder/include/binder/IpPrefix.h b/libs/binder/include/binder/IpPrefix.h
index c1cd3c2..a8faa3f 100644
--- a/libs/binder/include/binder/IpPrefix.h
+++ b/libs/binder/include/binder/IpPrefix.h
@@ -73,8 +73,8 @@
private:
union InternalUnion {
InternalUnion() = default;
- explicit InternalUnion(const struct in6_addr &addr):mIn6Addr(addr) { };
- explicit InternalUnion(const struct in_addr &addr):mInAddr(addr) { };
+ explicit InternalUnion(const struct in6_addr &addr):mIn6Addr(addr) { }
+ explicit InternalUnion(const struct in_addr &addr):mInAddr(addr) { }
struct in6_addr mIn6Addr;
struct in_addr mInAddr;
} mUnion;
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index cfe1f3a..9f5260a 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -522,8 +522,7 @@
status_t validateReadData(size_t len) const;
void updateWorkSourceRequestHeaderPosition() const;
- status_t finishFlattenBinder(const sp<IBinder>& binder,
- const flat_binder_object& flat);
+ status_t finishFlattenBinder(const sp<IBinder>& binder);
status_t finishUnflattenBinder(const sp<IBinder>& binder, sp<IBinder>* out) const;
status_t flattenBinder(const sp<IBinder>& binder);
status_t unflattenBinder(sp<IBinder>* out) const;
diff --git a/libs/binder/include/binder/ParcelableHolder.h b/libs/binder/include/binder/ParcelableHolder.h
index 4ea3dd3..ce5027e 100644
--- a/libs/binder/include/binder/ParcelableHolder.h
+++ b/libs/binder/include/binder/ParcelableHolder.h
@@ -30,7 +30,7 @@
class ParcelableHolder : public android::Parcelable {
public:
ParcelableHolder() = delete;
- explicit ParcelableHolder(Stability stability) : mStability(stability){};
+ explicit ParcelableHolder(Stability stability) : mStability(stability){}
virtual ~ParcelableHolder() = default;
ParcelableHolder(const ParcelableHolder& other) {
mParcelable = other.mParcelable;
@@ -40,7 +40,7 @@
mParcelPtr->appendFrom(other.mParcelPtr.get(), 0, other.mParcelPtr->dataSize());
}
mStability = other.mStability;
- };
+ }
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 037ee95..b558f27 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -21,7 +21,8 @@
use crate::proxy::{DeathRecipient, SpIBinder};
use crate::sys;
-use std::ffi::{c_void, CString};
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
use std::os::unix::io::AsRawFd;
use std::ptr;
@@ -205,6 +206,22 @@
pub(crate) unsafe fn from_ptr(ptr: *const sys::AIBinder_Class) -> InterfaceClass {
InterfaceClass(ptr)
}
+
+ /// Get the interface descriptor string of this class.
+ pub fn get_descriptor(&self) -> String {
+ unsafe {
+ // SAFETY: The descriptor returned by AIBinder_Class_getDescriptor
+ // is always a two-byte null terminated sequence of u16s. Thus, we
+ // can continue reading from the pointer until we hit a null value,
+ // and this pointer can be a valid slice if the slice length is <=
+ // the number of u16 elements before the null terminator.
+
+ let raw_descriptor: *const c_char = sys::AIBinder_Class_getDescriptor(self.0);
+ CStr::from_ptr(raw_descriptor).to_str()
+ .expect("Expected valid UTF-8 string from AIBinder_Class_getDescriptor")
+ .into()
+ }
+ }
}
impl From<InterfaceClass> for *const sys::AIBinder_Class {
@@ -507,12 +524,7 @@
}
fn from_binder(mut binder: $crate::SpIBinder) -> $crate::Result<Self> {
- use $crate::AssociateClass;
- if binder.associate_class(<$native as $crate::Remotable>::get_class()) {
- Ok(Self { binder, $($fname: $finit),* })
- } else {
- Err($crate::StatusCode::BAD_TYPE)
- }
+ Ok(Self { binder, $($fname: $finit),* })
}
}
@@ -567,16 +579,33 @@
impl $crate::FromIBinder for dyn $interface {
fn try_from(mut ibinder: $crate::SpIBinder) -> $crate::Result<Box<dyn $interface>> {
use $crate::AssociateClass;
- if !ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
- return Err($crate::StatusCode::BAD_TYPE.into());
+
+ let existing_class = ibinder.get_class();
+ if let Some(class) = existing_class {
+ if class != <$native as $crate::Remotable>::get_class() &&
+ class.get_descriptor() == <$native as $crate::Remotable>::get_descriptor()
+ {
+ // The binder object's descriptor string matches what we
+ // expect. We still need to treat this local or already
+ // associated object as remote, because we can't cast it
+ // into a Rust service object without a matching class
+ // pointer.
+ return Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?));
+ }
+ } else if ibinder.associate_class(<$native as $crate::Remotable>::get_class()) {
+ let service: $crate::Result<$crate::Binder<$native>> =
+ std::convert::TryFrom::try_from(ibinder.clone());
+ if let Ok(service) = service {
+ // We were able to associate with our expected class and
+ // the service is local.
+ return Ok(Box::new(service));
+ } else {
+ // Service is remote
+ return Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?));
+ }
}
- let service: $crate::Result<$crate::Binder<$native>> = std::convert::TryFrom::try_from(ibinder.clone());
- if let Ok(service) = service {
- Ok(Box::new(service))
- } else {
- Ok(Box::new(<$proxy as $crate::Proxy>::from_binder(ibinder)?))
- }
+ Err($crate::StatusCode::BAD_TYPE.into())
}
}
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 5002fc6..485bb42 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -91,7 +91,7 @@
/// Return the interface class of this binder object, if associated with
/// one.
- pub(crate) fn get_class(&mut self) -> Option<InterfaceClass> {
+ pub fn get_class(&mut self) -> Option<InterfaceClass> {
unsafe {
// Safety: `SpIBinder` guarantees that it always contains a valid
// `AIBinder` pointer. `AIBinder_getClass` returns either a null
diff --git a/libs/binder/rust/tests/Android.bp b/libs/binder/rust/tests/Android.bp
index 3db40ba..5ae9c53 100644
--- a/libs/binder/rust/tests/Android.bp
+++ b/libs/binder/rust/tests/Android.bp
@@ -30,3 +30,52 @@
auto_gen_config: false,
test_suites: ["general-tests"],
}
+
+cc_test {
+ name: "binderRustNdkInteropTest",
+ srcs: [
+ "binderRustNdkInteropTest.cpp",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libbinder_ndk",
+ ],
+ static_libs: [
+ "IBinderRustNdkInteropTest-ndk_platform",
+ "libbinder_ndk_rust_interop",
+ ],
+ test_suites: ["general-tests"],
+ require_root: true,
+
+ // rustBinderTestService uses a custom config
+ auto_gen_config: true,
+}
+
+aidl_interface {
+ name: "IBinderRustNdkInteropTest",
+ unstable: true,
+ srcs: [
+ "IBinderRustNdkInteropTest.aidl",
+ "IBinderRustNdkInteropTestOther.aidl",
+ ],
+ backend: {
+ ndk: {
+ enabled: true,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+rust_ffi_static {
+ name: "libbinder_ndk_rust_interop",
+ crate_name: "binder_ndk_rust_interop",
+ srcs: [
+ "ndk_rust_interop.rs",
+ ],
+ rustlibs: [
+ "libbinder_rs",
+ "IBinderRustNdkInteropTest-rust",
+ ],
+}
diff --git a/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl b/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl
new file mode 100644
index 0000000..7f5e837
--- /dev/null
+++ b/libs/binder/rust/tests/IBinderRustNdkInteropTest.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+interface IBinderRustNdkInteropTest {
+ @utf8InCpp String echo(@utf8InCpp String str);
+}
diff --git a/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl b/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl
new file mode 100644
index 0000000..82a0323
--- /dev/null
+++ b/libs/binder/rust/tests/IBinderRustNdkInteropTestOther.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+interface IBinderRustNdkInteropTestOther {
+ @utf8InCpp String echo(@utf8InCpp String str);
+}
diff --git a/libs/binder/rust/tests/binderRustNdkInteropTest.cpp b/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
new file mode 100644
index 0000000..59ca6ed
--- /dev/null
+++ b/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 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 <aidl/BnBinderRustNdkInteropTest.h>
+#include <aidl/IBinderRustNdkInteropTest.h>
+#include <android/binder_ibinder.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <binder/Status.h>
+#include <gtest/gtest.h>
+
+using namespace android;
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+using ::ndk::SpAIBinder;
+
+static const char* kNdkServerName = "NdkServer-BinderRustNdkInteropTest";
+static const char* kRustServerName = "RustServer-BinderRustNdkInteropTest";
+
+extern "C" {
+int rust_call_ndk(const char* service_name);
+int rust_start_service(const char* service_name);
+}
+
+class NdkServer : public aidl::BnBinderRustNdkInteropTest {
+ ScopedAStatus echo(const std::string& in, std::string* out) override {
+ *out = in;
+ return ScopedAStatus::ok();
+ }
+};
+
+TEST(RustNdkInterop, RustCanCallNdk) {
+ ASSERT_EQ(STATUS_OK, rust_call_ndk(kNdkServerName));
+}
+
+TEST(RustNdkInterop, NdkCanCallRust) {
+ ASSERT_EQ(STATUS_OK, rust_start_service(kRustServerName));
+
+ SpAIBinder binder = SpAIBinder(AServiceManager_checkService(kRustServerName));
+ ASSERT_NE(nullptr, binder.get());
+ EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
+
+ auto interface = aidl::IBinderRustNdkInteropTest::fromBinder(binder);
+ // TODO(b/167723746): this test requires that fromBinder allow association
+ // with an already associated local binder by treating it as remote.
+ EXPECT_EQ(interface, nullptr);
+
+ // std::string in("testing");
+ // std::string out;
+ // EXPECT_TRUE(interface->echo(in, &out).isOk());
+ // EXPECT_EQ(in, out);
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // so we can host a client and service concurrently
+ ABinderProcess_setThreadPoolMaxThreadCount(1);
+ ABinderProcess_startThreadPool();
+
+ std::shared_ptr<NdkServer> ndkServer = SharedRefBase::make<NdkServer>();
+ EXPECT_EQ(STATUS_OK, AServiceManager_addService(ndkServer->asBinder().get(), kNdkServerName));
+
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 953d328..497361a 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -173,6 +173,30 @@
}
}
+/// Trivial testing binder interface
+pub trait ITestSameDescriptor: Interface {}
+
+declare_binder_interface! {
+ ITestSameDescriptor["android.os.ITest"] {
+ native: BnTestSameDescriptor(on_transact_same_descriptor),
+ proxy: BpTestSameDescriptor,
+ }
+}
+
+fn on_transact_same_descriptor(
+ _service: &dyn ITestSameDescriptor,
+ _code: TransactionCode,
+ _data: &Parcel,
+ _reply: &mut Parcel,
+) -> binder::Result<()> {
+ Ok(())
+}
+
+impl ITestSameDescriptor for BpTestSameDescriptor {}
+
+impl ITestSameDescriptor for Binder<BnTestSameDescriptor> {}
+
+
#[cfg(test)]
mod tests {
use selinux_bindgen as selinux_sys;
@@ -185,9 +209,9 @@
use std::thread;
use std::time::Duration;
- use binder::{DeathRecipient, FromIBinder, IBinder, SpIBinder, StatusCode};
+ use binder::{Binder, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder, StatusCode};
- use super::{ITest, RUST_SERVICE_BINARY};
+ use super::{BnTest, ITest, ITestSameDescriptor, RUST_SERVICE_BINARY, TestService};
pub struct ScopedServiceProcess(Child);
@@ -435,4 +459,27 @@
assert_eq!(extension.test().unwrap(), extension_name);
}
}
+
+ /// Test re-associating a local binder object with a different class.
+ ///
+ /// This is needed because different binder service (e.g. NDK vs Rust)
+ /// implementations are incompatible and must not be interchanged. A local
+ /// service with the same descriptor string but a different class pointer
+ /// may have been created by an NDK service and is therefore incompatible
+ /// with the Rust service implementation. It must be treated as remote and
+ /// all API calls parceled and sent through transactions.
+ ///
+ /// Further tests of this behavior with the C NDK and Rust API are in
+ /// rust_ndk_interop.rs
+ #[test]
+ fn associate_existing_class() {
+ let service = Binder::new(BnTest(Box::new(TestService {
+ s: "testing_service".to_string(),
+ })));
+
+ // This should succeed although we will have to treat the service as
+ // remote.
+ let _interface: Box<dyn ITestSameDescriptor> = FromIBinder::try_from(service.as_binder())
+ .expect("Could not re-interpret service as the ITestSameDescriptor interface");
+ }
}
diff --git a/libs/binder/rust/tests/ndk_rust_interop.rs b/libs/binder/rust/tests/ndk_rust_interop.rs
new file mode 100644
index 0000000..70a6dc0
--- /dev/null
+++ b/libs/binder/rust/tests/ndk_rust_interop.rs
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+//! Rust Binder NDK interop tests
+
+use std::ffi::CStr;
+use std::os::raw::{c_char, c_int};
+use ::IBinderRustNdkInteropTest::binder::{self, Interface, StatusCode};
+use ::IBinderRustNdkInteropTest::aidl::IBinderRustNdkInteropTest::{
+ BnBinderRustNdkInteropTest, IBinderRustNdkInteropTest,
+};
+use ::IBinderRustNdkInteropTest::aidl::IBinderRustNdkInteropTestOther::{
+ IBinderRustNdkInteropTestOther,
+};
+
+/// Look up the provided AIDL service and call its echo method.
+///
+/// # Safety
+///
+/// service_name must be a valid, non-null C-style string (null-terminated).
+#[no_mangle]
+pub unsafe extern "C" fn rust_call_ndk(service_name: *const c_char) -> c_int {
+ let service_name = CStr::from_ptr(service_name).to_str().unwrap();
+
+ // The Rust class descriptor pointer will not match the NDK one, but the
+ // descriptor strings match so this needs to still associate.
+ let service: Box<dyn IBinderRustNdkInteropTest> = match binder::get_interface(service_name) {
+ Err(e) => {
+ eprintln!("Could not find Ndk service {}: {:?}", service_name, e);
+ return StatusCode::NAME_NOT_FOUND as c_int;
+ }
+ Ok(service) => service,
+ };
+
+ match service.echo("testing") {
+ Ok(s) => if s != "testing" {
+ return StatusCode::BAD_VALUE as c_int;
+ },
+ Err(e) => return e.into(),
+ }
+
+ // Try using the binder service through the wrong interface type
+ let wrong_service: Result<Box<dyn IBinderRustNdkInteropTestOther>, StatusCode> =
+ binder::get_interface(service_name);
+ match wrong_service {
+ Err(e) if e == StatusCode::BAD_TYPE => {}
+ Err(e) => {
+ eprintln!("Trying to use a service via the wrong interface errored with unexpected error {:?}", e);
+ return e as c_int;
+ }
+ Ok(_) => {
+ eprintln!("We should not be allowed to use a service via the wrong interface");
+ return StatusCode::BAD_TYPE as c_int;
+ }
+ }
+
+ StatusCode::OK as c_int
+}
+
+struct Service;
+
+impl Interface for Service {}
+
+impl IBinderRustNdkInteropTest for Service {
+ fn echo(&self, s: &str) -> binder::Result<String> {
+ Ok(s.to_string())
+ }
+}
+
+/// Start the interop Echo test service with the given service name.
+///
+/// # Safety
+///
+/// service_name must be a valid, non-null C-style string (null-terminated).
+#[no_mangle]
+pub unsafe extern "C" fn rust_start_service(service_name: *const c_char) -> c_int {
+ let service_name = CStr::from_ptr(service_name).to_str().unwrap();
+ let service = BnBinderRustNdkInteropTest::new_binder(Service);
+ match binder::add_service(&service_name, service.as_binder()) {
+ Ok(_) => StatusCode::OK as c_int,
+ Err(e) => e as c_int,
+ }
+}
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index 09f58cc..2f9d85e 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -184,7 +184,7 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wexit-time-destructors"
-IMPLEMENT_META_INTERFACE(Callback, "android.gfx.tests.ICallback");
+IMPLEMENT_META_INTERFACE(Callback, "android.gfx.tests.ICallback")
#pragma clang diagnostic pop
class BnCallback : public SafeBnInterface<ICallback> {
@@ -373,7 +373,7 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wexit-time-destructors"
-IMPLEMENT_META_INTERFACE(SafeInterfaceTest, "android.gfx.tests.ISafeInterfaceTest");
+IMPLEMENT_META_INTERFACE(SafeInterfaceTest, "android.gfx.tests.ISafeInterfaceTest")
static sp<IBinder::DeathRecipient> getDeathRecipient() {
static sp<IBinder::DeathRecipient> recipient = new ExitOnDeath;
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index cd7f37b..b878150 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -31,6 +31,9 @@
"libui",
"libutils",
],
+ include_dirs: [
+ "external/skia/src/gpu",
+ ],
whole_static_libs: ["libskia"],
local_include_dirs: ["include"],
export_include_dirs: ["include"],
@@ -75,6 +78,7 @@
filegroup {
name: "librenderengine_skia_sources",
srcs: [
+ "skia/AutoBackendTexture.cpp",
"skia/SkiaRenderEngine.cpp",
"skia/SkiaGLRenderEngine.cpp",
"skia/filters/BlurFilter.cpp",
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index be83ebc..279e648 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -15,6 +15,7 @@
*/
//#define LOG_NDEBUG 0
+#include "EGL/egl.h"
#undef LOG_TAG
#define LOG_TAG "RenderEngine"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
@@ -446,19 +447,37 @@
mPlaceholderBuffer, attributes);
ALOGE_IF(mPlaceholderImage == EGL_NO_IMAGE_KHR, "Failed to create placeholder image: %#x",
eglGetError());
+
+ mShadowTexture = std::make_unique<GLShadowTexture>();
}
GLESRenderEngine::~GLESRenderEngine() {
// Destroy the image manager first.
mImageManager = nullptr;
+ mShadowTexture = nullptr;
cleanFramebufferCache();
+ ProgramCache::getInstance().purgeCaches();
std::lock_guard<std::mutex> lock(mRenderingMutex);
+ glDisableVertexAttribArray(Program::position);
unbindFrameBuffer(mDrawingBuffer.get());
mDrawingBuffer = nullptr;
eglDestroyImageKHR(mEGLDisplay, mPlaceholderImage);
mImageCache.clear();
+ if (mStubSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEGLDisplay, mStubSurface);
+ }
+ if (mProtectedStubSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEGLDisplay, mProtectedStubSurface);
+ }
+ if (mEGLContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEGLDisplay, mEGLContext);
+ }
+ if (mProtectedEGLContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEGLDisplay, mProtectedEGLContext);
+ }
eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglTerminate(mEGLDisplay);
+ eglReleaseThread();
}
std::unique_ptr<Framebuffer> GLESRenderEngine::createFramebuffer() {
@@ -1800,7 +1819,7 @@
mState.cornerRadius = 0.0f;
mState.drawShadows = true;
- setupLayerTexturing(mShadowTexture.getTexture());
+ setupLayerTexturing(mShadowTexture->getTexture());
drawMesh(mesh);
mState.drawShadows = false;
}
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index c0449a1..92e1529 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -193,7 +193,7 @@
GLuint mVpWidth;
GLuint mVpHeight;
Description mState;
- GLShadowTexture mShadowTexture;
+ std::unique_ptr<GLShadowTexture> mShadowTexture = nullptr;
mat4 mSrgbToXyz;
mat4 mDisplayP3ToXyz;
@@ -294,7 +294,7 @@
friend class BlurFilter;
friend class GenericProgram;
std::unique_ptr<FlushTracer> mFlushTracer;
- std::unique_ptr<ImageManager> mImageManager = std::make_unique<ImageManager>(this);
+ std::unique_ptr<ImageManager> mImageManager;
};
} // namespace gl
diff --git a/libs/renderengine/gl/GLFramebuffer.cpp b/libs/renderengine/gl/GLFramebuffer.cpp
index 383486b..58d6caa 100644
--- a/libs/renderengine/gl/GLFramebuffer.cpp
+++ b/libs/renderengine/gl/GLFramebuffer.cpp
@@ -38,6 +38,7 @@
}
GLFramebuffer::~GLFramebuffer() {
+ setNativeWindowBuffer(nullptr, false, false);
glDeleteFramebuffers(1, &mFramebufferName);
glDeleteTextures(1, &mTextureName);
}
diff --git a/libs/renderengine/gl/Program.cpp b/libs/renderengine/gl/Program.cpp
index a172c56..26f6166 100644
--- a/libs/renderengine/gl/Program.cpp
+++ b/libs/renderengine/gl/Program.cpp
@@ -82,6 +82,14 @@
}
}
+Program::~Program() {
+ glDetachShader(mProgram, mVertexShader);
+ glDetachShader(mProgram, mFragmentShader);
+ glDeleteShader(mVertexShader);
+ glDeleteShader(mFragmentShader);
+ glDeleteProgram(mProgram);
+}
+
bool Program::isValid() const {
return mInitialized;
}
diff --git a/libs/renderengine/gl/Program.h b/libs/renderengine/gl/Program.h
index 4292645..41f1bf8 100644
--- a/libs/renderengine/gl/Program.h
+++ b/libs/renderengine/gl/Program.h
@@ -54,7 +54,7 @@
};
Program(const ProgramCache::Key& needs, const char* vertex, const char* fragment);
- ~Program() = default;
+ ~Program();
/* whether this object is usable */
bool isValid() const;
diff --git a/libs/renderengine/gl/ProgramCache.h b/libs/renderengine/gl/ProgramCache.h
index 37bb651..535d21c 100644
--- a/libs/renderengine/gl/ProgramCache.h
+++ b/libs/renderengine/gl/ProgramCache.h
@@ -203,6 +203,8 @@
// if none can be found.
void useProgram(const EGLContext context, const Description& description);
+ void purgeCaches() { mCaches.clear(); }
+
private:
// compute a cache Key from a Description
static Key computeKey(const Description& description);
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
new file mode 100644
index 0000000..d126c27
--- /dev/null
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2020 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 "AutoBackendTexture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <utils/Trace.h>
+
+#include "log/log_main.h"
+#include "utils/Trace.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+// Converts an android dataspace to a supported SkColorSpace
+// Supported dataspaces are
+// 1. sRGB
+// 2. Display P3
+// 3. BT2020 PQ
+// 4. BT2020 HLG
+// Unknown primaries are mapped to BT709, and unknown transfer functions
+// are mapped to sRGB.
+static sk_sp<SkColorSpace> toSkColorSpace(ui::Dataspace dataspace) {
+ skcms_Matrix3x3 gamut;
+ switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
+ case HAL_DATASPACE_STANDARD_BT709:
+ gamut = SkNamedGamut::kSRGB;
+ break;
+ case HAL_DATASPACE_STANDARD_BT2020:
+ gamut = SkNamedGamut::kRec2020;
+ break;
+ case HAL_DATASPACE_STANDARD_DCI_P3:
+ gamut = SkNamedGamut::kDisplayP3;
+ break;
+ default:
+ ALOGV("Unsupported Gamut: %d, defaulting to sRGB", dataspace);
+ gamut = SkNamedGamut::kSRGB;
+ break;
+ }
+
+ switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
+ case HAL_DATASPACE_TRANSFER_LINEAR:
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut);
+ case HAL_DATASPACE_TRANSFER_SRGB:
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
+ case HAL_DATASPACE_TRANSFER_ST2084:
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+ case HAL_DATASPACE_TRANSFER_HLG:
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut);
+ default:
+ ALOGV("Unsupported Gamma: %d, defaulting to sRGB transfer", dataspace);
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
+ }
+}
+
+AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer,
+ bool isRender) {
+ AHardwareBuffer_Desc desc;
+ AHardwareBuffer_describe(buffer, &desc);
+ bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
+ GrBackendFormat backendFormat =
+ GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeBackendTexture(context, buffer, desc.width, desc.height,
+ &mDeleteProc, &mUpdateProc, &mImageCtx,
+ createProtectedImage, backendFormat,
+ isRender);
+ mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+}
+
+void AutoBackendTexture::unref(bool releaseLocalResources) {
+ if (releaseLocalResources) {
+ mSurface = nullptr;
+ mImage = nullptr;
+ }
+
+ mUsageCount--;
+ if (mUsageCount <= 0) {
+ if (mBackendTexture.isValid()) {
+ mDeleteProc(mImageCtx);
+ mBackendTexture = {};
+ }
+ delete this;
+ }
+}
+
+// releaseSurfaceProc is invoked by SkSurface, when the texture is no longer in use.
+// "releaseContext" contains an "AutoBackendTexture*".
+void AutoBackendTexture::releaseSurfaceProc(SkSurface::ReleaseContext releaseContext) {
+ AutoBackendTexture* textureRelease = reinterpret_cast<AutoBackendTexture*>(releaseContext);
+ textureRelease->unref(false);
+}
+
+// releaseImageProc is invoked by SkImage, when the texture is no longer in use.
+// "releaseContext" contains an "AutoBackendTexture*".
+void AutoBackendTexture::releaseImageProc(SkImage::ReleaseContext releaseContext) {
+ AutoBackendTexture* textureRelease = reinterpret_cast<AutoBackendTexture*>(releaseContext);
+ textureRelease->unref(false);
+}
+
+sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
+ GrDirectContext* context) {
+ ATRACE_CALL();
+
+ if (mBackendTexture.isValid()) {
+ mUpdateProc(mImageCtx, context);
+ }
+
+ sk_sp<SkImage> image =
+ SkImage::MakeFromTexture(context, mBackendTexture, kTopLeft_GrSurfaceOrigin, mColorType,
+ alphaType, toSkColorSpace(dataspace), releaseImageProc, this);
+ if (image.get()) {
+ // The following ref will be counteracted by releaseProc, when SkImage is discarded.
+ ref();
+ }
+
+ mImage = image;
+ mDataspace = dataspace;
+ LOG_ALWAYS_FATAL_IF(mImage == nullptr, "Unable to generate SkImage from buffer");
+ return mImage;
+}
+
+sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace,
+ GrDirectContext* context) {
+ ATRACE_CALL();
+ if (!mSurface.get() || mDataspace != dataspace) {
+ sk_sp<SkSurface> surface =
+ SkSurface::MakeFromBackendTexture(context, mBackendTexture,
+ kTopLeft_GrSurfaceOrigin, 0, mColorType,
+ toSkColorSpace(dataspace), nullptr,
+ releaseSurfaceProc, this);
+ if (surface.get()) {
+ // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
+ ref();
+ }
+ mSurface = surface;
+ }
+
+ mDataspace = dataspace;
+ LOG_ALWAYS_FATAL_IF(mSurface == nullptr, "Unable to generate SkSurface");
+ return mSurface;
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
new file mode 100644
index 0000000..30f4b77
--- /dev/null
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 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 <GrAHardwareBufferUtils.h>
+#include <GrDirectContext.h>
+#include <SkImage.h>
+#include <SkSurface.h>
+#include <sys/types.h>
+
+#include "android-base/macros.h"
+#include "ui/GraphicTypes.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * AutoBackendTexture manages GPU image lifetime. It is a ref-counted object
+ * that keeps GPU resources alive until the last SkImage or SkSurface object using them is
+ * destroyed.
+ */
+class AutoBackendTexture {
+public:
+ // Local reference that supports RAII-style management of an AutoBackendTexture
+ // AutoBackendTexture by itself can't be managed in a similar fashion because
+ // of shared ownership with Skia objects, so we wrap it here instead.
+ class LocalRef {
+ public:
+ LocalRef() {}
+
+ ~LocalRef() {
+ // Destroying the texture is the same as setting it to null
+ setTexture(nullptr);
+ }
+
+ // Sets the texture to locally ref-track.
+ void setTexture(AutoBackendTexture* texture) {
+ if (mTexture != nullptr) {
+ mTexture->unref(true);
+ }
+
+ mTexture = texture;
+ if (mTexture != nullptr) {
+ mTexture->ref();
+ }
+ }
+
+ AutoBackendTexture* getTexture() const { return mTexture; }
+
+ DISALLOW_COPY_AND_ASSIGN(LocalRef);
+
+ private:
+ AutoBackendTexture* mTexture = nullptr;
+ };
+
+ // Creates a GrBackendTexture whose contents come from the provided buffer.
+ AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isRender);
+
+ void ref() { mUsageCount++; }
+
+ // releaseLocalResources is true if the underlying SkImage and SkSurface
+ // should be deleted from local tracking.
+ void unref(bool releaseLocalResources);
+
+ // Makes a new SkImage from the texture content.
+ // As SkImages are immutable but buffer content is not, we create
+ // a new SkImage every time.
+ sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
+ GrDirectContext* context);
+
+ // Makes a new SkSurface from the texture content, if needed.
+ sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context);
+
+private:
+ // The only way to invoke dtor is with unref, when mUsageCount is 0.
+ ~AutoBackendTexture() {}
+
+ GrBackendTexture mBackendTexture;
+ GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
+ GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
+ GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+
+ static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
+ static void releaseImageProc(SkImage::ReleaseContext releaseContext);
+
+ int mUsageCount = 0;
+
+ sk_sp<SkImage> mImage = nullptr;
+ sk_sp<SkSurface> mSurface = nullptr;
+ ui::Dataspace mDataspace = ui::Dataspace::UNKNOWN;
+ SkColorType mColorType = kUnknown_SkColorType;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 579260b..a0660fb 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -16,8 +16,10 @@
//#define LOG_NDEBUG 0
#include <cstdint>
+#include <memory>
#include "SkImageInfo.h"
+#include "log/log_main.h"
#include "system/graphics-base-v1.0.h"
#undef LOG_TAG
#define LOG_TAG "RenderEngine"
@@ -145,47 +147,6 @@
return err;
}
-// Converts an android dataspace to a supported SkColorSpace
-// Supported dataspaces are
-// 1. sRGB
-// 2. Display P3
-// 3. BT2020 PQ
-// 4. BT2020 HLG
-// Unknown primaries are mapped to BT709, and unknown transfer functions
-// are mapped to sRGB.
-static sk_sp<SkColorSpace> toColorSpace(ui::Dataspace dataspace) {
- skcms_Matrix3x3 gamut;
- switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
- case HAL_DATASPACE_STANDARD_BT709:
- gamut = SkNamedGamut::kSRGB;
- break;
- case HAL_DATASPACE_STANDARD_BT2020:
- gamut = SkNamedGamut::kRec2020;
- break;
- case HAL_DATASPACE_STANDARD_DCI_P3:
- gamut = SkNamedGamut::kDisplayP3;
- break;
- default:
- ALOGV("Unsupported Gamut: %d, defaulting to sRGB", dataspace);
- gamut = SkNamedGamut::kSRGB;
- break;
- }
-
- switch (dataspace & HAL_DATASPACE_TRANSFER_MASK) {
- case HAL_DATASPACE_TRANSFER_LINEAR:
- return SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, gamut);
- case HAL_DATASPACE_TRANSFER_SRGB:
- return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
- case HAL_DATASPACE_TRANSFER_ST2084:
- return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
- case HAL_DATASPACE_TRANSFER_HLG:
- return SkColorSpace::MakeRGB(SkNamedTransferFn::kHLG, gamut);
- default:
- ALOGV("Unsupported Gamma: %d, defaulting to sRGB transfer", dataspace);
- return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, gamut);
- }
-}
-
std::unique_ptr<SkiaGLRenderEngine> SkiaGLRenderEngine::create(
const RenderEngineCreationArgs& args) {
// initialize EGL for the default display
@@ -455,9 +416,15 @@
sourceTransfer != destTransfer;
}
+static bool needsLinearEffect(const mat4& colorTransform, ui::Dataspace sourceDataspace,
+ ui::Dataspace destinationDataspace) {
+ return colorTransform != mat4() || needsToneMapping(sourceDataspace, destinationDataspace);
+}
+
void SkiaGLRenderEngine::unbindExternalTextureBuffer(uint64_t bufferId) {
std::lock_guard<std::mutex> lock(mRenderingMutex);
- mImageCache.erase(bufferId);
+ mTextureCache.erase(bufferId);
+ mProtectedTextureCache.erase(bufferId);
}
status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
@@ -486,36 +453,36 @@
}
auto grContext = mInProtectedContext ? mProtectedGrContext : mGrContext;
- auto& cache = mInProtectedContext ? mProtectedSurfaceCache : mSurfaceCache;
+ auto& cache = mInProtectedContext ? mProtectedTextureCache : mTextureCache;
AHardwareBuffer_Desc bufferDesc;
AHardwareBuffer_describe(buffer->toAHardwareBuffer(), &bufferDesc);
LOG_ALWAYS_FATAL_IF(!hasUsage(bufferDesc, AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE),
"missing usage");
- sk_sp<SkSurface> surface;
+ std::shared_ptr<AutoBackendTexture::LocalRef> surfaceTextureRef = nullptr;
if (useFramebufferCache) {
auto iter = cache.find(buffer->getId());
if (iter != cache.end()) {
ALOGV("Cache hit!");
- surface = iter->second;
+ surfaceTextureRef = iter->second;
}
}
- if (!surface) {
- surface = SkSurface::MakeFromAHardwareBuffer(grContext.get(), buffer->toAHardwareBuffer(),
- GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin,
- mUseColorManagement
- ? toColorSpace(display.outputDataspace)
- : SkColorSpace::MakeSRGB(),
- nullptr);
- if (useFramebufferCache && surface) {
+
+ if (surfaceTextureRef == nullptr || surfaceTextureRef->getTexture() == nullptr) {
+ surfaceTextureRef = std::make_shared<AutoBackendTexture::LocalRef>();
+ surfaceTextureRef->setTexture(
+ new AutoBackendTexture(mGrContext.get(), buffer->toAHardwareBuffer(), true));
+ if (useFramebufferCache) {
ALOGD("Adding to cache");
- cache.insert({buffer->getId(), surface});
+ cache.insert({buffer->getId(), surfaceTextureRef});
}
}
- if (!surface) {
- ALOGE("Failed to make surface");
- return BAD_VALUE;
- }
+
+ sk_sp<SkSurface> surface =
+ surfaceTextureRef->getTexture()->getOrCreateSurface(mUseColorManagement
+ ? display.outputDataspace
+ : ui::Dataspace::SRGB,
+ mGrContext.get());
auto canvas = surface->getCanvas();
// Clear the entire canvas with a transparent black to prevent ghost images.
@@ -586,28 +553,37 @@
const auto& item = layer->source.buffer;
const auto bufferWidth = item.buffer->getBounds().width();
const auto bufferHeight = item.buffer->getBounds().height();
- sk_sp<SkImage> image;
- auto iter = mImageCache.find(item.buffer->getId());
- if (iter != mImageCache.end()) {
- image = iter->second;
+ std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef = nullptr;
+ auto iter = mTextureCache.find(item.buffer->getId());
+ if (iter != mTextureCache.end()) {
+ imageTextureRef = iter->second;
} else {
- image = SkImage::MakeFromAHardwareBuffer(
- item.buffer->toAHardwareBuffer(),
- item.isOpaque ? kOpaque_SkAlphaType
- : (item.usePremultipliedAlpha ? kPremul_SkAlphaType
- : kUnpremul_SkAlphaType),
- mUseColorManagement
- ? (needsToneMapping(layer->sourceDataspace, display.outputDataspace)
- // If we need to map to linear space, then
- // mark the source image with the same
- // colorspace as the destination surface so
- // that Skia's color management is a no-op.
- ? toColorSpace(display.outputDataspace)
- : toColorSpace(layer->sourceDataspace))
- : SkColorSpace::MakeSRGB());
- mImageCache.insert({item.buffer->getId(), image});
+ imageTextureRef = std::make_shared<AutoBackendTexture::LocalRef>();
+ imageTextureRef->setTexture(new AutoBackendTexture(mGrContext.get(),
+ item.buffer->toAHardwareBuffer(),
+ false));
+ mTextureCache.insert({buffer->getId(), imageTextureRef});
}
+ sk_sp<SkImage> image =
+ imageTextureRef->getTexture()
+ ->makeImage(mUseColorManagement
+ ? (needsLinearEffect(layer->colorTransform,
+ layer->sourceDataspace,
+ display.outputDataspace)
+ // If we need to map to linear space,
+ // then mark the source image with the
+ // same colorspace as the destination
+ // surface so that Skia's color
+ // management is a no-op.
+ ? display.outputDataspace
+ : layer->sourceDataspace)
+ : ui::Dataspace::SRGB,
+ item.isOpaque ? kOpaque_SkAlphaType
+ : (item.usePremultipliedAlpha
+ ? kPremul_SkAlphaType
+ : kUnpremul_SkAlphaType),
+ mGrContext.get());
SkMatrix matrix;
if (layer->geometry.roundedCornersRadius > 0) {
const auto roundedRect = getRoundedRect(layer);
@@ -664,13 +640,24 @@
}
if (mUseColorManagement &&
- needsToneMapping(layer->sourceDataspace, display.outputDataspace)) {
+ needsLinearEffect(layer->colorTransform, layer->sourceDataspace,
+ display.outputDataspace)) {
LinearEffect effect = LinearEffect{.inputDataspace = layer->sourceDataspace,
.outputDataspace = display.outputDataspace,
.undoPremultipliedAlpha = !item.isOpaque &&
item.usePremultipliedAlpha};
- sk_sp<SkRuntimeEffect> runtimeEffect = buildRuntimeEffect(effect);
+
+ auto effectIter = mRuntimeEffects.find(effect);
+ sk_sp<SkRuntimeEffect> runtimeEffect = nullptr;
+ if (effectIter == mRuntimeEffects.end()) {
+ runtimeEffect = buildRuntimeEffect(effect);
+ mRuntimeEffects.insert({effect, runtimeEffect});
+ } else {
+ runtimeEffect = effectIter->second;
+ }
+
paint.setShader(createLinearEffectShader(shader, effect, runtimeEffect,
+ layer->colorTransform,
display.maxLuminance,
layer->source.buffer.maxMasteringLuminance,
layer->source.buffer.maxContentLuminance));
@@ -921,10 +908,7 @@
return eglCreatePbufferSurface(display, placeholderConfig, attributes.data());
}
-void SkiaGLRenderEngine::cleanFramebufferCache() {
- mSurfaceCache.clear();
- mProtectedSurfaceCache.clear();
-}
+void SkiaGLRenderEngine::cleanFramebufferCache() {}
} // namespace skia
} // namespace renderengine
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 965cb41..f5eed1e 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -29,9 +29,13 @@
#include <mutex>
#include <unordered_map>
+#include "AutoBackendTexture.h"
#include "EGL/egl.h"
+#include "SkImageInfo.h"
#include "SkiaRenderEngine.h"
+#include "android-base/macros.h"
#include "filters/BlurFilter.h"
+#include "skia/filters/LinearEffect.h"
namespace android {
namespace renderengine {
@@ -92,8 +96,12 @@
const bool mUseColorManagement;
- // Cache of GL images that we'll store per GraphicBuffer ID
- std::unordered_map<uint64_t, sk_sp<SkImage>> mImageCache GUARDED_BY(mRenderingMutex);
+ // Cache of GL textures that we'll store per GraphicBuffer ID
+ std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
+ GUARDED_BY(mRenderingMutex);
+ std::unordered_map<uint64_t, std::shared_ptr<AutoBackendTexture::LocalRef>>
+ mProtectedTextureCache GUARDED_BY(mRenderingMutex);
+ std::unordered_map<LinearEffect, sk_sp<SkRuntimeEffect>, LinearEffectHasher> mRuntimeEffects;
// Mutex guarding rendering operations, so that:
// 1. GL operations aren't interleaved, and
// 2. Internal state related to rendering that is potentially modified by
@@ -107,8 +115,6 @@
// Same as above, but for protected content (eg. DRM)
sk_sp<GrDirectContext> mProtectedGrContext;
- std::unordered_map<uint64_t, sk_sp<SkSurface>> mSurfaceCache;
- std::unordered_map<uint64_t, sk_sp<SkSurface>> mProtectedSurfaceCache;
bool mInProtectedContext = false;
};
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index c6b8d3a..f19b64a 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -135,7 +135,7 @@
}
SkMatrix BlurFilter::getShaderMatrix() const {
- return SkMatrix::MakeScale(kInverseInputScale);
+ return SkMatrix::Scale(kInverseInputScale, kInverseInputScale);
}
} // namespace skia
diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp
index b628b3e..7680649 100644
--- a/libs/renderengine/skia/filters/LinearEffect.cpp
+++ b/libs/renderengine/skia/filters/LinearEffect.cpp
@@ -16,7 +16,10 @@
#include "LinearEffect.h"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
#include <SkString.h>
+#include <utils/Trace.h>
#include <optional>
@@ -427,6 +430,7 @@
}
sk_sp<SkRuntimeEffect> buildRuntimeEffect(const LinearEffect& linearEffect) {
+ ATRACE_CALL();
SkString shaderString;
generateEOTF(linearEffect.inputDataspace, shaderString);
generateXYZTransforms(shaderString);
@@ -443,8 +447,9 @@
sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader, const LinearEffect& linearEffect,
sk_sp<SkRuntimeEffect> runtimeEffect,
- float maxDisplayLuminance, float maxMasteringLuminance,
- float maxContentLuminance) {
+ const mat4& colorTransform, float maxDisplayLuminance,
+ float maxMasteringLuminance, float maxContentLuminance) {
+ ATRACE_CALL();
SkRuntimeShaderBuilder effectBuilder(runtimeEffect);
effectBuilder.child("input") = shader;
@@ -453,7 +458,7 @@
ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace);
effectBuilder.uniform("in_rgbToXyz") = mat4(inputColorSpace.getRGBtoXYZ());
- effectBuilder.uniform("in_xyzToRgb") = mat4(outputColorSpace.getXYZtoRGB());
+ effectBuilder.uniform("in_xyzToRgb") = colorTransform * mat4(outputColorSpace.getXYZtoRGB());
effectBuilder.uniform("in_displayMaxLuminance") = maxDisplayLuminance;
effectBuilder.uniform("in_inputMaxLuminance") =
std::min(maxMasteringLuminance, maxContentLuminance);
diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h
index 2615669..20b8338 100644
--- a/libs/renderengine/skia/filters/LinearEffect.h
+++ b/libs/renderengine/skia/filters/LinearEffect.h
@@ -16,9 +16,10 @@
#pragma once
+#include <math/mat4.h>
+
#include <optional>
-#include "SkColorMatrix.h"
#include "SkRuntimeEffect.h"
#include "SkShader.h"
#include "ui/GraphicTypes.h"
@@ -63,11 +64,31 @@
const bool undoPremultipliedAlpha = false;
};
+static inline bool operator==(const LinearEffect& lhs, const LinearEffect& rhs) {
+ return lhs.inputDataspace == rhs.inputDataspace && lhs.outputDataspace == rhs.outputDataspace &&
+ lhs.undoPremultipliedAlpha == rhs.undoPremultipliedAlpha;
+}
+
+struct LinearEffectHasher {
+ // Inspired by art/runtime/class_linker.cc
+ // Also this is what boost:hash_combine does
+ static size_t HashCombine(size_t seed, size_t val) {
+ return seed ^ (val + 0x9e3779b9 + (seed << 6) + (seed >> 2));
+ }
+ size_t operator()(const LinearEffect& le) const {
+ size_t result = std::hash<ui::Dataspace>{}(le.inputDataspace);
+ result = HashCombine(result, std::hash<ui::Dataspace>{}(le.outputDataspace));
+ return HashCombine(result, std::hash<bool>{}(le.undoPremultipliedAlpha));
+ }
+};
+
sk_sp<SkRuntimeEffect> buildRuntimeEffect(const LinearEffect& linearEffect);
// Generates a shader resulting from applying the a linear effect created from
-// LinearEffectARgs::buildEffect to an inputShader. We also provide additional HDR metadata upon
-// creating the shader:
+// LinearEffectArgs::buildEffect to an inputShader.
+// Optionally, a color transform may also be provided, which combines with the
+// matrix transforming from linear XYZ to linear RGB immediately before OETF.
+// We also provide additional HDR metadata upon creating the shader:
// * The max display luminance is the max luminance of the physical display in nits
// * The max mastering luminance is provided as the max luminance from the SMPTE 2086
// standard.
@@ -76,8 +97,8 @@
sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> inputShader,
const LinearEffect& linearEffect,
sk_sp<SkRuntimeEffect> runtimeEffect,
- float maxDisplayLuminance, float maxMasteringLuminance,
- float maxContentLuminance);
+ const mat4& colorTransform, float maxDisplayLuminance,
+ float maxMasteringLuminance, float maxContentLuminance);
} // namespace skia
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index d20fcc4..0e11c99 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#undef LOG_TAG
+#define LOG_TAG "RenderEngineTest"
+
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
@@ -37,8 +40,19 @@
namespace android {
-struct RenderEngineTest : public ::testing::Test {
- static void SetUpTestSuite() {
+class RenderEngineFactory {
+public:
+ virtual ~RenderEngineFactory() = default;
+
+ virtual std::string name() = 0;
+ virtual std::unique_ptr<renderengine::gl::GLESRenderEngine> createRenderEngine() = 0;
+};
+
+class GLESRenderEngineFactory : public RenderEngineFactory {
+public:
+ std::string name() override { return "GLESRenderEngineFactory"; }
+
+ std::unique_ptr<renderengine::gl::GLESRenderEngine> createRenderEngine() override {
renderengine::RenderEngineCreationArgs reCreationArgs =
renderengine::RenderEngineCreationArgs::Builder()
.setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
@@ -50,20 +64,32 @@
.setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
.setRenderEngineType(renderengine::RenderEngine::RenderEngineType::GLES)
.build();
- sRE = renderengine::gl::GLESRenderEngine::create(reCreationArgs);
-
- reCreationArgs.useColorManagement = true;
- sRECM = renderengine::gl::GLESRenderEngine::create(reCreationArgs);
+ return renderengine::gl::GLESRenderEngine::create(reCreationArgs);
}
+};
- static void TearDownTestSuite() {
- // The ordering here is important - sCurrentBuffer must live longer
- // than RenderEngine to avoid a null reference on tear-down.
- sRE = nullptr;
- sRECM = nullptr;
- sCurrentBuffer = nullptr;
+class GLESCMRenderEngineFactory : public RenderEngineFactory {
+public:
+ std::string name() override { return "GLESCMRenderEngineFactory"; }
+
+ std::unique_ptr<renderengine::gl::GLESRenderEngine> createRenderEngine() override {
+ renderengine::RenderEngineCreationArgs reCreationArgs =
+ renderengine::RenderEngineCreationArgs::Builder()
+ .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
+ .setImageCacheSize(1)
+ .setEnableProtectedContext(false)
+ .setPrecacheToneMapperShaderOnly(false)
+ .setSupportsBackgroundBlur(true)
+ .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
+ .setRenderEngineType(renderengine::RenderEngine::RenderEngineType::GLES)
+ .setUseColorManagerment(true)
+ .build();
+ return renderengine::gl::GLESRenderEngine::create(reCreationArgs);
}
+};
+class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
+public:
static sp<GraphicBuffer> allocateDefaultBuffer() {
return new GraphicBuffer(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT,
HAL_PIXEL_FORMAT_RGBA_8888, 1,
@@ -80,19 +106,24 @@
"input");
}
- RenderEngineTest() { mBuffer = allocateDefaultBuffer(); }
+ RenderEngineTest() {
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+ mBuffer = allocateDefaultBuffer();
+ }
~RenderEngineTest() {
if (WRITE_BUFFER_TO_FILE_ON_FAILURE && ::testing::Test::HasFailure()) {
writeBufferToFile("/data/texture_out_");
}
for (uint32_t texName : mTexNames) {
- sRE->deleteTextures(1, &texName);
- EXPECT_FALSE(sRE->isTextureNameKnownForTesting(texName));
+ mRE->deleteTextures(1, &texName);
+ EXPECT_FALSE(mRE->isTextureNameKnownForTesting(texName));
}
- for (uint32_t texName : mTexNamesCM) {
- sRECM->deleteTextures(1, &texName);
- }
+ const ::testing::TestInfo* const test_info =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
void writeBufferToFile(const char* basename) {
@@ -261,12 +292,11 @@
void invokeDraw(renderengine::DisplaySettings settings,
std::vector<const renderengine::LayerSettings*> layers,
- sp<GraphicBuffer> buffer, bool useColorManagement = false) {
+ sp<GraphicBuffer> buffer) {
base::unique_fd fence;
- status_t status = useColorManagement
- ? sRECM->drawLayers(settings, layers, buffer, true, base::unique_fd(), &fence)
- : sRE->drawLayers(settings, layers, buffer, true, base::unique_fd(), &fence);
- sCurrentBuffer = buffer;
+ status_t status =
+ mRE->drawLayers(settings, layers, buffer, true, base::unique_fd(), &fence);
+ mCurrentBuffer = buffer;
int fd = fence.release();
if (fd >= 0) {
@@ -276,11 +306,7 @@
ASSERT_EQ(NO_ERROR, status);
if (layers.size() > 0) {
- if (useColorManagement) {
- ASSERT_TRUE(sRECM->isFramebufferImageCachedForTesting(buffer->getId()));
- } else {
- ASSERT_TRUE(sRE->isFramebufferImageCachedForTesting(buffer->getId()));
- }
+ ASSERT_TRUE(mRE->isFramebufferImageCachedForTesting(buffer->getId()));
}
}
@@ -335,15 +361,12 @@
void fillBufferLayerTransform();
template <typename SourceVariant>
- void fillBufferWithColorTransform(bool useColorManagement = false);
+ void fillBufferWithColorTransform();
template <typename SourceVariant>
void fillBufferColorTransform();
template <typename SourceVariant>
- void fillBufferColorTransformCM();
-
- template <typename SourceVariant>
void fillRedBufferWithRoundedCorners();
template <typename SourceVariant>
@@ -378,38 +401,30 @@
const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
const ubyte4& backgroundColor);
- // Keep around the same renderengine object to save on initialization time.
- // For now, exercise the GL backend directly so that some caching specifics
- // can be tested without changing the interface.
- static std::unique_ptr<renderengine::gl::GLESRenderEngine> sRE;
- // renderengine object with Color Management enabled
- static std::unique_ptr<renderengine::gl::GLESRenderEngine> sRECM;
+ std::unique_ptr<renderengine::gl::GLESRenderEngine> mRE;
+
// Dumb hack to avoid NPE in the EGL driver: the GraphicBuffer needs to
// be freed *after* RenderEngine is destroyed, so that the EGL image is
// destroyed first.
- static sp<GraphicBuffer> sCurrentBuffer;
+ sp<GraphicBuffer> mCurrentBuffer;
sp<GraphicBuffer> mBuffer;
std::vector<uint32_t> mTexNames;
- std::vector<uint32_t> mTexNamesCM;
};
-std::unique_ptr<renderengine::gl::GLESRenderEngine> RenderEngineTest::sRE = nullptr;
-std::unique_ptr<renderengine::gl::GLESRenderEngine> RenderEngineTest::sRECM = nullptr;
-
-sp<GraphicBuffer> RenderEngineTest::sCurrentBuffer = nullptr;
-
struct ColorSourceVariant {
static void fillColor(renderengine::LayerSettings& layer, half r, half g, half b,
- RenderEngineTest* /*fixture*/, bool /*useColorManagement*/ = false) {
+ RenderEngineTest* /*fixture*/) {
layer.source.solidColor = half3(r, g, b);
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
}
};
struct RelaxOpaqueBufferVariant {
static void setOpaqueBit(renderengine::LayerSettings& layer) {
layer.source.buffer.isOpaque = false;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
}
static uint8_t getAlphaChannel() { return 255; }
@@ -418,6 +433,7 @@
struct ForceOpaqueBufferVariant {
static void setOpaqueBit(renderengine::LayerSettings& layer) {
layer.source.buffer.isOpaque = true;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
}
static uint8_t getAlphaChannel() {
@@ -430,16 +446,11 @@
template <typename OpaquenessVariant>
struct BufferSourceVariant {
static void fillColor(renderengine::LayerSettings& layer, half r, half g, half b,
- RenderEngineTest* fixture, bool useColorManagement = false) {
+ RenderEngineTest* fixture) {
sp<GraphicBuffer> buf = RenderEngineTest::allocateSourceBuffer(1, 1);
uint32_t texName;
- if (useColorManagement) {
- fixture->sRECM->genTextures(1, &texName);
- fixture->mTexNamesCM.push_back(texName);
- } else {
- fixture->sRE->genTextures(1, &texName);
- fixture->mTexNames.push_back(texName);
- }
+ fixture->mRE->genTextures(1, &texName);
+ fixture->mTexNames.push_back(texName);
uint8_t* pixels;
buf->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -460,6 +471,7 @@
layer.source.buffer.buffer = buf;
layer.source.buffer.textureName = texName;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
OpaquenessVariant::setOpaqueBit(layer);
}
};
@@ -469,10 +481,12 @@
renderengine::DisplaySettings settings;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layer;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
layer.geometry.boundaries = fullscreenRect().toFloatRect();
SourceVariant::fillColor(layer, r, g, b, this);
layer.alpha = a;
@@ -509,12 +523,14 @@
template <typename SourceVariant>
void RenderEngineTest::fillRedOffsetBuffer() {
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = offsetRect();
settings.clip = offsetRectAtZero();
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layer;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
layer.geometry.boundaries = offsetRectAtZero().toFloatRect();
SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
layer.alpha = 1.0f;
@@ -540,6 +556,7 @@
template <typename SourceVariant>
void RenderEngineTest::fillBufferCheckers(uint32_t orientationFlag) {
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
// Here logical space is 2x2
settings.clip = Rect(2, 2);
@@ -548,18 +565,21 @@
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layerOne;
+ layerOne.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
Rect rectOne(0, 0, 1, 1);
layerOne.geometry.boundaries = rectOne.toFloatRect();
SourceVariant::fillColor(layerOne, 1.0f, 0.0f, 0.0f, this);
layerOne.alpha = 1.0f;
renderengine::LayerSettings layerTwo;
+ layerTwo.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
Rect rectTwo(0, 1, 1, 2);
layerTwo.geometry.boundaries = rectTwo.toFloatRect();
SourceVariant::fillColor(layerTwo, 0.0f, 1.0f, 0.0f, this);
layerTwo.alpha = 1.0f;
renderengine::LayerSettings layerThree;
+ layerThree.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
Rect rectThree(1, 0, 2, 1);
layerThree.geometry.boundaries = rectThree.toFloatRect();
SourceVariant::fillColor(layerThree, 0.0f, 0.0f, 1.0f, this);
@@ -642,10 +662,12 @@
settings.physicalDisplay = fullscreenRect();
// Here logical space is 2x2
settings.clip = Rect(2, 2);
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layer;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
layer.geometry.boundaries = Rect(1, 1).toFloatRect();
// Translate one pixel diagonally
layer.geometry.positionTransform = mat4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1);
@@ -669,16 +691,18 @@
}
template <typename SourceVariant>
-void RenderEngineTest::fillBufferWithColorTransform(bool useColorManagement) {
+void RenderEngineTest::fillBufferWithColorTransform() {
renderengine::DisplaySettings settings;
settings.physicalDisplay = fullscreenRect();
settings.clip = Rect(1, 1);
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layer;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
layer.geometry.boundaries = Rect(1, 1).toFloatRect();
- SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this, useColorManagement);
+ SourceVariant::fillColor(layer, 0.5f, 0.25f, 0.125f, this);
layer.alpha = 1.0f;
// construct a fake color matrix
@@ -692,7 +716,7 @@
layers.push_back(&layer);
- invokeDraw(settings, layers, mBuffer, useColorManagement);
+ invokeDraw(settings, layers, mBuffer);
}
template <typename SourceVariant>
@@ -702,20 +726,16 @@
}
template <typename SourceVariant>
-void RenderEngineTest::fillBufferColorTransformCM() {
- fillBufferWithColorTransform<SourceVariant>(true);
- expectBufferColor(fullscreenRect(), 126, 0, 0, 255, 1);
-}
-
-template <typename SourceVariant>
void RenderEngineTest::fillRedBufferWithRoundedCorners() {
renderengine::DisplaySettings settings;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layer;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
layer.geometry.boundaries = fullscreenRect().toFloatRect();
layer.geometry.roundedCornersRadius = 5.0f;
layer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
@@ -756,18 +776,21 @@
auto center = DEFAULT_DISPLAY_WIDTH / 2;
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings backgroundLayer;
+ backgroundLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
backgroundLayer.geometry.boundaries = fullscreenRect().toFloatRect();
SourceVariant::fillColor(backgroundLayer, 0.0f, 1.0f, 0.0f, this);
backgroundLayer.alpha = 1.0f;
layers.push_back(&backgroundLayer);
renderengine::LayerSettings leftLayer;
+ leftLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
leftLayer.geometry.boundaries =
Rect(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT).toFloatRect();
SourceVariant::fillColor(leftLayer, 1.0f, 0.0f, 0.0f, this);
@@ -775,6 +798,7 @@
layers.push_back(&leftLayer);
renderengine::LayerSettings blurLayer;
+ blurLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
blurLayer.geometry.boundaries = fullscreenRect().toFloatRect();
blurLayer.backgroundBlurRadius = blurRadius;
blurLayer.alpha = 0;
@@ -793,10 +817,12 @@
renderengine::DisplaySettings settings;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
std::vector<const renderengine::LayerSettings*> layersFirst;
renderengine::LayerSettings layerOne;
+ layerOne.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
layerOne.geometry.boundaries =
FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH / 3.0, DEFAULT_DISPLAY_HEIGHT / 3.0);
SourceVariant::fillColor(layerOne, 1.0f, 0.0f, 0.0f, this);
@@ -811,6 +837,7 @@
std::vector<const renderengine::LayerSettings*> layersSecond;
renderengine::LayerSettings layerTwo;
+ layerTwo.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
layerTwo.geometry.boundaries =
FloatRect(DEFAULT_DISPLAY_WIDTH / 3.0, DEFAULT_DISPLAY_HEIGHT / 3.0,
DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
@@ -830,15 +857,17 @@
renderengine::DisplaySettings settings;
settings.physicalDisplay = fullscreenRect();
settings.clip = Rect(1, 1);
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layer;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
// Here will allocate a checker board texture, but transform texture
// coordinates so that only the upper left is applied.
sp<GraphicBuffer> buf = allocateSourceBuffer(2, 2);
uint32_t texName;
- RenderEngineTest::sRE->genTextures(1, &texName);
+ RenderEngineTest::mRE->genTextures(1, &texName);
this->mTexNames.push_back(texName);
uint8_t* pixels;
@@ -887,7 +916,7 @@
renderengine::LayerSettings layer;
sp<GraphicBuffer> buf = allocateSourceBuffer(1, 1);
uint32_t texName;
- RenderEngineTest::sRE->genTextures(1, &texName);
+ RenderEngineTest::mRE->genTextures(1, &texName);
this->mTexNames.push_back(texName);
uint8_t* pixels;
@@ -926,7 +955,7 @@
renderengine::LayerSettings layer;
sp<GraphicBuffer> buf = allocateSourceBuffer(1, 1);
uint32_t texName;
- RenderEngineTest::sRE->genTextures(1, &texName);
+ RenderEngineTest::mRE->genTextures(1, &texName);
this->mTexNames.push_back(texName);
uint8_t* pixels;
@@ -981,6 +1010,7 @@
const renderengine::ShadowSettings& shadow,
const ubyte4& casterColor, const ubyte4& backgroundColor) {
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
@@ -988,6 +1018,7 @@
// add background layer
renderengine::LayerSettings bgLayer;
+ bgLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
bgLayer.geometry.boundaries = fullscreenRect().toFloatRect();
ColorSourceVariant::fillColor(bgLayer, backgroundColor.r / 255.0f, backgroundColor.g / 255.0f,
backgroundColor.b / 255.0f, this);
@@ -996,6 +1027,7 @@
// add shadow layer
renderengine::LayerSettings shadowLayer;
+ shadowLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
shadowLayer.geometry.boundaries = castingLayer.geometry.boundaries;
shadowLayer.alpha = castingLayer.alpha;
shadowLayer.shadow = shadow;
@@ -1003,6 +1035,7 @@
// add layer casting the shadow
renderengine::LayerSettings layer = castingLayer;
+ layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
SourceVariant::fillColor(layer, casterColor.r / 255.0f, casterColor.g / 255.0f,
casterColor.b / 255.0f, this);
layers.push_back(&layer);
@@ -1010,25 +1043,39 @@
invokeDraw(settings, layers, mBuffer);
}
-TEST_F(RenderEngineTest, drawLayers_noLayersToDraw) {
+INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
+ testing::Values(std::make_shared<GLESRenderEngineFactory>(),
+ std::make_shared<GLESCMRenderEngineFactory>()));
+
+TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
drawEmptyLayers();
}
-TEST_F(RenderEngineTest, drawLayers_nullOutputBuffer) {
+TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layer;
layer.geometry.boundaries = fullscreenRect().toFloatRect();
BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
layers.push_back(&layer);
base::unique_fd fence;
- status_t status = sRE->drawLayers(settings, layers, nullptr, true, base::unique_fd(), &fence);
+ status_t status = mRE->drawLayers(settings, layers, nullptr, true, base::unique_fd(), &fence);
ASSERT_EQ(BAD_VALUE, status);
}
-TEST_F(RenderEngineTest, drawLayers_nullOutputFence) {
+TEST_P(RenderEngineTest, drawLayers_nullOutputFence) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
@@ -1039,14 +1086,18 @@
layer.alpha = 1.0;
layers.push_back(&layer);
- status_t status = sRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), nullptr);
- sCurrentBuffer = mBuffer;
+ status_t status = mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), nullptr);
+ mCurrentBuffer = mBuffer;
ASSERT_EQ(NO_ERROR, status);
expectBufferColor(fullscreenRect(), 255, 0, 0, 255);
}
-TEST_F(RenderEngineTest, drawLayers_doesNotCacheFramebuffer) {
+TEST_P(RenderEngineTest, drawLayers_doesNotCacheFramebuffer) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
@@ -1057,211 +1108,341 @@
layer.alpha = 1.0;
layers.push_back(&layer);
- status_t status = sRE->drawLayers(settings, layers, mBuffer, false, base::unique_fd(), nullptr);
- sCurrentBuffer = mBuffer;
+ status_t status = mRE->drawLayers(settings, layers, mBuffer, false, base::unique_fd(), nullptr);
+ mCurrentBuffer = mBuffer;
ASSERT_EQ(NO_ERROR, status);
- ASSERT_FALSE(sRE->isFramebufferImageCachedForTesting(mBuffer->getId()));
+ ASSERT_FALSE(mRE->isFramebufferImageCachedForTesting(mBuffer->getId()));
expectBufferColor(fullscreenRect(), 255, 0, 0, 255);
}
-TEST_F(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillRedBuffer<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillGreenBuffer<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBlueBuffer<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillRedTransparentBuffer<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferPhysicalOffset<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate0<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate90<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate180<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate270<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferLayerTransform<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferColorTransform<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransformCM_colorSource) {
- fillBufferColorTransformCM<ColorSourceVariant>();
-}
+TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
-TEST_F(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) {
fillBufferWithRoundedCorners<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferAndBlurBackground<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_overlayCorners_colorSource) {
+TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
overlayCorners<ColorSourceVariant>();
}
-TEST_F(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillRedBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillGreenBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBlueBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillRedTransparentBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferPhysicalOffset<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate0<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate90<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate180<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate270<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferLayerTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferColorTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransformCM_opaqueBufferSource) {
- fillBufferColorTransformCM<BufferSourceVariant<ForceOpaqueBufferVariant>>();
-}
+TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
-TEST_F(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) {
fillBufferWithRoundedCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferAndBlurBackground<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) {
+TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
overlayCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillRedBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillGreenBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBlueBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillRedTransparentBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferPhysicalOffset<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate0<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate90<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate180<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferCheckersRotate270<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferLayerTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferColorTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferColorTransformCM_bufferSource) {
- fillBufferColorTransformCM<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
-}
+TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
-TEST_F(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) {
fillBufferWithRoundedCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferAndBlurBackground<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_overlayCorners_bufferSource) {
+TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
overlayCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
-TEST_F(RenderEngineTest, drawLayers_fillBufferTextureTransform) {
+TEST_P(RenderEngineTest, drawLayers_fillBufferTextureTransform) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferTextureTransform();
}
-TEST_F(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) {
+TEST_P(RenderEngineTest, drawLayers_fillBuffer_premultipliesAlpha) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferWithPremultiplyAlpha();
}
-TEST_F(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) {
+TEST_P(RenderEngineTest, drawLayers_fillBuffer_withoutPremultiplyingAlpha) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
fillBufferWithoutPremultiplyAlpha();
}
-TEST_F(RenderEngineTest, drawLayers_clearRegion) {
+TEST_P(RenderEngineTest, drawLayers_clearRegion) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
clearRegion();
}
-TEST_F(RenderEngineTest, drawLayers_fillsBufferAndCachesImages) {
+TEST_P(RenderEngineTest, drawLayers_fillsBufferAndCachesImages) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
@@ -1274,21 +1455,24 @@
layers.push_back(&layer);
invokeDraw(settings, layers, mBuffer);
uint64_t bufferId = layer.source.buffer.buffer->getId();
- EXPECT_TRUE(sRE->isImageCachedForTesting(bufferId));
+ EXPECT_TRUE(mRE->isImageCachedForTesting(bufferId));
std::shared_ptr<renderengine::gl::ImageManager::Barrier> barrier =
- sRE->unbindExternalTextureBufferForTesting(bufferId);
+ mRE->unbindExternalTextureBufferForTesting(bufferId);
std::lock_guard<std::mutex> lock(barrier->mutex);
ASSERT_TRUE(barrier->condition.wait_for(barrier->mutex, std::chrono::seconds(5),
[&]() REQUIRES(barrier->mutex) {
return barrier->isOpen;
}));
- EXPECT_FALSE(sRE->isImageCachedForTesting(bufferId));
+ EXPECT_FALSE(mRE->isImageCachedForTesting(bufferId));
EXPECT_EQ(NO_ERROR, barrier->result);
}
-TEST_F(RenderEngineTest, cacheExternalBuffer_withNullBuffer) {
+TEST_P(RenderEngineTest, cacheExternalBuffer_withNullBuffer) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
std::shared_ptr<renderengine::gl::ImageManager::Barrier> barrier =
- sRE->cacheExternalTextureBufferForTesting(nullptr);
+ mRE->cacheExternalTextureBufferForTesting(nullptr);
std::lock_guard<std::mutex> lock(barrier->mutex);
ASSERT_TRUE(barrier->condition.wait_for(barrier->mutex, std::chrono::seconds(5),
[&]() REQUIRES(barrier->mutex) {
@@ -1298,11 +1482,14 @@
EXPECT_EQ(BAD_VALUE, barrier->result);
}
-TEST_F(RenderEngineTest, cacheExternalBuffer_cachesImages) {
+TEST_P(RenderEngineTest, cacheExternalBuffer_cachesImages) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
sp<GraphicBuffer> buf = allocateSourceBuffer(1, 1);
uint64_t bufferId = buf->getId();
std::shared_ptr<renderengine::gl::ImageManager::Barrier> barrier =
- sRE->cacheExternalTextureBufferForTesting(buf);
+ mRE->cacheExternalTextureBufferForTesting(buf);
{
std::lock_guard<std::mutex> lock(barrier->mutex);
ASSERT_TRUE(barrier->condition.wait_for(barrier->mutex, std::chrono::seconds(5),
@@ -1311,8 +1498,8 @@
}));
EXPECT_EQ(NO_ERROR, barrier->result);
}
- EXPECT_TRUE(sRE->isImageCachedForTesting(bufferId));
- barrier = sRE->unbindExternalTextureBufferForTesting(bufferId);
+ EXPECT_TRUE(mRE->isImageCachedForTesting(bufferId));
+ barrier = mRE->unbindExternalTextureBufferForTesting(bufferId);
{
std::lock_guard<std::mutex> lock(barrier->mutex);
ASSERT_TRUE(barrier->condition.wait_for(barrier->mutex, std::chrono::seconds(5),
@@ -1321,10 +1508,13 @@
}));
EXPECT_EQ(NO_ERROR, barrier->result);
}
- EXPECT_FALSE(sRE->isImageCachedForTesting(bufferId));
+ EXPECT_FALSE(mRE->isImageCachedForTesting(bufferId));
}
-TEST_F(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
+TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
const ubyte4 casterColor(255, 0, 0, 255);
const ubyte4 backgroundColor(255, 255, 255, 255);
const float shadowLength = 5.0f;
@@ -1341,13 +1531,17 @@
expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
}
-TEST_F(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
+TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
const ubyte4 casterColor(255, 0, 0, 255);
const ubyte4 backgroundColor(255, 255, 255, 255);
const float shadowLength = 5.0f;
Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
renderengine::LayerSettings castingLayer;
+ castingLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
renderengine::ShadowSettings settings =
@@ -1358,13 +1552,17 @@
expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
}
-TEST_F(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
+TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
const ubyte4 casterColor(255, 0, 0, 255);
const ubyte4 backgroundColor(255, 255, 255, 255);
const float shadowLength = 5.0f;
Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
renderengine::LayerSettings castingLayer;
+ castingLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
renderengine::ShadowSettings settings =
@@ -1376,7 +1574,10 @@
expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
}
-TEST_F(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
+TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
const ubyte4 casterColor(255, 0, 0, 255);
const ubyte4 backgroundColor(255, 255, 255, 255);
const float shadowLength = 5.0f;
@@ -1396,7 +1597,10 @@
expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
}
-TEST_F(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
+TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
const ubyte4 casterColor(255, 0, 0, 255);
const ubyte4 backgroundColor(255, 255, 255, 255);
const float shadowLength = 5.0f;
@@ -1421,10 +1625,14 @@
backgroundColor.a);
}
-TEST_F(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
+TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
renderengine::DisplaySettings settings;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
std::vector<const renderengine::LayerSettings*> layers;
renderengine::LayerSettings layer;
@@ -1434,23 +1642,27 @@
layers.push_back(&layer);
base::unique_fd fenceOne;
- sRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), &fenceOne);
+ mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), &fenceOne);
base::unique_fd fenceTwo;
- sRE->drawLayers(settings, layers, mBuffer, true, std::move(fenceOne), &fenceTwo);
+ mRE->drawLayers(settings, layers, mBuffer, true, std::move(fenceOne), &fenceTwo);
const int fd = fenceTwo.get();
if (fd >= 0) {
sync_wait(fd, -1);
}
// Only cleanup the first time.
- EXPECT_TRUE(sRE->cleanupPostRender(
+ EXPECT_TRUE(mRE->cleanupPostRender(
renderengine::RenderEngine::CleanupMode::CLEAN_OUTPUT_RESOURCES));
- EXPECT_FALSE(sRE->cleanupPostRender(
+ EXPECT_FALSE(mRE->cleanupPostRender(
renderengine::RenderEngine::CleanupMode::CLEAN_OUTPUT_RESOURCES));
}
-TEST_F(RenderEngineTest, cleanupPostRender_whenCleaningAll_replacesTextureMemory) {
+TEST_P(RenderEngineTest, cleanupPostRender_whenCleaningAll_replacesTextureMemory) {
+ const auto& renderEngineFactory = GetParam();
+ mRE = renderEngineFactory->createRenderEngine();
+
renderengine::DisplaySettings settings;
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
settings.clip = fullscreenRect();
@@ -1462,7 +1674,7 @@
layers.push_back(&layer);
base::unique_fd fence;
- sRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), &fence);
+ mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd(), &fence);
const int fd = fence.get();
if (fd >= 0) {
@@ -1471,15 +1683,15 @@
uint64_t bufferId = layer.source.buffer.buffer->getId();
uint32_t texName = layer.source.buffer.textureName;
- EXPECT_TRUE(sRE->isImageCachedForTesting(bufferId));
- EXPECT_EQ(bufferId, sRE->getBufferIdForTextureNameForTesting(texName));
+ EXPECT_TRUE(mRE->isImageCachedForTesting(bufferId));
+ EXPECT_EQ(bufferId, mRE->getBufferIdForTextureNameForTesting(texName));
- EXPECT_TRUE(sRE->cleanupPostRender(renderengine::RenderEngine::CleanupMode::CLEAN_ALL));
+ EXPECT_TRUE(mRE->cleanupPostRender(renderengine::RenderEngine::CleanupMode::CLEAN_ALL));
// Now check that our view of memory is good.
- EXPECT_FALSE(sRE->isImageCachedForTesting(bufferId));
- EXPECT_EQ(std::nullopt, sRE->getBufferIdForTextureNameForTesting(bufferId));
- EXPECT_TRUE(sRE->isTextureNameKnownForTesting(texName));
+ EXPECT_FALSE(mRE->isImageCachedForTesting(bufferId));
+ EXPECT_EQ(std::nullopt, mRE->getBufferIdForTextureNameForTesting(bufferId));
+ EXPECT_TRUE(mRE->isTextureNameKnownForTesting(texName));
}
} // namespace android
diff --git a/libs/vibrator/Android.bp b/libs/vibrator/Android.bp
index 1681fe2..49bc6bf 100644
--- a/libs/vibrator/Android.bp
+++ b/libs/vibrator/Android.bp
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-cc_library_shared {
+cc_library {
name: "libvibrator",
vendor_available: true,
double_loadable: true,
@@ -47,4 +47,11 @@
],
export_include_dirs: ["include"],
+
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
}
diff --git a/libs/vibrator/fuzzer/Android.bp b/libs/vibrator/fuzzer/Android.bp
new file mode 100644
index 0000000..8020151
--- /dev/null
+++ b/libs/vibrator/fuzzer/Android.bp
@@ -0,0 +1,43 @@
+/******************************************************************************
+ *
+ * Copyright (C) 2020 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.
+ *
+ *****************************************************************************
+ */
+
+cc_fuzz {
+ name: "vibrator_fuzzer",
+
+ host_supported: true,
+
+ srcs: [
+ "vibrator_fuzzer.cpp",
+ ],
+
+ static_libs: [
+ "liblog",
+ "libvibrator",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "libbase",
+ "libutils",
+ ],
+
+ fuzz_config: {
+ componentid: 155276,
+ },
+}
diff --git a/libs/vibrator/fuzzer/README.md b/libs/vibrator/fuzzer/README.md
new file mode 100644
index 0000000..43eb2d2
--- /dev/null
+++ b/libs/vibrator/fuzzer/README.md
@@ -0,0 +1,65 @@
+# Fuzzer for libvibrator
+
+## Plugin Design Considerations
+This fuzzer fuzzes native code present in libvibrator and does not cover the Java implementation ExternalVibration
+The fuzzer plugin is designed based on the understanding of the
+library and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+libvibrator supports the following parameters:
+1. Uid (parameter name: `uid`)
+2. Package Name (parameter name: `pkg`)
+3. Audio Content Type (parameter name: `content_type`)
+4. Audio Usage (parameter name: `usage`)
+5. Audio Source (parameter name: `source`)
+6. Audio flags (parameter name: `flags`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `uid` | `INT32_MIN` to `INT32_MAX` | Value obtained from FuzzedDataProvider |
+| `pkg` | Any std::string value | Value obtained from FuzzedDataProvider |
+| `content_type` | 0.`AUDIO_CONTENT_TYPE_UNKNOWN` 1.`AUDIO_CONTENT_TYPE_SPEECH` 2.`AUDIO_CONTENT_TYPE_MUSIC` 3.`AUDIO_CONTENT_TYPE_MOVIE` 4.`AUDIO_CONTENT_TYPE_SONIFICATION`| Value obtained from FuzzedDataProvider in the range 0 to 4|
+| `usage` | 0.`AUDIO_USAGE_UNKNOWN` 1.`AUDIO_USAGE_MEDIA` 2.`AUDIO_USAGE_VOICE_COMMUNICATION` 3.`AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING` 4.`AUDIO_USAGE_ALARM` 5.`AUDIO_USAGE_NOTIFICATION` 6.`AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE` 7.`AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST` 8.`AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT` 9.`AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED` 10.`AUDIO_USAGE_NOTIFICATION_EVENT` 11.`AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY` 12.`AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE` 13.`AUDIO_USAGE_ASSISTANCE_SONIFICATION` 14.`AUDIO_USAGE_GAME` 15.`AUDIO_USAGE_VIRTUAL_SOURCE` 16.`AUDIO_USAGE_ASSISTANT` 17.`AUDIO_USAGE_CALL_ASSISTANT` 18.`AUDIO_USAGE_EMERGENCY` 19.`AUDIO_USAGE_SAFETY` 20.`AUDIO_USAGE_VEHICLE_STATUS` 21.`AUDIO_USAGE_ANNOUNCEMENT`| Value obtained from FuzzedDataProvider in the range 0 to 21|
+| `source` | 0.`AUDIO_SOURCE_DEFAULT` 1.`AUDIO_SOURCE_MIC` 2.`AUDIO_SOURCE_VOICE_UPLINK` 3.`AUDIO_SOURCE_VOICE_DOWNLINK` 4.`AUDIO_SOURCE_VOICE_CALL` 5.`AUDIO_SOURCE_CAMCORDER` 6.`AUDIO_SOURCE_VOICE_RECOGNITION` 7.`AUDIO_SOURCE_VOICE_COMMUNICATION` 8.`AUDIO_SOURCE_REMOTE_SUBMIX` 9.`AUDIO_SOURCE_UNPROCESSED` 10.`AUDIO_SOURCE_VOICE_PERFORMANCE` 11.`AUDIO_SOURCE_ECHO_REFERENCE` 12.`AUDIO_SOURCE_FM_TUNER` | Value obtained from FuzzedDataProvider in the range 0 to 12 |
+| `flags` | `UINT32_MIN` to `UINT32_MAX` | Value obtained from FuzzedDataProvider |
+
+This also ensures that the plugin is always deterministic for any given input.
+
+##### Maximize utilization of input data
+The plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesn't `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build vibrator_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) vibrator_fuzzer
+```
+
+#### Steps to run
+Create a directory CORPUS_DIR and copy some files to that folder
+Push this directory to device.
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/arm64/vibrator_fuzzer/vibrator_fuzzer CORPUS_DIR
+```
+
+To run on host
+```
+ $ $ANDROID_HOST_OUT/fuzz/x86_64/vibrator_fuzzer/vibrator_fuzzer CORPUS_DIR
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/libs/vibrator/fuzzer/vibrator_fuzzer.cpp b/libs/vibrator/fuzzer/vibrator_fuzzer.cpp
new file mode 100644
index 0000000..68b3ca6
--- /dev/null
+++ b/libs/vibrator/fuzzer/vibrator_fuzzer.cpp
@@ -0,0 +1,130 @@
+/******************************************************************************
+ *
+ * Copyright (C) 2020 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 <binder/Parcel.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <vibrator/ExternalVibration.h>
+
+using namespace android;
+
+constexpr size_t MAX_STRING_LENGTH = 100;
+constexpr audio_content_type_t AUDIO_CONTENT_TYPE[] = {AUDIO_CONTENT_TYPE_UNKNOWN,
+ AUDIO_CONTENT_TYPE_SPEECH,
+ AUDIO_CONTENT_TYPE_MUSIC,
+ AUDIO_CONTENT_TYPE_MOVIE,
+ AUDIO_CONTENT_TYPE_SONIFICATION};
+constexpr audio_usage_t AUDIO_USAGE[] = {
+ AUDIO_USAGE_UNKNOWN,
+ AUDIO_USAGE_MEDIA,
+ AUDIO_USAGE_VOICE_COMMUNICATION,
+ AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING,
+ AUDIO_USAGE_ALARM,
+ AUDIO_USAGE_NOTIFICATION,
+ AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE,
+ AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+ AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+ AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+ AUDIO_USAGE_NOTIFICATION_EVENT,
+ AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY,
+ AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+ AUDIO_USAGE_ASSISTANCE_SONIFICATION,
+ AUDIO_USAGE_GAME,
+ AUDIO_USAGE_VIRTUAL_SOURCE,
+ AUDIO_USAGE_ASSISTANT,
+ AUDIO_USAGE_CALL_ASSISTANT,
+ AUDIO_USAGE_EMERGENCY,
+ AUDIO_USAGE_SAFETY,
+ AUDIO_USAGE_VEHICLE_STATUS,
+ AUDIO_USAGE_ANNOUNCEMENT,
+};
+constexpr audio_source_t AUDIO_SOURCE[] = {
+ AUDIO_SOURCE_DEFAULT, AUDIO_SOURCE_MIC,
+ AUDIO_SOURCE_VOICE_UPLINK, AUDIO_SOURCE_VOICE_DOWNLINK,
+ AUDIO_SOURCE_VOICE_CALL, AUDIO_SOURCE_CAMCORDER,
+ AUDIO_SOURCE_VOICE_RECOGNITION, AUDIO_SOURCE_VOICE_COMMUNICATION,
+ AUDIO_SOURCE_REMOTE_SUBMIX, AUDIO_SOURCE_UNPROCESSED,
+ AUDIO_SOURCE_VOICE_PERFORMANCE, AUDIO_SOURCE_ECHO_REFERENCE,
+ AUDIO_SOURCE_FM_TUNER,
+};
+constexpr size_t NUM_AUDIO_CONTENT_TYPE = std::size(AUDIO_CONTENT_TYPE);
+constexpr size_t NUM_AUDIO_USAGE = std::size(AUDIO_USAGE);
+constexpr size_t NUM_AUDIO_SOURCE = std::size(AUDIO_SOURCE);
+
+class TestVibrationController : public os::IExternalVibrationController {
+public:
+ explicit TestVibrationController() {}
+ IBinder *onAsBinder() override { return nullptr; }
+ binder::Status mute(/*out*/ bool *ret) override {
+ *ret = false;
+ return binder::Status::ok();
+ };
+ binder::Status unmute(/*out*/ bool *ret) override {
+ *ret = false;
+ return binder::Status::ok();
+ };
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ if (size < 1) {
+ return 0;
+ }
+ FuzzedDataProvider fdp = FuzzedDataProvider(data, size);
+ // Initialize the parameters using FuzzedDataProvider
+ int32_t uid = fdp.ConsumeIntegral<int32_t>();
+ std::string pkg = fdp.ConsumeRandomLengthString(MAX_STRING_LENGTH);
+ audio_attributes_t attributes;
+ attributes.content_type =
+ AUDIO_CONTENT_TYPE[fdp.ConsumeIntegralInRange<uint32_t>(0, NUM_AUDIO_CONTENT_TYPE - 1)];
+ attributes.usage = AUDIO_USAGE[fdp.ConsumeIntegralInRange<uint32_t>(0, NUM_AUDIO_USAGE - 1)];
+ attributes.source = AUDIO_SOURCE[fdp.ConsumeIntegralInRange<uint32_t>(0, NUM_AUDIO_SOURCE - 1)];
+ attributes.flags = static_cast<audio_flags_mask_t>(fdp.ConsumeIntegral<uint32_t>());
+
+ // Create an instance of TestVibrationController
+ sp<TestVibrationController> vibrationController = new TestVibrationController();
+ if (!vibrationController) {
+ return 0;
+ }
+
+ // Set all the parameters in the constructor call
+ sp<os::ExternalVibration> extVibration =
+ new os::ExternalVibration(uid, pkg, attributes, vibrationController);
+ if (!extVibration) {
+ return 0;
+ }
+
+ // Get all the parameters that were previously set
+ extVibration->getUid();
+ extVibration->getPackage();
+ extVibration->getAudioAttributes();
+ extVibration->getController();
+
+ // Set the parameters in a Parcel object and send it to libvibrator
+ // This parcel shall be read by libvibrator
+ Parcel parcel;
+ parcel.writeInt32(uid);
+ parcel.writeString16(String16(pkg.c_str()));
+ parcel.writeStrongBinder(IInterface::asBinder(vibrationController));
+ parcel.setDataPosition(0);
+ extVibration->readFromParcel(&parcel);
+
+ // Send a Parcel to libvibrator
+ // Parameters shall be written to this parcel by libvibrator
+ extVibration->writeToParcel(&parcel);
+ return 0;
+}
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 17f37c3..b620e2d 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -745,12 +745,17 @@
mOrientedRanges.clear();
}
- // Create pointer controller if needed.
+ // Create pointer controller if needed, and keep it around if Pointer Capture is enabled to
+ // preserve the cursor position.
if (mDeviceMode == DeviceMode::POINTER ||
- (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches)) {
+ (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
+ (mParameters.deviceType == Parameters::DeviceType::POINTER && mConfig.pointerCapture)) {
if (mPointerController == nullptr) {
mPointerController = getContext()->getPointerController(getDeviceId());
}
+ if (mConfig.pointerCapture) {
+ mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
} else {
mPointerController.reset();
}
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index ed0d75b..fdb8aaf 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -156,9 +156,7 @@
"Scheduler/EventThread.cpp",
"Scheduler/OneShotTimer.cpp",
"Scheduler/LayerHistory.cpp",
- "Scheduler/LayerHistoryV2.cpp",
"Scheduler/LayerInfo.cpp",
- "Scheduler/LayerInfoV2.cpp",
"Scheduler/MessageQueue.cpp",
"Scheduler/RefreshRateConfigs.cpp",
"Scheduler/Scheduler.cpp",
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index b6b754b..ab0d3df 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -1687,9 +1687,9 @@
crop.bottom);
if (layerState.frameRate.rate != 0 ||
layerState.frameRate.type != FrameRateCompatibility::Default) {
- StringAppendF(&result, "% 6.2ffps %15s seamless=%d", layerState.frameRate.rate,
+ StringAppendF(&result, "% 6.2ffps %15s seamless=%s", layerState.frameRate.rate,
frameRateCompatibilityString(layerState.frameRate.type).c_str(),
- layerState.frameRate.shouldBeSeamless);
+ toString(layerState.frameRate.seamlessness).c_str());
} else {
StringAppendF(&result, " ");
}
@@ -2754,7 +2754,7 @@
std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
return stream << "{rate=" << rate.rate
<< " type=" << Layer::frameRateCompatibilityString(rate.type)
- << " shouldBeSeamless=" << rate.shouldBeSeamless << "}";
+ << " seamlessness=" << toString(rate.seamlessness) << "}";
}
}; // namespace android
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 8d67ce5..75d68a1 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -49,7 +49,9 @@
#include "LayerVector.h"
#include "MonitoredProducer.h"
#include "RenderArea.h"
+#include "Scheduler/Seamlessness.h"
#include "SurfaceFlinger.h"
+#include "SurfaceTracing.h"
#include "TransactionCompletedThread.h"
using namespace android::surfaceflinger;
@@ -151,17 +153,21 @@
// Encapsulates the frame rate and compatibility of the layer. This information will be used
// when the display refresh rate is determined.
struct FrameRate {
+ using Seamlessness = scheduler::Seamlessness;
+
float rate;
FrameRateCompatibility type;
- bool shouldBeSeamless;
+ Seamlessness seamlessness;
- FrameRate() : rate(0), type(FrameRateCompatibility::Default), shouldBeSeamless(true) {}
+ FrameRate()
+ : rate(0),
+ type(FrameRateCompatibility::Default),
+ seamlessness(Seamlessness::Default) {}
FrameRate(float rate, FrameRateCompatibility type, bool shouldBeSeamless = true)
- : rate(rate), type(type), shouldBeSeamless(shouldBeSeamless) {}
+ : rate(rate), type(type), seamlessness(getSeamlessness(rate, shouldBeSeamless)) {}
bool operator==(const FrameRate& other) const {
- return rate == other.rate && type == other.type &&
- shouldBeSeamless == other.shouldBeSeamless;
+ return rate == other.rate && type == other.type && seamlessness == other.seamlessness;
}
bool operator!=(const FrameRate& other) const { return !(*this == other); }
@@ -169,6 +175,19 @@
// Convert an ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* value to a
// Layer::FrameRateCompatibility. Logs fatal if the compatibility value is invalid.
static FrameRateCompatibility convertCompatibility(int8_t compatibility);
+
+ private:
+ static Seamlessness getSeamlessness(float rate, bool shouldBeSeamless) {
+ if (rate == 0.0f) {
+ // Refresh rate of 0 is a special value which should reset the vote to
+ // its default value.
+ return Seamlessness::Default;
+ } else if (shouldBeSeamless) {
+ return Seamlessness::OnlySeamless;
+ } else {
+ return Seamlessness::SeamedAndSeamless;
+ }
+ }
};
struct State {
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 28af930..c0d00f3 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * Copyright 2020 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.
@@ -35,21 +35,21 @@
#include "LayerInfo.h"
#include "SchedulerUtils.h"
-namespace android::scheduler::impl {
+namespace android::scheduler {
namespace {
bool isLayerActive(const Layer& layer, const LayerInfo& info, nsecs_t threshold) {
+ // Layers with an explicit vote are always kept active
if (layer.getFrameRateForLayerTree().rate > 0) {
- return layer.isVisible();
+ return true;
}
+
return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
}
bool traceEnabled() {
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.sf.layer_history_trace", value, "0");
- return atoi(value);
+ return property_get_bool("debug.sf.layer_history_trace", false);
}
bool useFrameRatePriority() {
@@ -58,30 +58,44 @@
return atoi(value);
}
-void trace(const wp<Layer>& weak, int fps) {
+void trace(const wp<Layer>& weak, const LayerInfo& info, LayerHistory::LayerVoteType type,
+ int fps) {
const auto layer = weak.promote();
if (!layer) return;
- const auto& name = layer->getName();
- const auto tag = "LFPS " + name;
- ATRACE_INT(tag.c_str(), fps);
- ALOGD("%s: %s @ %d Hz", __FUNCTION__, name.c_str(), fps);
+ const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
+ ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
+ };
+
+ traceType(LayerHistory::LayerVoteType::NoVote, 1);
+ traceType(LayerHistory::LayerVoteType::Heuristic, fps);
+ traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
+ traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
+ traceType(LayerHistory::LayerVoteType::Min, 1);
+ traceType(LayerHistory::LayerVoteType::Max, 1);
+
+ ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
}
} // namespace
-LayerHistory::LayerHistory()
- : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {}
+LayerHistory::LayerHistory(const RefreshRateConfigs& refreshRateConfigs)
+ : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
+ LayerInfo::setTraceEnabled(mTraceEnabled);
+ LayerInfo::setRefreshRateConfigs(refreshRateConfigs);
+}
+
LayerHistory::~LayerHistory() = default;
-void LayerHistory::registerLayer(Layer* layer, float lowRefreshRate, float highRefreshRate,
- LayerVoteType /*type*/) {
- auto info = std::make_unique<LayerInfo>(lowRefreshRate, highRefreshRate);
+void LayerHistory::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
+ LayerVoteType type) {
+ const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
+ auto info = std::make_unique<LayerInfo>(layer->getName(), highRefreshRatePeriod, type);
std::lock_guard lock(mLock);
mLayerInfos.emplace_back(layer, std::move(info));
}
void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
- LayerUpdateType /*updateType*/) {
+ LayerUpdateType updateType) {
std::lock_guard lock(mLock);
const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
@@ -89,7 +103,7 @@
LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
const auto& info = it->second;
- info->setLastPresentTime(presentTime, now);
+ info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);
// Activate layer if inactive.
if (const auto end = activeLayers().end(); it >= end) {
@@ -99,54 +113,42 @@
}
LayerHistory::Summary LayerHistory::summarize(nsecs_t now) {
- ATRACE_CALL();
+ LayerHistory::Summary summary;
+
std::lock_guard lock(mLock);
partitionLayers(now);
- LayerHistory::Summary summary;
- for (const auto& [weakLayer, info] : activeLayers()) {
- const bool recent = info->isRecentlyActive(now);
- auto layer = weakLayer.promote();
- // Only use the layer if the reference still exists.
- if (layer || CC_UNLIKELY(mTraceEnabled)) {
- const auto layerFocused =
- Layer::isLayerFocusedBasedOnPriority(layer->getFrameRateSelectionPriority());
- // Check if frame rate was set on layer.
- const auto frameRate = layer->getFrameRateForLayerTree();
- if (frameRate.rate > 0.f) {
- const auto voteType = [&]() {
- switch (frameRate.type) {
- case Layer::FrameRateCompatibility::Default:
- return LayerVoteType::ExplicitDefault;
- case Layer::FrameRateCompatibility::ExactOrMultiple:
- return LayerVoteType::ExplicitExactOrMultiple;
- case Layer::FrameRateCompatibility::NoVote:
- return LayerVoteType::NoVote;
- }
- }();
- summary.push_back(
- RefreshRateConfigs::LayerRequirement{.name = layer->getName(),
- .vote = voteType,
- .desiredRefreshRate = frameRate.rate,
- .shouldBeSeamless =
- frameRate.shouldBeSeamless,
- .weight = 1.0f,
- .focused = layerFocused});
- } else if (recent) {
- summary.push_back(
- RefreshRateConfigs::LayerRequirement{.name = layer->getName(),
- .vote = LayerVoteType::Heuristic,
- .desiredRefreshRate =
- info->getRefreshRate(now),
- .shouldBeSeamless = true,
- .weight = 1.0f,
- .focused = layerFocused});
- }
+ for (const auto& [layer, info] : activeLayers()) {
+ const auto strong = layer.promote();
+ if (!strong) {
+ continue;
+ }
- if (CC_UNLIKELY(mTraceEnabled)) {
- trace(weakLayer, round<int>(frameRate.rate));
- }
+ const auto frameRateSelectionPriority = strong->getFrameRateSelectionPriority();
+ const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);
+ ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),
+ frameRateSelectionPriority, layerFocused ? "" : "not");
+
+ const auto vote = info->getRefreshRateVote(now);
+ // Skip NoVote layer as those don't have any requirements
+ if (vote.type == LayerHistory::LayerVoteType::NoVote) {
+ continue;
+ }
+
+ // Compute the layer's position on the screen
+ const Rect bounds = Rect(strong->getBounds());
+ const ui::Transform transform = strong->getTransform();
+ constexpr bool roundOutwards = true;
+ Rect transformed = transform.transform(bounds, roundOutwards);
+
+ const float layerArea = transformed.getWidth() * transformed.getHeight();
+ float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+ summary.push_back(
+ {strong->getName(), vote.type, vote.fps, vote.seamlessness, weight, layerFocused});
+
+ if (CC_UNLIKELY(mTraceEnabled)) {
+ trace(layer, *info, vote.type, static_cast<int>(std::round(vote.fps)));
}
}
@@ -162,14 +164,33 @@
auto& [weak, info] = mLayerInfos[i];
if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {
i++;
+ // Set layer vote if set
+ const auto frameRate = layer->getFrameRateForLayerTree();
+ const auto voteType = [&]() {
+ switch (frameRate.type) {
+ case Layer::FrameRateCompatibility::Default:
+ return LayerVoteType::ExplicitDefault;
+ case Layer::FrameRateCompatibility::ExactOrMultiple:
+ return LayerVoteType::ExplicitExactOrMultiple;
+ case Layer::FrameRateCompatibility::NoVote:
+ return LayerVoteType::NoVote;
+ }
+ }();
+
+ if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
+ const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
+ info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
+ } else {
+ info->resetLayerVote();
+ }
continue;
}
if (CC_UNLIKELY(mTraceEnabled)) {
- trace(weak, 0);
+ trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
}
- info->clearHistory();
+ info->onLayerInactive(now);
std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
}
@@ -190,10 +211,8 @@
std::lock_guard lock(mLock);
for (const auto& [layer, info] : activeLayers()) {
- info->clearHistory();
+ info->clearHistory(systemTime());
}
-
- mActiveLayersEnd = 0;
}
std::string LayerHistory::dump() const {
@@ -202,4 +221,4 @@
mActiveLayersEnd);
}
-} // namespace android::scheduler::impl
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 128699b..507ccc6 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -36,25 +36,23 @@
namespace scheduler {
class LayerHistoryTest;
-class LayerHistoryTestV2;
class LayerInfo;
-class LayerInfoV2;
class LayerHistory {
public:
using LayerVoteType = RefreshRateConfigs::LayerVoteType;
- virtual ~LayerHistory() = default;
+ LayerHistory(const RefreshRateConfigs&);
+ ~LayerHistory();
// Layers are unregistered when the weak reference expires.
- virtual void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
- LayerVoteType type) = 0;
+ void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate, LayerVoteType type);
// Sets the display size. Client is responsible for synchronization.
- virtual void setDisplayArea(uint32_t displayArea) = 0;
+ void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; }
// Sets whether a config change is pending to be applied
- virtual void setConfigChangePending(bool pending) = 0;
+ void setConfigChangePending(bool pending) { mConfigChangePending = pending; }
// Represents which layer activity is recorded
enum class LayerUpdateType {
@@ -64,45 +62,18 @@
};
// Marks the layer as active, and records the given state to its history.
- virtual void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) = 0;
+ void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType);
using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
// Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
- virtual Summary summarize(nsecs_t now) = 0;
+ Summary summarize(nsecs_t now);
- virtual void clear() = 0;
- virtual std::string dump() const = 0;
-};
-
-namespace impl {
-// Records per-layer history of scheduling-related information (primarily present time),
-// heuristically categorizes layers as active or inactive, and summarizes stats about
-// active layers (primarily maximum refresh rate). See go/content-fps-detection-in-scheduler.
-class LayerHistory : public android::scheduler::LayerHistory {
-public:
- LayerHistory();
- virtual ~LayerHistory();
-
- // Layers are unregistered when the weak reference expires.
- void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
- LayerVoteType type) override;
-
- void setDisplayArea(uint32_t /*displayArea*/) override {}
-
- void setConfigChangePending(bool /*pending*/) override {}
-
- // Marks the layer as active, and records the given state to its history.
- void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) override;
-
- // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
- android::scheduler::LayerHistory::Summary summarize(nsecs_t now) override;
-
- void clear() override;
- std::string dump() const override;
+ void clear();
+ std::string dump() const;
private:
- friend class android::scheduler::LayerHistoryTest;
+ friend LayerHistoryTest;
friend TestableScheduler;
using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfo>>;
@@ -130,65 +101,6 @@
LayerInfos mLayerInfos GUARDED_BY(mLock);
size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
- // Whether to emit systrace output and debug logs.
- const bool mTraceEnabled;
-
- // Whether to use priority sent from WindowManager to determine the relevancy of the layer.
- const bool mUseFrameRatePriority;
-};
-
-class LayerHistoryV2 : public android::scheduler::LayerHistory {
-public:
- LayerHistoryV2(const scheduler::RefreshRateConfigs&);
- virtual ~LayerHistoryV2();
-
- // Layers are unregistered when the weak reference expires.
- void registerLayer(Layer*, float lowRefreshRate, float highRefreshRate,
- LayerVoteType type) override;
-
- // Sets the display size. Client is responsible for synchronization.
- void setDisplayArea(uint32_t displayArea) override { mDisplayArea = displayArea; }
-
- void setConfigChangePending(bool pending) override { mConfigChangePending = pending; }
-
- // Marks the layer as active, and records the given state to its history.
- void record(Layer*, nsecs_t presentTime, nsecs_t now, LayerUpdateType updateType) override;
-
- // Rebuilds sets of active/inactive layers, and accumulates stats for active layers.
- android::scheduler::LayerHistory::Summary summarize(nsecs_t /*now*/) override;
-
- void clear() override;
- std::string dump() const override;
-
-private:
- friend android::scheduler::LayerHistoryTestV2;
- friend TestableScheduler;
-
- using LayerPair = std::pair<wp<Layer>, std::unique_ptr<LayerInfoV2>>;
- using LayerInfos = std::vector<LayerPair>;
-
- struct ActiveLayers {
- LayerInfos& infos;
- const size_t index;
-
- auto begin() { return infos.begin(); }
- auto end() { return begin() + static_cast<long>(index); }
- };
-
- ActiveLayers activeLayers() REQUIRES(mLock) { return {mLayerInfos, mActiveLayersEnd}; }
-
- // Iterates over layers in a single pass, swapping pairs such that active layers precede
- // inactive layers, and inactive layers precede expired layers. Removes expired layers by
- // truncating after inactive layers.
- void partitionLayers(nsecs_t now) REQUIRES(mLock);
-
- mutable std::mutex mLock;
-
- // Partitioned such that active layers precede inactive layers. For fast lookup, the few active
- // layers are at the front, and weak pointers are stored in contiguous memory to hit the cache.
- LayerInfos mLayerInfos GUARDED_BY(mLock);
- size_t mActiveLayersEnd GUARDED_BY(mLock) = 0;
-
uint32_t mDisplayArea = 0;
// Whether to emit systrace output and debug logs.
@@ -201,6 +113,5 @@
std::atomic<bool> mConfigChangePending = false;
};
-} // namespace impl
} // namespace scheduler
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp b/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
deleted file mode 100644
index a63ccc1..0000000
--- a/services/surfaceflinger/Scheduler/LayerHistoryV2.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LayerHistoryV2"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "LayerHistory.h"
-
-#include <android-base/stringprintf.h>
-#include <cutils/properties.h>
-#include <utils/Log.h>
-#include <utils/Timers.h>
-#include <utils/Trace.h>
-
-#include <algorithm>
-#include <cmath>
-#include <string>
-#include <utility>
-
-#include "../Layer.h"
-#include "LayerInfoV2.h"
-#include "SchedulerUtils.h"
-
-namespace android::scheduler::impl {
-
-namespace {
-
-bool isLayerActive(const Layer& layer, const LayerInfoV2& info, nsecs_t threshold) {
- // Layers with an explicit vote are always kept active
- if (layer.getFrameRateForLayerTree().rate > 0) {
- return true;
- }
-
- return layer.isVisible() && info.getLastUpdatedTime() >= threshold;
-}
-
-bool traceEnabled() {
- return property_get_bool("debug.sf.layer_history_trace", false);
-}
-
-bool useFrameRatePriority() {
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.sf.use_frame_rate_priority", value, "1");
- return atoi(value);
-}
-
-void trace(const wp<Layer>& weak, const LayerInfoV2& info, LayerHistory::LayerVoteType type,
- int fps) {
- const auto layer = weak.promote();
- if (!layer) return;
-
- const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
- ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
- };
-
- traceType(LayerHistory::LayerVoteType::NoVote, 1);
- traceType(LayerHistory::LayerVoteType::Heuristic, fps);
- traceType(LayerHistory::LayerVoteType::ExplicitDefault, fps);
- traceType(LayerHistory::LayerVoteType::ExplicitExactOrMultiple, fps);
- traceType(LayerHistory::LayerVoteType::Min, 1);
- traceType(LayerHistory::LayerVoteType::Max, 1);
-
- ALOGD("%s: %s @ %d Hz", __FUNCTION__, layer->getName().c_str(), fps);
-}
-} // namespace
-
-LayerHistoryV2::LayerHistoryV2(const scheduler::RefreshRateConfigs& refreshRateConfigs)
- : mTraceEnabled(traceEnabled()), mUseFrameRatePriority(useFrameRatePriority()) {
- LayerInfoV2::setTraceEnabled(mTraceEnabled);
- LayerInfoV2::setRefreshRateConfigs(refreshRateConfigs);
-}
-
-LayerHistoryV2::~LayerHistoryV2() = default;
-
-void LayerHistoryV2::registerLayer(Layer* layer, float /*lowRefreshRate*/, float highRefreshRate,
- LayerVoteType type) {
- const nsecs_t highRefreshRatePeriod = static_cast<nsecs_t>(1e9f / highRefreshRate);
- auto info = std::make_unique<LayerInfoV2>(layer->getName(), highRefreshRatePeriod, type);
- std::lock_guard lock(mLock);
- mLayerInfos.emplace_back(layer, std::move(info));
-}
-
-void LayerHistoryV2::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
- LayerUpdateType updateType) {
- std::lock_guard lock(mLock);
-
- const auto it = std::find_if(mLayerInfos.begin(), mLayerInfos.end(),
- [layer](const auto& pair) { return pair.first == layer; });
- LOG_FATAL_IF(it == mLayerInfos.end(), "%s: unknown layer %p", __FUNCTION__, layer);
-
- const auto& info = it->second;
- info->setLastPresentTime(presentTime, now, updateType, mConfigChangePending);
-
- // Activate layer if inactive.
- if (const auto end = activeLayers().end(); it >= end) {
- std::iter_swap(it, end);
- mActiveLayersEnd++;
- }
-}
-
-LayerHistoryV2::Summary LayerHistoryV2::summarize(nsecs_t now) {
- LayerHistory::Summary summary;
-
- std::lock_guard lock(mLock);
-
- partitionLayers(now);
-
- for (const auto& [layer, info] : activeLayers()) {
- const auto strong = layer.promote();
- if (!strong) {
- continue;
- }
-
- const auto frameRateSelectionPriority = strong->getFrameRateSelectionPriority();
- const auto layerFocused = Layer::isLayerFocusedBasedOnPriority(frameRateSelectionPriority);
- ALOGV("%s has priority: %d %s focused", strong->getName().c_str(),
- frameRateSelectionPriority, layerFocused ? "" : "not");
-
- const auto vote = info->getRefreshRateVote(now);
- // Skip NoVote layer as those don't have any requirements
- if (vote.type == LayerHistory::LayerVoteType::NoVote) {
- continue;
- }
-
- // Compute the layer's position on the screen
- const Rect bounds = Rect(strong->getBounds());
- const ui::Transform transform = strong->getTransform();
- constexpr bool roundOutwards = true;
- Rect transformed = transform.transform(bounds, roundOutwards);
-
- const float layerArea = transformed.getWidth() * transformed.getHeight();
- float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
- summary.push_back({strong->getName(), vote.type, vote.fps, vote.shouldBeSeamless, weight,
- layerFocused});
-
- if (CC_UNLIKELY(mTraceEnabled)) {
- trace(layer, *info, vote.type, static_cast<int>(std::round(vote.fps)));
- }
- }
-
- return summary;
-}
-
-void LayerHistoryV2::partitionLayers(nsecs_t now) {
- const nsecs_t threshold = getActiveLayerThreshold(now);
-
- // Collect expired and inactive layers after active layers.
- size_t i = 0;
- while (i < mActiveLayersEnd) {
- auto& [weak, info] = mLayerInfos[i];
- if (const auto layer = weak.promote(); layer && isLayerActive(*layer, *info, threshold)) {
- i++;
- // Set layer vote if set
- const auto frameRate = layer->getFrameRateForLayerTree();
- const auto voteType = [&]() {
- switch (frameRate.type) {
- case Layer::FrameRateCompatibility::Default:
- return LayerVoteType::ExplicitDefault;
- case Layer::FrameRateCompatibility::ExactOrMultiple:
- return LayerVoteType::ExplicitExactOrMultiple;
- case Layer::FrameRateCompatibility::NoVote:
- return LayerVoteType::NoVote;
- }
- }();
-
- if (frameRate.rate > 0 || voteType == LayerVoteType::NoVote) {
- const auto type = layer->isVisible() ? voteType : LayerVoteType::NoVote;
- info->setLayerVote({type, frameRate.rate, frameRate.shouldBeSeamless});
- } else {
- info->resetLayerVote();
- }
- continue;
- }
-
- if (CC_UNLIKELY(mTraceEnabled)) {
- trace(weak, *info, LayerHistory::LayerVoteType::NoVote, 0);
- }
-
- info->onLayerInactive(now);
- std::swap(mLayerInfos[i], mLayerInfos[--mActiveLayersEnd]);
- }
-
- // Collect expired layers after inactive layers.
- size_t end = mLayerInfos.size();
- while (i < end) {
- if (mLayerInfos[i].first.promote()) {
- i++;
- } else {
- std::swap(mLayerInfos[i], mLayerInfos[--end]);
- }
- }
-
- mLayerInfos.erase(mLayerInfos.begin() + static_cast<long>(end), mLayerInfos.end());
-}
-
-void LayerHistoryV2::clear() {
- std::lock_guard lock(mLock);
-
- for (const auto& [layer, info] : activeLayers()) {
- info->clearHistory(systemTime());
- }
-}
-
-std::string LayerHistoryV2::dump() const {
- std::lock_guard lock(mLock);
- return base::StringPrintf("LayerHistoryV2{size=%zu, active=%zu}", mLayerInfos.size(),
- mActiveLayersEnd);
-}
-
-} // namespace android::scheduler::impl
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 6d9dd43..66ac98a 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 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.
@@ -14,36 +14,284 @@
* limitations under the License.
*/
+// #define LOG_NDEBUG 0
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
#include "LayerInfo.h"
#include <algorithm>
#include <utility>
+#include <cutils/compiler.h>
+#include <cutils/trace.h>
+
+#undef LOG_TAG
+#define LOG_TAG "LayerInfo"
+
namespace android::scheduler {
-LayerInfo::LayerInfo(float lowRefreshRate, float highRefreshRate)
- : mLowRefreshRate(lowRefreshRate), mHighRefreshRate(highRefreshRate) {}
+const RefreshRateConfigs* LayerInfo::sRefreshRateConfigs = nullptr;
+bool LayerInfo::sTraceEnabled = false;
-void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now) {
+LayerInfo::LayerInfo(const std::string& name, nsecs_t highRefreshRatePeriod,
+ LayerHistory::LayerVoteType defaultVote)
+ : mName(name),
+ mHighRefreshRatePeriod(highRefreshRatePeriod),
+ mDefaultVote(defaultVote),
+ mLayerVote({defaultVote, 0.0f}),
+ mRefreshRateHistory(name) {}
+
+void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
+ bool pendingConfigChange) {
lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
- // Buffers can come with a present time far in the future. That keeps them relevant.
mLastUpdatedTime = std::max(lastPresentTime, now);
- mPresentTimeHistory.insertPresentTime(mLastUpdatedTime);
+ switch (updateType) {
+ case LayerUpdateType::AnimationTX:
+ mLastAnimationTime = std::max(lastPresentTime, now);
+ break;
+ case LayerUpdateType::SetFrameRate:
+ case LayerUpdateType::Buffer:
+ FrameTimeData frameTime = {.presetTime = lastPresentTime,
+ .queueTime = mLastUpdatedTime,
+ .pendingConfigChange = pendingConfigChange};
+ mFrameTimes.push_back(frameTime);
+ if (mFrameTimes.size() > HISTORY_SIZE) {
+ mFrameTimes.pop_front();
+ }
+ break;
+ }
+}
- if (mLastPresentTime == 0) {
- // First frame
- mLastPresentTime = lastPresentTime;
- return;
+bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const {
+ return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
+ mFrameTimeValidSince.time_since_epoch())
+ .count();
+}
+
+bool LayerInfo::isFrequent(nsecs_t now) const {
+ // If we know nothing about this layer we consider it as frequent as it might be the start
+ // of an animation.
+ if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
+ return true;
}
- const nsecs_t period = lastPresentTime - mLastPresentTime;
- mLastPresentTime = lastPresentTime;
- // Ignore time diff that are too high - those are stale values
- if (period > MAX_ACTIVE_LAYER_PERIOD_NS.count()) return;
+ // Find the first active frame
+ auto it = mFrameTimes.begin();
+ for (; it != mFrameTimes.end(); ++it) {
+ if (it->queueTime >= getActiveLayerThreshold(now)) {
+ break;
+ }
+ }
- const float fps = std::min(1e9f / period, mHighRefreshRate);
- mRefreshRateHistory.insertRefreshRate(fps);
+ const auto numFrames = std::distance(it, mFrameTimes.end());
+ if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
+ return false;
+ }
+
+ // Layer is considered frequent if the average frame rate is higher than the threshold
+ const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
+ return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
+}
+
+bool LayerInfo::isAnimating(nsecs_t now) const {
+ return mLastAnimationTime >= getActiveLayerThreshold(now);
+}
+
+bool LayerInfo::hasEnoughDataForHeuristic() const {
+ // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
+ if (mFrameTimes.size() < 2) {
+ ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
+ return false;
+ }
+
+ if (!isFrameTimeValid(mFrameTimes.front())) {
+ ALOGV("stale frames still captured");
+ return false;
+ }
+
+ const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
+ if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
+ ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
+ totalDuration / 1e9f);
+ return false;
+ }
+
+ return true;
+}
+
+std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
+ nsecs_t totalPresentTimeDeltas = 0;
+ nsecs_t totalQueueTimeDeltas = 0;
+ bool missingPresentTime = false;
+ int numFrames = 0;
+ for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
+ // Ignore frames captured during a config change
+ if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
+ return std::nullopt;
+ }
+
+ totalQueueTimeDeltas +=
+ std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
+ numFrames++;
+
+ if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
+ missingPresentTime = true;
+ // If there are no presentation timestamps and we haven't calculated
+ // one in the past then we can't calculate the refresh rate
+ if (mLastRefreshRate.reported == 0) {
+ return std::nullopt;
+ }
+ continue;
+ }
+
+ totalPresentTimeDeltas +=
+ std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
+ }
+
+ // Calculate the average frame time based on presentation timestamps. If those
+ // doesn't exist, we look at the time the buffer was queued only. We can do that only if
+ // we calculated a refresh rate based on presentation timestamps in the past. The reason
+ // we look at the queue time is to handle cases where hwui attaches presentation timestamps
+ // when implementing render ahead for specific refresh rates. When hwui no longer provides
+ // presentation timestamps we look at the queue time to see if the current refresh rate still
+ // matches the content.
+
+ const auto averageFrameTime =
+ static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
+ numFrames;
+ return static_cast<nsecs_t>(averageFrameTime);
+}
+
+std::optional<float> LayerInfo::calculateRefreshRateIfPossible(nsecs_t now) {
+ static constexpr float MARGIN = 1.0f; // 1Hz
+ if (!hasEnoughDataForHeuristic()) {
+ ALOGV("Not enough data");
+ return std::nullopt;
+ }
+
+ const auto averageFrameTime = calculateAverageFrameTime();
+ if (averageFrameTime.has_value()) {
+ const auto refreshRate = 1e9f / *averageFrameTime;
+ const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
+ if (refreshRateConsistent) {
+ const auto knownRefreshRate =
+ sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
+
+ // To avoid oscillation, use the last calculated refresh rate if it is
+ // close enough
+ if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
+ mLastRefreshRate.reported != knownRefreshRate) {
+ mLastRefreshRate.calculated = refreshRate;
+ mLastRefreshRate.reported = knownRefreshRate;
+ }
+
+ ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
+ refreshRate, mLastRefreshRate.reported);
+ } else {
+ ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
+ refreshRate, mLastRefreshRate.reported);
+ }
+ }
+
+ return mLastRefreshRate.reported == 0 ? std::nullopt
+ : std::make_optional(mLastRefreshRate.reported);
+}
+
+LayerInfo::LayerVote LayerInfo::getRefreshRateVote(nsecs_t now) {
+ if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
+ ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
+ return mLayerVote;
+ }
+
+ if (isAnimating(now)) {
+ ALOGV("%s is animating", mName.c_str());
+ mLastRefreshRate.animatingOrInfrequent = true;
+ return {LayerHistory::LayerVoteType::Max, 0};
+ }
+
+ if (!isFrequent(now)) {
+ ALOGV("%s is infrequent", mName.c_str());
+ mLastRefreshRate.animatingOrInfrequent = true;
+ return {LayerHistory::LayerVoteType::Min, 0};
+ }
+
+ // If the layer was previously tagged as animating or infrequent, we clear
+ // the history as it is likely the layer just changed its behavior
+ // and we should not look at stale data
+ if (mLastRefreshRate.animatingOrInfrequent) {
+ clearHistory(now);
+ }
+
+ auto refreshRate = calculateRefreshRateIfPossible(now);
+ if (refreshRate.has_value()) {
+ ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
+ return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
+ }
+
+ ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
+ return {LayerHistory::LayerVoteType::Max, 0};
+}
+
+const char* LayerInfo::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
+ if (mTraceTags.count(type) == 0) {
+ const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
+ mTraceTags.emplace(type, tag);
+ }
+
+ return mTraceTags.at(type).c_str();
+}
+
+LayerInfo::RefreshRateHistory::HeuristicTraceTagData
+LayerInfo::RefreshRateHistory::makeHeuristicTraceTagData() const {
+ const std::string prefix = "LFPS ";
+ const std::string suffix = "Heuristic ";
+ return {.min = prefix + mName + suffix + "min",
+ .max = prefix + mName + suffix + "max",
+ .consistent = prefix + mName + suffix + "consistent",
+ .average = prefix + mName + suffix + "average"};
+}
+
+void LayerInfo::RefreshRateHistory::clear() {
+ mRefreshRates.clear();
+}
+
+bool LayerInfo::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
+ mRefreshRates.push_back({refreshRate, now});
+ while (mRefreshRates.size() >= HISTORY_SIZE ||
+ now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
+ mRefreshRates.pop_front();
+ }
+
+ if (CC_UNLIKELY(sTraceEnabled)) {
+ if (!mHeuristicTraceTagData.has_value()) {
+ mHeuristicTraceTagData = makeHeuristicTraceTagData();
+ }
+
+ ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
+ }
+
+ return isConsistent();
+}
+
+bool LayerInfo::RefreshRateHistory::isConsistent() const {
+ if (mRefreshRates.empty()) return true;
+
+ const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
+ const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
+ const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
+
+ if (CC_UNLIKELY(sTraceEnabled)) {
+ if (!mHeuristicTraceTagData.has_value()) {
+ mHeuristicTraceTagData = makeHeuristicTraceTagData();
+ }
+
+ ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
+ ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
+ ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
+ }
+
+ return consistent;
}
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 820624b..e434670 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 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.
@@ -21,6 +21,9 @@
#include <chrono>
#include <deque>
+#include "LayerHistory.h"
+#include "RefreshRateConfigs.h"
+#include "Scheduler/Seamlessness.h"
#include "SchedulerUtils.h"
namespace android {
@@ -41,129 +44,179 @@
// Stores history of present times and refresh rates for a layer.
class LayerInfo {
+ using LayerUpdateType = LayerHistory::LayerUpdateType;
+
// Layer is considered frequent if the earliest value in the window of most recent present times
// is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
// favor of a low refresh rate.
static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
- static constexpr std::chrono::nanoseconds MAX_FREQUENT_LAYER_PERIOD_NS = 250ms;
-
- /**
- * Struct that keeps the information about the refresh rate for last
- * HISTORY_SIZE frames. This is used to better determine the refresh rate
- * for individual layers.
- */
- class RefreshRateHistory {
- public:
- explicit RefreshRateHistory(float highRefreshRate) : mHighRefreshRate(highRefreshRate) {}
-
- void insertRefreshRate(float refreshRate) {
- mElements.push_back(refreshRate);
- if (mElements.size() > HISTORY_SIZE) {
- mElements.pop_front();
- }
- }
-
- float getRefreshRateAvg() const {
- return mElements.empty() ? mHighRefreshRate : calculate_mean(mElements);
- }
-
- void clearHistory() { mElements.clear(); }
-
- private:
- const float mHighRefreshRate;
-
- static constexpr size_t HISTORY_SIZE = 30;
- std::deque<float> mElements;
- };
-
- /**
- * Struct that keeps the information about the present time for last
- * HISTORY_SIZE frames. This is used to better determine whether the given layer
- * is still relevant and it's refresh rate should be considered.
- */
- class PresentTimeHistory {
- public:
- static constexpr size_t HISTORY_SIZE = 90;
-
- void insertPresentTime(nsecs_t presentTime) {
- mElements.push_back(presentTime);
- if (mElements.size() > HISTORY_SIZE) {
- mElements.pop_front();
- }
- }
-
- // Returns whether the earliest present time is within the active threshold.
- bool isRecentlyActive(nsecs_t now) const {
- if (mElements.size() < 2) {
- return false;
- }
-
- // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
- if (mElements.size() < HISTORY_SIZE &&
- mElements.back() - mElements.front() < HISTORY_DURATION.count()) {
- return false;
- }
-
- return mElements.back() >= getActiveLayerThreshold(now);
- }
-
- bool isFrequent(nsecs_t now) const {
- // Assume layer is infrequent if too few present times have been recorded.
- if (mElements.size() < FREQUENT_LAYER_WINDOW_SIZE) {
- return false;
- }
-
- // Layer is frequent if the earliest value in the window of most recent present times is
- // within threshold.
- const auto it = mElements.end() - FREQUENT_LAYER_WINDOW_SIZE;
- const nsecs_t threshold = now - MAX_FREQUENT_LAYER_PERIOD_NS.count();
- return *it >= threshold;
- }
-
- void clearHistory() { mElements.clear(); }
-
- private:
- std::deque<nsecs_t> mElements;
- static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
- };
+ static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
+ static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
+ std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
friend class LayerHistoryTest;
public:
- LayerInfo(float lowRefreshRate, float highRefreshRate);
+ // Holds information about the layer vote
+ struct LayerVote {
+ LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
+ float fps = 0.0f;
+ Seamlessness seamlessness = Seamlessness::Default;
+ };
+
+ static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
+
+ static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
+ sRefreshRateConfigs = &refreshRateConfigs;
+ }
+
+ LayerInfo(const std::string& name, nsecs_t highRefreshRatePeriod,
+ LayerHistory::LayerVoteType defaultVote);
LayerInfo(const LayerInfo&) = delete;
LayerInfo& operator=(const LayerInfo&) = delete;
- // Records the last requested oresent time. It also stores information about when
+ // Records the last requested present time. It also stores information about when
// the layer was last updated. If the present time is farther in the future than the
// updated time, the updated time is the present time.
- void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now);
+ void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
+ bool pendingConfigChange);
- bool isRecentlyActive(nsecs_t now) const { return mPresentTimeHistory.isRecentlyActive(now); }
- bool isFrequent(nsecs_t now) const { return mPresentTimeHistory.isFrequent(now); }
+ // Sets an explicit layer vote. This usually comes directly from the application via
+ // ANativeWindow_setFrameRate API
+ void setLayerVote(LayerVote vote) { mLayerVote = vote; }
- float getRefreshRate(nsecs_t now) const {
- return isFrequent(now) ? mRefreshRateHistory.getRefreshRateAvg() : mLowRefreshRate;
- }
+ // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
+ // This is used for layers that called to setLayerVote() and then removed the vote, so that the
+ // layer can go back to whatever vote it had before the app voted for it.
+ void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
+
+ // Resets the layer vote to its default.
+ void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, Seamlessness::Default}; }
+
+ LayerVote getRefreshRateVote(nsecs_t now);
// Return the last updated time. If the present time is farther in the future than the
// updated time, the updated time is the present time.
nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
- void clearHistory() {
- mRefreshRateHistory.clearHistory();
- mPresentTimeHistory.clearHistory();
+ // Returns a C string for tracing a vote
+ const char* getTraceTag(LayerHistory::LayerVoteType type) const;
+
+ void onLayerInactive(nsecs_t now) {
+ // Mark mFrameTimeValidSince to now to ignore all previous frame times.
+ // We are not deleting the old frame to keep track of whether we should treat the first
+ // buffer as Max as we don't know anything about this layer or Min as this layer is
+ // posting infrequent updates.
+ const auto timePoint = std::chrono::nanoseconds(now);
+ mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
+ mLastRefreshRate = {};
+ mRefreshRateHistory.clear();
+ }
+
+ void clearHistory(nsecs_t now) {
+ onLayerInactive(now);
+ mFrameTimes.clear();
}
private:
- const float mLowRefreshRate;
- const float mHighRefreshRate;
+ // Used to store the layer timestamps
+ struct FrameTimeData {
+ nsecs_t presetTime; // desiredPresentTime, if provided
+ nsecs_t queueTime; // buffer queue time
+ bool pendingConfigChange;
+ };
+
+ // Holds information about the calculated and reported refresh rate
+ struct RefreshRateHeuristicData {
+ // Rate calculated on the layer
+ float calculated = 0.0f;
+ // Last reported rate for LayerInfo::getRefreshRate()
+ float reported = 0.0f;
+ // Whether the last reported rate for LayerInfo::getRefreshRate()
+ // was due to animation or infrequent updates
+ bool animatingOrInfrequent = false;
+ };
+
+ // Class to store past calculated refresh rate and determine whether
+ // the refresh rate calculated is consistent with past values
+ class RefreshRateHistory {
+ public:
+ static constexpr auto HISTORY_SIZE = 90;
+ static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
+
+ RefreshRateHistory(const std::string& name) : mName(name) {}
+
+ // Clears History
+ void clear();
+
+ // Adds a new refresh rate and returns true if it is consistent
+ bool add(float refreshRate, nsecs_t now);
+
+ private:
+ friend class LayerHistoryTest;
+
+ // Holds the refresh rate when it was calculated
+ struct RefreshRateData {
+ float refreshRate = 0.0f;
+ nsecs_t timestamp = 0;
+
+ bool operator<(const RefreshRateData& other) const {
+ return refreshRate < other.refreshRate;
+ }
+ };
+
+ // Holds tracing strings
+ struct HeuristicTraceTagData {
+ std::string min;
+ std::string max;
+ std::string consistent;
+ std::string average;
+ };
+
+ bool isConsistent() const;
+ HeuristicTraceTagData makeHeuristicTraceTagData() const;
+
+ const std::string mName;
+ mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
+ std::deque<RefreshRateData> mRefreshRates;
+ static constexpr float MARGIN_FPS = 1.0;
+ };
+
+ bool isFrequent(nsecs_t now) const;
+ bool isAnimating(nsecs_t now) const;
+ bool hasEnoughDataForHeuristic() const;
+ std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
+ std::optional<nsecs_t> calculateAverageFrameTime() const;
+ bool isFrameTimeValid(const FrameTimeData&) const;
+
+ const std::string mName;
+
+ // Used for sanitizing the heuristic data
+ const nsecs_t mHighRefreshRatePeriod;
+ LayerHistory::LayerVoteType mDefaultVote;
+
+ LayerVote mLayerVote;
nsecs_t mLastUpdatedTime = 0;
- nsecs_t mLastPresentTime = 0;
- RefreshRateHistory mRefreshRateHistory{mHighRefreshRate};
- PresentTimeHistory mPresentTimeHistory;
+
+ nsecs_t mLastAnimationTime = 0;
+
+ RefreshRateHeuristicData mLastRefreshRate;
+
+ std::deque<FrameTimeData> mFrameTimes;
+ std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
+ std::chrono::steady_clock::now();
+ static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
+ static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
+
+ RefreshRateHistory mRefreshRateHistory;
+
+ mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
+
+ // Shared for all LayerInfo instances
+ static const RefreshRateConfigs* sRefreshRateConfigs;
+ static bool sTraceEnabled;
};
} // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp b/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
deleted file mode 100644
index 94e7e20..0000000
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.cpp
+++ /dev/null
@@ -1,297 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// #define LOG_NDEBUG 0
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "LayerInfoV2.h"
-
-#include <algorithm>
-#include <utility>
-
-#include <cutils/compiler.h>
-#include <cutils/trace.h>
-
-#undef LOG_TAG
-#define LOG_TAG "LayerInfoV2"
-
-namespace android::scheduler {
-
-const RefreshRateConfigs* LayerInfoV2::sRefreshRateConfigs = nullptr;
-bool LayerInfoV2::sTraceEnabled = false;
-
-LayerInfoV2::LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
- LayerHistory::LayerVoteType defaultVote)
- : mName(name),
- mHighRefreshRatePeriod(highRefreshRatePeriod),
- mDefaultVote(defaultVote),
- mLayerVote({defaultVote, 0.0f}),
- mRefreshRateHistory(name) {}
-
-void LayerInfoV2::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now,
- LayerUpdateType updateType, bool pendingConfigChange) {
- lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
-
- mLastUpdatedTime = std::max(lastPresentTime, now);
- switch (updateType) {
- case LayerUpdateType::AnimationTX:
- mLastAnimationTime = std::max(lastPresentTime, now);
- break;
- case LayerUpdateType::SetFrameRate:
- case LayerUpdateType::Buffer:
- FrameTimeData frameTime = {.presetTime = lastPresentTime,
- .queueTime = mLastUpdatedTime,
- .pendingConfigChange = pendingConfigChange};
- mFrameTimes.push_back(frameTime);
- if (mFrameTimes.size() > HISTORY_SIZE) {
- mFrameTimes.pop_front();
- }
- break;
- }
-}
-
-bool LayerInfoV2::isFrameTimeValid(const FrameTimeData& frameTime) const {
- return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
- mFrameTimeValidSince.time_since_epoch())
- .count();
-}
-
-bool LayerInfoV2::isFrequent(nsecs_t now) const {
- // If we know nothing about this layer we consider it as frequent as it might be the start
- // of an animation.
- if (mFrameTimes.size() < FREQUENT_LAYER_WINDOW_SIZE) {
- return true;
- }
-
- // Find the first active frame
- auto it = mFrameTimes.begin();
- for (; it != mFrameTimes.end(); ++it) {
- if (it->queueTime >= getActiveLayerThreshold(now)) {
- break;
- }
- }
-
- const auto numFrames = std::distance(it, mFrameTimes.end());
- if (numFrames < FREQUENT_LAYER_WINDOW_SIZE) {
- return false;
- }
-
- // Layer is considered frequent if the average frame rate is higher than the threshold
- const auto totalTime = mFrameTimes.back().queueTime - it->queueTime;
- return (1e9f * (numFrames - 1)) / totalTime >= MIN_FPS_FOR_FREQUENT_LAYER;
-}
-
-bool LayerInfoV2::isAnimating(nsecs_t now) const {
- return mLastAnimationTime >= getActiveLayerThreshold(now);
-}
-
-bool LayerInfoV2::hasEnoughDataForHeuristic() const {
- // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
- if (mFrameTimes.size() < 2) {
- ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
- return false;
- }
-
- if (!isFrameTimeValid(mFrameTimes.front())) {
- ALOGV("stale frames still captured");
- return false;
- }
-
- const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
- if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
- ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
- totalDuration / 1e9f);
- return false;
- }
-
- return true;
-}
-
-std::optional<nsecs_t> LayerInfoV2::calculateAverageFrameTime() const {
- nsecs_t totalPresentTimeDeltas = 0;
- nsecs_t totalQueueTimeDeltas = 0;
- bool missingPresentTime = false;
- int numFrames = 0;
- for (auto it = mFrameTimes.begin(); it != mFrameTimes.end() - 1; ++it) {
- // Ignore frames captured during a config change
- if (it->pendingConfigChange || (it + 1)->pendingConfigChange) {
- return std::nullopt;
- }
-
- totalQueueTimeDeltas +=
- std::max(((it + 1)->queueTime - it->queueTime), mHighRefreshRatePeriod);
- numFrames++;
-
- if (!missingPresentTime && (it->presetTime == 0 || (it + 1)->presetTime == 0)) {
- missingPresentTime = true;
- // If there are no presentation timestamps and we haven't calculated
- // one in the past then we can't calculate the refresh rate
- if (mLastRefreshRate.reported == 0) {
- return std::nullopt;
- }
- continue;
- }
-
- totalPresentTimeDeltas +=
- std::max(((it + 1)->presetTime - it->presetTime), mHighRefreshRatePeriod);
- }
-
- // Calculate the average frame time based on presentation timestamps. If those
- // doesn't exist, we look at the time the buffer was queued only. We can do that only if
- // we calculated a refresh rate based on presentation timestamps in the past. The reason
- // we look at the queue time is to handle cases where hwui attaches presentation timestamps
- // when implementing render ahead for specific refresh rates. When hwui no longer provides
- // presentation timestamps we look at the queue time to see if the current refresh rate still
- // matches the content.
-
- const auto averageFrameTime =
- static_cast<float>(missingPresentTime ? totalQueueTimeDeltas : totalPresentTimeDeltas) /
- numFrames;
- return static_cast<nsecs_t>(averageFrameTime);
-}
-
-std::optional<float> LayerInfoV2::calculateRefreshRateIfPossible(nsecs_t now) {
- static constexpr float MARGIN = 1.0f; // 1Hz
- if (!hasEnoughDataForHeuristic()) {
- ALOGV("Not enough data");
- return std::nullopt;
- }
-
- const auto averageFrameTime = calculateAverageFrameTime();
- if (averageFrameTime.has_value()) {
- const auto refreshRate = 1e9f / *averageFrameTime;
- const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
- if (refreshRateConsistent) {
- const auto knownRefreshRate =
- sRefreshRateConfigs->findClosestKnownFrameRate(refreshRate);
-
- // To avoid oscillation, use the last calculated refresh rate if it is
- // close enough
- if (std::abs(mLastRefreshRate.calculated - refreshRate) > MARGIN &&
- mLastRefreshRate.reported != knownRefreshRate) {
- mLastRefreshRate.calculated = refreshRate;
- mLastRefreshRate.reported = knownRefreshRate;
- }
-
- ALOGV("%s %.2fHz rounded to nearest known frame rate %.2fHz", mName.c_str(),
- refreshRate, mLastRefreshRate.reported);
- } else {
- ALOGV("%s Not stable (%.2fHz) returning last known frame rate %.2fHz", mName.c_str(),
- refreshRate, mLastRefreshRate.reported);
- }
- }
-
- return mLastRefreshRate.reported == 0 ? std::nullopt
- : std::make_optional(mLastRefreshRate.reported);
-}
-
-LayerInfoV2::LayerVote LayerInfoV2::getRefreshRateVote(nsecs_t now) {
- if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
- ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
- return mLayerVote;
- }
-
- if (isAnimating(now)) {
- ALOGV("%s is animating", mName.c_str());
- mLastRefreshRate.animatingOrInfrequent = true;
- return {LayerHistory::LayerVoteType::Max, 0};
- }
-
- if (!isFrequent(now)) {
- ALOGV("%s is infrequent", mName.c_str());
- mLastRefreshRate.animatingOrInfrequent = true;
- return {LayerHistory::LayerVoteType::Min, 0};
- }
-
- // If the layer was previously tagged as animating or infrequent, we clear
- // the history as it is likely the layer just changed its behavior
- // and we should not look at stale data
- if (mLastRefreshRate.animatingOrInfrequent) {
- clearHistory(now);
- }
-
- auto refreshRate = calculateRefreshRateIfPossible(now);
- if (refreshRate.has_value()) {
- ALOGV("%s calculated refresh rate: %.2f", mName.c_str(), refreshRate.value());
- return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
- }
-
- ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
- return {LayerHistory::LayerVoteType::Max, 0};
-}
-
-const char* LayerInfoV2::getTraceTag(android::scheduler::LayerHistory::LayerVoteType type) const {
- if (mTraceTags.count(type) == 0) {
- const auto tag = "LFPS " + mName + " " + RefreshRateConfigs::layerVoteTypeString(type);
- mTraceTags.emplace(type, tag);
- }
-
- return mTraceTags.at(type).c_str();
-}
-
-LayerInfoV2::RefreshRateHistory::HeuristicTraceTagData
-LayerInfoV2::RefreshRateHistory::makeHeuristicTraceTagData() const {
- const std::string prefix = "LFPS ";
- const std::string suffix = "Heuristic ";
- return {.min = prefix + mName + suffix + "min",
- .max = prefix + mName + suffix + "max",
- .consistent = prefix + mName + suffix + "consistent",
- .average = prefix + mName + suffix + "average"};
-}
-
-void LayerInfoV2::RefreshRateHistory::clear() {
- mRefreshRates.clear();
-}
-
-bool LayerInfoV2::RefreshRateHistory::add(float refreshRate, nsecs_t now) {
- mRefreshRates.push_back({refreshRate, now});
- while (mRefreshRates.size() >= HISTORY_SIZE ||
- now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
- mRefreshRates.pop_front();
- }
-
- if (CC_UNLIKELY(sTraceEnabled)) {
- if (!mHeuristicTraceTagData.has_value()) {
- mHeuristicTraceTagData = makeHeuristicTraceTagData();
- }
-
- ATRACE_INT(mHeuristicTraceTagData->average.c_str(), static_cast<int>(refreshRate));
- }
-
- return isConsistent();
-}
-
-bool LayerInfoV2::RefreshRateHistory::isConsistent() const {
- if (mRefreshRates.empty()) return true;
-
- const auto max = std::max_element(mRefreshRates.begin(), mRefreshRates.end());
- const auto min = std::min_element(mRefreshRates.begin(), mRefreshRates.end());
- const auto consistent = max->refreshRate - min->refreshRate <= MARGIN_FPS;
-
- if (CC_UNLIKELY(sTraceEnabled)) {
- if (!mHeuristicTraceTagData.has_value()) {
- mHeuristicTraceTagData = makeHeuristicTraceTagData();
- }
-
- ATRACE_INT(mHeuristicTraceTagData->max.c_str(), static_cast<int>(max->refreshRate));
- ATRACE_INT(mHeuristicTraceTagData->min.c_str(), static_cast<int>(min->refreshRate));
- ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
- }
-
- return consistent;
-}
-
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfoV2.h b/services/surfaceflinger/Scheduler/LayerInfoV2.h
deleted file mode 100644
index 2305bc3..0000000
--- a/services/surfaceflinger/Scheduler/LayerInfoV2.h
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright 2020 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 <utils/Timers.h>
-
-#include <chrono>
-#include <deque>
-
-#include "LayerHistory.h"
-#include "RefreshRateConfigs.h"
-#include "SchedulerUtils.h"
-
-namespace android {
-
-class Layer;
-
-namespace scheduler {
-
-using namespace std::chrono_literals;
-
-// Maximum period between presents for a layer to be considered active.
-constexpr std::chrono::nanoseconds MAX_ACTIVE_LAYER_PERIOD_NS = 1200ms;
-
-// Earliest present time for a layer to be considered active.
-constexpr nsecs_t getActiveLayerThreshold(nsecs_t now) {
- return now - MAX_ACTIVE_LAYER_PERIOD_NS.count();
-}
-
-// Stores history of present times and refresh rates for a layer.
-class LayerInfoV2 {
- using LayerUpdateType = LayerHistory::LayerUpdateType;
-
- // Layer is considered frequent if the earliest value in the window of most recent present times
- // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
- // favor of a low refresh rate.
- static constexpr size_t FREQUENT_LAYER_WINDOW_SIZE = 3;
- static constexpr float MIN_FPS_FOR_FREQUENT_LAYER = 10.0f;
- static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS =
- std::chrono::nanoseconds(static_cast<nsecs_t>(1e9f / MIN_FPS_FOR_FREQUENT_LAYER)) + 1ms;
-
- friend class LayerHistoryTestV2;
-
-public:
- // Holds information about the layer vote
- struct LayerVote {
- LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
- float fps = 0.0f;
- bool shouldBeSeamless = true;
- };
-
- static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
-
- static void setRefreshRateConfigs(const RefreshRateConfigs& refreshRateConfigs) {
- sRefreshRateConfigs = &refreshRateConfigs;
- }
-
- LayerInfoV2(const std::string& name, nsecs_t highRefreshRatePeriod,
- LayerHistory::LayerVoteType defaultVote);
-
- LayerInfoV2(const LayerInfo&) = delete;
- LayerInfoV2& operator=(const LayerInfoV2&) = delete;
-
- // Records the last requested present time. It also stores information about when
- // the layer was last updated. If the present time is farther in the future than the
- // updated time, the updated time is the present time.
- void setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUpdateType updateType,
- bool pendingConfigChange);
-
- // Sets an explicit layer vote. This usually comes directly from the application via
- // ANativeWindow_setFrameRate API
- void setLayerVote(LayerVote vote) { mLayerVote = vote; }
-
- // Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
- // This is used for layers that called to setLayerVote() and then removed the vote, so that the
- // layer can go back to whatever vote it had before the app voted for it.
- void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
-
- // Resets the layer vote to its default.
- void resetLayerVote() { mLayerVote = {mDefaultVote, 0.0f, true}; }
-
- LayerVote getRefreshRateVote(nsecs_t now);
-
- // Return the last updated time. If the present time is farther in the future than the
- // updated time, the updated time is the present time.
- nsecs_t getLastUpdatedTime() const { return mLastUpdatedTime; }
-
- // Returns a C string for tracing a vote
- const char* getTraceTag(LayerHistory::LayerVoteType type) const;
-
- void onLayerInactive(nsecs_t now) {
- // Mark mFrameTimeValidSince to now to ignore all previous frame times.
- // We are not deleting the old frame to keep track of whether we should treat the first
- // buffer as Max as we don't know anything about this layer or Min as this layer is
- // posting infrequent updates.
- const auto timePoint = std::chrono::nanoseconds(now);
- mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
- mLastRefreshRate = {};
- mRefreshRateHistory.clear();
- }
-
- void clearHistory(nsecs_t now) {
- onLayerInactive(now);
- mFrameTimes.clear();
- }
-
-private:
- // Used to store the layer timestamps
- struct FrameTimeData {
- nsecs_t presetTime; // desiredPresentTime, if provided
- nsecs_t queueTime; // buffer queue time
- bool pendingConfigChange;
- };
-
- // Holds information about the calculated and reported refresh rate
- struct RefreshRateHeuristicData {
- // Rate calculated on the layer
- float calculated = 0.0f;
- // Last reported rate for LayerInfoV2::getRefreshRate()
- float reported = 0.0f;
- // Whether the last reported rate for LayerInfoV2::getRefreshRate()
- // was due to animation or infrequent updates
- bool animatingOrInfrequent = false;
- };
-
- // Class to store past calculated refresh rate and determine whether
- // the refresh rate calculated is consistent with past values
- class RefreshRateHistory {
- public:
- static constexpr auto HISTORY_SIZE = 90;
- static constexpr std::chrono::nanoseconds HISTORY_DURATION = 2s;
-
- RefreshRateHistory(const std::string& name) : mName(name) {}
-
- // Clears History
- void clear();
-
- // Adds a new refresh rate and returns true if it is consistent
- bool add(float refreshRate, nsecs_t now);
-
- private:
- friend class LayerHistoryTestV2;
-
- // Holds the refresh rate when it was calculated
- struct RefreshRateData {
- float refreshRate = 0.0f;
- nsecs_t timestamp = 0;
-
- bool operator<(const RefreshRateData& other) const {
- return refreshRate < other.refreshRate;
- }
- };
-
- // Holds tracing strings
- struct HeuristicTraceTagData {
- std::string min;
- std::string max;
- std::string consistent;
- std::string average;
- };
-
- bool isConsistent() const;
- HeuristicTraceTagData makeHeuristicTraceTagData() const;
-
- const std::string mName;
- mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
- std::deque<RefreshRateData> mRefreshRates;
- static constexpr float MARGIN_FPS = 1.0;
- };
-
- bool isFrequent(nsecs_t now) const;
- bool isAnimating(nsecs_t now) const;
- bool hasEnoughDataForHeuristic() const;
- std::optional<float> calculateRefreshRateIfPossible(nsecs_t now);
- std::optional<nsecs_t> calculateAverageFrameTime() const;
- bool isFrameTimeValid(const FrameTimeData&) const;
-
- const std::string mName;
-
- // Used for sanitizing the heuristic data
- const nsecs_t mHighRefreshRatePeriod;
- LayerHistory::LayerVoteType mDefaultVote;
-
- LayerVote mLayerVote;
-
- nsecs_t mLastUpdatedTime = 0;
-
- nsecs_t mLastAnimationTime = 0;
-
- RefreshRateHeuristicData mLastRefreshRate;
-
- std::deque<FrameTimeData> mFrameTimes;
- std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
- std::chrono::steady_clock::now();
- static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
- static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
-
- RefreshRateHistory mRefreshRateHistory;
-
- mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
-
- // Shared for all LayerInfo instances
- static const RefreshRateConfigs* sRefreshRateConfigs;
- static bool sTraceEnabled;
-};
-
-} // namespace scheduler
-} // namespace android
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index 8b4283c..83fa20e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -27,6 +27,14 @@
#define LOG_TAG "RefreshRateConfigs"
namespace android::scheduler {
+namespace {
+std::string formatLayerInfo(const RefreshRateConfigs::LayerRequirement& layer, float weight) {
+ return base::StringPrintf("%s (type=%s, weight=%.2f seamlessness=%s) %.2fHz",
+ layer.name.c_str(),
+ RefreshRateConfigs::layerVoteTypeString(layer.vote).c_str(), weight,
+ toString(layer.seamlessness).c_str(), layer.desiredRefreshRate);
+}
+} // namespace
using AllRefreshRatesMapType = RefreshRateConfigs::AllRefreshRatesMapType;
using RefreshRate = RefreshRateConfigs::RefreshRate;
@@ -61,60 +69,6 @@
primaryRange.max, appRequestRange.min, appRequestRange.max);
}
-const RefreshRate& RefreshRateConfigs::getRefreshRateForContent(
- const std::vector<LayerRequirement>& layers) const {
- std::lock_guard lock(mLock);
- int contentFramerate = 0;
- int explicitContentFramerate = 0;
- for (const auto& layer : layers) {
- const auto desiredRefreshRateRound = round<int>(layer.desiredRefreshRate);
- if (layer.vote == LayerVoteType::ExplicitDefault ||
- layer.vote == LayerVoteType::ExplicitExactOrMultiple) {
- if (desiredRefreshRateRound > explicitContentFramerate) {
- explicitContentFramerate = desiredRefreshRateRound;
- }
- } else {
- if (desiredRefreshRateRound > contentFramerate) {
- contentFramerate = desiredRefreshRateRound;
- }
- }
- }
-
- if (explicitContentFramerate != 0) {
- contentFramerate = explicitContentFramerate;
- } else if (contentFramerate == 0) {
- contentFramerate = round<int>(mMaxSupportedRefreshRate->getFps());
- }
- ATRACE_INT("ContentFPS", contentFramerate);
-
- // Find the appropriate refresh rate with minimal error
- auto iter = min_element(mPrimaryRefreshRates.cbegin(), mPrimaryRefreshRates.cend(),
- [contentFramerate](const auto& lhs, const auto& rhs) -> bool {
- return std::abs(lhs->fps - contentFramerate) <
- std::abs(rhs->fps - contentFramerate);
- });
-
- // Some content aligns better on higher refresh rate. For example for 45fps we should choose
- // 90Hz config. However we should still prefer a lower refresh rate if the content doesn't
- // align well with both
- const RefreshRate* bestSoFar = *iter;
- constexpr float MARGIN = 0.05f;
- float ratio = (*iter)->fps / contentFramerate;
- if (std::abs(std::round(ratio) - ratio) > MARGIN) {
- while (iter != mPrimaryRefreshRates.cend()) {
- ratio = (*iter)->fps / contentFramerate;
-
- if (std::abs(std::round(ratio) - ratio) <= MARGIN) {
- bestSoFar = *iter;
- break;
- }
- ++iter;
- }
- }
-
- return *bestSoFar;
-}
-
std::pair<nsecs_t, nsecs_t> RefreshRateConfigs::getDisplayFrames(nsecs_t layerPeriod,
nsecs_t displayPeriod) const {
auto [displayFramesQuot, displayFramesRem] = std::div(layerPeriod, displayPeriod);
@@ -170,7 +124,7 @@
maxExplicitWeight = std::max(maxExplicitWeight, layer.weight);
}
- if (!layer.shouldBeSeamless) {
+ if (layer.seamlessness == Seamlessness::SeamedAndSeamless) {
seamedLayers++;
}
}
@@ -229,27 +183,38 @@
auto weight = layer.weight;
for (auto i = 0u; i < scores.size(); i++) {
- // If there are no layers with shouldBeSeamless=false and the current
- // config group is different from the default one, this means a layer with
- // shouldBeSeamless=false has just disappeared and we should switch back to
- // the default config group.
- const bool isSeamlessSwitch = seamedLayers > 0
- ? scores[i].first->getConfigGroup() == mCurrentRefreshRate->getConfigGroup()
- : scores[i].first->getConfigGroup() == defaultConfig->getConfigGroup();
+ const bool isSeamlessSwitch =
+ scores[i].first->getConfigGroup() == mCurrentRefreshRate->getConfigGroup();
- if (layer.shouldBeSeamless && !isSeamlessSwitch) {
- ALOGV("%s (weight %.2f) ignores %s (group=%d) to avoid non-seamless switch."
- "Current config = %s",
- layer.name.c_str(), weight, scores[i].first->name.c_str(),
- scores[i].first->getConfigGroup(), mCurrentRefreshRate->toString().c_str());
+ if (layer.seamlessness == Seamlessness::OnlySeamless && !isSeamlessSwitch) {
+ ALOGV("%s ignores %s to avoid non-seamless switch. Current config = %s",
+ formatLayerInfo(layer, weight).c_str(), scores[i].first->toString().c_str(),
+ mCurrentRefreshRate->toString().c_str());
continue;
}
- if (!layer.shouldBeSeamless && !isSeamlessSwitch && !layer.focused) {
- ALOGV("%s (weight %.2f) ignores %s (group=%d) because it's not focused"
- " and the switch is going to be seamed. Current config = %s",
- layer.name.c_str(), weight, scores[i].first->name.c_str(),
- scores[i].first->getConfigGroup(), mCurrentRefreshRate->toString().c_str());
+ if (layer.seamlessness == Seamlessness::SeamedAndSeamless && !isSeamlessSwitch &&
+ !layer.focused) {
+ ALOGV("%s ignores %s because it's not focused and the switch is going to be seamed."
+ " Current config = %s",
+ formatLayerInfo(layer, weight).c_str(), scores[i].first->toString().c_str(),
+ mCurrentRefreshRate->toString().c_str());
+ continue;
+ }
+
+ // Layers with default seamlessness vote for the current config group if
+ // there are layers with seamlessness=SeamedAndSeamless and for the default
+ // config group otherwise. In second case, if the current config group is different
+ // from the default, this means a layer with seamlessness=SeamedAndSeamless has just
+ // disappeared.
+ const bool isInPolicyForDefault = seamedLayers > 0
+ ? scores[i].first->getConfigGroup() == mCurrentRefreshRate->getConfigGroup()
+ : scores[i].first->getConfigGroup() == defaultConfig->getConfigGroup();
+
+ if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault &&
+ !layer.focused) {
+ ALOGV("%s ignores %s. Current config = %s", formatLayerInfo(layer, weight).c_str(),
+ scores[i].first->toString().c_str(), mCurrentRefreshRate->toString().c_str());
continue;
}
@@ -267,7 +232,7 @@
const auto ratio = scores[i].first->fps / scores.back().first->fps;
// use ratio^2 to get a lower score the more we get further from peak
const auto layerScore = ratio * ratio;
- ALOGV("%s (Max, weight %.2f) gives %s score of %.2f", layer.name.c_str(), weight,
+ ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
scores[i].first->name.c_str(), layerScore);
scores[i].second += weight * layerScore;
continue;
@@ -290,9 +255,8 @@
static_cast<float>(actualLayerPeriod));
}();
- ALOGV("%s (ExplicitDefault, weight %.2f) %.2fHz gives %s score of %.2f",
- layer.name.c_str(), weight, 1e9f / layerPeriod, scores[i].first->name.c_str(),
- layerScore);
+ ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
+ scores[i].first->name.c_str(), layerScore);
scores[i].second += weight * layerScore;
continue;
}
@@ -332,8 +296,7 @@
// Slightly prefer seamless switches.
constexpr float kSeamedSwitchPenalty = 0.95f;
const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
- ALOGV("%s (%s, weight %.2f) %.2fHz gives %s score of %.2f", layer.name.c_str(),
- layerVoteTypeString(layer.vote).c_str(), weight, 1e9f / layerPeriod,
+ ALOGV("%s gives %s score of %.2f", formatLayerInfo(layer, weight).c_str(),
scores[i].first->name.c_str(), layerScore);
scores[i].second += weight * layerScore * seamlessness;
continue;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index a873777..6e0c0d3 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -27,6 +27,7 @@
#include "DisplayHardware/HWComposer.h"
#include "HwcStrongTypes.h"
#include "Scheduler/SchedulerUtils.h"
+#include "Scheduler/Seamlessness.h"
#include "Scheduler/StrongTyping.h"
namespace android::scheduler {
@@ -222,7 +223,7 @@
// Layer's desired refresh rate, if applicable.
float desiredRefreshRate = 0.0f;
// If a seamless mode switch is required.
- bool shouldBeSeamless = true;
+ Seamlessness seamlessness = Seamlessness::Default;
// Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
// would have on choosing the refresh rate.
float weight = 0.0f;
@@ -231,17 +232,14 @@
bool operator==(const LayerRequirement& other) const {
return name == other.name && vote == other.vote &&
- desiredRefreshRate == other.desiredRefreshRate && weight == other.weight &&
+ desiredRefreshRate == other.desiredRefreshRate &&
+ seamlessness == other.seamlessness && weight == other.weight &&
focused == other.focused;
}
bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
};
- // Returns the refresh rate that fits best to the given layers.
- const RefreshRate& getRefreshRateForContent(const std::vector<LayerRequirement>& layers) const
- EXCLUDES(mLock);
-
// Global state describing signals that affect refresh rate choice.
struct GlobalSignals {
// Whether the user touched the screen recently. Used to apply touch boost.
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index f6d712e..52bf483 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -89,9 +89,8 @@
timerSlack.count(), vsyncMoveThreshold.count());
}
-const char* toContentDetectionString(bool useContentDetection, bool useContentDetectionV2) {
- if (!useContentDetection) return "off";
- return useContentDetectionV2 ? "V2" : "V1";
+const char* toContentDetectionString(bool useContentDetection) {
+ return useContentDetection ? "on" : "off";
}
} // namespace
@@ -119,14 +118,13 @@
Scheduler::Scheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
: Scheduler(configs, callback,
{.supportKernelTimer = sysprop::support_kernel_idle_timer(false),
- .useContentDetection = sysprop::use_content_detection_for_refresh_rate(false),
- .useContentDetectionV2 =
- base::GetBoolProperty("debug.sf.use_content_detection_v2"s, true)}) {}
+ .useContentDetection = sysprop::use_content_detection_for_refresh_rate(false)}) {
+}
Scheduler::Scheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
Options options)
: Scheduler(createVsyncSchedule(options.supportKernelTimer), configs, callback,
- createLayerHistory(configs, options.useContentDetectionV2), options) {
+ createLayerHistory(configs), options) {
using namespace sysprop;
const int setIdleTimerMs = base::GetIntProperty("debug.sf.set_idle_timer_ms"s, 0);
@@ -195,14 +193,10 @@
}
std::unique_ptr<LayerHistory> Scheduler::createLayerHistory(
- const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2) {
+ const scheduler::RefreshRateConfigs& configs) {
if (!configs.canSwitch()) return nullptr;
- if (useContentDetectionV2) {
- return std::make_unique<scheduler::impl::LayerHistoryV2>(configs);
- }
-
- return std::make_unique<scheduler::impl::LayerHistory>();
+ return std::make_unique<scheduler::LayerHistory>(configs);
}
std::unique_ptr<VSyncSource> Scheduler::makePrimaryDispSyncSource(
@@ -528,14 +522,6 @@
// still need to be registered.
mLayerHistory->registerLayer(layer, minFps, maxFps,
scheduler::LayerHistory::LayerVoteType::Max);
- } else if (!mOptions.useContentDetectionV2) {
- // In V1 of content detection, all layers are registered as Heuristic (unless it's
- // wallpaper).
- const auto highFps =
- layer->getWindowType() == InputWindowInfo::Type::WALLPAPER ? minFps : maxFps;
-
- mLayerHistory->registerLayer(layer, minFps, highFps,
- scheduler::LayerHistory::LayerVoteType::Heuristic);
} else {
if (layer->getWindowType() == InputWindowInfo::Type::WALLPAPER) {
// Running Wallpaper at Min is considered as part of content detection.
@@ -574,8 +560,6 @@
return;
}
mFeatures.contentRequirements = summary;
- mFeatures.contentDetectionV1 =
- !summary.empty() ? ContentDetectionState::On : ContentDetectionState::Off;
scheduler::RefreshRateConfigs::GlobalSignals consideredSignals;
newConfigId = calculateRefreshRateConfigIndexType(&consideredSignals);
@@ -682,8 +666,7 @@
StringAppendF(&result, "+ Touch timer: %s\n",
mTouchTimer ? mTouchTimer->dump().c_str() : "off");
StringAppendF(&result, "+ Content detection: %s %s\n\n",
- toContentDetectionString(mOptions.useContentDetection,
- mOptions.useContentDetectionV2),
+ toContentDetectionString(mOptions.useContentDetection),
mLayerHistory ? mLayerHistory->dump().c_str() : "(no layer history)");
}
@@ -740,29 +723,6 @@
const bool touchActive = mTouchTimer && mFeatures.touch == TouchState::Active;
const bool idle = mIdleTimer && mFeatures.idleTimer == TimerState::Expired;
- if (!mOptions.useContentDetectionV2) {
- // As long as touch is active we want to be in performance mode.
- if (touchActive) {
- return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
- }
-
- // If timer has expired as it means there is no new content on the screen.
- if (idle) {
- if (consideredSignals) consideredSignals->idle = true;
- return mRefreshRateConfigs.getMinRefreshRateByPolicy().getConfigId();
- }
-
- // If content detection is off we choose performance as we don't know the content fps.
- if (mFeatures.contentDetectionV1 == ContentDetectionState::Off) {
- // NOTE: V1 always calls this, but this is not a default behavior for V2.
- return mRefreshRateConfigs.getMaxRefreshRateByPolicy().getConfigId();
- }
-
- // Content detection is on, find the appropriate refresh rate with minimal error
- return mRefreshRateConfigs.getRefreshRateForContent(mFeatures.contentRequirements)
- .getConfigId();
- }
-
return mRefreshRateConfigs
.getBestRefreshRate(mFeatures.contentRequirements, {.touch = touchActive, .idle = idle},
consideredSignals)
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 76e8f57..f16e1f9 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -183,8 +183,6 @@
bool supportKernelTimer;
// Whether to use content detection at all.
bool useContentDetection;
- // Whether to use improved content detection.
- bool useContentDetectionV2;
};
struct VsyncSchedule {
@@ -201,8 +199,7 @@
std::unique_ptr<LayerHistory>, Options);
static VsyncSchedule createVsyncSchedule(bool supportKernelIdleTimer);
- static std::unique_ptr<LayerHistory> createLayerHistory(const scheduler::RefreshRateConfigs&,
- bool useContentDetectionV2);
+ static std::unique_ptr<LayerHistory> createLayerHistory(const scheduler::RefreshRateConfigs&);
// Create a connection on the given EventThread.
ConnectionHandle createConnection(std::unique_ptr<EventThread>);
@@ -270,7 +267,6 @@
std::mutex mFeatureStateLock;
struct {
- ContentDetectionState contentDetectionV1 = ContentDetectionState::Off;
TimerState idleTimer = TimerState::Reset;
TouchState touch = TouchState::Inactive;
TimerState displayPowerTimer = TimerState::Expired;
diff --git a/services/surfaceflinger/Scheduler/Seamlessness.h b/services/surfaceflinger/Scheduler/Seamlessness.h
new file mode 100644
index 0000000..3e42a4d
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/Seamlessness.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2020 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 <cstring>
+#include <ostream>
+
+namespace android {
+namespace scheduler {
+
+// The seamlessness requirement of a Layer.
+enum class Seamlessness {
+ // Indicates a requirement for a seamless mode switch.
+ OnlySeamless,
+ // Indicates that both seamless and seamed mode switches are allowed.
+ SeamedAndSeamless,
+ // Indicates no preference for seamlessness. For such layers the system will
+ // prefer seamless switches, but also non-seamless switches to the group of the
+ // default config are allowed.
+ Default
+};
+
+inline std::string toString(Seamlessness seamlessness) {
+ switch (seamlessness) {
+ case Seamlessness::OnlySeamless:
+ return "OnlySeamless";
+ case Seamlessness::SeamedAndSeamless:
+ return "SeamedAndSeamless";
+ case Seamlessness::Default:
+ return "Default";
+ }
+}
+
+// Used by gtest
+inline std::ostream& operator<<(std::ostream& os, Seamlessness val) {
+ return os << toString(val);
+}
+
+} // namespace scheduler
+} // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index f9fe6e1..576bd50 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -606,8 +606,7 @@
return *mCompositionEngine.get();
}
-void SurfaceFlinger::bootFinished()
-{
+void SurfaceFlinger::bootFinished() {
if (mBootFinished == true) {
ALOGE("Extra call to bootFinished");
return;
@@ -2106,8 +2105,7 @@
getBE().mCompositorTiming.presentLatency = snappedCompositeToPresentLatency;
}
-void SurfaceFlinger::postComposition()
-{
+void SurfaceFlinger::postComposition() {
ATRACE_CALL();
ALOGV("postComposition");
@@ -2296,8 +2294,7 @@
}
}
-void SurfaceFlinger::handleTransaction(uint32_t transactionFlags)
-{
+void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) {
ATRACE_CALL();
// here we keep a copy of the drawing state (that is the state that's
@@ -2659,8 +2656,7 @@
mDrawingState.displays = mCurrentState.displays;
}
-void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags)
-{
+void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags) {
const nsecs_t expectedPresentTime = mExpectedPresentTime.load();
// Notify all layers of available frames
@@ -3011,8 +3007,7 @@
}
}
-bool SurfaceFlinger::handlePageFlip()
-{
+bool SurfaceFlinger::handlePageFlip() {
ATRACE_CALL();
ALOGV("handlePageFlip");
@@ -3092,8 +3087,7 @@
return !mLayersWithQueuedFrames.empty() && newDataLatched;
}
-void SurfaceFlinger::invalidateHwcGeometry()
-{
+void SurfaceFlinger::invalidateHwcGeometry() {
mGeometryInvalid = true;
}
@@ -4041,7 +4035,14 @@
sp<Layer>* outLayer) {
LayerCreationArgs args(this, client, std::move(name), w, h, flags, std::move(metadata));
args.textureName = getNewTexture();
- sp<BufferStateLayer> layer = getFactory().createBufferStateLayer(args);
+ sp<BufferStateLayer> layer;
+ {
+ // TODO (b/173538294): Investigate why we need mStateLock here and above in
+ // createBufferQueue layer. Is it the renderengine::Image?
+ Mutex::Autolock lock(mStateLock);
+ layer = getFactory().createBufferStateLayer(args);
+
+ }
*handle = layer->getHandle();
*outLayer = layer;
@@ -4073,8 +4074,7 @@
setTransactionFlags(eTransactionNeeded);
}
-void SurfaceFlinger::onHandleDestroyed(sp<Layer>& layer)
-{
+void SurfaceFlinger::onHandleDestroyed(sp<Layer>& layer) {
Mutex::Autolock lock(mStateLock);
// If a layer has a parent, we allow it to out-live it's handle
// with the idea that the parent holds a reference and will eventually
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 18f3745..7427e27 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -48,7 +48,6 @@
"HWComposerTest.cpp",
"OneShotTimerTest.cpp",
"LayerHistoryTest.cpp",
- "LayerHistoryTestV2.cpp",
"LayerMetadataTest.cpp",
"MessageQueueTest.cpp",
"PromiseTest.cpp",
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 0fbe8ec..fbb4637 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * Copyright 2020 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.
@@ -14,13 +14,10 @@
* limitations under the License.
*/
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
#undef LOG_TAG
#define LOG_TAG "LayerHistoryTest"
+#include <Layer.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
@@ -39,32 +36,74 @@
class LayerHistoryTest : public testing::Test {
protected:
- static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::PresentTimeHistory::HISTORY_SIZE;
+ static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::MAX_FREQUENT_LAYER_PERIOD_NS;
+ static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::FREQUENT_LAYER_WINDOW_SIZE;
+ static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
+ static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
+ LayerInfo::RefreshRateHistory::HISTORY_DURATION;
static constexpr float LO_FPS = 30.f;
- static constexpr nsecs_t LO_FPS_PERIOD = 33'333'333;
+ static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
static constexpr float HI_FPS = 90.f;
- static constexpr nsecs_t HI_FPS_PERIOD = 11'111'111;
+ static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
- impl::LayerHistory& history() { return *mScheduler->mutableLayerHistory(); }
- const impl::LayerHistory& history() const { return *mScheduler->mutableLayerHistory(); }
+ LayerHistory& history() { return *mScheduler->mutableLayerHistory(); }
+ const LayerHistory& history() const { return *mScheduler->mutableLayerHistory(); }
size_t layerCount() const { return mScheduler->layerHistorySize(); }
size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
- size_t frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
const auto& infos = history().mLayerInfos;
- return std::count_if(infos.begin(), infos.begin() + history().mActiveLayersEnd,
+ return std::count_if(infos.begin(),
+ infos.begin() + static_cast<long>(history().mActiveLayersEnd),
[now](const auto& pair) { return pair.second->isFrequent(now); });
}
+ auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ const auto& infos = history().mLayerInfos;
+ return std::count_if(infos.begin(),
+ infos.begin() + static_cast<long>(history().mActiveLayersEnd),
+ [now](const auto& pair) { return pair.second->isAnimating(now); });
+ }
+
+ void setDefaultLayerVote(Layer* layer,
+ LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
+ for (auto& [weak, info] : history().mLayerInfos) {
+ if (auto strong = weak.promote(); strong && strong.get() == layer) {
+ info->setDefaultLayerVote(vote);
+ return;
+ }
+ }
+ }
+
auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
+ auto createLayer(std::string name) {
+ return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
+ }
+
+ void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
+ float desiredRefreshRate, int numFrames) {
+ const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
+ LayerHistory::Summary summary;
+ for (int i = 0; i < numFrames; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += framePeriod;
+
+ summary = history().summarize(time);
+ }
+
+ ASSERT_EQ(1, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
+ << "Frame rate is " << frameRate;
+ }
Hwc2::mock::Display mDisplay;
RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
@@ -78,14 +117,10 @@
HwcConfigIndexType(0)};
mock::NoOpSchedulerCallback mSchedulerCallback;
- static constexpr bool kUseContentDetectionV2 = false;
- TestableScheduler* const mScheduler =
- new TestableScheduler(mConfigs, mSchedulerCallback, kUseContentDetectionV2);
+ TestableScheduler* const mScheduler = new TestableScheduler(mConfigs, mSchedulerCallback);
TestableSurfaceFlinger mFlinger;
-
- const nsecs_t mTime = systemTime();
};
namespace {
@@ -93,83 +128,259 @@
TEST_F(LayerHistoryTest, oneLayer) {
const auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
- // no layers are returned if no layers are active.
- ASSERT_TRUE(history().summarize(mTime).empty());
+ const nsecs_t time = systemTime();
+
+ // No layers returned if no layers are active.
+ EXPECT_TRUE(history().summarize(time).empty());
EXPECT_EQ(0, activeLayerCount());
- // no layers are returned if active layers have insufficient history.
+ // Max returned if active layers have insufficient history.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- history().record(layer.get(), 0, mTime, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_TRUE(history().summarize(mTime).empty());
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
EXPECT_EQ(1, activeLayerCount());
}
- // High FPS is returned once enough history has been recorded.
+ // Max is returned since we have enough history but there is no timestamp votes.
for (int i = 0; i < 10; i++) {
- history().record(layer.get(), 0, mTime, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(1, history().summarize(mTime).size());
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(mTime)[0].desiredRefreshRate);
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
EXPECT_EQ(1, activeLayerCount());
}
}
+TEST_F(LayerHistoryTest, oneInvisibleLayer) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+
+ history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ auto summary = history().summarize(time);
+ ASSERT_EQ(1, history().summarize(time).size());
+ // Layer is still considered inactive so we expect to get Min
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
+
+ summary = history().summarize(time);
+ EXPECT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+}
+
TEST_F(LayerHistoryTest, explicitTimestamp) {
const auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
- nsecs_t time = mTime;
+ nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
- ASSERT_EQ(1, history().summarize(mTime).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(mTime)[0].desiredRefreshRate);
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
}
+TEST_F(LayerHistoryTest, oneLayerNoVote) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerMinVote) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerMaxVote) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Max);
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += LO_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_TRUE(history().summarize(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitVote) {
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(
+ Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive, but the vote stays
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitExactVote) {
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(
+ Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive, but the vote stays
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
TEST_F(LayerHistoryTest, multipleLayers) {
auto layer1 = createLayer();
auto layer2 = createLayer();
auto layer3 = createLayer();
EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer1, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer2, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer2, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer3, getFrameSelectionPriority()).WillRepeatedly(Return(1));
EXPECT_CALL(*layer3, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
- nsecs_t time = mTime;
+
+ nsecs_t time = systemTime();
EXPECT_EQ(3, layerCount());
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
+ LayerHistory::Summary summary;
+
// layer1 is active but infrequent.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
@@ -177,26 +388,30 @@
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
// layer1 is still active but infrequent.
history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(2, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer1 is no longer active.
// layer2 is frequent and has low refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
@@ -210,26 +425,36 @@
history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
// layer3 becomes recently active.
history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(2, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+ summary = history().summarize(time);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
// layer1 expires.
layer1.clear();
- ASSERT_EQ(2, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
+ summary = history().summarize(time);
+ ASSERT_EQ(2, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
EXPECT_EQ(2, layerCount());
EXPECT_EQ(2, activeLayerCount());
EXPECT_EQ(2, frequentLayerCount(time));
@@ -239,42 +464,290 @@
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer2 expires.
layer2.clear();
- ASSERT_TRUE(history().summarize(time).empty());
+ summary = history().summarize(time);
+ EXPECT_TRUE(summary.empty());
EXPECT_EQ(1, layerCount());
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
// layer3 becomes active and has high refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
+ summary = history().summarize(time);
}
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[0].desiredRefreshRate);
+ ASSERT_EQ(1, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
EXPECT_EQ(1, layerCount());
EXPECT_EQ(1, activeLayerCount());
EXPECT_EQ(1, frequentLayerCount(time));
// layer3 expires.
layer3.clear();
- ASSERT_TRUE(history().summarize(time).empty());
+ summary = history().summarize(time);
+ EXPECT_TRUE(summary.empty());
EXPECT_EQ(0, layerCount());
EXPECT_EQ(0, activeLayerCount());
EXPECT_EQ(0, frequentLayerCount(time));
}
+TEST_F(LayerHistoryTest, inactiveLayers) {
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ // the very first updates makes the layer frequent
+ for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ }
+
+ // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ // advance the time for the previous frame to be inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+
+ // Now event if we post a quick few frame we should stay infrequent
+ for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ }
+
+ // More quick frames will get us to frequent again
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, invisibleExplicitLayer) {
+ auto explicitVisiblelayer = createLayer();
+ auto explicitInvisiblelayer = createLayer();
+
+ EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(
+ Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+ EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
+ EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(
+ Layer::FrameRate(90.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+ nsecs_t time = systemTime();
+
+ // Post a buffer to the layers to make them active
+ history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(explicitInvisiblelayer.get(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+
+ EXPECT_EQ(2, layerCount());
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ history().summarize(time)[0].vote);
+ EXPECT_FLOAT_EQ(60.0f, history().summarize(time)[0].desiredRefreshRate);
+ EXPECT_EQ(2, activeLayerCount());
+ EXPECT_EQ(2, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // layer is active but infrequent.
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ }
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // another update with the same cadence keep in infrequent
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // an update as animation will immediately vote for Max
+ history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ ASSERT_EQ(1, history().summarize(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(1, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, heuristicLayer60Hz) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+ for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
+ recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ }
+}
+
+TEST_F(LayerHistoryTest, heuristicLayer60_30Hz) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+ recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+
+ recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
+}
+
+class LayerHistoryTestParameterized : public LayerHistoryTest,
+ public testing::WithParamInterface<std::chrono::nanoseconds> {
+};
+
+TEST_P(LayerHistoryTestParameterized, HeuristicLayerWithInfrequentLayer) {
+ std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
+ auto heuristicLayer = createLayer("HeuristicLayer");
+
+ EXPECT_CALL(*heuristicLayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*heuristicLayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(Layer::FrameRate()));
+
+ auto infrequentLayer = createLayer("InfrequentLayer");
+ EXPECT_CALL(*infrequentLayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*infrequentLayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(Layer::FrameRate()));
+
+ const nsecs_t startTime = systemTime();
+
+ const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
+ history().record(heuristicLayer.get(), startTime, startTime,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().record(infrequentLayer.get(), startTime, startTime,
+ LayerHistory::LayerUpdateType::Buffer);
+
+ nsecs_t time = startTime;
+ nsecs_t lastInfrequentUpdate = startTime;
+ const int totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
+ int infrequentLayerUpdates = 0;
+ while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
+ time += heuristicUpdateDelta.count();
+ history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+
+ if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
+ ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
+ totalInfrequentLayerUpdates);
+ lastInfrequentUpdate = time;
+ history().record(infrequentLayer.get(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ infrequentLayerUpdates++;
+ }
+
+ if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
+ ASSERT_NE(0, history().summarize(time).size());
+ ASSERT_GE(2, history().summarize(time).size());
+
+ bool max = false;
+ bool min = false;
+ float heuristic = 0;
+ for (const auto& layer : history().summarize(time)) {
+ if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
+ heuristic = layer.desiredRefreshRate;
+ } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
+ max = true;
+ } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
+ min = true;
+ }
+ }
+
+ if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
+ EXPECT_FLOAT_EQ(24.0f, heuristic);
+ EXPECT_FALSE(max);
+ if (history().summarize(time).size() == 2) {
+ EXPECT_TRUE(min);
+ }
+ }
+ }
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryTestParameterized,
+ ::testing::Values(1s, 2s, 3s, 4s, 5s));
+
} // namespace
} // namespace android::scheduler
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
deleted file mode 100644
index 3b50321..0000000
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTestV2.cpp
+++ /dev/null
@@ -1,756 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LayerHistoryTestV2"
-
-#include <Layer.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <log/log.h>
-
-#include "Scheduler/LayerHistory.h"
-#include "Scheduler/LayerInfoV2.h"
-#include "TestableScheduler.h"
-#include "TestableSurfaceFlinger.h"
-#include "mock/MockLayer.h"
-#include "mock/MockSchedulerCallback.h"
-
-using testing::_;
-using testing::Return;
-
-namespace android::scheduler {
-
-class LayerHistoryTestV2 : public testing::Test {
-protected:
- static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfoV2::HISTORY_SIZE;
- static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfoV2::MAX_FREQUENT_LAYER_PERIOD_NS;
- static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfoV2::FREQUENT_LAYER_WINDOW_SIZE;
- static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfoV2::HISTORY_DURATION;
- static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
- LayerInfoV2::RefreshRateHistory::HISTORY_DURATION;
-
- static constexpr float LO_FPS = 30.f;
- static constexpr auto LO_FPS_PERIOD = static_cast<nsecs_t>(1e9f / LO_FPS);
-
- static constexpr float HI_FPS = 90.f;
- static constexpr auto HI_FPS_PERIOD = static_cast<nsecs_t>(1e9f / HI_FPS);
-
- LayerHistoryTestV2() { mFlinger.resetScheduler(mScheduler); }
-
- void SetUp() override { ASSERT_TRUE(mScheduler->hasLayerHistory()); }
-
- impl::LayerHistoryV2& history() { return *mScheduler->mutableLayerHistoryV2(); }
- const impl::LayerHistoryV2& history() const { return *mScheduler->mutableLayerHistoryV2(); }
-
- size_t layerCount() const { return mScheduler->layerHistorySize(); }
- size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS { return history().mActiveLayersEnd; }
-
- auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
- const auto& infos = history().mLayerInfos;
- return std::count_if(infos.begin(),
- infos.begin() + static_cast<long>(history().mActiveLayersEnd),
- [now](const auto& pair) { return pair.second->isFrequent(now); });
- }
-
- auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
- const auto& infos = history().mLayerInfos;
- return std::count_if(infos.begin(),
- infos.begin() + static_cast<long>(history().mActiveLayersEnd),
- [now](const auto& pair) { return pair.second->isAnimating(now); });
- }
-
- void setLayerInfoVote(Layer* layer,
- LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
- for (auto& [weak, info] : history().mLayerInfos) {
- if (auto strong = weak.promote(); strong && strong.get() == layer) {
- info->setDefaultLayerVote(vote);
- info->setLayerVote({vote, 0, false});
- return;
- }
- }
- }
-
- auto createLayer() { return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger())); }
- auto createLayer(std::string name) {
- return sp<mock::MockLayer>(new mock::MockLayer(mFlinger.flinger(), std::move(name)));
- }
-
- void recordFramesAndExpect(const sp<mock::MockLayer>& layer, nsecs_t& time, float frameRate,
- float desiredRefreshRate, int numFrames) {
- const nsecs_t framePeriod = static_cast<nsecs_t>(1e9f / frameRate);
- impl::LayerHistoryV2::Summary summary;
- for (int i = 0; i < numFrames; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += framePeriod;
-
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- ASSERT_FLOAT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate)
- << "Frame rate is " << frameRate;
- }
-
- Hwc2::mock::Display mDisplay;
- RefreshRateConfigs mConfigs{{HWC2::Display::Config::Builder(mDisplay, 0)
- .setVsyncPeriod(int32_t(LO_FPS_PERIOD))
- .setConfigGroup(0)
- .build(),
- HWC2::Display::Config::Builder(mDisplay, 1)
- .setVsyncPeriod(int32_t(HI_FPS_PERIOD))
- .setConfigGroup(0)
- .build()},
- HwcConfigIndexType(0)};
-
- mock::NoOpSchedulerCallback mSchedulerCallback;
- static constexpr bool kUseContentDetectionV2 = true;
-
- TestableScheduler* const mScheduler =
- new TestableScheduler(mConfigs, mSchedulerCallback, kUseContentDetectionV2);
-
- TestableSurfaceFlinger mFlinger;
-};
-
-namespace {
-
-TEST_F(LayerHistoryTestV2, oneLayer) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- const nsecs_t time = systemTime();
-
- // No layers returned if no layers are active.
- EXPECT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
-
- // Max returned if active layers have insufficient history.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- }
-
- // Max is returned since we have enough history but there is no timestamp votes.
- for (int i = 0; i < 10; i++) {
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- }
-}
-
-TEST_F(LayerHistoryTestV2, oneInvisibleLayer) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
-
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
- auto summary = history().summarize(time);
- ASSERT_EQ(1, history().summarize(time).size());
- // Layer is still considered inactive so we expect to get Min
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
-
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
-
- summary = history().summarize(time);
- EXPECT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
-}
-
-TEST_F(LayerHistoryTestV2, explicitTimestamp) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += LO_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerNoVote) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- }
-
- ASSERT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerMinVote) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Min);
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerMaxVote) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Max);
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += LO_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_TRUE(history().summarize(time).empty());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerExplicitVote) {
- auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree())
- .WillRepeatedly(
- Return(Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::Default)));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive, but the vote stays
- setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, oneLayerExplicitExactVote) {
- auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(
- Layer::FrameRate(73.4f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
-
- nsecs_t time = systemTime();
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
- history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer became inactive, but the vote stays
- setLayerInfoVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
- history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(73.4f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, multipleLayers) {
- auto layer1 = createLayer();
- auto layer2 = createLayer();
- auto layer3 = createLayer();
-
- EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer2, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer3, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
-
- EXPECT_EQ(3, layerCount());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-
- impl::LayerHistoryV2::Summary summary;
-
- // layer1 is active but infrequent.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-
- // layer2 is frequent and has high refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- // layer1 is still active but infrequent.
- history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-
- ASSERT_EQ(2, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
- ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, history().summarize(time)[1].desiredRefreshRate);
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer1 is no longer active.
- // layer2 is frequent and has low refresh rate.
- for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += LO_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer2 still has low refresh rate.
- // layer3 has high refresh rate but not enough history.
- constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- if (i % RATIO == 0) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- }
-
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(2, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(2, frequentLayerCount(time));
-
- // layer3 becomes recently active.
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- summary = history().summarize(time);
- ASSERT_EQ(2, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(2, frequentLayerCount(time));
-
- // layer1 expires.
- layer1.clear();
- summary = history().summarize(time);
- ASSERT_EQ(2, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[1].desiredRefreshRate);
- EXPECT_EQ(2, layerCount());
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(2, frequentLayerCount(time));
-
- // layer2 still has low refresh rate.
- // layer3 becomes inactive.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += LO_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(LO_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer2 expires.
- layer2.clear();
- summary = history().summarize(time);
- EXPECT_TRUE(summary.empty());
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-
- // layer3 becomes active and has high refresh rate.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
- summary = history().summarize(time);
- }
-
- ASSERT_EQ(1, summary.size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
- EXPECT_FLOAT_EQ(HI_FPS, summary[0].desiredRefreshRate);
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-
- // layer3 expires.
- layer3.clear();
- summary = history().summarize(time);
- EXPECT_TRUE(summary.empty());
- EXPECT_EQ(0, layerCount());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, inactiveLayers) {
- auto layer = createLayer();
-
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
-
- // the very first updates makes the layer frequent
- for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
- EXPECT_EQ(1, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
- }
-
- // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
- EXPECT_EQ(1, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
-
- // advance the time for the previous frame to be inactive
- time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-
- // Now event if we post a quick few frame we should stay infrequent
- for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
-
- EXPECT_EQ(1, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- }
-
- // More quick frames will get us to frequent again
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += HI_FPS_PERIOD;
-
- EXPECT_EQ(1, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(1, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, invisibleExplicitLayer) {
- auto explicitVisiblelayer = createLayer();
- auto explicitInvisiblelayer = createLayer();
-
- EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(
- Layer::FrameRate(60.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
- EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
- EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(
- Layer::FrameRate(90.0f, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
- nsecs_t time = systemTime();
-
- // Post a buffer to the layers to make them active
- history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- history().record(explicitInvisiblelayer.get(), time, time,
- LayerHistory::LayerUpdateType::Buffer);
-
- EXPECT_EQ(2, layerCount());
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
- history().summarize(time)[0].vote);
- EXPECT_FLOAT_EQ(60.0f, history().summarize(time)[0].desiredRefreshRate);
- EXPECT_EQ(2, activeLayerCount());
- EXPECT_EQ(2, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, infrequentAnimatingLayer) {
- auto layer = createLayer();
-
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
-
- EXPECT_EQ(1, layerCount());
- EXPECT_EQ(0, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- EXPECT_EQ(0, animatingLayerCount(time));
-
- // layer is active but infrequent.
- for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
- }
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- EXPECT_EQ(0, animatingLayerCount(time));
-
- // another update with the same cadence keep in infrequent
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Min, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- EXPECT_EQ(0, animatingLayerCount(time));
-
- // an update as animation will immediately vote for Max
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
- time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
- ASSERT_EQ(1, history().summarize(time).size());
- EXPECT_EQ(LayerHistory::LayerVoteType::Max, history().summarize(time)[0].vote);
- EXPECT_EQ(1, activeLayerCount());
- EXPECT_EQ(0, frequentLayerCount(time));
- EXPECT_EQ(1, animatingLayerCount(time));
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayer60Hz) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
- for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
- recordFramesAndExpect(layer, time, fps, 60.0f, PRESENT_TIME_HISTORY_SIZE);
- }
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayer60_30Hz) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 30.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 30.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 60.0f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 60.0f, 60.0f, PRESENT_TIME_HISTORY_SIZE);
-}
-
-TEST_F(LayerHistoryTestV2, heuristicLayerNotOscillating) {
- const auto layer = createLayer();
- EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
- nsecs_t time = systemTime();
-
- recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.90f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.00f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 26.90f, 24.0f, PRESENT_TIME_HISTORY_SIZE);
- recordFramesAndExpect(layer, time, 27.10f, 30.0f, PRESENT_TIME_HISTORY_SIZE);
-}
-
-class LayerHistoryTestV2Parameterized
- : public LayerHistoryTestV2,
- public testing::WithParamInterface<std::chrono::nanoseconds> {};
-
-TEST_P(LayerHistoryTestV2Parameterized, HeuristicLayerWithInfrequentLayer) {
- std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
- auto heuristicLayer = createLayer("HeuristicLayer");
-
- EXPECT_CALL(*heuristicLayer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*heuristicLayer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(Layer::FrameRate()));
-
- auto infrequentLayer = createLayer("InfrequentLayer");
- EXPECT_CALL(*infrequentLayer, isVisible()).WillRepeatedly(Return(true));
- EXPECT_CALL(*infrequentLayer, getFrameRateForLayerTree())
- .WillRepeatedly(Return(Layer::FrameRate()));
-
- const nsecs_t startTime = systemTime();
-
- const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
- history().record(heuristicLayer.get(), startTime, startTime,
- LayerHistory::LayerUpdateType::Buffer);
- history().record(infrequentLayer.get(), startTime, startTime,
- LayerHistory::LayerUpdateType::Buffer);
-
- nsecs_t time = startTime;
- nsecs_t lastInfrequentUpdate = startTime;
- const int totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
- int infrequentLayerUpdates = 0;
- while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
- time += heuristicUpdateDelta.count();
- history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
-
- if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
- ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
- totalInfrequentLayerUpdates);
- lastInfrequentUpdate = time;
- history().record(infrequentLayer.get(), time, time,
- LayerHistory::LayerUpdateType::Buffer);
- infrequentLayerUpdates++;
- }
-
- if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
- ASSERT_NE(0, history().summarize(time).size());
- ASSERT_GE(2, history().summarize(time).size());
-
- bool max = false;
- bool min = false;
- float heuristic = 0;
- for (const auto& layer : history().summarize(time)) {
- if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
- heuristic = layer.desiredRefreshRate;
- } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
- max = true;
- } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
- min = true;
- }
- }
-
- if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
- EXPECT_FLOAT_EQ(24.0f, heuristic);
- EXPECT_FALSE(max);
- if (history().summarize(time).size() == 2) {
- EXPECT_TRUE(min);
- }
- }
- }
- }
-}
-
-INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryTestV2Parameterized,
- ::testing::Values(1s, 2s, 3s, 4s, 5s));
-
-} // namespace
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 2fbc72a..f2b7191 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -304,63 +304,6 @@
}
}
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent) {
- auto refreshRateConfigs =
- std::make_unique<RefreshRateConfigs>(m60_90Device,
- /*currentConfigId=*/HWC_CONFIG_ID_60);
-
- const auto makeLayerRequirements = [](float refreshRate) -> std::vector<LayerRequirement> {
- return {{"testLayer", LayerVoteType::Heuristic, refreshRate, /*shouldBeSeamless*/ true,
- /*weight*/ 1.0f, /*focused*/ false}};
- };
-
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {60, 60}}), 0);
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_90, {90, 90}}), 0);
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
- ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {0, 120}}), 0);
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(90.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(60.0f)));
- EXPECT_EQ(mExpected90Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(45.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(30.0f)));
- EXPECT_EQ(mExpected60Config,
- refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
-}
-
TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) {
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_72_90Device, /*currentConfigId=*/
@@ -847,29 +790,6 @@
}
}
-TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getRefreshRateForContent_Explicit) {
- auto refreshRateConfigs =
- std::make_unique<RefreshRateConfigs>(m60_90Device,
- /*currentConfigId=*/HWC_CONFIG_ID_60);
-
- auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f},
- LayerRequirement{.weight = 1.0f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 60.0f;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 90.0f;
- EXPECT_EQ(mExpected90Config, refreshRateConfigs->getRefreshRateForContent(layers));
-
- lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 90.0f;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 60.0f;
- EXPECT_EQ(mExpected60Config, refreshRateConfigs->getRefreshRateForContent(layers));
-}
-
TEST_F(RefreshRateConfigsTest, twoDeviceConfigs_getBestRefreshRate_Explicit) {
auto refreshRateConfigs =
std::make_unique<RefreshRateConfigs>(m60_90Device,
@@ -1263,7 +1183,7 @@
auto& layer = layers[0];
layer.vote = LayerVoteType::ExplicitDefault;
layer.desiredRefreshRate = 90.0f;
- layer.shouldBeSeamless = false;
+ layer.seamlessness = Seamlessness::SeamedAndSeamless;
layer.name = "90Hz ExplicitDefault";
layer.focused = true;
@@ -1280,32 +1200,49 @@
.getConfigId());
// Verify that we won't change the group if seamless switch is required.
- layer.shouldBeSeamless = true;
+ layer.seamlessness = Seamlessness::OnlySeamless;
ASSERT_EQ(HWC_CONFIG_ID_60,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
.getConfigId());
- // At this point the default config in the DisplayManager policy with be 60Hz.
- // Verify that if the current config is in another group and there are no layers with
- // shouldBeSeamless=false we'll go back to the default group.
+ // Verify that we won't do a seamless switch if we request the same mode as the default
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
layer.desiredRefreshRate = 60.0f;
layer.name = "60Hz ExplicitDefault";
- layer.shouldBeSeamless = true;
+ layer.seamlessness = Seamlessness::OnlySeamless;
+ ASSERT_EQ(HWC_CONFIG_ID_90,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+ .getConfigId());
+
+ // Verify that if the current config is in another group and there are no layers with
+ // seamlessness=SeamedAndSeamless we'll go back to the default group.
+ layer.desiredRefreshRate = 60.0f;
+ layer.name = "60Hz ExplicitDefault";
+ layer.seamlessness = Seamlessness::Default;
ASSERT_EQ(HWC_CONFIG_ID_60,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
.getConfigId());
- // If there's a layer with shouldBeSeamless=false, another layer with shouldBeSeamless=true
- // can't change the config group.
+ // If there's a layer with seamlessness=SeamedAndSeamless, another layer with
+ // seamlessness=OnlySeamless can't change the config group.
refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_90);
- auto layer2 = LayerRequirement{.weight = 0.5f};
+ layer.seamlessness = Seamlessness::OnlySeamless;
+
+ layers.push_back(LayerRequirement{.weight = 0.5f});
+ auto& layer2 = layers[layers.size() - 1];
layer2.vote = LayerVoteType::ExplicitDefault;
layer2.desiredRefreshRate = 90.0f;
layer2.name = "90Hz ExplicitDefault";
- layer2.shouldBeSeamless = false;
+ layer2.seamlessness = Seamlessness::SeamedAndSeamless;
layer2.focused = false;
- layers.push_back(layer2);
+
+ ASSERT_EQ(HWC_CONFIG_ID_90,
+ refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
+ .getConfigId());
+
+ // If there's a layer with seamlessness=SeamedAndSeamless, another layer with
+ // seamlessness=Default can't change the config group.
+ layers[0].seamlessness = Seamlessness::Default;
ASSERT_EQ(HWC_CONFIG_ID_90,
refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false})
.getConfigId());
@@ -1326,7 +1263,7 @@
auto& layer = layers[0];
layer.vote = LayerVoteType::ExplicitExactOrMultiple;
layer.desiredRefreshRate = 60.0f;
- layer.shouldBeSeamless = false;
+ layer.seamlessness = Seamlessness::SeamedAndSeamless;
layer.name = "60Hz ExplicitExactOrMultiple";
layer.focused = true;
@@ -1355,13 +1292,13 @@
LayerRequirement>{LayerRequirement{.name = "60Hz ExplicitDefault",
.vote = LayerVoteType::ExplicitDefault,
.desiredRefreshRate = 60.0f,
- .shouldBeSeamless = false,
+ .seamlessness = Seamlessness::SeamedAndSeamless,
.weight = 0.5f,
.focused = false},
LayerRequirement{.name = "25Hz ExplicitExactOrMultiple",
.vote = LayerVoteType::ExplicitExactOrMultiple,
.desiredRefreshRate = 25.0f,
- .shouldBeSeamless = true,
+ .seamlessness = Seamlessness::OnlySeamless,
.weight = 1.0f,
.focused = true}};
auto& seamedLayer = layers[0];
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 647689b..fb02a33 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -68,8 +68,7 @@
}
} mExpectDisableVsync{mSchedulerCallback};
- static constexpr bool kUseContentDetectionV2 = false;
- TestableScheduler mScheduler{mConfigs, mSchedulerCallback, kUseContentDetectionV2};
+ TestableScheduler mScheduler{mConfigs, mSchedulerCallback};
Scheduler::ConnectionHandle mConnectionHandle;
mock::EventThread* mEventThread;
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 1b6e9ed..2192977 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -32,21 +32,16 @@
class TestableScheduler : public Scheduler {
public:
- TestableScheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
- bool useContentDetectionV2)
+ TestableScheduler(const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
: TestableScheduler(std::make_unique<mock::VsyncController>(),
- std::make_unique<mock::VSyncTracker>(), configs, callback,
- useContentDetectionV2) {}
+ std::make_unique<mock::VSyncTracker>(), configs, callback) {}
TestableScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
- const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback,
- bool useContentDetectionV2)
+ const scheduler::RefreshRateConfigs& configs, ISchedulerCallback& callback)
: Scheduler({std::move(vsyncController), std::move(vsyncTracker), nullptr}, configs,
- callback, createLayerHistory(configs, useContentDetectionV2),
- {.supportKernelTimer = false,
- .useContentDetection = true,
- .useContentDetectionV2 = useContentDetectionV2}) {}
+ callback, createLayerHistory(configs),
+ {.supportKernelTimer = false, .useContentDetection = true}) {}
// Used to inject mock event thread.
ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
@@ -62,20 +57,11 @@
bool hasLayerHistory() const { return static_cast<bool>(mLayerHistory); }
- auto* mutableLayerHistory() {
- LOG_ALWAYS_FATAL_IF(mOptions.useContentDetectionV2);
- return static_cast<scheduler::impl::LayerHistory*>(mLayerHistory.get());
- }
-
- auto* mutableLayerHistoryV2() {
- LOG_ALWAYS_FATAL_IF(!mOptions.useContentDetectionV2);
- return static_cast<scheduler::impl::LayerHistoryV2*>(mLayerHistory.get());
- }
+ auto* mutableLayerHistory() { return mLayerHistory.get(); }
size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
if (!mLayerHistory) return 0;
- return mOptions.useContentDetectionV2 ? mutableLayerHistoryV2()->mLayerInfos.size()
- : mutableLayerHistory()->mLayerInfos.size();
+ return mutableLayerHistory()->mLayerInfos.size();
}
void replaceTouchTimer(int64_t millis) {
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index eba3f8e..8a0276a 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -227,10 +227,8 @@
mFactory.createVsyncConfiguration(*mFlinger->mRefreshRateConfigs);
mFlinger->mVsyncModulator.emplace(mFlinger->mVsyncConfiguration->getCurrentConfigs());
- constexpr bool kUseContentDetectionV2 = false;
mScheduler = new TestableScheduler(std::move(vsyncController), std::move(vsyncTracker),
- *mFlinger->mRefreshRateConfigs, *(callback ?: this),
- kUseContentDetectionV2);
+ *mFlinger->mRefreshRateConfigs, *(callback ?: this));
mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));