diff options
Diffstat (limited to 'libs')
315 files changed, 31058 insertions, 5820 deletions
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk index 2f287000f746..6bbfcd2f9d8a 100644 --- a/libs/androidfw/Android.mk +++ b/libs/androidfw/Android.mk @@ -21,6 +21,7 @@ commonSources := \ Asset.cpp \ AssetDir.cpp \ AssetManager.cpp \ + LocaleData.cpp \ misc.cpp \ ObbFile.cpp \ ResourceTypes.cpp \ @@ -33,17 +34,17 @@ deviceSources := \ $(commonSources) \ BackupData.cpp \ BackupHelpers.cpp \ - CursorWindow.cpp + CursorWindow.cpp \ + DisplayEventDispatcher.cpp hostSources := $(commonSources) # For the host # ===================================================== include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_MODULE:= libandroidfw -LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_HOST_OS := darwin linux windows LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code LOCAL_SRC_FILES:= $(hostSources) @@ -56,19 +57,17 @@ include $(BUILD_HOST_STATIC_LIBRARY) # ===================================================== include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk LOCAL_MODULE:= libandroidfw -LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES:= $(deviceSources) LOCAL_C_INCLUDES := \ - external/zlib \ system/core/include LOCAL_STATIC_LIBRARIES := libziparchive libbase LOCAL_SHARED_LIBRARIES := \ libbinder \ liblog \ libcutils \ + libgui \ libutils \ libz diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 2dc1c96259c0..715c875d064d 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -34,9 +34,7 @@ #include <utils/String8.h> #include <utils/threads.h> #include <utils/Timers.h> -#ifdef HAVE_ANDROID_OS -#include <cutils/trace.h> -#endif +#include <utils/Trace.h> #include <assert.h> #include <dirent.h> @@ -54,14 +52,6 @@ _rc; }) #endif -#ifdef HAVE_ANDROID_OS -#define MY_TRACE_BEGIN(x) ATRACE_BEGIN(x) -#define MY_TRACE_END() ATRACE_END() -#else -#define MY_TRACE_BEGIN(x) -#define MY_TRACE_END() -#endif - using namespace android; static const bool kIsDebug = false; @@ -176,7 +166,8 @@ AssetManager::~AssetManager(void) delete[] mVendor; } -bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) +bool AssetManager::addAssetPath( + const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset) { AutoMutex _l(mLock); @@ -222,6 +213,7 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) } delete manifestAsset; + ap.isSystemAsset = isSystemAsset; mAssetPaths.add(ap); // new paths are always added at the end @@ -229,16 +221,17 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) *cookie = static_cast<int32_t>(mAssetPaths.size()); } -#ifdef HAVE_ANDROID_OS +#ifdef __ANDROID__ // Load overlays, if any asset_path oap; for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) { + oap.isSystemAsset = isSystemAsset; mAssetPaths.add(oap); } #endif if (mResources != NULL) { - appendPathToResTable(ap); + appendPathToResTable(ap, appAsLib); } return true; @@ -340,7 +333,7 @@ bool AssetManager::addDefaultAssets() String8 path(root); path.appendPath(kSystemAssets); - return addAssetPath(path, NULL); + return addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */); } int32_t AssetManager::nextAssetPath(const int32_t cookie) const @@ -610,7 +603,7 @@ FileType AssetManager::getFileType(const char* fileName) return kFileTypeRegular; } -bool AssetManager::appendPathToResTable(const asset_path& ap) const { +bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const { // skip those ap's that correspond to system overlays if (ap.isSystemOverlay) { return true; @@ -620,7 +613,7 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const { ResTable* sharedRes = NULL; bool shared = true; bool onlyEmptyResources = true; - MY_TRACE_BEGIN(ap.path.string()); + ATRACE_NAME(ap.path.string()); Asset* idmap = openIdmapLocked(ap); size_t nextEntryIdx = mResources->getTableCount(); ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); @@ -657,7 +650,7 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const { ALOGV("Creating shared resources for %s", ap.path.string()); sharedRes = new ResTable(); sharedRes->add(ass, idmap, nextEntryIdx + 1, false); -#ifdef HAVE_ANDROID_OS +#ifdef __ANDROID__ const char* data = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set"); String8 overlaysListPath(data); @@ -682,10 +675,10 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const { ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); - mResources->add(sharedRes); + mResources->add(sharedRes, ap.isSystemAsset); } else { ALOGV("Parsing resources for %s", ap.path.string()); - mResources->add(ass, idmap, nextEntryIdx + 1, !shared); + mResources->add(ass, idmap, nextEntryIdx + 1, !shared, appAsLib, ap.isSystemAsset); } onlyEmptyResources = false; @@ -700,8 +693,6 @@ bool AssetManager::appendPathToResTable(const asset_path& ap) const { if (idmap != NULL) { delete idmap; } - MY_TRACE_END(); - return onlyEmptyResources; } @@ -749,6 +740,7 @@ const ResTable* AssetManager::getResTable(bool required) const void AssetManager::updateResourceParamsLocked() const { + ATRACE_CALL(); ResTable* res = mResources; if (!res) { return; @@ -831,11 +823,11 @@ bool AssetManager::isUpToDate() return mZipSet.isUpToDate(); } -void AssetManager::getLocales(Vector<String8>* locales) const +void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocales) const { ResTable* res = mResources; if (res != NULL) { - res->getLocales(locales); + res->getLocales(locales, includeSystemLocales); } const size_t numLocales = locales->size(); @@ -1545,7 +1537,7 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg */ int dirNameLen = dirName.length(); void *iterationCookie; - if (!pZip->startIteration(&iterationCookie)) { + if (!pZip->startIteration(&iterationCookie, dirName.string(), NULL)) { ALOGW("ZipFileRO::startIteration returned false"); return false; } @@ -1560,9 +1552,7 @@ bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMerg continue; } //printf("Comparing %s in %s?\n", nameBuf, dirName.string()); - if (dirNameLen == 0 || - (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 && - nameBuf[dirNameLen] == '/')) + if (dirNameLen == 0 || nameBuf[dirNameLen] == '/') { const char* cp; const char* nextSlash; diff --git a/libs/androidfw/DisplayEventDispatcher.cpp b/libs/androidfw/DisplayEventDispatcher.cpp new file mode 100644 index 000000000000..b8ef9ea49293 --- /dev/null +++ b/libs/androidfw/DisplayEventDispatcher.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "DisplayEventDispatcher" + +#include <cinttypes> +#include <cstdint> + +#include <androidfw/DisplayEventDispatcher.h> +#include <gui/DisplayEventReceiver.h> +#include <utils/Log.h> +#include <utils/Looper.h> + +#include <utils/Timers.h> + +namespace android { + +// Number of events to read at a time from the DisplayEventDispatcher pipe. +// The value should be large enough that we can quickly drain the pipe +// using just a few large reads. +static const size_t EVENT_BUFFER_SIZE = 100; + +DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper) : + mLooper(looper), mWaitingForVsync(false) { + ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this); +} + +status_t DisplayEventDispatcher::initialize() { + status_t result = mReceiver.initCheck(); + if (result) { + ALOGW("Failed to initialize display event receiver, status=%d", result); + return result; + } + + int rc = mLooper->addFd(mReceiver.getFd(), 0, Looper::EVENT_INPUT, + this, NULL); + if (rc < 0) { + return UNKNOWN_ERROR; + } + return OK; +} + +void DisplayEventDispatcher::dispose() { + ALOGV("dispatcher %p ~ Disposing display event dispatcher.", this); + + if (!mReceiver.initCheck()) { + mLooper->removeFd(mReceiver.getFd()); + } +} + +status_t DisplayEventDispatcher::scheduleVsync() { + if (!mWaitingForVsync) { + ALOGV("dispatcher %p ~ Scheduling vsync.", this); + + // Drain all pending events. + nsecs_t vsyncTimestamp; + int32_t vsyncDisplayId; + uint32_t vsyncCount; + if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) { + ALOGE("dispatcher %p ~ last event processed while scheduling was for %" PRId64 "", + this, ns2ms(static_cast<nsecs_t>(vsyncTimestamp))); + } + + status_t status = mReceiver.requestNextVsync(); + if (status) { + ALOGW("Failed to request next vsync, status=%d", status); + return status; + } + + mWaitingForVsync = true; + } + return OK; +} + +int DisplayEventDispatcher::handleEvent(int, int events, void*) { + if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { + ALOGE("Display event receiver pipe was closed or an error occurred. " + "events=0x%x", events); + return 0; // remove the callback + } + + if (!(events & Looper::EVENT_INPUT)) { + ALOGW("Received spurious callback for unhandled poll event. " + "events=0x%x", events); + return 1; // keep the callback + } + + // Drain all pending events, keep the last vsync. + nsecs_t vsyncTimestamp; + int32_t vsyncDisplayId; + uint32_t vsyncCount; + if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount)) { + ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64 ", id=%d, count=%d", + this, ns2ms(vsyncTimestamp), vsyncDisplayId, vsyncCount); + mWaitingForVsync = false; + dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount); + } + + return 1; // keep the callback +} + +bool DisplayEventDispatcher::processPendingEvents( + nsecs_t* outTimestamp, int32_t* outId, uint32_t* outCount) { + bool gotVsync = false; + DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE]; + ssize_t n; + while ((n = mReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) { + ALOGV("dispatcher %p ~ Read %d events.", this, int(n)); + for (ssize_t i = 0; i < n; i++) { + const DisplayEventReceiver::Event& ev = buf[i]; + switch (ev.header.type) { + case DisplayEventReceiver::DISPLAY_EVENT_VSYNC: + // Later vsync events will just overwrite the info from earlier + // ones. That's fine, we only care about the most recent. + gotVsync = true; + *outTimestamp = ev.header.timestamp; + *outId = ev.header.id; + *outCount = ev.vsync.count; + break; + case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: + dispatchHotplug(ev.header.timestamp, ev.header.id, ev.hotplug.connected); + break; + default: + ALOGW("dispatcher %p ~ ignoring unknown event type %#x", this, ev.header.type); + break; + } + } + } + if (n < 0) { + ALOGW("Failed to get events from display event dispatcher, status=%d", status_t(n)); + } + return gotVsync; +} +} diff --git a/libs/androidfw/LocaleData.cpp b/libs/androidfw/LocaleData.cpp new file mode 100644 index 000000000000..038ef5839fe2 --- /dev/null +++ b/libs/androidfw/LocaleData.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 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 <cstdint> +#include <cstdlib> +#include <cstring> +#include <string> +#include <unordered_map> +#include <unordered_set> + +#include <androidfw/LocaleData.h> + +namespace android { + +#include "LocaleDataTables.cpp" + +inline uint32_t packLocale(const char* language, const char* region) { + return (((uint8_t) language[0]) << 24u) | (((uint8_t) language[1]) << 16u) | + (((uint8_t) region[0]) << 8u) | ((uint8_t) region[1]); +} + +inline uint32_t dropRegion(uint32_t packed_locale) { + return packed_locale & 0xFFFF0000lu; +} + +inline bool hasRegion(uint32_t packed_locale) { + return (packed_locale & 0x0000FFFFlu) != 0; +} + +const size_t SCRIPT_LENGTH = 4; +const size_t SCRIPT_PARENTS_COUNT = sizeof(SCRIPT_PARENTS)/sizeof(SCRIPT_PARENTS[0]); +const uint32_t PACKED_ROOT = 0; // to represent the root locale + +uint32_t findParent(uint32_t packed_locale, const char* script) { + if (hasRegion(packed_locale)) { + for (size_t i = 0; i < SCRIPT_PARENTS_COUNT; i++) { + if (memcmp(script, SCRIPT_PARENTS[i].script, SCRIPT_LENGTH) == 0) { + auto map = SCRIPT_PARENTS[i].map; + auto lookup_result = map->find(packed_locale); + if (lookup_result != map->end()) { + return lookup_result->second; + } + break; + } + } + return dropRegion(packed_locale); + } + return PACKED_ROOT; +} + +// Find the ancestors of a locale, and fill 'out' with it (assumes out has enough +// space). If any of the members of stop_list was seen, write it in the +// output but stop afterwards. +// +// This also outputs the index of the last written ancestor in the stop_list +// to stop_list_index, which will be -1 if it is not found in the stop_list. +// +// Returns the number of ancestors written in the output, which is always +// at least one. +// +// (If 'out' is nullptr, we do everything the same way but we simply don't write +// any results in 'out'.) +size_t findAncestors(uint32_t* out, ssize_t* stop_list_index, + uint32_t packed_locale, const char* script, + const uint32_t* stop_list, size_t stop_set_length) { + uint32_t ancestor = packed_locale; + size_t count = 0; + do { + if (out != nullptr) out[count] = ancestor; + count++; + for (size_t i = 0; i < stop_set_length; i++) { + if (stop_list[i] == ancestor) { + *stop_list_index = (ssize_t) i; + return count; + } + } + ancestor = findParent(ancestor, script); + } while (ancestor != PACKED_ROOT); + *stop_list_index = (ssize_t) -1; + return count; +} + +size_t findDistance(uint32_t supported, + const char* script, + const uint32_t* request_ancestors, + size_t request_ancestors_count) { + ssize_t request_ancestors_index; + const size_t supported_ancestor_count = findAncestors( + nullptr, &request_ancestors_index, + supported, script, + request_ancestors, request_ancestors_count); + // Since both locales share the same root, there will always be a shared + // ancestor, so the distance in the parent tree is the sum of the distance + // of 'supported' to the lowest common ancestor (number of ancestors + // written for 'supported' minus 1) plus the distance of 'request' to the + // lowest common ancestor (the index of the ancestor in request_ancestors). + return supported_ancestor_count + request_ancestors_index - 1; +} + +inline bool isRepresentative(uint32_t language_and_region, const char* script) { + const uint64_t packed_locale = ( + (((uint64_t) language_and_region) << 32u) | + (((uint64_t) script[0]) << 24u) | + (((uint64_t) script[1]) << 16u) | + (((uint64_t) script[2]) << 8u) | + ((uint64_t) script[3])); + + return (REPRESENTATIVE_LOCALES.count(packed_locale) != 0); +} + +int localeDataCompareRegions( + const char* left_region, const char* right_region, + const char* requested_language, const char* requested_script, + const char* requested_region) { + + if (left_region[0] == right_region[0] && left_region[1] == right_region[1]) { + return 0; + } + const uint32_t left = packLocale(requested_language, left_region); + const uint32_t right = packLocale(requested_language, right_region); + const uint32_t request = packLocale(requested_language, requested_region); + + uint32_t request_ancestors[MAX_PARENT_DEPTH+1]; + ssize_t left_right_index; + // Find the parents of the request, but stop as soon as we saw left or right + const uint32_t left_and_right[] = {left, right}; + const size_t ancestor_count = findAncestors( + request_ancestors, &left_right_index, + request, requested_script, + left_and_right, sizeof(left_and_right)/sizeof(left_and_right[0])); + if (left_right_index == 0) { // We saw left earlier + return 1; + } + if (left_right_index == 1) { // We saw right earlier + return -1; + } + + // If we are here, neither left nor right are an ancestor of the + // request. This means that all the ancestors have been computed and + // the last ancestor is just the language by itself. We will use the + // distance in the parent tree for determining the better match. + const size_t left_distance = findDistance( + left, requested_script, request_ancestors, ancestor_count); + const size_t right_distance = findDistance( + right, requested_script, request_ancestors, ancestor_count); + if (left_distance != right_distance) { + return (int) right_distance - (int) left_distance; // smaller distance is better + } + + // If we are here, left and right are equidistant from the request. We will + // try and see if any of them is a representative locale. + const bool left_is_representative = isRepresentative(left, requested_script); + const bool right_is_representative = isRepresentative(right, requested_script); + if (left_is_representative != right_is_representative) { + return (int) left_is_representative - (int) right_is_representative; + } + + // We have no way of figuring out which locale is a better match. For + // the sake of stability, we consider the locale with the lower region + // code (in dictionary order) better, with two-letter codes before + // three-digit codes (since two-letter codes are more specific). + return (int64_t) right - (int64_t) left; +} + +void localeDataComputeScript(char out[4], const char* language, const char* region) { + if (language[0] == '\0') { + memset(out, '\0', SCRIPT_LENGTH); + return; + } + uint32_t lookup_key = packLocale(language, region); + auto lookup_result = LIKELY_SCRIPTS.find(lookup_key); + if (lookup_result == LIKELY_SCRIPTS.end()) { + // We couldn't find the locale. Let's try without the region + if (region[0] != '\0') { + lookup_key = dropRegion(lookup_key); + lookup_result = LIKELY_SCRIPTS.find(lookup_key); + if (lookup_result != LIKELY_SCRIPTS.end()) { + memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH); + return; + } + } + // We don't know anything about the locale + memset(out, '\0', SCRIPT_LENGTH); + return; + } else { + // We found the locale. + memcpy(out, SCRIPT_CODES[lookup_result->second], SCRIPT_LENGTH); + } +} + +const uint32_t ENGLISH_STOP_LIST[2] = { + 0x656E0000lu, // en + 0x656E8400lu, // en-001 +}; +const char ENGLISH_CHARS[2] = {'e', 'n'}; +const char LATIN_CHARS[4] = {'L', 'a', 't', 'n'}; + +bool localeDataIsCloseToUsEnglish(const char* region) { + const uint32_t locale = packLocale(ENGLISH_CHARS, region); + ssize_t stop_list_index; + findAncestors(nullptr, &stop_list_index, locale, LATIN_CHARS, ENGLISH_STOP_LIST, 2); + // A locale is like US English if we see "en" before "en-001" in its ancestor list. + return stop_list_index == 0; // 'en' is first in ENGLISH_STOP_LIST +} + +} // namespace android diff --git a/libs/androidfw/LocaleDataTables.cpp b/libs/androidfw/LocaleDataTables.cpp new file mode 100644 index 000000000000..1ac508525061 --- /dev/null +++ b/libs/androidfw/LocaleDataTables.cpp @@ -0,0 +1,1701 @@ +// Auto-generated by frameworks/base/tools/localedata/extract_icu_data.py + +const char SCRIPT_CODES[][4] = { + /* 0 */ {'A', 'h', 'o', 'm'}, + /* 1 */ {'A', 'r', 'a', 'b'}, + /* 2 */ {'A', 'r', 'm', 'i'}, + /* 3 */ {'A', 'r', 'm', 'n'}, + /* 4 */ {'A', 'v', 's', 't'}, + /* 5 */ {'B', 'a', 'm', 'u'}, + /* 6 */ {'B', 'a', 's', 's'}, + /* 7 */ {'B', 'e', 'n', 'g'}, + /* 8 */ {'B', 'r', 'a', 'h'}, + /* 9 */ {'C', 'a', 'n', 's'}, + /* 10 */ {'C', 'a', 'r', 'i'}, + /* 11 */ {'C', 'h', 'a', 'm'}, + /* 12 */ {'C', 'h', 'e', 'r'}, + /* 13 */ {'C', 'o', 'p', 't'}, + /* 14 */ {'C', 'p', 'r', 't'}, + /* 15 */ {'C', 'y', 'r', 'l'}, + /* 16 */ {'D', 'e', 'v', 'a'}, + /* 17 */ {'E', 'g', 'y', 'p'}, + /* 18 */ {'E', 't', 'h', 'i'}, + /* 19 */ {'G', 'e', 'o', 'r'}, + /* 20 */ {'G', 'o', 't', 'h'}, + /* 21 */ {'G', 'r', 'e', 'k'}, + /* 22 */ {'G', 'u', 'j', 'r'}, + /* 23 */ {'G', 'u', 'r', 'u'}, + /* 24 */ {'H', 'a', 'n', 's'}, + /* 25 */ {'H', 'a', 'n', 't'}, + /* 26 */ {'H', 'a', 't', 'r'}, + /* 27 */ {'H', 'e', 'b', 'r'}, + /* 28 */ {'H', 'l', 'u', 'w'}, + /* 29 */ {'H', 'm', 'n', 'g'}, + /* 30 */ {'I', 't', 'a', 'l'}, + /* 31 */ {'J', 'p', 'a', 'n'}, + /* 32 */ {'K', 'a', 'l', 'i'}, + /* 33 */ {'K', 'a', 'n', 'a'}, + /* 34 */ {'K', 'h', 'a', 'r'}, + /* 35 */ {'K', 'h', 'm', 'r'}, + /* 36 */ {'K', 'n', 'd', 'a'}, + /* 37 */ {'K', 'o', 'r', 'e'}, + /* 38 */ {'K', 't', 'h', 'i'}, + /* 39 */ {'L', 'a', 'n', 'a'}, + /* 40 */ {'L', 'a', 'o', 'o'}, + /* 41 */ {'L', 'a', 't', 'n'}, + /* 42 */ {'L', 'e', 'p', 'c'}, + /* 43 */ {'L', 'i', 'n', 'a'}, + /* 44 */ {'L', 'i', 's', 'u'}, + /* 45 */ {'L', 'y', 'c', 'i'}, + /* 46 */ {'L', 'y', 'd', 'i'}, + /* 47 */ {'M', 'a', 'n', 'd'}, + /* 48 */ {'M', 'a', 'n', 'i'}, + /* 49 */ {'M', 'e', 'r', 'c'}, + /* 50 */ {'M', 'l', 'y', 'm'}, + /* 51 */ {'M', 'o', 'n', 'g'}, + /* 52 */ {'M', 'r', 'o', 'o'}, + /* 53 */ {'M', 'y', 'm', 'r'}, + /* 54 */ {'N', 'a', 'r', 'b'}, + /* 55 */ {'N', 'k', 'o', 'o'}, + /* 56 */ {'O', 'g', 'a', 'm'}, + /* 57 */ {'O', 'r', 'k', 'h'}, + /* 58 */ {'O', 'r', 'y', 'a'}, + /* 59 */ {'P', 'a', 'u', 'c'}, + /* 60 */ {'P', 'h', 'l', 'i'}, + /* 61 */ {'P', 'h', 'n', 'x'}, + /* 62 */ {'P', 'l', 'r', 'd'}, + /* 63 */ {'P', 'r', 't', 'i'}, + /* 64 */ {'R', 'u', 'n', 'r'}, + /* 65 */ {'S', 'a', 'm', 'r'}, + /* 66 */ {'S', 'a', 'r', 'b'}, + /* 67 */ {'S', 'a', 'u', 'r'}, + /* 68 */ {'S', 'g', 'n', 'w'}, + /* 69 */ {'S', 'i', 'n', 'h'}, + /* 70 */ {'S', 'o', 'r', 'a'}, + /* 71 */ {'S', 'y', 'r', 'c'}, + /* 72 */ {'T', 'a', 'l', 'e'}, + /* 73 */ {'T', 'a', 'l', 'u'}, + /* 74 */ {'T', 'a', 'm', 'l'}, + /* 75 */ {'T', 'a', 'v', 't'}, + /* 76 */ {'T', 'e', 'l', 'u'}, + /* 77 */ {'T', 'f', 'n', 'g'}, + /* 78 */ {'T', 'h', 'a', 'a'}, + /* 79 */ {'T', 'h', 'a', 'i'}, + /* 80 */ {'T', 'i', 'b', 't'}, + /* 81 */ {'U', 'g', 'a', 'r'}, + /* 82 */ {'V', 'a', 'i', 'i'}, + /* 83 */ {'X', 'p', 'e', 'o'}, + /* 84 */ {'X', 's', 'u', 'x'}, + /* 85 */ {'Y', 'i', 'i', 'i'}, + /* 86 */ {'~', '~', '~', 'A'}, + /* 87 */ {'~', '~', '~', 'B'}, +}; + + +const std::unordered_map<uint32_t, uint8_t> LIKELY_SCRIPTS({ + {0x61610000u, 41u}, // aa -> Latn + {0x61620000u, 15u}, // ab -> Cyrl + {0xC4200000u, 41u}, // abr -> Latn + {0x90400000u, 41u}, // ace -> Latn + {0x9C400000u, 41u}, // ach -> Latn + {0x80600000u, 41u}, // ada -> Latn + {0xE0600000u, 15u}, // ady -> Cyrl + {0x61650000u, 4u}, // ae -> Avst + {0x84800000u, 1u}, // aeb -> Arab + {0x61660000u, 41u}, // af -> Latn + {0xC0C00000u, 41u}, // agq -> Latn + {0xB8E00000u, 0u}, // aho -> Ahom + {0x616B0000u, 41u}, // ak -> Latn + {0xA9400000u, 84u}, // akk -> Xsux + {0xB5600000u, 41u}, // aln -> Latn + {0xCD600000u, 15u}, // alt -> Cyrl + {0x616D0000u, 18u}, // am -> Ethi + {0xB9800000u, 41u}, // amo -> Latn + {0xE5C00000u, 41u}, // aoz -> Latn + {0x61720000u, 1u}, // ar -> Arab + {0x61725842u, 87u}, // ar-XB -> ~~~B + {0x8A200000u, 2u}, // arc -> Armi + {0xB6200000u, 41u}, // arn -> Latn + {0xBA200000u, 41u}, // aro -> Latn + {0xC2200000u, 1u}, // arq -> Arab + {0xE2200000u, 1u}, // ary -> Arab + {0xE6200000u, 1u}, // arz -> Arab + {0x61730000u, 7u}, // as -> Beng + {0x82400000u, 41u}, // asa -> Latn + {0x92400000u, 68u}, // ase -> Sgnw + {0xCE400000u, 41u}, // ast -> Latn + {0xA6600000u, 41u}, // atj -> Latn + {0x61760000u, 15u}, // av -> Cyrl + {0x82C00000u, 16u}, // awa -> Deva + {0x61790000u, 41u}, // ay -> Latn + {0x617A0000u, 41u}, // az -> Latn + {0x617A4951u, 1u}, // az-IQ -> Arab + {0x617A4952u, 1u}, // az-IR -> Arab + {0x617A5255u, 15u}, // az-RU -> Cyrl + {0x62610000u, 15u}, // ba -> Cyrl + {0xAC010000u, 1u}, // bal -> Arab + {0xB4010000u, 41u}, // ban -> Latn + {0xBC010000u, 16u}, // bap -> Deva + {0xC4010000u, 41u}, // bar -> Latn + {0xC8010000u, 41u}, // bas -> Latn + {0xDC010000u, 5u}, // bax -> Bamu + {0x88210000u, 41u}, // bbc -> Latn + {0xA4210000u, 41u}, // bbj -> Latn + {0xA0410000u, 41u}, // bci -> Latn + {0x62650000u, 15u}, // be -> Cyrl + {0xA4810000u, 1u}, // bej -> Arab + {0xB0810000u, 41u}, // bem -> Latn + {0xD8810000u, 41u}, // bew -> Latn + {0xE4810000u, 41u}, // bez -> Latn + {0x8CA10000u, 41u}, // bfd -> Latn + {0xC0A10000u, 74u}, // bfq -> Taml + {0xCCA10000u, 1u}, // bft -> Arab + {0xE0A10000u, 16u}, // bfy -> Deva + {0x62670000u, 15u}, // bg -> Cyrl + {0x88C10000u, 16u}, // bgc -> Deva + {0xB4C10000u, 1u}, // bgn -> Arab + {0xDCC10000u, 21u}, // bgx -> Grek + {0x62680000u, 38u}, // bh -> Kthi + {0x84E10000u, 16u}, // bhb -> Deva + {0xA0E10000u, 16u}, // bhi -> Deva + {0xA8E10000u, 41u}, // bhk -> Latn + {0xB8E10000u, 16u}, // bho -> Deva + {0x62690000u, 41u}, // bi -> Latn + {0xA9010000u, 41u}, // bik -> Latn + {0xB5010000u, 41u}, // bin -> Latn + {0xA5210000u, 16u}, // bjj -> Deva + {0xB5210000u, 41u}, // bjn -> Latn + {0xB1410000u, 41u}, // bkm -> Latn + {0xD1410000u, 41u}, // bku -> Latn + {0xCD610000u, 75u}, // blt -> Tavt + {0x626D0000u, 41u}, // bm -> Latn + {0xC1810000u, 41u}, // bmq -> Latn + {0x626E0000u, 7u}, // bn -> Beng + {0x626F0000u, 80u}, // bo -> Tibt + {0xE1E10000u, 7u}, // bpy -> Beng + {0xA2010000u, 1u}, // bqi -> Arab + {0xD6010000u, 41u}, // bqv -> Latn + {0x62720000u, 41u}, // br -> Latn + {0x82210000u, 16u}, // bra -> Deva + {0x9E210000u, 1u}, // brh -> Arab + {0xDE210000u, 16u}, // brx -> Deva + {0x62730000u, 41u}, // bs -> Latn + {0xC2410000u, 6u}, // bsq -> Bass + {0xCA410000u, 41u}, // bss -> Latn + {0xBA610000u, 41u}, // bto -> Latn + {0xD6610000u, 16u}, // btv -> Deva + {0x82810000u, 15u}, // bua -> Cyrl + {0x8A810000u, 41u}, // buc -> Latn + {0x9A810000u, 41u}, // bug -> Latn + {0xB2810000u, 41u}, // bum -> Latn + {0x86A10000u, 41u}, // bvb -> Latn + {0xB7010000u, 18u}, // byn -> Ethi + {0xD7010000u, 41u}, // byv -> Latn + {0x93210000u, 41u}, // bze -> Latn + {0x63610000u, 41u}, // ca -> Latn + {0x9C420000u, 41u}, // cch -> Latn + {0xBC420000u, 7u}, // ccp -> Beng + {0x63650000u, 15u}, // ce -> Cyrl + {0x84820000u, 41u}, // ceb -> Latn + {0x98C20000u, 41u}, // cgg -> Latn + {0x63680000u, 41u}, // ch -> Latn + {0xA8E20000u, 41u}, // chk -> Latn + {0xB0E20000u, 15u}, // chm -> Cyrl + {0xB8E20000u, 41u}, // cho -> Latn + {0xBCE20000u, 41u}, // chp -> Latn + {0xC4E20000u, 12u}, // chr -> Cher + {0x81220000u, 1u}, // cja -> Arab + {0xB1220000u, 11u}, // cjm -> Cham + {0x85420000u, 1u}, // ckb -> Arab + {0x636F0000u, 41u}, // co -> Latn + {0xBDC20000u, 13u}, // cop -> Copt + {0xC9E20000u, 41u}, // cps -> Latn + {0x63720000u, 9u}, // cr -> Cans + {0xA6220000u, 9u}, // crj -> Cans + {0xAA220000u, 9u}, // crk -> Cans + {0xAE220000u, 9u}, // crl -> Cans + {0xB2220000u, 9u}, // crm -> Cans + {0xCA220000u, 41u}, // crs -> Latn + {0x63730000u, 41u}, // cs -> Latn + {0x86420000u, 41u}, // csb -> Latn + {0xDA420000u, 9u}, // csw -> Cans + {0x8E620000u, 59u}, // ctd -> Pauc + {0x63750000u, 15u}, // cu -> Cyrl + {0x63760000u, 15u}, // cv -> Cyrl + {0x63790000u, 41u}, // cy -> Latn + {0x64610000u, 41u}, // da -> Latn + {0xA8030000u, 41u}, // dak -> Latn + {0xC4030000u, 15u}, // dar -> Cyrl + {0xD4030000u, 41u}, // dav -> Latn + {0x88430000u, 1u}, // dcc -> Arab + {0x64650000u, 41u}, // de -> Latn + {0xB4830000u, 41u}, // den -> Latn + {0xC4C30000u, 41u}, // dgr -> Latn + {0x91230000u, 41u}, // dje -> Latn + {0xA5A30000u, 41u}, // dnj -> Latn + {0xA1C30000u, 1u}, // doi -> Arab + {0x86430000u, 41u}, // dsb -> Latn + {0xB2630000u, 41u}, // dtm -> Latn + {0xBE630000u, 41u}, // dtp -> Latn + {0x82830000u, 41u}, // dua -> Latn + {0x64760000u, 78u}, // dv -> Thaa + {0xBB030000u, 41u}, // dyo -> Latn + {0xD3030000u, 41u}, // dyu -> Latn + {0x647A0000u, 80u}, // dz -> Tibt + {0xD0240000u, 41u}, // ebu -> Latn + {0x65650000u, 41u}, // ee -> Latn + {0xA0A40000u, 41u}, // efi -> Latn + {0xACC40000u, 41u}, // egl -> Latn + {0xE0C40000u, 17u}, // egy -> Egyp + {0xE1440000u, 32u}, // eky -> Kali + {0x656C0000u, 21u}, // el -> Grek + {0x656E0000u, 41u}, // en -> Latn + {0x656E5841u, 86u}, // en-XA -> ~~~A + {0x656F0000u, 41u}, // eo -> Latn + {0x65730000u, 41u}, // es -> Latn + {0xD2440000u, 41u}, // esu -> Latn + {0x65740000u, 41u}, // et -> Latn + {0xCE640000u, 30u}, // ett -> Ital + {0x65750000u, 41u}, // eu -> Latn + {0xBAC40000u, 41u}, // ewo -> Latn + {0xCEE40000u, 41u}, // ext -> Latn + {0x66610000u, 1u}, // fa -> Arab + {0xB4050000u, 41u}, // fan -> Latn + {0x66660000u, 41u}, // ff -> Latn + {0xB0A50000u, 41u}, // ffm -> Latn + {0x66690000u, 41u}, // fi -> Latn + {0x81050000u, 1u}, // fia -> Arab + {0xAD050000u, 41u}, // fil -> Latn + {0xCD050000u, 41u}, // fit -> Latn + {0x666A0000u, 41u}, // fj -> Latn + {0x666F0000u, 41u}, // fo -> Latn + {0xB5C50000u, 41u}, // fon -> Latn + {0x66720000u, 41u}, // fr -> Latn + {0x8A250000u, 41u}, // frc -> Latn + {0xBE250000u, 41u}, // frp -> Latn + {0xC6250000u, 41u}, // frr -> Latn + {0xCA250000u, 41u}, // frs -> Latn + {0x8E850000u, 41u}, // fud -> Latn + {0xC2850000u, 41u}, // fuq -> Latn + {0xC6850000u, 41u}, // fur -> Latn + {0xD6850000u, 41u}, // fuv -> Latn + {0xC6A50000u, 41u}, // fvr -> Latn + {0x66790000u, 41u}, // fy -> Latn + {0x67610000u, 41u}, // ga -> Latn + {0x80060000u, 41u}, // gaa -> Latn + {0x98060000u, 41u}, // gag -> Latn + {0xB4060000u, 24u}, // gan -> Hans + {0xE0060000u, 41u}, // gay -> Latn + {0xB0260000u, 16u}, // gbm -> Deva + {0xE4260000u, 1u}, // gbz -> Arab + {0xC4460000u, 41u}, // gcr -> Latn + {0x67640000u, 41u}, // gd -> Latn + {0xE4860000u, 18u}, // gez -> Ethi + {0xB4C60000u, 16u}, // ggn -> Deva + {0xAD060000u, 41u}, // gil -> Latn + {0xA9260000u, 1u}, // gjk -> Arab + {0xD1260000u, 1u}, // gju -> Arab + {0x676C0000u, 41u}, // gl -> Latn + {0xA9660000u, 1u}, // glk -> Arab + {0x676E0000u, 41u}, // gn -> Latn + {0xB1C60000u, 16u}, // gom -> Deva + {0xB5C60000u, 76u}, // gon -> Telu + {0xC5C60000u, 41u}, // gor -> Latn + {0xC9C60000u, 41u}, // gos -> Latn + {0xCDC60000u, 20u}, // got -> Goth + {0x8A260000u, 14u}, // grc -> Cprt + {0xCE260000u, 7u}, // grt -> Beng + {0xDA460000u, 41u}, // gsw -> Latn + {0x67750000u, 22u}, // gu -> Gujr + {0x86860000u, 41u}, // gub -> Latn + {0x8A860000u, 41u}, // guc -> Latn + {0xC6860000u, 41u}, // gur -> Latn + {0xE6860000u, 41u}, // guz -> Latn + {0x67760000u, 41u}, // gv -> Latn + {0xC6A60000u, 16u}, // gvr -> Deva + {0xA2C60000u, 41u}, // gwi -> Latn + {0x68610000u, 41u}, // ha -> Latn + {0x6861434Du, 1u}, // ha-CM -> Arab + {0x68615344u, 1u}, // ha-SD -> Arab + {0xA8070000u, 24u}, // hak -> Hans + {0xD8070000u, 41u}, // haw -> Latn + {0xE4070000u, 1u}, // haz -> Arab + {0x68650000u, 27u}, // he -> Hebr + {0x68690000u, 16u}, // hi -> Deva + {0x95070000u, 41u}, // hif -> Latn + {0xAD070000u, 41u}, // hil -> Latn + {0xD1670000u, 28u}, // hlu -> Hluw + {0x8D870000u, 62u}, // hmd -> Plrd + {0x8DA70000u, 1u}, // hnd -> Arab + {0x91A70000u, 16u}, // hne -> Deva + {0xA5A70000u, 29u}, // hnj -> Hmng + {0xB5A70000u, 41u}, // hnn -> Latn + {0xB9A70000u, 1u}, // hno -> Arab + {0x686F0000u, 41u}, // ho -> Latn + {0x89C70000u, 16u}, // hoc -> Deva + {0xA5C70000u, 16u}, // hoj -> Deva + {0x68720000u, 41u}, // hr -> Latn + {0x86470000u, 41u}, // hsb -> Latn + {0xB6470000u, 24u}, // hsn -> Hans + {0x68740000u, 41u}, // ht -> Latn + {0x68750000u, 41u}, // hu -> Latn + {0x68790000u, 3u}, // hy -> Armn + {0x687A0000u, 41u}, // hz -> Latn + {0x69610000u, 41u}, // ia -> Latn + {0x80280000u, 41u}, // iba -> Latn + {0x84280000u, 41u}, // ibb -> Latn + {0x69640000u, 41u}, // id -> Latn + {0x69670000u, 41u}, // ig -> Latn + {0x69690000u, 85u}, // ii -> Yiii + {0x696B0000u, 41u}, // ik -> Latn + {0xCD480000u, 41u}, // ikt -> Latn + {0xB9680000u, 41u}, // ilo -> Latn + {0x696E0000u, 41u}, // in -> Latn + {0x9DA80000u, 15u}, // inh -> Cyrl + {0x69730000u, 41u}, // is -> Latn + {0x69740000u, 41u}, // it -> Latn + {0x69750000u, 9u}, // iu -> Cans + {0x69770000u, 27u}, // iw -> Hebr + {0x9F280000u, 41u}, // izh -> Latn + {0x6A610000u, 31u}, // ja -> Jpan + {0xB0090000u, 41u}, // jam -> Latn + {0xB8C90000u, 41u}, // jgo -> Latn + {0x6A690000u, 27u}, // ji -> Hebr + {0x89890000u, 41u}, // jmc -> Latn + {0xAD890000u, 16u}, // jml -> Deva + {0xCE890000u, 41u}, // jut -> Latn + {0x6A760000u, 41u}, // jv -> Latn + {0x6A770000u, 41u}, // jw -> Latn + {0x6B610000u, 19u}, // ka -> Geor + {0x800A0000u, 15u}, // kaa -> Cyrl + {0x840A0000u, 41u}, // kab -> Latn + {0x880A0000u, 41u}, // kac -> Latn + {0xA40A0000u, 41u}, // kaj -> Latn + {0xB00A0000u, 41u}, // kam -> Latn + {0xB80A0000u, 41u}, // kao -> Latn + {0x8C2A0000u, 15u}, // kbd -> Cyrl + {0x984A0000u, 41u}, // kcg -> Latn + {0xA84A0000u, 41u}, // kck -> Latn + {0x906A0000u, 41u}, // kde -> Latn + {0xCC6A0000u, 79u}, // kdt -> Thai + {0x808A0000u, 41u}, // kea -> Latn + {0xB48A0000u, 41u}, // ken -> Latn + {0xB8AA0000u, 41u}, // kfo -> Latn + {0xC4AA0000u, 16u}, // kfr -> Deva + {0xE0AA0000u, 16u}, // kfy -> Deva + {0x6B670000u, 41u}, // kg -> Latn + {0x90CA0000u, 41u}, // kge -> Latn + {0xBCCA0000u, 41u}, // kgp -> Latn + {0x80EA0000u, 41u}, // kha -> Latn + {0x84EA0000u, 73u}, // khb -> Talu + {0xB4EA0000u, 16u}, // khn -> Deva + {0xC0EA0000u, 41u}, // khq -> Latn + {0xCCEA0000u, 53u}, // kht -> Mymr + {0xD8EA0000u, 1u}, // khw -> Arab + {0x6B690000u, 41u}, // ki -> Latn + {0xD10A0000u, 41u}, // kiu -> Latn + {0x6B6A0000u, 41u}, // kj -> Latn + {0x992A0000u, 40u}, // kjg -> Laoo + {0x6B6B0000u, 15u}, // kk -> Cyrl + {0x6B6B4146u, 1u}, // kk-AF -> Arab + {0x6B6B434Eu, 1u}, // kk-CN -> Arab + {0x6B6B4952u, 1u}, // kk-IR -> Arab + {0x6B6B4D4Eu, 1u}, // kk-MN -> Arab + {0xA54A0000u, 41u}, // kkj -> Latn + {0x6B6C0000u, 41u}, // kl -> Latn + {0xB56A0000u, 41u}, // kln -> Latn + {0x6B6D0000u, 35u}, // km -> Khmr + {0x858A0000u, 41u}, // kmb -> Latn + {0x6B6E0000u, 36u}, // kn -> Knda + {0x6B6F0000u, 37u}, // ko -> Kore + {0xA1CA0000u, 15u}, // koi -> Cyrl + {0xA9CA0000u, 16u}, // kok -> Deva + {0xC9CA0000u, 41u}, // kos -> Latn + {0x91EA0000u, 41u}, // kpe -> Latn + {0x8A2A0000u, 15u}, // krc -> Cyrl + {0xA22A0000u, 41u}, // kri -> Latn + {0xA62A0000u, 41u}, // krj -> Latn + {0xAE2A0000u, 41u}, // krl -> Latn + {0xD22A0000u, 16u}, // kru -> Deva + {0x6B730000u, 1u}, // ks -> Arab + {0x864A0000u, 41u}, // ksb -> Latn + {0x964A0000u, 41u}, // ksf -> Latn + {0x9E4A0000u, 41u}, // ksh -> Latn + {0x6B750000u, 41u}, // ku -> Latn + {0x6B754952u, 1u}, // ku-IR -> Arab + {0x6B754C42u, 1u}, // ku-LB -> Arab + {0xB28A0000u, 15u}, // kum -> Cyrl + {0x6B760000u, 15u}, // kv -> Cyrl + {0xC6AA0000u, 41u}, // kvr -> Latn + {0xDEAA0000u, 1u}, // kvx -> Arab + {0x6B770000u, 41u}, // kw -> Latn + {0xB2EA0000u, 79u}, // kxm -> Thai + {0xBEEA0000u, 1u}, // kxp -> Arab + {0x6B790000u, 15u}, // ky -> Cyrl + {0x6B79434Eu, 1u}, // ky-CN -> Arab + {0x6B795452u, 41u}, // ky-TR -> Latn + {0x6C610000u, 41u}, // la -> Latn + {0x840B0000u, 43u}, // lab -> Lina + {0x8C0B0000u, 27u}, // lad -> Hebr + {0x980B0000u, 41u}, // lag -> Latn + {0x9C0B0000u, 1u}, // lah -> Arab + {0xA40B0000u, 41u}, // laj -> Latn + {0x6C620000u, 41u}, // lb -> Latn + {0x902B0000u, 15u}, // lbe -> Cyrl + {0xD82B0000u, 41u}, // lbw -> Latn + {0xBC4B0000u, 79u}, // lcp -> Thai + {0xBC8B0000u, 42u}, // lep -> Lepc + {0xE48B0000u, 15u}, // lez -> Cyrl + {0x6C670000u, 41u}, // lg -> Latn + {0x6C690000u, 41u}, // li -> Latn + {0x950B0000u, 16u}, // lif -> Deva + {0xA50B0000u, 41u}, // lij -> Latn + {0xC90B0000u, 44u}, // lis -> Lisu + {0xBD2B0000u, 41u}, // ljp -> Latn + {0xA14B0000u, 1u}, // lki -> Arab + {0xCD4B0000u, 41u}, // lkt -> Latn + {0xB58B0000u, 76u}, // lmn -> Telu + {0xB98B0000u, 41u}, // lmo -> Latn + {0x6C6E0000u, 41u}, // ln -> Latn + {0x6C6F0000u, 40u}, // lo -> Laoo + {0xADCB0000u, 41u}, // lol -> Latn + {0xE5CB0000u, 41u}, // loz -> Latn + {0x8A2B0000u, 1u}, // lrc -> Arab + {0x6C740000u, 41u}, // lt -> Latn + {0x9A6B0000u, 41u}, // ltg -> Latn + {0x6C750000u, 41u}, // lu -> Latn + {0x828B0000u, 41u}, // lua -> Latn + {0xBA8B0000u, 41u}, // luo -> Latn + {0xE28B0000u, 41u}, // luy -> Latn + {0xE68B0000u, 1u}, // luz -> Arab + {0x6C760000u, 41u}, // lv -> Latn + {0xAECB0000u, 79u}, // lwl -> Thai + {0x9F2B0000u, 24u}, // lzh -> Hans + {0xE72B0000u, 41u}, // lzz -> Latn + {0x8C0C0000u, 41u}, // mad -> Latn + {0x940C0000u, 41u}, // maf -> Latn + {0x980C0000u, 16u}, // mag -> Deva + {0xA00C0000u, 16u}, // mai -> Deva + {0xA80C0000u, 41u}, // mak -> Latn + {0xB40C0000u, 41u}, // man -> Latn + {0xB40C474Eu, 55u}, // man-GN -> Nkoo + {0xC80C0000u, 41u}, // mas -> Latn + {0xE40C0000u, 41u}, // maz -> Latn + {0x946C0000u, 15u}, // mdf -> Cyrl + {0x9C6C0000u, 41u}, // mdh -> Latn + {0xC46C0000u, 41u}, // mdr -> Latn + {0xB48C0000u, 41u}, // men -> Latn + {0xC48C0000u, 41u}, // mer -> Latn + {0x80AC0000u, 1u}, // mfa -> Arab + {0x90AC0000u, 41u}, // mfe -> Latn + {0x6D670000u, 41u}, // mg -> Latn + {0x9CCC0000u, 41u}, // mgh -> Latn + {0xB8CC0000u, 41u}, // mgo -> Latn + {0xBCCC0000u, 16u}, // mgp -> Deva + {0xE0CC0000u, 41u}, // mgy -> Latn + {0x6D680000u, 41u}, // mh -> Latn + {0x6D690000u, 41u}, // mi -> Latn + {0xB50C0000u, 41u}, // min -> Latn + {0xC90C0000u, 26u}, // mis -> Hatr + {0x6D6B0000u, 15u}, // mk -> Cyrl + {0x6D6C0000u, 50u}, // ml -> Mlym + {0xC96C0000u, 41u}, // mls -> Latn + {0x6D6E0000u, 15u}, // mn -> Cyrl + {0x6D6E434Eu, 51u}, // mn-CN -> Mong + {0xA1AC0000u, 7u}, // mni -> Beng + {0xD9AC0000u, 53u}, // mnw -> Mymr + {0x91CC0000u, 41u}, // moe -> Latn + {0x9DCC0000u, 41u}, // moh -> Latn + {0xC9CC0000u, 41u}, // mos -> Latn + {0x6D720000u, 16u}, // mr -> Deva + {0x8E2C0000u, 16u}, // mrd -> Deva + {0xA62C0000u, 15u}, // mrj -> Cyrl + {0xD22C0000u, 52u}, // mru -> Mroo + {0x6D730000u, 41u}, // ms -> Latn + {0x6D734343u, 1u}, // ms-CC -> Arab + {0x6D734944u, 1u}, // ms-ID -> Arab + {0x6D740000u, 41u}, // mt -> Latn + {0xC66C0000u, 16u}, // mtr -> Deva + {0x828C0000u, 41u}, // mua -> Latn + {0xCA8C0000u, 41u}, // mus -> Latn + {0xE2AC0000u, 1u}, // mvy -> Arab + {0xAACC0000u, 41u}, // mwk -> Latn + {0xC6CC0000u, 16u}, // mwr -> Deva + {0xD6CC0000u, 41u}, // mwv -> Latn + {0x8AEC0000u, 41u}, // mxc -> Latn + {0x6D790000u, 53u}, // my -> Mymr + {0xD70C0000u, 15u}, // myv -> Cyrl + {0xDF0C0000u, 41u}, // myx -> Latn + {0xE70C0000u, 47u}, // myz -> Mand + {0xB72C0000u, 1u}, // mzn -> Arab + {0x6E610000u, 41u}, // na -> Latn + {0xB40D0000u, 24u}, // nan -> Hans + {0xBC0D0000u, 41u}, // nap -> Latn + {0xC00D0000u, 41u}, // naq -> Latn + {0x6E620000u, 41u}, // nb -> Latn + {0x9C4D0000u, 41u}, // nch -> Latn + {0x6E640000u, 41u}, // nd -> Latn + {0x886D0000u, 41u}, // ndc -> Latn + {0xC86D0000u, 41u}, // nds -> Latn + {0x6E650000u, 16u}, // ne -> Deva + {0xD88D0000u, 16u}, // new -> Deva + {0x6E670000u, 41u}, // ng -> Latn + {0xACCD0000u, 41u}, // ngl -> Latn + {0x90ED0000u, 41u}, // nhe -> Latn + {0xD8ED0000u, 41u}, // nhw -> Latn + {0xA50D0000u, 41u}, // nij -> Latn + {0xD10D0000u, 41u}, // niu -> Latn + {0xB92D0000u, 41u}, // njo -> Latn + {0x6E6C0000u, 41u}, // nl -> Latn + {0x998D0000u, 41u}, // nmg -> Latn + {0x6E6E0000u, 41u}, // nn -> Latn + {0x9DAD0000u, 41u}, // nnh -> Latn + {0x6E6F0000u, 41u}, // no -> Latn + {0x8DCD0000u, 39u}, // nod -> Lana + {0x91CD0000u, 16u}, // noe -> Deva + {0xB5CD0000u, 64u}, // non -> Runr + {0xBA0D0000u, 55u}, // nqo -> Nkoo + {0x6E720000u, 41u}, // nr -> Latn + {0xAA4D0000u, 9u}, // nsk -> Cans + {0xBA4D0000u, 41u}, // nso -> Latn + {0xCA8D0000u, 41u}, // nus -> Latn + {0x6E760000u, 41u}, // nv -> Latn + {0xC2ED0000u, 41u}, // nxq -> Latn + {0x6E790000u, 41u}, // ny -> Latn + {0xB30D0000u, 41u}, // nym -> Latn + {0xB70D0000u, 41u}, // nyn -> Latn + {0xA32D0000u, 41u}, // nzi -> Latn + {0x6F630000u, 41u}, // oc -> Latn + {0x6F6D0000u, 41u}, // om -> Latn + {0x6F720000u, 58u}, // or -> Orya + {0x6F730000u, 15u}, // os -> Cyrl + {0xAA6E0000u, 57u}, // otk -> Orkh + {0x70610000u, 23u}, // pa -> Guru + {0x7061504Bu, 1u}, // pa-PK -> Arab + {0x980F0000u, 41u}, // pag -> Latn + {0xAC0F0000u, 60u}, // pal -> Phli + {0xB00F0000u, 41u}, // pam -> Latn + {0xBC0F0000u, 41u}, // pap -> Latn + {0xD00F0000u, 41u}, // pau -> Latn + {0x8C4F0000u, 41u}, // pcd -> Latn + {0xB04F0000u, 41u}, // pcm -> Latn + {0x886F0000u, 41u}, // pdc -> Latn + {0xCC6F0000u, 41u}, // pdt -> Latn + {0xB88F0000u, 83u}, // peo -> Xpeo + {0xACAF0000u, 41u}, // pfl -> Latn + {0xB4EF0000u, 61u}, // phn -> Phnx + {0x814F0000u, 8u}, // pka -> Brah + {0xB94F0000u, 41u}, // pko -> Latn + {0x706C0000u, 41u}, // pl -> Latn + {0xC98F0000u, 41u}, // pms -> Latn + {0xCDAF0000u, 21u}, // pnt -> Grek + {0xB5CF0000u, 41u}, // pon -> Latn + {0x822F0000u, 34u}, // pra -> Khar + {0x8E2F0000u, 1u}, // prd -> Arab + {0x9A2F0000u, 41u}, // prg -> Latn + {0x70730000u, 1u}, // ps -> Arab + {0x70740000u, 41u}, // pt -> Latn + {0xD28F0000u, 41u}, // puu -> Latn + {0x71750000u, 41u}, // qu -> Latn + {0x8A900000u, 41u}, // quc -> Latn + {0x9A900000u, 41u}, // qug -> Latn + {0xA4110000u, 16u}, // raj -> Deva + {0x94510000u, 41u}, // rcf -> Latn + {0xA4910000u, 41u}, // rej -> Latn + {0xB4D10000u, 41u}, // rgn -> Latn + {0x81110000u, 41u}, // ria -> Latn + {0x95110000u, 77u}, // rif -> Tfng + {0x95114E4Cu, 41u}, // rif-NL -> Latn + {0xC9310000u, 16u}, // rjs -> Deva + {0xCD510000u, 7u}, // rkt -> Beng + {0x726D0000u, 41u}, // rm -> Latn + {0x95910000u, 41u}, // rmf -> Latn + {0xB9910000u, 41u}, // rmo -> Latn + {0xCD910000u, 1u}, // rmt -> Arab + {0xD1910000u, 41u}, // rmu -> Latn + {0x726E0000u, 41u}, // rn -> Latn + {0x99B10000u, 41u}, // rng -> Latn + {0x726F0000u, 41u}, // ro -> Latn + {0x85D10000u, 41u}, // rob -> Latn + {0x95D10000u, 41u}, // rof -> Latn + {0xB2710000u, 41u}, // rtm -> Latn + {0x72750000u, 15u}, // ru -> Cyrl + {0x92910000u, 15u}, // rue -> Cyrl + {0x9A910000u, 41u}, // rug -> Latn + {0x72770000u, 41u}, // rw -> Latn + {0xAAD10000u, 41u}, // rwk -> Latn + {0xD3110000u, 33u}, // ryu -> Kana + {0x73610000u, 16u}, // sa -> Deva + {0x94120000u, 41u}, // saf -> Latn + {0x9C120000u, 15u}, // sah -> Cyrl + {0xC0120000u, 41u}, // saq -> Latn + {0xC8120000u, 41u}, // sas -> Latn + {0xCC120000u, 41u}, // sat -> Latn + {0xE4120000u, 67u}, // saz -> Saur + {0xBC320000u, 41u}, // sbp -> Latn + {0x73630000u, 41u}, // sc -> Latn + {0xA8520000u, 16u}, // sck -> Deva + {0xB4520000u, 41u}, // scn -> Latn + {0xB8520000u, 41u}, // sco -> Latn + {0xC8520000u, 41u}, // scs -> Latn + {0x73640000u, 1u}, // sd -> Arab + {0x88720000u, 41u}, // sdc -> Latn + {0x9C720000u, 1u}, // sdh -> Arab + {0x73650000u, 41u}, // se -> Latn + {0x94920000u, 41u}, // sef -> Latn + {0x9C920000u, 41u}, // seh -> Latn + {0xA0920000u, 41u}, // sei -> Latn + {0xC8920000u, 41u}, // ses -> Latn + {0x73670000u, 41u}, // sg -> Latn + {0x80D20000u, 56u}, // sga -> Ogam + {0xC8D20000u, 41u}, // sgs -> Latn + {0x73680000u, 41u}, // sh -> Latn + {0xA0F20000u, 77u}, // shi -> Tfng + {0xB4F20000u, 53u}, // shn -> Mymr + {0x73690000u, 69u}, // si -> Sinh + {0x8D120000u, 41u}, // sid -> Latn + {0x736B0000u, 41u}, // sk -> Latn + {0xC5520000u, 1u}, // skr -> Arab + {0x736C0000u, 41u}, // sl -> Latn + {0xA1720000u, 41u}, // sli -> Latn + {0xE1720000u, 41u}, // sly -> Latn + {0x736D0000u, 41u}, // sm -> Latn + {0x81920000u, 41u}, // sma -> Latn + {0xA5920000u, 41u}, // smj -> Latn + {0xB5920000u, 41u}, // smn -> Latn + {0xBD920000u, 65u}, // smp -> Samr + {0xC9920000u, 41u}, // sms -> Latn + {0x736E0000u, 41u}, // sn -> Latn + {0xA9B20000u, 41u}, // snk -> Latn + {0x736F0000u, 41u}, // so -> Latn + {0xD1D20000u, 79u}, // sou -> Thai + {0x73710000u, 41u}, // sq -> Latn + {0x73720000u, 15u}, // sr -> Cyrl + {0x73724D45u, 41u}, // sr-ME -> Latn + {0x7372524Fu, 41u}, // sr-RO -> Latn + {0x73725255u, 41u}, // sr-RU -> Latn + {0x73725452u, 41u}, // sr-TR -> Latn + {0x86320000u, 70u}, // srb -> Sora + {0xB6320000u, 41u}, // srn -> Latn + {0xC6320000u, 41u}, // srr -> Latn + {0xDE320000u, 16u}, // srx -> Deva + {0x73730000u, 41u}, // ss -> Latn + {0xE2520000u, 41u}, // ssy -> Latn + {0x73740000u, 41u}, // st -> Latn + {0xC2720000u, 41u}, // stq -> Latn + {0x73750000u, 41u}, // su -> Latn + {0xAA920000u, 41u}, // suk -> Latn + {0xCA920000u, 41u}, // sus -> Latn + {0x73760000u, 41u}, // sv -> Latn + {0x73770000u, 41u}, // sw -> Latn + {0x86D20000u, 1u}, // swb -> Arab + {0x8AD20000u, 41u}, // swc -> Latn + {0x9AD20000u, 41u}, // swg -> Latn + {0xD6D20000u, 16u}, // swv -> Deva + {0xB6F20000u, 41u}, // sxn -> Latn + {0xAF120000u, 7u}, // syl -> Beng + {0xC7120000u, 71u}, // syr -> Syrc + {0xAF320000u, 41u}, // szl -> Latn + {0x74610000u, 74u}, // ta -> Taml + {0xA4130000u, 16u}, // taj -> Deva + {0xD8330000u, 41u}, // tbw -> Latn + {0xE0530000u, 36u}, // tcy -> Knda + {0x8C730000u, 72u}, // tdd -> Tale + {0x98730000u, 16u}, // tdg -> Deva + {0x9C730000u, 16u}, // tdh -> Deva + {0x74650000u, 76u}, // te -> Telu + {0xB0930000u, 41u}, // tem -> Latn + {0xB8930000u, 41u}, // teo -> Latn + {0xCC930000u, 41u}, // tet -> Latn + {0x74670000u, 15u}, // tg -> Cyrl + {0x7467504Bu, 1u}, // tg-PK -> Arab + {0x74680000u, 79u}, // th -> Thai + {0xACF30000u, 16u}, // thl -> Deva + {0xC0F30000u, 16u}, // thq -> Deva + {0xC4F30000u, 16u}, // thr -> Deva + {0x74690000u, 18u}, // ti -> Ethi + {0x99130000u, 18u}, // tig -> Ethi + {0xD5130000u, 41u}, // tiv -> Latn + {0x746B0000u, 41u}, // tk -> Latn + {0xAD530000u, 41u}, // tkl -> Latn + {0xC5530000u, 41u}, // tkr -> Latn + {0xCD530000u, 16u}, // tkt -> Deva + {0x746C0000u, 41u}, // tl -> Latn + {0xE1730000u, 41u}, // tly -> Latn + {0x9D930000u, 41u}, // tmh -> Latn + {0x746E0000u, 41u}, // tn -> Latn + {0x746F0000u, 41u}, // to -> Latn + {0x99D30000u, 41u}, // tog -> Latn + {0xA1F30000u, 41u}, // tpi -> Latn + {0x74720000u, 41u}, // tr -> Latn + {0xD2330000u, 41u}, // tru -> Latn + {0xD6330000u, 41u}, // trv -> Latn + {0x74730000u, 41u}, // ts -> Latn + {0x8E530000u, 21u}, // tsd -> Grek + {0x96530000u, 16u}, // tsf -> Deva + {0x9A530000u, 41u}, // tsg -> Latn + {0xA6530000u, 80u}, // tsj -> Tibt + {0x74740000u, 15u}, // tt -> Cyrl + {0xA6730000u, 41u}, // ttj -> Latn + {0xCA730000u, 79u}, // tts -> Thai + {0xCE730000u, 41u}, // ttt -> Latn + {0xB2930000u, 41u}, // tum -> Latn + {0xAEB30000u, 41u}, // tvl -> Latn + {0xC2D30000u, 41u}, // twq -> Latn + {0x74790000u, 41u}, // ty -> Latn + {0xD7130000u, 15u}, // tyv -> Cyrl + {0xB3330000u, 41u}, // tzm -> Latn + {0xB0740000u, 15u}, // udm -> Cyrl + {0x75670000u, 1u}, // ug -> Arab + {0x75674B5Au, 15u}, // ug-KZ -> Cyrl + {0x75674D4Eu, 15u}, // ug-MN -> Cyrl + {0x80D40000u, 81u}, // uga -> Ugar + {0x756B0000u, 15u}, // uk -> Cyrl + {0xA1740000u, 41u}, // uli -> Latn + {0x85940000u, 41u}, // umb -> Latn + {0xC5B40000u, 7u}, // unr -> Beng + {0xC5B44E50u, 16u}, // unr-NP -> Deva + {0xDDB40000u, 7u}, // unx -> Beng + {0x75720000u, 1u}, // ur -> Arab + {0x757A0000u, 41u}, // uz -> Latn + {0x757A4146u, 1u}, // uz-AF -> Arab + {0x757A434Eu, 15u}, // uz-CN -> Cyrl + {0xA0150000u, 82u}, // vai -> Vaii + {0x76650000u, 41u}, // ve -> Latn + {0x88950000u, 41u}, // vec -> Latn + {0xBC950000u, 41u}, // vep -> Latn + {0x76690000u, 41u}, // vi -> Latn + {0x89150000u, 41u}, // vic -> Latn + {0xC9750000u, 41u}, // vls -> Latn + {0x95950000u, 41u}, // vmf -> Latn + {0xD9950000u, 41u}, // vmw -> Latn + {0x766F0000u, 41u}, // vo -> Latn + {0xCDD50000u, 41u}, // vot -> Latn + {0xBA350000u, 41u}, // vro -> Latn + {0xB6950000u, 41u}, // vun -> Latn + {0x77610000u, 41u}, // wa -> Latn + {0x90160000u, 41u}, // wae -> Latn + {0xAC160000u, 18u}, // wal -> Ethi + {0xC4160000u, 41u}, // war -> Latn + {0xBC360000u, 41u}, // wbp -> Latn + {0xC0360000u, 76u}, // wbq -> Telu + {0xC4360000u, 16u}, // wbr -> Deva + {0xC9760000u, 41u}, // wls -> Latn + {0xA1B60000u, 1u}, // wni -> Arab + {0x776F0000u, 41u}, // wo -> Latn + {0xB2760000u, 16u}, // wtm -> Deva + {0xD2960000u, 24u}, // wuu -> Hans + {0xD4170000u, 41u}, // xav -> Latn + {0xC4570000u, 10u}, // xcr -> Cari + {0x78680000u, 41u}, // xh -> Latn + {0x89770000u, 45u}, // xlc -> Lyci + {0x8D770000u, 46u}, // xld -> Lydi + {0x95970000u, 19u}, // xmf -> Geor + {0xB5970000u, 48u}, // xmn -> Mani + {0xC5970000u, 49u}, // xmr -> Merc + {0x81B70000u, 54u}, // xna -> Narb + {0xC5B70000u, 16u}, // xnr -> Deva + {0x99D70000u, 41u}, // xog -> Latn + {0xC5F70000u, 63u}, // xpr -> Prti + {0x82570000u, 66u}, // xsa -> Sarb + {0xC6570000u, 16u}, // xsr -> Deva + {0xB8180000u, 41u}, // yao -> Latn + {0xBC180000u, 41u}, // yap -> Latn + {0xD4180000u, 41u}, // yav -> Latn + {0x84380000u, 41u}, // ybb -> Latn + {0x79690000u, 27u}, // yi -> Hebr + {0x796F0000u, 41u}, // yo -> Latn + {0xAE380000u, 41u}, // yrl -> Latn + {0x82980000u, 41u}, // yua -> Latn + {0x7A610000u, 41u}, // za -> Latn + {0x98190000u, 41u}, // zag -> Latn + {0xA4790000u, 1u}, // zdj -> Arab + {0x80990000u, 41u}, // zea -> Latn + {0x9CD90000u, 77u}, // zgh -> Tfng + {0x7A680000u, 24u}, // zh -> Hans + {0x7A684155u, 25u}, // zh-AU -> Hant + {0x7A68424Eu, 25u}, // zh-BN -> Hant + {0x7A684742u, 25u}, // zh-GB -> Hant + {0x7A684746u, 25u}, // zh-GF -> Hant + {0x7A68484Bu, 25u}, // zh-HK -> Hant + {0x7A684944u, 25u}, // zh-ID -> Hant + {0x7A684D4Fu, 25u}, // zh-MO -> Hant + {0x7A684D59u, 25u}, // zh-MY -> Hant + {0x7A685041u, 25u}, // zh-PA -> Hant + {0x7A685046u, 25u}, // zh-PF -> Hant + {0x7A685048u, 25u}, // zh-PH -> Hant + {0x7A685352u, 25u}, // zh-SR -> Hant + {0x7A685448u, 25u}, // zh-TH -> Hant + {0x7A685457u, 25u}, // zh-TW -> Hant + {0x7A685553u, 25u}, // zh-US -> Hant + {0x7A68564Eu, 25u}, // zh-VN -> Hant + {0xA1990000u, 41u}, // zmi -> Latn + {0x7A750000u, 41u}, // zu -> Latn + {0x83390000u, 41u}, // zza -> Latn +}); + +std::unordered_set<uint64_t> REPRESENTATIVE_LOCALES({ + 0x616145544C61746Ellu, // aa_Latn_ET + 0x616247454379726Cllu, // ab_Cyrl_GE + 0xC42047484C61746Ellu, // abr_Latn_GH + 0x904049444C61746Ellu, // ace_Latn_ID + 0x9C4055474C61746Ellu, // ach_Latn_UG + 0x806047484C61746Ellu, // ada_Latn_GH + 0xE06052554379726Cllu, // ady_Cyrl_RU + 0x6165495241767374llu, // ae_Avst_IR + 0x8480544E41726162llu, // aeb_Arab_TN + 0x61665A414C61746Ellu, // af_Latn_ZA + 0xC0C0434D4C61746Ellu, // agq_Latn_CM + 0xB8E0494E41686F6Dllu, // aho_Ahom_IN + 0x616B47484C61746Ellu, // ak_Latn_GH + 0xA940495158737578llu, // akk_Xsux_IQ + 0xB560584B4C61746Ellu, // aln_Latn_XK + 0xCD6052554379726Cllu, // alt_Cyrl_RU + 0x616D455445746869llu, // am_Ethi_ET + 0xB9804E474C61746Ellu, // amo_Latn_NG + 0xE5C049444C61746Ellu, // aoz_Latn_ID + 0x6172454741726162llu, // ar_Arab_EG + 0x8A20495241726D69llu, // arc_Armi_IR + 0x8A204A4F4E626174llu, // arc_Nbat_JO + 0x8A20535950616C6Dllu, // arc_Palm_SY + 0xB620434C4C61746Ellu, // arn_Latn_CL + 0xBA20424F4C61746Ellu, // aro_Latn_BO + 0xC220445A41726162llu, // arq_Arab_DZ + 0xE2204D4141726162llu, // ary_Arab_MA + 0xE620454741726162llu, // arz_Arab_EG + 0x6173494E42656E67llu, // as_Beng_IN + 0x8240545A4C61746Ellu, // asa_Latn_TZ + 0x9240555353676E77llu, // ase_Sgnw_US + 0xCE4045534C61746Ellu, // ast_Latn_ES + 0xA66043414C61746Ellu, // atj_Latn_CA + 0x617652554379726Cllu, // av_Cyrl_RU + 0x82C0494E44657661llu, // awa_Deva_IN + 0x6179424F4C61746Ellu, // ay_Latn_BO + 0x617A495241726162llu, // az_Arab_IR + 0x617A415A4C61746Ellu, // az_Latn_AZ + 0x626152554379726Cllu, // ba_Cyrl_RU + 0xAC01504B41726162llu, // bal_Arab_PK + 0xB40149444C61746Ellu, // ban_Latn_ID + 0xBC014E5044657661llu, // bap_Deva_NP + 0xC40141544C61746Ellu, // bar_Latn_AT + 0xC801434D4C61746Ellu, // bas_Latn_CM + 0xDC01434D42616D75llu, // bax_Bamu_CM + 0x882149444C61746Ellu, // bbc_Latn_ID + 0xA421434D4C61746Ellu, // bbj_Latn_CM + 0xA04143494C61746Ellu, // bci_Latn_CI + 0x626542594379726Cllu, // be_Cyrl_BY + 0xA481534441726162llu, // bej_Arab_SD + 0xB0815A4D4C61746Ellu, // bem_Latn_ZM + 0xD88149444C61746Ellu, // bew_Latn_ID + 0xE481545A4C61746Ellu, // bez_Latn_TZ + 0x8CA1434D4C61746Ellu, // bfd_Latn_CM + 0xC0A1494E54616D6Cllu, // bfq_Taml_IN + 0xCCA1504B41726162llu, // bft_Arab_PK + 0xE0A1494E44657661llu, // bfy_Deva_IN + 0x626742474379726Cllu, // bg_Cyrl_BG + 0x88C1494E44657661llu, // bgc_Deva_IN + 0xB4C1504B41726162llu, // bgn_Arab_PK + 0xDCC154524772656Bllu, // bgx_Grek_TR + 0x6268494E4B746869llu, // bh_Kthi_IN + 0x84E1494E44657661llu, // bhb_Deva_IN + 0xA0E1494E44657661llu, // bhi_Deva_IN + 0xA8E150484C61746Ellu, // bhk_Latn_PH + 0xB8E1494E44657661llu, // bho_Deva_IN + 0x626956554C61746Ellu, // bi_Latn_VU + 0xA90150484C61746Ellu, // bik_Latn_PH + 0xB5014E474C61746Ellu, // bin_Latn_NG + 0xA521494E44657661llu, // bjj_Deva_IN + 0xB52149444C61746Ellu, // bjn_Latn_ID + 0xB141434D4C61746Ellu, // bkm_Latn_CM + 0xD14150484C61746Ellu, // bku_Latn_PH + 0xCD61564E54617674llu, // blt_Tavt_VN + 0x626D4D4C4C61746Ellu, // bm_Latn_ML + 0xC1814D4C4C61746Ellu, // bmq_Latn_ML + 0x626E424442656E67llu, // bn_Beng_BD + 0x626F434E54696274llu, // bo_Tibt_CN + 0xE1E1494E42656E67llu, // bpy_Beng_IN + 0xA201495241726162llu, // bqi_Arab_IR + 0xD60143494C61746Ellu, // bqv_Latn_CI + 0x627246524C61746Ellu, // br_Latn_FR + 0x8221494E44657661llu, // bra_Deva_IN + 0x9E21504B41726162llu, // brh_Arab_PK + 0xDE21494E44657661llu, // brx_Deva_IN + 0x627342414C61746Ellu, // bs_Latn_BA + 0xC2414C5242617373llu, // bsq_Bass_LR + 0xCA41434D4C61746Ellu, // bss_Latn_CM + 0xBA6150484C61746Ellu, // bto_Latn_PH + 0xD661504B44657661llu, // btv_Deva_PK + 0x828152554379726Cllu, // bua_Cyrl_RU + 0x8A8159544C61746Ellu, // buc_Latn_YT + 0x9A8149444C61746Ellu, // bug_Latn_ID + 0xB281434D4C61746Ellu, // bum_Latn_CM + 0x86A147514C61746Ellu, // bvb_Latn_GQ + 0xB701455245746869llu, // byn_Ethi_ER + 0xD701434D4C61746Ellu, // byv_Latn_CM + 0x93214D4C4C61746Ellu, // bze_Latn_ML + 0x636145534C61746Ellu, // ca_Latn_ES + 0x9C424E474C61746Ellu, // cch_Latn_NG + 0xBC42494E42656E67llu, // ccp_Beng_IN + 0xBC42424443616B6Dllu, // ccp_Cakm_BD + 0x636552554379726Cllu, // ce_Cyrl_RU + 0x848250484C61746Ellu, // ceb_Latn_PH + 0x98C255474C61746Ellu, // cgg_Latn_UG + 0x636847554C61746Ellu, // ch_Latn_GU + 0xA8E2464D4C61746Ellu, // chk_Latn_FM + 0xB0E252554379726Cllu, // chm_Cyrl_RU + 0xB8E255534C61746Ellu, // cho_Latn_US + 0xBCE243414C61746Ellu, // chp_Latn_CA + 0xC4E2555343686572llu, // chr_Cher_US + 0x81224B4841726162llu, // cja_Arab_KH + 0xB122564E4368616Dllu, // cjm_Cham_VN + 0x8542495141726162llu, // ckb_Arab_IQ + 0x636F46524C61746Ellu, // co_Latn_FR + 0xBDC24547436F7074llu, // cop_Copt_EG + 0xC9E250484C61746Ellu, // cps_Latn_PH + 0x6372434143616E73llu, // cr_Cans_CA + 0xA622434143616E73llu, // crj_Cans_CA + 0xAA22434143616E73llu, // crk_Cans_CA + 0xAE22434143616E73llu, // crl_Cans_CA + 0xB222434143616E73llu, // crm_Cans_CA + 0xCA2253434C61746Ellu, // crs_Latn_SC + 0x6373435A4C61746Ellu, // cs_Latn_CZ + 0x8642504C4C61746Ellu, // csb_Latn_PL + 0xDA42434143616E73llu, // csw_Cans_CA + 0x8E624D4D50617563llu, // ctd_Pauc_MM + 0x637552554379726Cllu, // cu_Cyrl_RU + 0x63754247476C6167llu, // cu_Glag_BG + 0x637652554379726Cllu, // cv_Cyrl_RU + 0x637947424C61746Ellu, // cy_Latn_GB + 0x6461444B4C61746Ellu, // da_Latn_DK + 0xA80355534C61746Ellu, // dak_Latn_US + 0xC40352554379726Cllu, // dar_Cyrl_RU + 0xD4034B454C61746Ellu, // dav_Latn_KE + 0x8843494E41726162llu, // dcc_Arab_IN + 0x646544454C61746Ellu, // de_Latn_DE + 0xB48343414C61746Ellu, // den_Latn_CA + 0xC4C343414C61746Ellu, // dgr_Latn_CA + 0x91234E454C61746Ellu, // dje_Latn_NE + 0xA5A343494C61746Ellu, // dnj_Latn_CI + 0xA1C3494E41726162llu, // doi_Arab_IN + 0x864344454C61746Ellu, // dsb_Latn_DE + 0xB2634D4C4C61746Ellu, // dtm_Latn_ML + 0xBE634D594C61746Ellu, // dtp_Latn_MY + 0x8283434D4C61746Ellu, // dua_Latn_CM + 0x64764D5654686161llu, // dv_Thaa_MV + 0xBB03534E4C61746Ellu, // dyo_Latn_SN + 0xD30342464C61746Ellu, // dyu_Latn_BF + 0x647A425454696274llu, // dz_Tibt_BT + 0xD0244B454C61746Ellu, // ebu_Latn_KE + 0x656547484C61746Ellu, // ee_Latn_GH + 0xA0A44E474C61746Ellu, // efi_Latn_NG + 0xACC449544C61746Ellu, // egl_Latn_IT + 0xE0C4454745677970llu, // egy_Egyp_EG + 0xE1444D4D4B616C69llu, // eky_Kali_MM + 0x656C47524772656Bllu, // el_Grek_GR + 0x656E47424C61746Ellu, // en_Latn_GB + 0x656E55534C61746Ellu, // en_Latn_US + 0x656E474253686177llu, // en_Shaw_GB + 0x657345534C61746Ellu, // es_Latn_ES + 0x65734D584C61746Ellu, // es_Latn_MX + 0x657355534C61746Ellu, // es_Latn_US + 0xD24455534C61746Ellu, // esu_Latn_US + 0x657445454C61746Ellu, // et_Latn_EE + 0xCE6449544974616Cllu, // ett_Ital_IT + 0x657545534C61746Ellu, // eu_Latn_ES + 0xBAC4434D4C61746Ellu, // ewo_Latn_CM + 0xCEE445534C61746Ellu, // ext_Latn_ES + 0x6661495241726162llu, // fa_Arab_IR + 0xB40547514C61746Ellu, // fan_Latn_GQ + 0x6666534E4C61746Ellu, // ff_Latn_SN + 0xB0A54D4C4C61746Ellu, // ffm_Latn_ML + 0x666946494C61746Ellu, // fi_Latn_FI + 0x8105534441726162llu, // fia_Arab_SD + 0xAD0550484C61746Ellu, // fil_Latn_PH + 0xCD0553454C61746Ellu, // fit_Latn_SE + 0x666A464A4C61746Ellu, // fj_Latn_FJ + 0x666F464F4C61746Ellu, // fo_Latn_FO + 0xB5C5424A4C61746Ellu, // fon_Latn_BJ + 0x667246524C61746Ellu, // fr_Latn_FR + 0x8A2555534C61746Ellu, // frc_Latn_US + 0xBE2546524C61746Ellu, // frp_Latn_FR + 0xC62544454C61746Ellu, // frr_Latn_DE + 0xCA2544454C61746Ellu, // frs_Latn_DE + 0x8E8557464C61746Ellu, // fud_Latn_WF + 0xC2854E454C61746Ellu, // fuq_Latn_NE + 0xC68549544C61746Ellu, // fur_Latn_IT + 0xD6854E474C61746Ellu, // fuv_Latn_NG + 0xC6A553444C61746Ellu, // fvr_Latn_SD + 0x66794E4C4C61746Ellu, // fy_Latn_NL + 0x676149454C61746Ellu, // ga_Latn_IE + 0x800647484C61746Ellu, // gaa_Latn_GH + 0x98064D444C61746Ellu, // gag_Latn_MD + 0xB406434E48616E73llu, // gan_Hans_CN + 0xE00649444C61746Ellu, // gay_Latn_ID + 0xB026494E44657661llu, // gbm_Deva_IN + 0xE426495241726162llu, // gbz_Arab_IR + 0xC44647464C61746Ellu, // gcr_Latn_GF + 0x676447424C61746Ellu, // gd_Latn_GB + 0xE486455445746869llu, // gez_Ethi_ET + 0xB4C64E5044657661llu, // ggn_Deva_NP + 0xAD064B494C61746Ellu, // gil_Latn_KI + 0xA926504B41726162llu, // gjk_Arab_PK + 0xD126504B41726162llu, // gju_Arab_PK + 0x676C45534C61746Ellu, // gl_Latn_ES + 0xA966495241726162llu, // glk_Arab_IR + 0x676E50594C61746Ellu, // gn_Latn_PY + 0xB1C6494E44657661llu, // gom_Deva_IN + 0xB5C6494E54656C75llu, // gon_Telu_IN + 0xC5C649444C61746Ellu, // gor_Latn_ID + 0xC9C64E4C4C61746Ellu, // gos_Latn_NL + 0xCDC65541476F7468llu, // got_Goth_UA + 0x8A26435943707274llu, // grc_Cprt_CY + 0x8A2647524C696E62llu, // grc_Linb_GR + 0xCE26494E42656E67llu, // grt_Beng_IN + 0xDA4643484C61746Ellu, // gsw_Latn_CH + 0x6775494E47756A72llu, // gu_Gujr_IN + 0x868642524C61746Ellu, // gub_Latn_BR + 0x8A86434F4C61746Ellu, // guc_Latn_CO + 0xC68647484C61746Ellu, // gur_Latn_GH + 0xE6864B454C61746Ellu, // guz_Latn_KE + 0x6776494D4C61746Ellu, // gv_Latn_IM + 0xC6A64E5044657661llu, // gvr_Deva_NP + 0xA2C643414C61746Ellu, // gwi_Latn_CA + 0x68614E474C61746Ellu, // ha_Latn_NG + 0xA807434E48616E73llu, // hak_Hans_CN + 0xD80755534C61746Ellu, // haw_Latn_US + 0xE407414641726162llu, // haz_Arab_AF + 0x6865494C48656272llu, // he_Hebr_IL + 0x6869494E44657661llu, // hi_Deva_IN + 0x9507464A4C61746Ellu, // hif_Latn_FJ + 0xAD0750484C61746Ellu, // hil_Latn_PH + 0xD1675452486C7577llu, // hlu_Hluw_TR + 0x8D87434E506C7264llu, // hmd_Plrd_CN + 0x8DA7504B41726162llu, // hnd_Arab_PK + 0x91A7494E44657661llu, // hne_Deva_IN + 0xA5A74C41486D6E67llu, // hnj_Hmng_LA + 0xB5A750484C61746Ellu, // hnn_Latn_PH + 0xB9A7504B41726162llu, // hno_Arab_PK + 0x686F50474C61746Ellu, // ho_Latn_PG + 0x89C7494E44657661llu, // hoc_Deva_IN + 0xA5C7494E44657661llu, // hoj_Deva_IN + 0x687248524C61746Ellu, // hr_Latn_HR + 0x864744454C61746Ellu, // hsb_Latn_DE + 0xB647434E48616E73llu, // hsn_Hans_CN + 0x687448544C61746Ellu, // ht_Latn_HT + 0x687548554C61746Ellu, // hu_Latn_HU + 0x6879414D41726D6Ellu, // hy_Armn_AM + 0x687A4E414C61746Ellu, // hz_Latn_NA + 0x696146524C61746Ellu, // ia_Latn_FR + 0x80284D594C61746Ellu, // iba_Latn_MY + 0x84284E474C61746Ellu, // ibb_Latn_NG + 0x696449444C61746Ellu, // id_Latn_ID + 0x69674E474C61746Ellu, // ig_Latn_NG + 0x6969434E59696969llu, // ii_Yiii_CN + 0x696B55534C61746Ellu, // ik_Latn_US + 0xCD4843414C61746Ellu, // ikt_Latn_CA + 0xB96850484C61746Ellu, // ilo_Latn_PH + 0x696E49444C61746Ellu, // in_Latn_ID + 0x9DA852554379726Cllu, // inh_Cyrl_RU + 0x697349534C61746Ellu, // is_Latn_IS + 0x697449544C61746Ellu, // it_Latn_IT + 0x6975434143616E73llu, // iu_Cans_CA + 0x6977494C48656272llu, // iw_Hebr_IL + 0x9F2852554C61746Ellu, // izh_Latn_RU + 0x6A614A504A70616Ellu, // ja_Jpan_JP + 0xB0094A4D4C61746Ellu, // jam_Latn_JM + 0xB8C9434D4C61746Ellu, // jgo_Latn_CM + 0x6A69554148656272llu, // ji_Hebr_UA + 0x8989545A4C61746Ellu, // jmc_Latn_TZ + 0xAD894E5044657661llu, // jml_Deva_NP + 0xCE89444B4C61746Ellu, // jut_Latn_DK + 0x6A7649444C61746Ellu, // jv_Latn_ID + 0x6A7749444C61746Ellu, // jw_Latn_ID + 0x6B61474547656F72llu, // ka_Geor_GE + 0x800A555A4379726Cllu, // kaa_Cyrl_UZ + 0x840A445A4C61746Ellu, // kab_Latn_DZ + 0x880A4D4D4C61746Ellu, // kac_Latn_MM + 0xA40A4E474C61746Ellu, // kaj_Latn_NG + 0xB00A4B454C61746Ellu, // kam_Latn_KE + 0xB80A4D4C4C61746Ellu, // kao_Latn_ML + 0x8C2A52554379726Cllu, // kbd_Cyrl_RU + 0x984A4E474C61746Ellu, // kcg_Latn_NG + 0xA84A5A574C61746Ellu, // kck_Latn_ZW + 0x906A545A4C61746Ellu, // kde_Latn_TZ + 0xCC6A544854686169llu, // kdt_Thai_TH + 0x808A43564C61746Ellu, // kea_Latn_CV + 0xB48A434D4C61746Ellu, // ken_Latn_CM + 0xB8AA43494C61746Ellu, // kfo_Latn_CI + 0xC4AA494E44657661llu, // kfr_Deva_IN + 0xE0AA494E44657661llu, // kfy_Deva_IN + 0x6B6743444C61746Ellu, // kg_Latn_CD + 0x90CA49444C61746Ellu, // kge_Latn_ID + 0xBCCA42524C61746Ellu, // kgp_Latn_BR + 0x80EA494E4C61746Ellu, // kha_Latn_IN + 0x84EA434E54616C75llu, // khb_Talu_CN + 0xB4EA494E44657661llu, // khn_Deva_IN + 0xC0EA4D4C4C61746Ellu, // khq_Latn_ML + 0xCCEA494E4D796D72llu, // kht_Mymr_IN + 0xD8EA504B41726162llu, // khw_Arab_PK + 0x6B694B454C61746Ellu, // ki_Latn_KE + 0xD10A54524C61746Ellu, // kiu_Latn_TR + 0x6B6A4E414C61746Ellu, // kj_Latn_NA + 0x992A4C414C616F6Fllu, // kjg_Laoo_LA + 0x6B6B434E41726162llu, // kk_Arab_CN + 0x6B6B4B5A4379726Cllu, // kk_Cyrl_KZ + 0xA54A434D4C61746Ellu, // kkj_Latn_CM + 0x6B6C474C4C61746Ellu, // kl_Latn_GL + 0xB56A4B454C61746Ellu, // kln_Latn_KE + 0x6B6D4B484B686D72llu, // km_Khmr_KH + 0x858A414F4C61746Ellu, // kmb_Latn_AO + 0x6B6E494E4B6E6461llu, // kn_Knda_IN + 0x6B6F4B524B6F7265llu, // ko_Kore_KR + 0xA1CA52554379726Cllu, // koi_Cyrl_RU + 0xA9CA494E44657661llu, // kok_Deva_IN + 0xC9CA464D4C61746Ellu, // kos_Latn_FM + 0x91EA4C524C61746Ellu, // kpe_Latn_LR + 0x8A2A52554379726Cllu, // krc_Cyrl_RU + 0xA22A534C4C61746Ellu, // kri_Latn_SL + 0xA62A50484C61746Ellu, // krj_Latn_PH + 0xAE2A52554C61746Ellu, // krl_Latn_RU + 0xD22A494E44657661llu, // kru_Deva_IN + 0x6B73494E41726162llu, // ks_Arab_IN + 0x864A545A4C61746Ellu, // ksb_Latn_TZ + 0x964A434D4C61746Ellu, // ksf_Latn_CM + 0x9E4A44454C61746Ellu, // ksh_Latn_DE + 0x6B75495141726162llu, // ku_Arab_IQ + 0x6B7554524C61746Ellu, // ku_Latn_TR + 0xB28A52554379726Cllu, // kum_Cyrl_RU + 0x6B7652554379726Cllu, // kv_Cyrl_RU + 0xC6AA49444C61746Ellu, // kvr_Latn_ID + 0xDEAA504B41726162llu, // kvx_Arab_PK + 0x6B7747424C61746Ellu, // kw_Latn_GB + 0xB2EA544854686169llu, // kxm_Thai_TH + 0xBEEA504B41726162llu, // kxp_Arab_PK + 0x6B79434E41726162llu, // ky_Arab_CN + 0x6B794B474379726Cllu, // ky_Cyrl_KG + 0x6B7954524C61746Ellu, // ky_Latn_TR + 0x6C6156414C61746Ellu, // la_Latn_VA + 0x840B47524C696E61llu, // lab_Lina_GR + 0x8C0B494C48656272llu, // lad_Hebr_IL + 0x980B545A4C61746Ellu, // lag_Latn_TZ + 0x9C0B504B41726162llu, // lah_Arab_PK + 0xA40B55474C61746Ellu, // laj_Latn_UG + 0x6C624C554C61746Ellu, // lb_Latn_LU + 0x902B52554379726Cllu, // lbe_Cyrl_RU + 0xD82B49444C61746Ellu, // lbw_Latn_ID + 0xBC4B434E54686169llu, // lcp_Thai_CN + 0xBC8B494E4C657063llu, // lep_Lepc_IN + 0xE48B52554379726Cllu, // lez_Cyrl_RU + 0x6C6755474C61746Ellu, // lg_Latn_UG + 0x6C694E4C4C61746Ellu, // li_Latn_NL + 0x950B4E5044657661llu, // lif_Deva_NP + 0x950B494E4C696D62llu, // lif_Limb_IN + 0xA50B49544C61746Ellu, // lij_Latn_IT + 0xC90B434E4C697375llu, // lis_Lisu_CN + 0xBD2B49444C61746Ellu, // ljp_Latn_ID + 0xA14B495241726162llu, // lki_Arab_IR + 0xCD4B55534C61746Ellu, // lkt_Latn_US + 0xB58B494E54656C75llu, // lmn_Telu_IN + 0xB98B49544C61746Ellu, // lmo_Latn_IT + 0x6C6E43444C61746Ellu, // ln_Latn_CD + 0x6C6F4C414C616F6Fllu, // lo_Laoo_LA + 0xADCB43444C61746Ellu, // lol_Latn_CD + 0xE5CB5A4D4C61746Ellu, // loz_Latn_ZM + 0x8A2B495241726162llu, // lrc_Arab_IR + 0x6C744C544C61746Ellu, // lt_Latn_LT + 0x9A6B4C564C61746Ellu, // ltg_Latn_LV + 0x6C7543444C61746Ellu, // lu_Latn_CD + 0x828B43444C61746Ellu, // lua_Latn_CD + 0xBA8B4B454C61746Ellu, // luo_Latn_KE + 0xE28B4B454C61746Ellu, // luy_Latn_KE + 0xE68B495241726162llu, // luz_Arab_IR + 0x6C764C564C61746Ellu, // lv_Latn_LV + 0xAECB544854686169llu, // lwl_Thai_TH + 0x9F2B434E48616E73llu, // lzh_Hans_CN + 0xE72B54524C61746Ellu, // lzz_Latn_TR + 0x8C0C49444C61746Ellu, // mad_Latn_ID + 0x940C434D4C61746Ellu, // maf_Latn_CM + 0x980C494E44657661llu, // mag_Deva_IN + 0xA00C494E44657661llu, // mai_Deva_IN + 0xA80C49444C61746Ellu, // mak_Latn_ID + 0xB40C474D4C61746Ellu, // man_Latn_GM + 0xB40C474E4E6B6F6Fllu, // man_Nkoo_GN + 0xC80C4B454C61746Ellu, // mas_Latn_KE + 0xE40C4D584C61746Ellu, // maz_Latn_MX + 0x946C52554379726Cllu, // mdf_Cyrl_RU + 0x9C6C50484C61746Ellu, // mdh_Latn_PH + 0xC46C49444C61746Ellu, // mdr_Latn_ID + 0xB48C534C4C61746Ellu, // men_Latn_SL + 0xC48C4B454C61746Ellu, // mer_Latn_KE + 0x80AC544841726162llu, // mfa_Arab_TH + 0x90AC4D554C61746Ellu, // mfe_Latn_MU + 0x6D674D474C61746Ellu, // mg_Latn_MG + 0x9CCC4D5A4C61746Ellu, // mgh_Latn_MZ + 0xB8CC434D4C61746Ellu, // mgo_Latn_CM + 0xBCCC4E5044657661llu, // mgp_Deva_NP + 0xE0CC545A4C61746Ellu, // mgy_Latn_TZ + 0x6D684D484C61746Ellu, // mh_Latn_MH + 0x6D694E5A4C61746Ellu, // mi_Latn_NZ + 0xB50C49444C61746Ellu, // min_Latn_ID + 0xC90C495148617472llu, // mis_Hatr_IQ + 0x6D6B4D4B4379726Cllu, // mk_Cyrl_MK + 0x6D6C494E4D6C796Dllu, // ml_Mlym_IN + 0xC96C53444C61746Ellu, // mls_Latn_SD + 0x6D6E4D4E4379726Cllu, // mn_Cyrl_MN + 0x6D6E434E4D6F6E67llu, // mn_Mong_CN + 0xA1AC494E42656E67llu, // mni_Beng_IN + 0xD9AC4D4D4D796D72llu, // mnw_Mymr_MM + 0x91CC43414C61746Ellu, // moe_Latn_CA + 0x9DCC43414C61746Ellu, // moh_Latn_CA + 0xC9CC42464C61746Ellu, // mos_Latn_BF + 0x6D72494E44657661llu, // mr_Deva_IN + 0x8E2C4E5044657661llu, // mrd_Deva_NP + 0xA62C52554379726Cllu, // mrj_Cyrl_RU + 0xD22C42444D726F6Fllu, // mru_Mroo_BD + 0x6D734D594C61746Ellu, // ms_Latn_MY + 0x6D744D544C61746Ellu, // mt_Latn_MT + 0xC66C494E44657661llu, // mtr_Deva_IN + 0x828C434D4C61746Ellu, // mua_Latn_CM + 0xCA8C55534C61746Ellu, // mus_Latn_US + 0xE2AC504B41726162llu, // mvy_Arab_PK + 0xAACC4D4C4C61746Ellu, // mwk_Latn_ML + 0xC6CC494E44657661llu, // mwr_Deva_IN + 0xD6CC49444C61746Ellu, // mwv_Latn_ID + 0x8AEC5A574C61746Ellu, // mxc_Latn_ZW + 0x6D794D4D4D796D72llu, // my_Mymr_MM + 0xD70C52554379726Cllu, // myv_Cyrl_RU + 0xDF0C55474C61746Ellu, // myx_Latn_UG + 0xE70C49524D616E64llu, // myz_Mand_IR + 0xB72C495241726162llu, // mzn_Arab_IR + 0x6E614E524C61746Ellu, // na_Latn_NR + 0xB40D434E48616E73llu, // nan_Hans_CN + 0xBC0D49544C61746Ellu, // nap_Latn_IT + 0xC00D4E414C61746Ellu, // naq_Latn_NA + 0x6E624E4F4C61746Ellu, // nb_Latn_NO + 0x9C4D4D584C61746Ellu, // nch_Latn_MX + 0x6E645A574C61746Ellu, // nd_Latn_ZW + 0x886D4D5A4C61746Ellu, // ndc_Latn_MZ + 0xC86D44454C61746Ellu, // nds_Latn_DE + 0x6E654E5044657661llu, // ne_Deva_NP + 0xD88D4E5044657661llu, // new_Deva_NP + 0x6E674E414C61746Ellu, // ng_Latn_NA + 0xACCD4D5A4C61746Ellu, // ngl_Latn_MZ + 0x90ED4D584C61746Ellu, // nhe_Latn_MX + 0xD8ED4D584C61746Ellu, // nhw_Latn_MX + 0xA50D49444C61746Ellu, // nij_Latn_ID + 0xD10D4E554C61746Ellu, // niu_Latn_NU + 0xB92D494E4C61746Ellu, // njo_Latn_IN + 0x6E6C4E4C4C61746Ellu, // nl_Latn_NL + 0x998D434D4C61746Ellu, // nmg_Latn_CM + 0x6E6E4E4F4C61746Ellu, // nn_Latn_NO + 0x9DAD434D4C61746Ellu, // nnh_Latn_CM + 0x6E6F4E4F4C61746Ellu, // no_Latn_NO + 0x8DCD54484C616E61llu, // nod_Lana_TH + 0x91CD494E44657661llu, // noe_Deva_IN + 0xB5CD534552756E72llu, // non_Runr_SE + 0xBA0D474E4E6B6F6Fllu, // nqo_Nkoo_GN + 0x6E725A414C61746Ellu, // nr_Latn_ZA + 0xAA4D434143616E73llu, // nsk_Cans_CA + 0xBA4D5A414C61746Ellu, // nso_Latn_ZA + 0xCA8D53534C61746Ellu, // nus_Latn_SS + 0x6E7655534C61746Ellu, // nv_Latn_US + 0xC2ED434E4C61746Ellu, // nxq_Latn_CN + 0x6E794D574C61746Ellu, // ny_Latn_MW + 0xB30D545A4C61746Ellu, // nym_Latn_TZ + 0xB70D55474C61746Ellu, // nyn_Latn_UG + 0xA32D47484C61746Ellu, // nzi_Latn_GH + 0x6F6346524C61746Ellu, // oc_Latn_FR + 0x6F6D45544C61746Ellu, // om_Latn_ET + 0x6F72494E4F727961llu, // or_Orya_IN + 0x6F7347454379726Cllu, // os_Cyrl_GE + 0xAA6E4D4E4F726B68llu, // otk_Orkh_MN + 0x7061504B41726162llu, // pa_Arab_PK + 0x7061494E47757275llu, // pa_Guru_IN + 0x980F50484C61746Ellu, // pag_Latn_PH + 0xAC0F495250686C69llu, // pal_Phli_IR + 0xAC0F434E50686C70llu, // pal_Phlp_CN + 0xB00F50484C61746Ellu, // pam_Latn_PH + 0xBC0F41574C61746Ellu, // pap_Latn_AW + 0xD00F50574C61746Ellu, // pau_Latn_PW + 0x8C4F46524C61746Ellu, // pcd_Latn_FR + 0xB04F4E474C61746Ellu, // pcm_Latn_NG + 0x886F55534C61746Ellu, // pdc_Latn_US + 0xCC6F43414C61746Ellu, // pdt_Latn_CA + 0xB88F49525870656Fllu, // peo_Xpeo_IR + 0xACAF44454C61746Ellu, // pfl_Latn_DE + 0xB4EF4C4250686E78llu, // phn_Phnx_LB + 0x814F494E42726168llu, // pka_Brah_IN + 0xB94F4B454C61746Ellu, // pko_Latn_KE + 0x706C504C4C61746Ellu, // pl_Latn_PL + 0xC98F49544C61746Ellu, // pms_Latn_IT + 0xCDAF47524772656Bllu, // pnt_Grek_GR + 0xB5CF464D4C61746Ellu, // pon_Latn_FM + 0x822F504B4B686172llu, // pra_Khar_PK + 0x8E2F495241726162llu, // prd_Arab_IR + 0x7073414641726162llu, // ps_Arab_AF + 0x707442524C61746Ellu, // pt_Latn_BR + 0xD28F47414C61746Ellu, // puu_Latn_GA + 0x717550454C61746Ellu, // qu_Latn_PE + 0x8A9047544C61746Ellu, // quc_Latn_GT + 0x9A9045434C61746Ellu, // qug_Latn_EC + 0xA411494E44657661llu, // raj_Deva_IN + 0x945152454C61746Ellu, // rcf_Latn_RE + 0xA49149444C61746Ellu, // rej_Latn_ID + 0xB4D149544C61746Ellu, // rgn_Latn_IT + 0x8111494E4C61746Ellu, // ria_Latn_IN + 0x95114D4154666E67llu, // rif_Tfng_MA + 0xC9314E5044657661llu, // rjs_Deva_NP + 0xCD51424442656E67llu, // rkt_Beng_BD + 0x726D43484C61746Ellu, // rm_Latn_CH + 0x959146494C61746Ellu, // rmf_Latn_FI + 0xB99143484C61746Ellu, // rmo_Latn_CH + 0xCD91495241726162llu, // rmt_Arab_IR + 0xD19153454C61746Ellu, // rmu_Latn_SE + 0x726E42494C61746Ellu, // rn_Latn_BI + 0x99B14D5A4C61746Ellu, // rng_Latn_MZ + 0x726F524F4C61746Ellu, // ro_Latn_RO + 0x85D149444C61746Ellu, // rob_Latn_ID + 0x95D1545A4C61746Ellu, // rof_Latn_TZ + 0xB271464A4C61746Ellu, // rtm_Latn_FJ + 0x727552554379726Cllu, // ru_Cyrl_RU + 0x929155414379726Cllu, // rue_Cyrl_UA + 0x9A9153424C61746Ellu, // rug_Latn_SB + 0x727752574C61746Ellu, // rw_Latn_RW + 0xAAD1545A4C61746Ellu, // rwk_Latn_TZ + 0xD3114A504B616E61llu, // ryu_Kana_JP + 0x7361494E44657661llu, // sa_Deva_IN + 0x941247484C61746Ellu, // saf_Latn_GH + 0x9C1252554379726Cllu, // sah_Cyrl_RU + 0xC0124B454C61746Ellu, // saq_Latn_KE + 0xC81249444C61746Ellu, // sas_Latn_ID + 0xCC12494E4C61746Ellu, // sat_Latn_IN + 0xE412494E53617572llu, // saz_Saur_IN + 0xBC32545A4C61746Ellu, // sbp_Latn_TZ + 0x736349544C61746Ellu, // sc_Latn_IT + 0xA852494E44657661llu, // sck_Deva_IN + 0xB45249544C61746Ellu, // scn_Latn_IT + 0xB85247424C61746Ellu, // sco_Latn_GB + 0xC85243414C61746Ellu, // scs_Latn_CA + 0x7364504B41726162llu, // sd_Arab_PK + 0x7364494E44657661llu, // sd_Deva_IN + 0x7364494E4B686F6Allu, // sd_Khoj_IN + 0x7364494E53696E64llu, // sd_Sind_IN + 0x887249544C61746Ellu, // sdc_Latn_IT + 0x9C72495241726162llu, // sdh_Arab_IR + 0x73654E4F4C61746Ellu, // se_Latn_NO + 0x949243494C61746Ellu, // sef_Latn_CI + 0x9C924D5A4C61746Ellu, // seh_Latn_MZ + 0xA0924D584C61746Ellu, // sei_Latn_MX + 0xC8924D4C4C61746Ellu, // ses_Latn_ML + 0x736743464C61746Ellu, // sg_Latn_CF + 0x80D249454F67616Dllu, // sga_Ogam_IE + 0xC8D24C544C61746Ellu, // sgs_Latn_LT + 0xA0F24D4154666E67llu, // shi_Tfng_MA + 0xB4F24D4D4D796D72llu, // shn_Mymr_MM + 0x73694C4B53696E68llu, // si_Sinh_LK + 0x8D1245544C61746Ellu, // sid_Latn_ET + 0x736B534B4C61746Ellu, // sk_Latn_SK + 0xC552504B41726162llu, // skr_Arab_PK + 0x736C53494C61746Ellu, // sl_Latn_SI + 0xA172504C4C61746Ellu, // sli_Latn_PL + 0xE17249444C61746Ellu, // sly_Latn_ID + 0x736D57534C61746Ellu, // sm_Latn_WS + 0x819253454C61746Ellu, // sma_Latn_SE + 0xA59253454C61746Ellu, // smj_Latn_SE + 0xB59246494C61746Ellu, // smn_Latn_FI + 0xBD92494C53616D72llu, // smp_Samr_IL + 0xC99246494C61746Ellu, // sms_Latn_FI + 0x736E5A574C61746Ellu, // sn_Latn_ZW + 0xA9B24D4C4C61746Ellu, // snk_Latn_ML + 0x736F534F4C61746Ellu, // so_Latn_SO + 0xD1D2544854686169llu, // sou_Thai_TH + 0x7371414C4C61746Ellu, // sq_Latn_AL + 0x737252534379726Cllu, // sr_Cyrl_RS + 0x737252534C61746Ellu, // sr_Latn_RS + 0x8632494E536F7261llu, // srb_Sora_IN + 0xB63253524C61746Ellu, // srn_Latn_SR + 0xC632534E4C61746Ellu, // srr_Latn_SN + 0xDE32494E44657661llu, // srx_Deva_IN + 0x73735A414C61746Ellu, // ss_Latn_ZA + 0xE25245524C61746Ellu, // ssy_Latn_ER + 0x73745A414C61746Ellu, // st_Latn_ZA + 0xC27244454C61746Ellu, // stq_Latn_DE + 0x737549444C61746Ellu, // su_Latn_ID + 0xAA92545A4C61746Ellu, // suk_Latn_TZ + 0xCA92474E4C61746Ellu, // sus_Latn_GN + 0x737653454C61746Ellu, // sv_Latn_SE + 0x7377545A4C61746Ellu, // sw_Latn_TZ + 0x86D2595441726162llu, // swb_Arab_YT + 0x8AD243444C61746Ellu, // swc_Latn_CD + 0x9AD244454C61746Ellu, // swg_Latn_DE + 0xD6D2494E44657661llu, // swv_Deva_IN + 0xB6F249444C61746Ellu, // sxn_Latn_ID + 0xAF12424442656E67llu, // syl_Beng_BD + 0xC712495153797263llu, // syr_Syrc_IQ + 0xAF32504C4C61746Ellu, // szl_Latn_PL + 0x7461494E54616D6Cllu, // ta_Taml_IN + 0xA4134E5044657661llu, // taj_Deva_NP + 0xD83350484C61746Ellu, // tbw_Latn_PH + 0xE053494E4B6E6461llu, // tcy_Knda_IN + 0x8C73434E54616C65llu, // tdd_Tale_CN + 0x98734E5044657661llu, // tdg_Deva_NP + 0x9C734E5044657661llu, // tdh_Deva_NP + 0x7465494E54656C75llu, // te_Telu_IN + 0xB093534C4C61746Ellu, // tem_Latn_SL + 0xB89355474C61746Ellu, // teo_Latn_UG + 0xCC93544C4C61746Ellu, // tet_Latn_TL + 0x7467504B41726162llu, // tg_Arab_PK + 0x7467544A4379726Cllu, // tg_Cyrl_TJ + 0x7468544854686169llu, // th_Thai_TH + 0xACF34E5044657661llu, // thl_Deva_NP + 0xC0F34E5044657661llu, // thq_Deva_NP + 0xC4F34E5044657661llu, // thr_Deva_NP + 0x7469455445746869llu, // ti_Ethi_ET + 0x9913455245746869llu, // tig_Ethi_ER + 0xD5134E474C61746Ellu, // tiv_Latn_NG + 0x746B544D4C61746Ellu, // tk_Latn_TM + 0xAD53544B4C61746Ellu, // tkl_Latn_TK + 0xC553415A4C61746Ellu, // tkr_Latn_AZ + 0xCD534E5044657661llu, // tkt_Deva_NP + 0x746C50484C61746Ellu, // tl_Latn_PH + 0xE173415A4C61746Ellu, // tly_Latn_AZ + 0x9D934E454C61746Ellu, // tmh_Latn_NE + 0x746E5A414C61746Ellu, // tn_Latn_ZA + 0x746F544F4C61746Ellu, // to_Latn_TO + 0x99D34D574C61746Ellu, // tog_Latn_MW + 0xA1F350474C61746Ellu, // tpi_Latn_PG + 0x747254524C61746Ellu, // tr_Latn_TR + 0xD23354524C61746Ellu, // tru_Latn_TR + 0xD63354574C61746Ellu, // trv_Latn_TW + 0x74735A414C61746Ellu, // ts_Latn_ZA + 0x8E5347524772656Bllu, // tsd_Grek_GR + 0x96534E5044657661llu, // tsf_Deva_NP + 0x9A5350484C61746Ellu, // tsg_Latn_PH + 0xA653425454696274llu, // tsj_Tibt_BT + 0x747452554379726Cllu, // tt_Cyrl_RU + 0xA67355474C61746Ellu, // ttj_Latn_UG + 0xCA73544854686169llu, // tts_Thai_TH + 0xCE73415A4C61746Ellu, // ttt_Latn_AZ + 0xB2934D574C61746Ellu, // tum_Latn_MW + 0xAEB354564C61746Ellu, // tvl_Latn_TV + 0xC2D34E454C61746Ellu, // twq_Latn_NE + 0x747950464C61746Ellu, // ty_Latn_PF + 0xD71352554379726Cllu, // tyv_Cyrl_RU + 0xB3334D414C61746Ellu, // tzm_Latn_MA + 0xB07452554379726Cllu, // udm_Cyrl_RU + 0x7567434E41726162llu, // ug_Arab_CN + 0x75674B5A4379726Cllu, // ug_Cyrl_KZ + 0x80D4535955676172llu, // uga_Ugar_SY + 0x756B55414379726Cllu, // uk_Cyrl_UA + 0xA174464D4C61746Ellu, // uli_Latn_FM + 0x8594414F4C61746Ellu, // umb_Latn_AO + 0xC5B4494E42656E67llu, // unr_Beng_IN + 0xC5B44E5044657661llu, // unr_Deva_NP + 0xDDB4494E42656E67llu, // unx_Beng_IN + 0x7572504B41726162llu, // ur_Arab_PK + 0x757A414641726162llu, // uz_Arab_AF + 0x757A555A4C61746Ellu, // uz_Latn_UZ + 0xA0154C5256616969llu, // vai_Vaii_LR + 0x76655A414C61746Ellu, // ve_Latn_ZA + 0x889549544C61746Ellu, // vec_Latn_IT + 0xBC9552554C61746Ellu, // vep_Latn_RU + 0x7669564E4C61746Ellu, // vi_Latn_VN + 0x891553584C61746Ellu, // vic_Latn_SX + 0xC97542454C61746Ellu, // vls_Latn_BE + 0x959544454C61746Ellu, // vmf_Latn_DE + 0xD9954D5A4C61746Ellu, // vmw_Latn_MZ + 0xCDD552554C61746Ellu, // vot_Latn_RU + 0xBA3545454C61746Ellu, // vro_Latn_EE + 0xB695545A4C61746Ellu, // vun_Latn_TZ + 0x776142454C61746Ellu, // wa_Latn_BE + 0x901643484C61746Ellu, // wae_Latn_CH + 0xAC16455445746869llu, // wal_Ethi_ET + 0xC41650484C61746Ellu, // war_Latn_PH + 0xBC3641554C61746Ellu, // wbp_Latn_AU + 0xC036494E54656C75llu, // wbq_Telu_IN + 0xC436494E44657661llu, // wbr_Deva_IN + 0xC97657464C61746Ellu, // wls_Latn_WF + 0xA1B64B4D41726162llu, // wni_Arab_KM + 0x776F534E4C61746Ellu, // wo_Latn_SN + 0xB276494E44657661llu, // wtm_Deva_IN + 0xD296434E48616E73llu, // wuu_Hans_CN + 0xD41742524C61746Ellu, // xav_Latn_BR + 0xC457545243617269llu, // xcr_Cari_TR + 0x78685A414C61746Ellu, // xh_Latn_ZA + 0x897754524C796369llu, // xlc_Lyci_TR + 0x8D7754524C796469llu, // xld_Lydi_TR + 0x9597474547656F72llu, // xmf_Geor_GE + 0xB597434E4D616E69llu, // xmn_Mani_CN + 0xC59753444D657263llu, // xmr_Merc_SD + 0x81B753414E617262llu, // xna_Narb_SA + 0xC5B7494E44657661llu, // xnr_Deva_IN + 0x99D755474C61746Ellu, // xog_Latn_UG + 0xC5F7495250727469llu, // xpr_Prti_IR + 0x8257594553617262llu, // xsa_Sarb_YE + 0xC6574E5044657661llu, // xsr_Deva_NP + 0xB8184D5A4C61746Ellu, // yao_Latn_MZ + 0xBC18464D4C61746Ellu, // yap_Latn_FM + 0xD418434D4C61746Ellu, // yav_Latn_CM + 0x8438434D4C61746Ellu, // ybb_Latn_CM + 0x796F4E474C61746Ellu, // yo_Latn_NG + 0xAE3842524C61746Ellu, // yrl_Latn_BR + 0x82984D584C61746Ellu, // yua_Latn_MX + 0x7A61434E4C61746Ellu, // za_Latn_CN + 0x981953444C61746Ellu, // zag_Latn_SD + 0xA4794B4D41726162llu, // zdj_Arab_KM + 0x80994E4C4C61746Ellu, // zea_Latn_NL + 0x9CD94D4154666E67llu, // zgh_Tfng_MA + 0x7A685457426F706Fllu, // zh_Bopo_TW + 0x7A68434E48616E73llu, // zh_Hans_CN + 0x7A68545748616E74llu, // zh_Hant_TW + 0xA1994D594C61746Ellu, // zmi_Latn_MY + 0x7A755A414C61746Ellu, // zu_Latn_ZA + 0x833954524C61746Ellu, // zza_Latn_TR +}); + +const std::unordered_map<uint32_t, uint32_t> ARAB_PARENTS({ + {0x6172445Au, 0x61729420u}, // ar-DZ -> ar-015 + {0x61724548u, 0x61729420u}, // ar-EH -> ar-015 + {0x61724C59u, 0x61729420u}, // ar-LY -> ar-015 + {0x61724D41u, 0x61729420u}, // ar-MA -> ar-015 + {0x6172544Eu, 0x61729420u}, // ar-TN -> ar-015 +}); + +const std::unordered_map<uint32_t, uint32_t> HANT_PARENTS({ + {0x7A684D4Fu, 0x7A68484Bu}, // zh-Hant-MO -> zh-Hant-HK +}); + +const std::unordered_map<uint32_t, uint32_t> LATN_PARENTS({ + {0x656E80A1u, 0x656E8400u}, // en-150 -> en-001 + {0x656E4147u, 0x656E8400u}, // en-AG -> en-001 + {0x656E4149u, 0x656E8400u}, // en-AI -> en-001 + {0x656E4154u, 0x656E80A1u}, // en-AT -> en-150 + {0x656E4155u, 0x656E8400u}, // en-AU -> en-001 + {0x656E4242u, 0x656E8400u}, // en-BB -> en-001 + {0x656E4245u, 0x656E8400u}, // en-BE -> en-001 + {0x656E424Du, 0x656E8400u}, // en-BM -> en-001 + {0x656E4253u, 0x656E8400u}, // en-BS -> en-001 + {0x656E4257u, 0x656E8400u}, // en-BW -> en-001 + {0x656E425Au, 0x656E8400u}, // en-BZ -> en-001 + {0x656E4341u, 0x656E8400u}, // en-CA -> en-001 + {0x656E4343u, 0x656E8400u}, // en-CC -> en-001 + {0x656E4348u, 0x656E80A1u}, // en-CH -> en-150 + {0x656E434Bu, 0x656E8400u}, // en-CK -> en-001 + {0x656E434Du, 0x656E8400u}, // en-CM -> en-001 + {0x656E4358u, 0x656E8400u}, // en-CX -> en-001 + {0x656E4359u, 0x656E8400u}, // en-CY -> en-001 + {0x656E4445u, 0x656E80A1u}, // en-DE -> en-150 + {0x656E4447u, 0x656E8400u}, // en-DG -> en-001 + {0x656E444Bu, 0x656E80A1u}, // en-DK -> en-150 + {0x656E444Du, 0x656E8400u}, // en-DM -> en-001 + {0x656E4552u, 0x656E8400u}, // en-ER -> en-001 + {0x656E4649u, 0x656E80A1u}, // en-FI -> en-150 + {0x656E464Au, 0x656E8400u}, // en-FJ -> en-001 + {0x656E464Bu, 0x656E8400u}, // en-FK -> en-001 + {0x656E464Du, 0x656E8400u}, // en-FM -> en-001 + {0x656E4742u, 0x656E8400u}, // en-GB -> en-001 + {0x656E4744u, 0x656E8400u}, // en-GD -> en-001 + {0x656E4747u, 0x656E8400u}, // en-GG -> en-001 + {0x656E4748u, 0x656E8400u}, // en-GH -> en-001 + {0x656E4749u, 0x656E8400u}, // en-GI -> en-001 + {0x656E474Du, 0x656E8400u}, // en-GM -> en-001 + {0x656E4759u, 0x656E8400u}, // en-GY -> en-001 + {0x656E484Bu, 0x656E8400u}, // en-HK -> en-001 + {0x656E4945u, 0x656E8400u}, // en-IE -> en-001 + {0x656E494Cu, 0x656E8400u}, // en-IL -> en-001 + {0x656E494Du, 0x656E8400u}, // en-IM -> en-001 + {0x656E494Eu, 0x656E8400u}, // en-IN -> en-001 + {0x656E494Fu, 0x656E8400u}, // en-IO -> en-001 + {0x656E4A45u, 0x656E8400u}, // en-JE -> en-001 + {0x656E4A4Du, 0x656E8400u}, // en-JM -> en-001 + {0x656E4B45u, 0x656E8400u}, // en-KE -> en-001 + {0x656E4B49u, 0x656E8400u}, // en-KI -> en-001 + {0x656E4B4Eu, 0x656E8400u}, // en-KN -> en-001 + {0x656E4B59u, 0x656E8400u}, // en-KY -> en-001 + {0x656E4C43u, 0x656E8400u}, // en-LC -> en-001 + {0x656E4C52u, 0x656E8400u}, // en-LR -> en-001 + {0x656E4C53u, 0x656E8400u}, // en-LS -> en-001 + {0x656E4D47u, 0x656E8400u}, // en-MG -> en-001 + {0x656E4D4Fu, 0x656E8400u}, // en-MO -> en-001 + {0x656E4D53u, 0x656E8400u}, // en-MS -> en-001 + {0x656E4D54u, 0x656E8400u}, // en-MT -> en-001 + {0x656E4D55u, 0x656E8400u}, // en-MU -> en-001 + {0x656E4D57u, 0x656E8400u}, // en-MW -> en-001 + {0x656E4D59u, 0x656E8400u}, // en-MY -> en-001 + {0x656E4E41u, 0x656E8400u}, // en-NA -> en-001 + {0x656E4E46u, 0x656E8400u}, // en-NF -> en-001 + {0x656E4E47u, 0x656E8400u}, // en-NG -> en-001 + {0x656E4E4Cu, 0x656E80A1u}, // en-NL -> en-150 + {0x656E4E52u, 0x656E8400u}, // en-NR -> en-001 + {0x656E4E55u, 0x656E8400u}, // en-NU -> en-001 + {0x656E4E5Au, 0x656E8400u}, // en-NZ -> en-001 + {0x656E5047u, 0x656E8400u}, // en-PG -> en-001 + {0x656E5048u, 0x656E8400u}, // en-PH -> en-001 + {0x656E504Bu, 0x656E8400u}, // en-PK -> en-001 + {0x656E504Eu, 0x656E8400u}, // en-PN -> en-001 + {0x656E5057u, 0x656E8400u}, // en-PW -> en-001 + {0x656E5257u, 0x656E8400u}, // en-RW -> en-001 + {0x656E5342u, 0x656E8400u}, // en-SB -> en-001 + {0x656E5343u, 0x656E8400u}, // en-SC -> en-001 + {0x656E5344u, 0x656E8400u}, // en-SD -> en-001 + {0x656E5345u, 0x656E80A1u}, // en-SE -> en-150 + {0x656E5347u, 0x656E8400u}, // en-SG -> en-001 + {0x656E5348u, 0x656E8400u}, // en-SH -> en-001 + {0x656E5349u, 0x656E80A1u}, // en-SI -> en-150 + {0x656E534Cu, 0x656E8400u}, // en-SL -> en-001 + {0x656E5353u, 0x656E8400u}, // en-SS -> en-001 + {0x656E5358u, 0x656E8400u}, // en-SX -> en-001 + {0x656E535Au, 0x656E8400u}, // en-SZ -> en-001 + {0x656E5443u, 0x656E8400u}, // en-TC -> en-001 + {0x656E544Bu, 0x656E8400u}, // en-TK -> en-001 + {0x656E544Fu, 0x656E8400u}, // en-TO -> en-001 + {0x656E5454u, 0x656E8400u}, // en-TT -> en-001 + {0x656E5456u, 0x656E8400u}, // en-TV -> en-001 + {0x656E545Au, 0x656E8400u}, // en-TZ -> en-001 + {0x656E5547u, 0x656E8400u}, // en-UG -> en-001 + {0x656E5643u, 0x656E8400u}, // en-VC -> en-001 + {0x656E5647u, 0x656E8400u}, // en-VG -> en-001 + {0x656E5655u, 0x656E8400u}, // en-VU -> en-001 + {0x656E5753u, 0x656E8400u}, // en-WS -> en-001 + {0x656E5A41u, 0x656E8400u}, // en-ZA -> en-001 + {0x656E5A4Du, 0x656E8400u}, // en-ZM -> en-001 + {0x656E5A57u, 0x656E8400u}, // en-ZW -> en-001 + {0x65734152u, 0x6573A424u}, // es-AR -> es-419 + {0x6573424Fu, 0x6573A424u}, // es-BO -> es-419 + {0x6573434Cu, 0x6573A424u}, // es-CL -> es-419 + {0x6573434Fu, 0x6573A424u}, // es-CO -> es-419 + {0x65734352u, 0x6573A424u}, // es-CR -> es-419 + {0x65734355u, 0x6573A424u}, // es-CU -> es-419 + {0x6573444Fu, 0x6573A424u}, // es-DO -> es-419 + {0x65734543u, 0x6573A424u}, // es-EC -> es-419 + {0x65734754u, 0x6573A424u}, // es-GT -> es-419 + {0x6573484Eu, 0x6573A424u}, // es-HN -> es-419 + {0x65734D58u, 0x6573A424u}, // es-MX -> es-419 + {0x65734E49u, 0x6573A424u}, // es-NI -> es-419 + {0x65735041u, 0x6573A424u}, // es-PA -> es-419 + {0x65735045u, 0x6573A424u}, // es-PE -> es-419 + {0x65735052u, 0x6573A424u}, // es-PR -> es-419 + {0x65735059u, 0x6573A424u}, // es-PY -> es-419 + {0x65735356u, 0x6573A424u}, // es-SV -> es-419 + {0x65735553u, 0x6573A424u}, // es-US -> es-419 + {0x65735559u, 0x6573A424u}, // es-UY -> es-419 + {0x65735645u, 0x6573A424u}, // es-VE -> es-419 + {0x7074414Fu, 0x70745054u}, // pt-AO -> pt-PT + {0x70744356u, 0x70745054u}, // pt-CV -> pt-PT + {0x70744757u, 0x70745054u}, // pt-GW -> pt-PT + {0x70744D4Fu, 0x70745054u}, // pt-MO -> pt-PT + {0x70744D5Au, 0x70745054u}, // pt-MZ -> pt-PT + {0x70745354u, 0x70745054u}, // pt-ST -> pt-PT + {0x7074544Cu, 0x70745054u}, // pt-TL -> pt-PT +}); + +const struct { + const char script[4]; + const std::unordered_map<uint32_t, uint32_t>* map; +} SCRIPT_PARENTS[] = { + {{'A', 'r', 'a', 'b'}, &ARAB_PARENTS}, + {{'H', 'a', 'n', 't'}, &HANT_PARENTS}, + {{'L', 'a', 't', 'n'}, &LATN_PARENTS}, +}; + +const size_t MAX_PARENT_DEPTH = 3; diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 62aabb13858d..c3dfb89180de 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -24,7 +24,9 @@ #include <stdlib.h> #include <string.h> +#include <algorithm> #include <limits> +#include <memory> #include <type_traits> #include <androidfw/ByteBucketArray.h> @@ -47,7 +49,7 @@ namespace android { -#ifdef HAVE_WINSOCK +#if defined(_WIN32) #undef nhtol #undef htonl #define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) ) @@ -560,7 +562,7 @@ status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) if ((mHeader->flags&ResStringPool_header::UTF8_FLAG && ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) || - (!mHeader->flags&ResStringPool_header::UTF8_FLAG && + (!(mHeader->flags&ResStringPool_header::UTF8_FLAG) && ((uint16_t*)mStrings)[mStringPoolSize-1] != 0)) { ALOGW("Bad string block: last string is not 0-terminated\n"); return (mError=BAD_TYPE); @@ -727,7 +729,7 @@ const char16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const AutoMutex lock(mDecodeLock); if (mCache == NULL) { -#ifndef HAVE_ANDROID_OS +#ifndef __ANDROID__ if (kDebugStringPoolNoisy) { ALOGI("CREATING STRING CACHE OF %zu bytes", mHeader->stringCount*sizeof(char16_t**)); @@ -1868,7 +1870,10 @@ void ResTable_config::swapHtoD() { } // The language & region are equal, so compare the scripts and variants. - int script = memcmp(l.localeScript, r.localeScript, sizeof(l.localeScript)); + const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'}; + const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript; + const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript; + int script = memcmp(lScript, rScript, sizeof(l.localeScript)); if (script) { return script; } @@ -2012,11 +2017,11 @@ int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const { // scripts since it seems more useful to do so. We will consider // "en-US-POSIX" to be more specific than "en-Latn-US". - const int score = ((localeScript[0] != 0) ? 1 : 0) + - ((localeVariant[0] != 0) ? 2 : 0); + const int score = ((localeScript[0] != '\0' && !localeScriptWasComputed) ? 1 : 0) + + ((localeVariant[0] != '\0') ? 2 : 0); - const int oScore = ((o.localeScript[0] != 0) ? 1 : 0) + - ((o.localeVariant[0] != 0) ? 2 : 0); + const int oScore = (o.localeScript[0] != '\0' && !o.localeScriptWasComputed ? 1 : 0) + + ((o.localeVariant[0] != '\0') ? 2 : 0); return score - oScore; @@ -2165,6 +2170,88 @@ bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const { return false; } +bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, + const ResTable_config* requested) const { + if (requested->locale == 0) { + // The request doesn't have a locale, so no resource is better + // than the other. + return false; + } + + if (locale == 0 && o.locale == 0) { + // The locales parts of both resources are empty, so no one is better + // than the other. + return false; + } + + // Non-matching locales have been filtered out, so both resources + // match the requested locale. + // + // Because of the locale-related checks in match() and the checks, we know + // that: + // 1) The resource languages are either empty or match the request; + // and + // 2) If the request's script is known, the resource scripts are either + // unknown or match the request. + + if (language[0] != o.language[0]) { + // The languages of the two resources are not the same. We can only + // assume that one of the two resources matched the request because one + // doesn't have a language and the other has a matching language. + // + // We consider the one that has the language specified a better match. + // + // The exception is that we consider no-language resources a better match + // for US English and similar locales than locales that are a descendant + // of Internatinal English (en-001), since no-language resources are + // where the US English resource have traditionally lived for most apps. + if (requested->language[0] == 'e' && requested->language[1] == 'n') { + if (requested->country[0] == 'U' && requested->country[1] == 'S') { + // For US English itself, we consider a no-locale resource a + // better match if the other resource has a country other than + // US specified. + if (language[0] != '\0') { + return country[0] == '\0' || (country[0] == 'U' && country[1] == 'S'); + } else { + return !(o.country[0] == '\0' || (o.country[0] == 'U' && o.country[1] == 'S')); + } + } else if (localeDataIsCloseToUsEnglish(requested->country)) { + if (language[0] != '\0') { + return localeDataIsCloseToUsEnglish(country); + } else { + return !localeDataIsCloseToUsEnglish(o.country); + } + } + } + return (language[0] != '\0'); + } + + // If we are here, both the resources have the same non-empty language as + // the request. + // + // Because the languages are the same, computeScript() always + // returns a non-empty script for languages it knows about, and we have passed + // the script checks in match(), the scripts are either all unknown or are + // all the same. So we can't gain anything by checking the scripts. We need + // to check the region and variant. + + // See if any of the regions is better than the other + const int region_comparison = localeDataCompareRegions( + country, o.country, + language, requested->localeScript, requested->country); + if (region_comparison != 0) { + return (region_comparison > 0); + } + + // The regions are the same. Try the variant. + if (requested->localeVariant[0] != '\0' + && strncmp(localeVariant, requested->localeVariant, sizeof(localeVariant)) == 0) { + return (strncmp(o.localeVariant, requested->localeVariant, sizeof(localeVariant)) != 0); + } + + return false; +} + bool ResTable_config::isBetterThan(const ResTable_config& o, const ResTable_config* requested) const { if (requested) { @@ -2178,26 +2265,8 @@ bool ResTable_config::isBetterThan(const ResTable_config& o, } } - if (locale || o.locale) { - if ((language[0] != o.language[0]) && requested->language[0]) { - return (language[0]); - } - - if ((country[0] != o.country[0]) && requested->country[0]) { - return (country[0]); - } - } - - if (localeScript[0] || o.localeScript[0]) { - if (localeScript[0] != o.localeScript[0] && requested->localeScript[0]) { - return localeScript[0]; - } - } - - if (localeVariant[0] || o.localeVariant[0]) { - if (localeVariant[0] != o.localeVariant[0] && requested->localeVariant[0]) { - return localeVariant[0]; - } + if (isLocaleBetterThan(o, requested)) { + return true; } if (screenLayout || o.screenLayout) { @@ -2445,20 +2514,51 @@ bool ResTable_config::match(const ResTable_config& settings) const { } } if (locale != 0) { - // Don't consider the script & variants when deciding matches. + // Don't consider country and variants when deciding matches. + // (Theoretically, the variant can also affect the script. For + // example, "ar-alalc97" probably implies the Latin script, but since + // CLDR doesn't support getting likely scripts for that, we'll assume + // the variant doesn't change the script.) // - // If we two configs differ only in their script or language, they - // can be weeded out in the isMoreSpecificThan test. - if (language[0] != 0 - && (language[0] != settings.language[0] - || language[1] != settings.language[1])) { + // If two configs differ only in their country and variant, + // they can be weeded out in the isMoreSpecificThan test. + if (language[0] != settings.language[0] || language[1] != settings.language[1]) { return false; } - if (country[0] != 0 - && (country[0] != settings.country[0] - || country[1] != settings.country[1])) { - return false; + // For backward compatibility and supporting private-use locales, we + // fall back to old behavior if we couldn't determine the script for + // either of the desired locale or the provided locale. But if we could determine + // the scripts, they should be the same for the locales to match. + bool countriesMustMatch = false; + char computed_script[4]; + const char* script; + if (settings.localeScript[0] == '\0') { // could not determine the request's script + countriesMustMatch = true; + } else { + if (localeScript[0] == '\0' && !localeScriptWasComputed) { + // script was not provided or computed, so we try to compute it + localeDataComputeScript(computed_script, language, country); + if (computed_script[0] == '\0') { // we could not compute the script + countriesMustMatch = true; + } else { + script = computed_script; + } + } else { // script was provided, so just use it + script = localeScript; + } + } + + if (countriesMustMatch) { + if (country[0] != '\0' + && (country[0] != settings.country[0] + || country[1] != settings.country[1])) { + return false; + } + } else { + if (memcmp(script, settings.localeScript, sizeof(settings.localeScript)) != 0) { + return false; + } } } @@ -2586,8 +2686,8 @@ void ResTable_config::appendDirLocale(String8& out) const { if (!language[0]) { return; } - - if (!localeScript[0] && !localeVariant[0]) { + const bool scriptWasProvided = localeScript[0] != '\0' && !localeScriptWasComputed; + if (!scriptWasProvided && !localeVariant[0]) { // Legacy format. if (out.size() > 0) { out.append("-"); @@ -2605,7 +2705,7 @@ void ResTable_config::appendDirLocale(String8& out) const { return; } - // We are writing the modified bcp47 tag. + // We are writing the modified BCP 47 tag. // It starts with 'b+' and uses '+' as a separator. if (out.size() > 0) { @@ -2617,7 +2717,7 @@ void ResTable_config::appendDirLocale(String8& out) const { size_t len = unpackLanguage(buf); out.append(buf, len); - if (localeScript[0]) { + if (scriptWasProvided) { out.append("+"); out.append(localeScript, sizeof(localeScript)); } @@ -2630,7 +2730,7 @@ void ResTable_config::appendDirLocale(String8& out) const { if (localeVariant[0]) { out.append("+"); - out.append(localeVariant, sizeof(localeVariant)); + out.append(localeVariant, strnlen(localeVariant, sizeof(localeVariant))); } } @@ -2648,7 +2748,7 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN]) const { charsWritten += unpackLanguage(str); } - if (localeScript[0]) { + if (localeScript[0] && !localeScriptWasComputed) { if (charsWritten) { str[charsWritten++] = '-'; } @@ -2682,11 +2782,15 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN]) const { config->language[0] ? config->packRegion(start) : config->packLanguage(start); break; case 4: - config->localeScript[0] = toupper(start[0]); - for (size_t i = 1; i < 4; ++i) { - config->localeScript[i] = tolower(start[i]); + if ('0' <= start[0] && start[0] <= '9') { + // this is a variant, so fall through + } else { + config->localeScript[0] = toupper(start[0]); + for (size_t i = 1; i < 4; ++i) { + config->localeScript[i] = tolower(start[i]); + } + break; } - break; case 5: case 6: case 7: @@ -2720,6 +2824,10 @@ void ResTable_config::setBcp47Locale(const char* in) { const size_t size = in + strlen(in) - start; assignLocaleComponent(this, start, size); + localeScriptWasComputed = (localeScript[0] == '\0'); + if (localeScriptWasComputed) { + computeScript(); + } } String8 ResTable_config::toString() const { @@ -3080,13 +3188,15 @@ struct ResTable::Package // table that defined the package); the ones after are skins on top of it. struct ResTable::PackageGroup { - PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id) + PackageGroup( + ResTable* _owner, const String16& _name, uint32_t _id, + bool appAsLib, bool _isSystemAsset) : owner(_owner) , name(_name) , id(_id) , largestTypeId(0) - , bags(NULL) - , dynamicRefTable(static_cast<uint8_t>(_id)) + , dynamicRefTable(static_cast<uint8_t>(_id), appAsLib) + , isSystemAsset(_isSystemAsset) { } ~PackageGroup() { @@ -3111,36 +3221,41 @@ struct ResTable::PackageGroup } } + /** + * Clear all cache related data that depends on parameters/configuration. + * This includes the bag caches and filtered types. + */ void clearBagCache() { - if (bags) { + for (size_t i = 0; i < typeCacheEntries.size(); i++) { if (kDebugTableNoisy) { - printf("bags=%p\n", bags); + printf("type=%zu\n", i); } - for (size_t i = 0; i < bags->size(); i++) { + const TypeList& typeList = types[i]; + if (!typeList.isEmpty()) { + TypeCacheEntry& cacheEntry = typeCacheEntries.editItemAt(i); + + // Reset the filtered configurations. + cacheEntry.filteredConfigs.clear(); + + bag_set** typeBags = cacheEntry.cachedBags; if (kDebugTableNoisy) { - printf("type=%zu\n", i); + printf("typeBags=%p\n", typeBags); } - const TypeList& typeList = types[i]; - if (!typeList.isEmpty()) { - bag_set** typeBags = bags->get(i); + + if (typeBags) { + const size_t N = typeList[0]->entryCount; if (kDebugTableNoisy) { - printf("typeBags=%p\n", typeBags); + printf("type->entryCount=%zu\n", N); } - if (typeBags) { - const size_t N = typeList[0]->entryCount; - if (kDebugTableNoisy) { - printf("type->entryCount=%zu\n", N); + for (size_t j = 0; j < N; j++) { + if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF) { + free(typeBags[j]); } - for (size_t j = 0; j < N; j++) { - if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF) - free(typeBags[j]); - } - free(typeBags); } + free(typeBags); + cacheEntry.cachedBags = NULL; } } - delete bags; - bags = NULL; } } @@ -3168,9 +3283,11 @@ struct ResTable::PackageGroup uint8_t largestTypeId; - // Computed attribute bags, first indexed by the type and second - // by the entry in that type. - ByteBucketArray<bag_set**>* bags; + // Cached objects dependent on the parameters/configuration of this ResTable. + // Gets cleared whenever the parameters/configuration changes. + // These are stored here in a parallel structure because the data in `types` may + // be shared by other ResTable's (framework resources are shared this way). + ByteBucketArray<TypeCacheEntry> typeCacheEntries; // The table mapping dynamic references to resolved references for // this package group. @@ -3178,14 +3295,10 @@ struct ResTable::PackageGroup // by having these tables in a per-package scope rather than // per-package-group. DynamicRefTable dynamicRefTable; -}; -struct ResTable::bag_set -{ - size_t numAttrs; // number in array - size_t availAttrs; // total space in array - uint32_t typeSpecFlags; - // Followed by 'numAttr' bag_entry structures. + // If the package group comes from a system asset. Used in + // determining non-system locales. + const bool isSystemAsset; }; ResTable::Theme::Theme(const ResTable& table) @@ -3532,7 +3645,7 @@ ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool cop { memset(&mParams, 0, sizeof(mParams)); memset(mPackageMap, 0, sizeof(mPackageMap)); - addInternal(data, size, NULL, 0, cookie, copyData); + addInternal(data, size, NULL, 0, false, cookie, copyData); LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table"); if (kDebugTableSuperNoisy) { ALOGI("Creating ResTable %p\n", this); @@ -3553,12 +3666,12 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const } status_t ResTable::add(const void* data, size_t size, const int32_t cookie, bool copyData) { - return addInternal(data, size, NULL, 0, cookie, copyData); + return addInternal(data, size, NULL, 0, false, cookie, copyData); } status_t ResTable::add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize, - const int32_t cookie, bool copyData) { - return addInternal(data, size, idmapData, idmapDataSize, cookie, copyData); + const int32_t cookie, bool copyData, bool appAsLib) { + return addInternal(data, size, idmapData, idmapDataSize, appAsLib, cookie, copyData); } status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) { @@ -3568,10 +3681,13 @@ status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData) { return UNKNOWN_ERROR; } - return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, 0, cookie, copyData); + return addInternal(data, static_cast<size_t>(asset->getLength()), NULL, false, 0, cookie, + copyData); } -status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) { +status_t ResTable::add( + Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData, + bool appAsLib, bool isSystemAsset) { const void* data = asset->getBuffer(true); if (data == NULL) { ALOGW("Unable to get buffer of resource asset file"); @@ -3590,20 +3706,21 @@ status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bo } return addInternal(data, static_cast<size_t>(asset->getLength()), - idmapData, idmapSize, cookie, copyData); + idmapData, idmapSize, appAsLib, cookie, copyData, isSystemAsset); } -status_t ResTable::add(ResTable* src) +status_t ResTable::add(ResTable* src, bool isSystemAsset) { mError = src->mError; - for (size_t i=0; i<src->mHeaders.size(); i++) { + for (size_t i=0; i < src->mHeaders.size(); i++) { mHeaders.add(src->mHeaders[i]); } - for (size_t i=0; i<src->mPackageGroups.size(); i++) { + for (size_t i=0; i < src->mPackageGroups.size(); i++) { PackageGroup* srcPg = src->mPackageGroups[i]; - PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id); + PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id, + false /* appAsLib */, isSystemAsset || srcPg->isSystemAsset); for (size_t j=0; j<srcPg->packages.size(); j++) { pg->packages.add(srcPg->packages[j]); } @@ -3644,7 +3761,7 @@ status_t ResTable::addEmpty(const int32_t cookie) { } status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize, - const int32_t cookie, bool copyData) + bool appAsLib, const int32_t cookie, bool copyData, bool isSystemAsset) { if (!data) { return NO_ERROR; @@ -3747,7 +3864,8 @@ status_t ResTable::addInternal(const void* data, size_t dataSize, const void* id return (mError=BAD_TYPE); } - if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) { + if (parsePackage( + (ResTable_package*)chunk, header, appAsLib, isSystemAsset) != NO_ERROR) { return mError; } curPackage++; @@ -3818,7 +3936,9 @@ bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* ou if (Res_GETPACKAGE(resID)+1 == 0) { ALOGW("No package identifier when getting name for resource number 0x%08x", resID); } else { +#ifndef STATIC_ANDROIDFW_FOR_TOOLS ALOGW("No known package when getting name for resource number 0x%08x", resID); +#endif } return false; } @@ -4074,39 +4194,32 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, } // First see if we've already computed this bag... - if (grp->bags) { - bag_set** typeSet = grp->bags->get(t); - if (typeSet) { - bag_set* set = typeSet[e]; - if (set) { - if (set != (bag_set*)0xFFFFFFFF) { - if (outTypeSpecFlags != NULL) { - *outTypeSpecFlags = set->typeSpecFlags; - } - *outBag = (bag_entry*)(set+1); - if (kDebugTableSuperNoisy) { - ALOGI("Found existing bag for: 0x%x\n", resID); - } - return set->numAttrs; + TypeCacheEntry& cacheEntry = grp->typeCacheEntries.editItemAt(t); + bag_set** typeSet = cacheEntry.cachedBags; + if (typeSet) { + bag_set* set = typeSet[e]; + if (set) { + if (set != (bag_set*)0xFFFFFFFF) { + if (outTypeSpecFlags != NULL) { + *outTypeSpecFlags = set->typeSpecFlags; } - ALOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.", - resID); - return BAD_INDEX; + *outBag = (bag_entry*)(set+1); + if (kDebugTableSuperNoisy) { + ALOGI("Found existing bag for: 0x%x\n", resID); + } + return set->numAttrs; } + ALOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.", + resID); + return BAD_INDEX; } } // Bag not found, we need to compute it! - if (!grp->bags) { - grp->bags = new ByteBucketArray<bag_set**>(); - if (!grp->bags) return NO_MEMORY; - } - - bag_set** typeSet = grp->bags->get(t); if (!typeSet) { typeSet = (bag_set**)calloc(NENTRY, sizeof(bag_set*)); if (!typeSet) return NO_MEMORY; - grp->bags->set(t, typeSet); + cacheEntry.cachedBags = typeSet; } // Mark that we are currently working on this one. @@ -4312,18 +4425,56 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, void ResTable::setParameters(const ResTable_config* params) { - mLock.lock(); + AutoMutex _lock(mLock); + AutoMutex _lock2(mFilteredConfigLock); + if (kDebugTableGetEntry) { ALOGI("Setting parameters: %s\n", params->toString().string()); } mParams = *params; - for (size_t i=0; i<mPackageGroups.size(); i++) { + for (size_t p = 0; p < mPackageGroups.size(); p++) { + PackageGroup* packageGroup = mPackageGroups.editItemAt(p); if (kDebugTableNoisy) { - ALOGI("CLEARING BAGS FOR GROUP %zu!", i); + ALOGI("CLEARING BAGS FOR GROUP %zu!", p); + } + packageGroup->clearBagCache(); + + // Find which configurations match the set of parameters. This allows for a much + // faster lookup in getEntry() if the set of values is narrowed down. + for (size_t t = 0; t < packageGroup->types.size(); t++) { + if (packageGroup->types[t].isEmpty()) { + continue; + } + + TypeList& typeList = packageGroup->types.editItemAt(t); + + // Retrieve the cache entry for this type. + TypeCacheEntry& cacheEntry = packageGroup->typeCacheEntries.editItemAt(t); + + for (size_t ts = 0; ts < typeList.size(); ts++) { + Type* type = typeList.editItemAt(ts); + + std::shared_ptr<Vector<const ResTable_type*>> newFilteredConfigs = + std::make_shared<Vector<const ResTable_type*>>(); + + for (size_t ti = 0; ti < type->configs.size(); ti++) { + ResTable_config config; + config.copyFromDtoH(type->configs[ti]->config); + + if (config.match(mParams)) { + newFilteredConfigs->add(type->configs[ti]); + } + } + + if (kDebugTableNoisy) { + ALOGD("Updating pkg=%zu type=%zu with %zu filtered configs", + p, t, newFilteredConfigs->size()); + } + + cacheEntry.filteredConfigs.add(newFilteredConfigs); + } } - mPackageGroups[i]->clearBagCache(); } - mLock.unlock(); } void ResTable::getParameters(ResTable_config* params) const @@ -5660,11 +5811,22 @@ const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) con return NULL; } -void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap) const -{ +static bool compareResTableConfig(const ResTable_config& a, const ResTable_config& b) { + return a.compare(b) < 0; +} + +void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap, + bool ignoreAndroidPackage, bool includeSystemConfigs) const { const size_t packageCount = mPackageGroups.size(); + String16 android("android"); for (size_t i = 0; i < packageCount; i++) { const PackageGroup* packageGroup = mPackageGroups[i]; + if (ignoreAndroidPackage && android == packageGroup->name) { + continue; + } + if (!includeSystemConfigs && packageGroup->isSystemAsset) { + continue; + } const size_t typeCount = packageGroup->types.size(); for (size_t j = 0; j < typeCount; j++) { const TypeList& typeList = packageGroup->types[j]; @@ -5683,17 +5845,11 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi ResTable_config cfg; memset(&cfg, 0, sizeof(ResTable_config)); cfg.copyFromDtoH(config->config); - // only insert unique - const size_t N = configs->size(); - size_t n; - for (n = 0; n < N; n++) { - if (0 == (*configs)[n].compare(cfg)) { - break; - } - } - // if we didn't find it - if (n == N) { - configs->add(cfg); + + auto iter = std::lower_bound(configs->begin(), configs->end(), cfg, + compareResTableConfig); + if (iter == configs->end() || iter->compare(cfg) != 0) { + configs->insertAt(cfg, std::distance(configs->begin(), iter)); } } } @@ -5701,26 +5857,29 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi } } -void ResTable::getLocales(Vector<String8>* locales) const +static bool compareString8AndCString(const String8& str, const char* cStr) { + return strcmp(str.string(), cStr) < 0; +} + +void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales) const { Vector<ResTable_config> configs; ALOGV("calling getConfigurations"); - getConfigurations(&configs); + getConfigurations(&configs, + false /* ignoreMipmap */, + false /* ignoreAndroidPackage */, + includeSystemLocales /* includeSystemConfigs */); ALOGV("called getConfigurations size=%d", (int)configs.size()); const size_t I = configs.size(); char locale[RESTABLE_MAX_LOCALE_LEN]; for (size_t i=0; i<I; i++) { configs[i].getBcp47Locale(locale); - const size_t J = locales->size(); - size_t j; - for (j=0; j<J; j++) { - if (0 == strcmp(locale, (*locales)[j].string())) { - break; - } - } - if (j == J) { - locales->add(String8(locale)); + + auto iter = std::lower_bound(locales->begin(), locales->end(), locale, + compareString8AndCString); + if (iter == locales->end() || strcmp(iter->string(), locale) != 0) { + locales->insertAt(String8(locale), std::distance(locales->begin(), iter)); } } } @@ -5846,9 +6005,32 @@ status_t ResTable::getEntry( specFlags = -1; } - const size_t numConfigs = typeSpec->configs.size(); + const Vector<const ResTable_type*>* candidateConfigs = &typeSpec->configs; + + std::shared_ptr<Vector<const ResTable_type*>> filteredConfigs; + if (config && memcmp(&mParams, config, sizeof(mParams)) == 0) { + // Grab the lock first so we can safely get the current filtered list. + AutoMutex _lock(mFilteredConfigLock); + + // This configuration is equal to the one we have previously cached for, + // so use the filtered configs. + + const TypeCacheEntry& cacheEntry = packageGroup->typeCacheEntries[typeIndex]; + if (i < cacheEntry.filteredConfigs.size()) { + if (cacheEntry.filteredConfigs[i]) { + // Grab a reference to the shared_ptr so it doesn't get destroyed while + // going through this list. + filteredConfigs = cacheEntry.filteredConfigs[i]; + + // Use this filtered list. + candidateConfigs = filteredConfigs.get(); + } + } + } + + const size_t numConfigs = candidateConfigs->size(); for (size_t c = 0; c < numConfigs; c++) { - const ResTable_type* const thisType = typeSpec->configs[c]; + const ResTable_type* const thisType = candidateConfigs->itemAt(c); if (thisType == NULL) { continue; } @@ -5931,7 +6113,7 @@ status_t ResTable::getEntry( } status_t ResTable::parsePackage(const ResTable_package* const pkg, - const Header* const header) + const Header* const header, bool appAsLib, bool isSystemAsset) { const uint8_t* base = (const uint8_t*)pkg; status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset), @@ -5979,8 +6161,8 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, if (id >= 256) { LOG_ALWAYS_FATAL("Package id out of range"); return NO_ERROR; - } else if (id == 0) { - // This is a library so assign an ID + } else if (id == 0 || appAsLib || isSystemAsset) { + // This is a library or a system asset, so assign an ID id = mNextPackageId++; } @@ -6012,7 +6194,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])]; strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0])); - group = new PackageGroup(this, String16(tmpName), id); + group = new PackageGroup(this, String16(tmpName), id, appAsLib, isSystemAsset); if (group == NULL) { delete package; return (mError=NO_MEMORY); @@ -6224,8 +6406,9 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, return NO_ERROR; } -DynamicRefTable::DynamicRefTable(uint8_t packageId) +DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib) : mAssignedPackageId(packageId) + , mAppAsLib(appAsLib) { memset(mLookupTable, 0, sizeof(mLookupTable)); @@ -6310,16 +6493,18 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { uint32_t res = *resId; size_t packageId = Res_GETPACKAGE(res) + 1; - if (packageId == APP_PACKAGE_ID) { + if (packageId == APP_PACKAGE_ID && !mAppAsLib) { // No lookup needs to be done, app package IDs are absolute. return NO_ERROR; } - if (packageId == 0) { + if (packageId == 0 || (packageId == APP_PACKAGE_ID && mAppAsLib)) { // The package ID is 0x00. That means that a shared library is accessing - // its own local resource, so we fix up the resource with the calling - // package ID. - *resId |= ((uint32_t) mAssignedPackageId) << 24; + // its own local resource. + // Or if app resource is loaded as shared library, the resource which has + // app package Id is local resources. + // so we fix up those resources with the calling package ID. + *resId = (0xFFFFFF & (*resId)) | (((uint32_t) mAssignedPackageId) << 24); return NO_ERROR; } @@ -6341,7 +6526,10 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { } status_t DynamicRefTable::lookupResourceValue(Res_value* value) const { - if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE) { + if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE && + (value->dataType != Res_value::TYPE_REFERENCE || !mAppAsLib)) { + // If the package is loaded as shared library, the resource reference + // also need to be fixed. return NO_ERROR; } diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp index a6f6d8c4ff16..49fe8a261178 100644 --- a/libs/androidfw/ZipFileRO.cpp +++ b/libs/androidfw/ZipFileRO.cpp @@ -39,7 +39,7 @@ using namespace android; class _ZipEntryRO { public: ZipEntry entry; - ZipEntryName name; + ZipString name; void *cookie; _ZipEntryRO() : cookie(NULL) {} @@ -80,7 +80,7 @@ ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const { _ZipEntryRO* data = new _ZipEntryRO; - data->name = ZipEntryName(entryName); + data->name = ZipString(entryName); const int32_t error = FindEntry(mHandle, data->name, &(data->entry)); if (error) { @@ -133,8 +133,8 @@ bool ZipFileRO::startIteration(void** cookie) { bool ZipFileRO::startIteration(void** cookie, const char* prefix, const char* suffix) { _ZipEntryRO* ze = new _ZipEntryRO; - ZipEntryName pe(prefix ? prefix : ""); - ZipEntryName se(suffix ? suffix : ""); + ZipString pe(prefix ? prefix : ""); + ZipString se(suffix ? suffix : ""); int32_t error = StartIteration(mHandle, &(ze->cookie), prefix ? &pe : NULL, suffix ? &se : NULL); diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk index a353575b4073..2bc026b79ca2 100644 --- a/libs/androidfw/tests/Android.mk +++ b/libs/androidfw/tests/Android.mk @@ -21,6 +21,7 @@ LOCAL_PATH:= $(call my-dir) testFiles := \ + AppAsLib_test.cpp \ AttributeFinder_test.cpp \ ByteBucketArray_test.cpp \ Config_test.cpp \ diff --git a/libs/androidfw/tests/AppAsLib_test.cpp b/libs/androidfw/tests/AppAsLib_test.cpp new file mode 100644 index 000000000000..8489acf6246f --- /dev/null +++ b/libs/androidfw/tests/AppAsLib_test.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 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 <androidfw/ResourceTypes.h> + +#include "data/appaslib/R.h" + +#include <gtest/gtest.h> + +using namespace android; + +namespace { + +#include "data/appaslib/appaslib_arsc.h" +#include "data/appaslib/appaslib_lib_arsc.h" + +// This tests the app resources loaded as app. +TEST(AppAsLibTest, loadedAsApp) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(appaslib_arsc, appaslib_arsc_len)); + + Res_value val; + ssize_t block = table.getResource(appaslib::R::app::integer::number1, &val); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + ASSERT_EQ(appaslib::R::app::array::integerArray1, val.data); +} + +// This tests the app resources loaded as shared-lib. +TEST(AppAsLibTest, loadedAsSharedLib) { + ResTable table; + // Load as shared library. + ASSERT_EQ(NO_ERROR, table.add(appaslib_arsc, appaslib_arsc_len, NULL, 0, -1, false, true)); + + Res_value val; + ssize_t block = table.getResource(appaslib::R::lib::integer::number1, &val); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + ASSERT_EQ(appaslib::R::lib::array::integerArray1, val.data); +} + +// This tests the shared-lib loaded with appAsLib as true. +TEST(AppAsLibTest, loadedSharedLib) { + ResTable table; + // Load shared library with appAsLib as true. + ASSERT_EQ(NO_ERROR, table.add(appaslib_lib_arsc, appaslib_lib_arsc_len, NULL, 0, -1, false, true)); + + Res_value val; + ssize_t block = table.getResource(appaslib::R::lib::integer::number1, &val); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType); + ASSERT_EQ(appaslib::R::lib::array::integerArray1, val.data); +} + +} diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp index 92af7fe790bd..e25b616dcbd9 100644 --- a/libs/androidfw/tests/BackupData_test.cpp +++ b/libs/androidfw/tests/BackupData_test.cpp @@ -108,7 +108,7 @@ TEST_F(BackupDataTest, WriteAndReadSingle) { EXPECT_EQ(DATA1[i], dataBytes[i]) << "data character " << i << " should be equal"; } - delete dataBytes; + delete[] dataBytes; delete writer; delete reader; } diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp index 49995942a562..2bf9b12b6ce5 100644 --- a/libs/androidfw/tests/ConfigLocale_test.cpp +++ b/libs/androidfw/tests/ConfigLocale_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <androidfw/LocaleData.h> #include <androidfw/ResourceTypes.h> #include <utils/Log.h> #include <utils/String8.h> @@ -28,7 +29,7 @@ TEST(ConfigLocaleTest, packAndUnpack2LetterLanguage) { EXPECT_EQ('e', config.language[0]); EXPECT_EQ('n', config.language[1]); - char out[4] = { 1, 1, 1, 1}; + char out[4] = {1, 1, 1, 1}; config.unpackLanguage(out); EXPECT_EQ('e', out[0]); EXPECT_EQ('n', out[1]); @@ -51,7 +52,7 @@ TEST(ConfigLocaleTest, packAndUnpack2LetterRegion) { EXPECT_EQ('U', config.country[0]); EXPECT_EQ('S', config.country[1]); - char out[4] = { 1, 1, 1, 1}; + char out[4] = {1, 1, 1, 1}; config.unpackRegion(out); EXPECT_EQ('U', out[0]); EXPECT_EQ('S', out[1]); @@ -67,7 +68,7 @@ TEST(ConfigLocaleTest, packAndUnpack3LetterLanguage) { EXPECT_EQ('\x99', config.language[0]); EXPECT_EQ('\xA4', config.language[1]); - char out[4] = { 1, 1, 1, 1}; + char out[4] = {1, 1, 1, 1}; config.unpackLanguage(out); EXPECT_EQ('e', out[0]); EXPECT_EQ('n', out[1]); @@ -91,7 +92,7 @@ TEST(ConfigLocaleTest, packAndUnpack3LetterLanguageAtOffset16) { EXPECT_EQ(char(0xbc), config.language[0]); EXPECT_EQ(char(0xd3), config.language[1]); - char out[4] = { 1, 1, 1, 1}; + char out[4] = {1, 1, 1, 1}; config.unpackLanguage(out); EXPECT_EQ('t', out[0]); EXPECT_EQ('g', out[1]); @@ -103,7 +104,7 @@ TEST(ConfigLocaleTest, packAndUnpack3LetterRegion) { ResTable_config config; config.packRegion("419"); - char out[4] = { 1, 1, 1, 1}; + char out[4] = {1, 1, 1, 1}; config.unpackRegion(out); EXPECT_EQ('4', out[0]); @@ -124,6 +125,10 @@ TEST(ConfigLocaleTest, packAndUnpack3LetterRegion) { if (script != NULL) { memcpy(out->localeScript, script, 4); + out->localeScriptWasComputed = false; + } else { + out->computeScript(); + out->localeScriptWasComputed = true; } if (variant != NULL) { @@ -177,11 +182,12 @@ TEST(ConfigLocaleTest, setLocale) { EXPECT_EQ('n', test.language[1]); EXPECT_EQ('U', test.country[0]); EXPECT_EQ('S', test.country[1]); - EXPECT_EQ(0, test.localeScript[0]); + EXPECT_TRUE(test.localeScriptWasComputed); + EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4)); EXPECT_EQ(0, test.localeVariant[0]); test.setBcp47Locale("eng-419"); - char out[4] = { 1, 1, 1, 1}; + char out[4] = {1, 1, 1, 1}; test.unpackLanguage(out); EXPECT_EQ('e', out[0]); EXPECT_EQ('n', out[1]); @@ -193,17 +199,458 @@ TEST(ConfigLocaleTest, setLocale) { EXPECT_EQ('1', out[1]); EXPECT_EQ('9', out[2]); - test.setBcp47Locale("en-Latn-419"); - memset(out, 1, 4); EXPECT_EQ('e', test.language[0]); EXPECT_EQ('n', test.language[1]); - EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4)); + EXPECT_FALSE(test.localeScriptWasComputed); + memset(out, 1, 4); test.unpackRegion(out); EXPECT_EQ('4', out[0]); EXPECT_EQ('1', out[1]); EXPECT_EQ('9', out[2]); + + test.setBcp47Locale("de-1901"); + memset(out, 1, 4); + test.unpackLanguage(out); + EXPECT_EQ('d', out[0]); + EXPECT_EQ('e', out[1]); + EXPECT_EQ('\0', out[2]); + EXPECT_TRUE(test.localeScriptWasComputed); + EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4)); + memset(out, 1, 4); + test.unpackRegion(out); + EXPECT_EQ('\0', out[0]); + EXPECT_EQ(0, strcmp("1901", test.localeVariant)); + + test.setBcp47Locale("de-Latn-1901"); + memset(out, 1, 4); + test.unpackLanguage(out); + EXPECT_EQ('d', out[0]); + EXPECT_EQ('e', out[1]); + EXPECT_EQ('\0', out[2]); + EXPECT_FALSE(test.localeScriptWasComputed); + EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4)); + memset(out, 1, 4); + test.unpackRegion(out); + EXPECT_EQ('\0', out[0]); + EXPECT_EQ(0, strcmp("1901", test.localeVariant)); +} + +TEST(ConfigLocaleTest, computeScript) { + ResTable_config config; + + fillIn(NULL, NULL, NULL, NULL, &config); + EXPECT_EQ(0, memcmp("\0\0\0\0", config.localeScript, 4)); + + fillIn("zh", "TW", NULL, NULL, &config); + EXPECT_EQ(0, memcmp("Hant", config.localeScript, 4)); + + fillIn("zh", "CN", NULL, NULL, &config); + EXPECT_EQ(0, memcmp("Hans", config.localeScript, 4)); + + fillIn("az", NULL, NULL, NULL, &config); + EXPECT_EQ(0, memcmp("Latn", config.localeScript, 4)); + + fillIn("az", "AZ", NULL, NULL, &config); + EXPECT_EQ(0, memcmp("Latn", config.localeScript, 4)); + + fillIn("az", "IR", NULL, NULL, &config); + EXPECT_EQ(0, memcmp("Arab", config.localeScript, 4)); + + fillIn("peo", NULL, NULL, NULL, &config); + EXPECT_EQ(0, memcmp("Xpeo", config.localeScript, 4)); + + fillIn("qaa", NULL, NULL, NULL, &config); + EXPECT_EQ(0, memcmp("\0\0\0\0", config.localeScript, 4)); +} + +TEST(ConfigLocaleTest, getBcp47Locale_script) { + ResTable_config config; + fillIn("en", NULL, "Latn", NULL, &config); + + char out[RESTABLE_MAX_LOCALE_LEN]; + config.localeScriptWasComputed = false; + config.getBcp47Locale(out); + EXPECT_EQ(0, strcmp("en-Latn", out)); + + config.localeScriptWasComputed = true; + config.getBcp47Locale(out); + EXPECT_EQ(0, strcmp("en", out)); +} + +TEST(ConfigLocaleTest, match) { + ResTable_config supported, requested; + + fillIn(NULL, NULL, NULL, NULL, &supported); + fillIn("fr", "CA", NULL, NULL, &requested); + // Empty locale matches everything (as a default). + EXPECT_TRUE(supported.match(requested)); + + fillIn("en", "CA", NULL, NULL, &supported); + fillIn("fr", "CA", NULL, NULL, &requested); + // Different languages don't match. + EXPECT_FALSE(supported.match(requested)); + + fillIn("qaa", "FR", NULL, NULL, &supported); + fillIn("qaa", "CA", NULL, NULL, &requested); + // If we can't infer the scripts, different regions don't match. + EXPECT_FALSE(supported.match(requested)); + + fillIn("qaa", "FR", "Latn", NULL, &supported); + fillIn("qaa", "CA", NULL, NULL, &requested); + // If we can't infer any of the scripts, different regions don't match. + EXPECT_FALSE(supported.match(requested)); + + fillIn("qaa", "FR", NULL, NULL, &supported); + fillIn("qaa", "CA", "Latn", NULL, &requested); + // If we can't infer any of the scripts, different regions don't match. + EXPECT_FALSE(supported.match(requested)); + + fillIn("qaa", NULL, NULL, NULL, &supported); + fillIn("qaa", "CA", NULL, NULL, &requested); + // language-only resources still support language+region requests, even if we can't infer the + // script. + EXPECT_TRUE(supported.match(requested)); + + fillIn("qaa", "CA", NULL, NULL, &supported); + fillIn("qaa", "CA", NULL, NULL, &requested); + // Even if we can't infer the scripts, exactly equal locales match. + EXPECT_TRUE(supported.match(requested)); + + fillIn("az", NULL, NULL, NULL, &supported); + fillIn("az", NULL, "Latn", NULL, &requested); + // If the resolved scripts are the same, it doesn't matter if they were explicitly provided + // or not, and they match. + EXPECT_TRUE(supported.match(requested)); + + fillIn("az", NULL, NULL, NULL, &supported); + fillIn("az", NULL, "Cyrl", NULL, &requested); + // If the resolved scripts are different, they don't match. + EXPECT_FALSE(supported.match(requested)); + + fillIn("az", NULL, NULL, NULL, &supported); + fillIn("az", "IR", NULL, NULL, &requested); + // If the resolved scripts are different, they don't match. + EXPECT_FALSE(supported.match(requested)); + + fillIn("az", "IR", NULL, NULL, &supported); + fillIn("az", NULL, "Arab", NULL, &requested); + // If the resolved scripts are the same, it doesn't matter if they were explicitly provided + // or not, and they match. + EXPECT_TRUE(supported.match(requested)); + + fillIn("en", NULL, NULL, NULL, &supported); + fillIn("en", "XA", NULL, NULL, &requested); + // en-XA is a pseudo-locale, and English resources are not a match for it. + EXPECT_FALSE(supported.match(requested)); + + fillIn("en", "XA", NULL, NULL, &supported); + fillIn("en", NULL, NULL, NULL, &requested); + // en-XA is a pseudo-locale, and its resources don't support English locales. + EXPECT_FALSE(supported.match(requested)); + + fillIn("en", "XA", NULL, NULL, &supported); + fillIn("en", "XA", NULL, NULL, &requested); + // Even if they are pseudo-locales, exactly equal locales match. + EXPECT_TRUE(supported.match(requested)); + + fillIn("ar", NULL, NULL, NULL, &supported); + fillIn("ar", "XB", NULL, NULL, &requested); + // ar-XB is a pseudo-locale, and Arabic resources are not a match for it. + EXPECT_FALSE(supported.match(requested)); + + fillIn("ar", "XB", NULL, NULL, &supported); + fillIn("ar", NULL, NULL, NULL, &requested); + // ar-XB is a pseudo-locale, and its resources don't support Arabic locales. + EXPECT_FALSE(supported.match(requested)); + + fillIn("ar", "XB", NULL, NULL, &supported); + fillIn("ar", "XB", NULL, NULL, &requested); + // Even if they are pseudo-locales, exactly equal locales match. + EXPECT_TRUE(supported.match(requested)); +} + +TEST(ConfigLocaleTest, match_emptyScript) { + ResTable_config supported, requested; + + fillIn("fr", "FR", NULL, NULL, &supported); + fillIn("fr", "CA", NULL, NULL, &requested); + + // emulate packages built with older AAPT + memset(supported.localeScript, '\0', 4); + supported.localeScriptWasComputed = false; + + EXPECT_TRUE(supported.match(requested)); +} + +TEST(ConfigLocaleTest, isLocaleBetterThan_basics) { + ResTable_config config1, config2, request; + + fillIn(NULL, NULL, NULL, NULL, &request); + fillIn("fr", "FR", NULL, NULL, &config1); + fillIn("fr", "CA", NULL, NULL, &config2); + EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("fr", "CA", NULL, NULL, &request); + fillIn(NULL, NULL, NULL, NULL, &config1); + fillIn(NULL, NULL, NULL, NULL, &config2); + EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("fr", "CA", NULL, NULL, &request); + fillIn("fr", "FR", NULL, NULL, &config1); + fillIn(NULL, NULL, NULL, NULL, &config2); + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("de", "DE", NULL, NULL, &request); + fillIn("de", "DE", NULL, "1901", &config1); + fillIn("de", "DE", NULL, "1996", &config2); + EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("de", "DE", NULL, "1901", &request); + fillIn("de", "DE", NULL, "1901", &config1); + fillIn("de", "DE", NULL, NULL, &config2); + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("de", "DE", NULL, "1901", &request); + fillIn("de", "DE", NULL, "1996", &config1); + fillIn("de", "DE", NULL, NULL, &config2); + EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); +} + +TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) { + ResTable_config config1, config2, request; + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", "419", NULL, NULL, &config1); + fillIn("es", "419", NULL, NULL, &config2); + // Both supported locales are the same, so none is better than the other. + EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", "AR", NULL, NULL, &config1); + fillIn("es", "419", NULL, NULL, &config2); + // An exact locale match is better than a parent. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", "419", NULL, NULL, &config1); + fillIn("es", NULL, NULL, NULL, &config2); + // A closer parent is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", "419", NULL, NULL, &config1); + fillIn("es", "ES", NULL, NULL, &config2); + // A parent is better than a non-parent representative locale. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", NULL, NULL, NULL, &config1); + fillIn("es", "ES", NULL, NULL, &config2); + // A parent is better than a non-parent representative locale. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", "PE", NULL, NULL, &config1); + fillIn("es", "ES", NULL, NULL, &config2); + // A closer locale is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", "MX", NULL, NULL, &config1); + fillIn("es", "BO", NULL, NULL, &config2); + // A representative locale is better if they are equidistant. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", "US", NULL, NULL, &config1); + fillIn("es", "BO", NULL, NULL, &config2); + // A representative locale is better if they are equidistant. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "AR", NULL, NULL, &request); + fillIn("es", "MX", NULL, NULL, &config1); + fillIn("es", "US", NULL, NULL, &config2); + // If all is equal, the locale earlier in the dictionary is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("es", "GQ", NULL, NULL, &request); + fillIn("es", "IC", NULL, NULL, &config1); + fillIn("es", "419", NULL, NULL, &config2); + // If all is equal, the locale earlier in the dictionary is better and + // letters are better than numbers. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "GB", NULL, NULL, &request); + fillIn("en", "001", NULL, NULL, &config1); + fillIn("en", NULL, NULL, NULL, &config2); + // A closer parent is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "PR", NULL, NULL, &request); + fillIn("en", NULL, NULL, NULL, &config1); + fillIn("en", "001", NULL, NULL, &config2); + // A parent is better than a non-parent. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "DE", NULL, NULL, &request); + fillIn("en", "150", NULL, NULL, &config1); + fillIn("en", "001", NULL, NULL, &config2); + // A closer parent is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "IN", NULL, NULL, &request); + fillIn("en", "AU", NULL, NULL, &config1); + fillIn("en", "US", NULL, NULL, &config2); + // A closer locale is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "PR", NULL, NULL, &request); + fillIn("en", "001", NULL, NULL, &config1); + fillIn("en", "GB", NULL, NULL, &config2); + // A closer locale is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "IN", NULL, NULL, &request); + fillIn("en", "GB", NULL, NULL, &config1); + fillIn("en", "AU", NULL, NULL, &config2); + // A representative locale is better if they are equidistant. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "IN", NULL, NULL, &request); + fillIn("en", "AU", NULL, NULL, &config1); + fillIn("en", "CA", NULL, NULL, &config2); + // If all is equal, the locale earlier in the dictionary is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("pt", "MZ", NULL, NULL, &request); + fillIn("pt", "PT", NULL, NULL, &config1); + fillIn("pt", NULL, NULL, NULL, &config2); + // A closer parent is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("pt", "MZ", NULL, NULL, &request); + fillIn("pt", "PT", NULL, NULL, &config1); + fillIn("pt", "BR", NULL, NULL, &config2); + // A parent is better than a non-parent. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("zh", "MO", "Hant", NULL, &request); + fillIn("zh", "HK", "Hant", NULL, &config1); + fillIn("zh", "TW", "Hant", NULL, &config2); + // A parent is better than a non-parent. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("zh", "US", "Hant", NULL, &request); + fillIn("zh", "TW", "Hant", NULL, &config1); + fillIn("zh", "HK", "Hant", NULL, &config2); + // A representative locale is better if they are equidistant. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("ar", "DZ", NULL, NULL, &request); + fillIn("ar", "015", NULL, NULL, &config1); + fillIn("ar", NULL, NULL, NULL, &config2); + // A closer parent is better. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("ar", "EG", NULL, NULL, &request); + fillIn("ar", NULL, NULL, NULL, &config1); + fillIn("ar", "015", NULL, NULL, &config2); + // A parent is better than a non-parent. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("ar", "QA", NULL, NULL, &request); + fillIn("ar", "EG", NULL, NULL, &config1); + fillIn("ar", "BH", NULL, NULL, &config2); + // A representative locale is better if they are equidistant. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("ar", "QA", NULL, NULL, &request); + fillIn("ar", "SA", NULL, NULL, &config1); + fillIn("ar", "015", NULL, NULL, &config2); + // If all is equal, the locale earlier in the dictionary is better and + // letters are better than numbers. + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); +} + +// Default resources are considered better matches for US English +// and US-like English locales than International English locales +TEST(ConfigLocaleTest, isLocaleBetterThan_UsEnglishIsSpecial) { + ResTable_config config1, config2, request; + + fillIn("en", "US", NULL, NULL, &request); + fillIn(NULL, NULL, NULL, NULL, &config1); + fillIn("en", "001", NULL, NULL, &config2); + // default is better than International English + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "US", NULL, NULL, &request); + fillIn(NULL, NULL, NULL, NULL, &config1); + fillIn("en", "GB", NULL, NULL, &config2); + // default is better than British English + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "PR", NULL, NULL, &request); + fillIn(NULL, NULL, NULL, NULL, &config1); + fillIn("en", "001", NULL, NULL, &config2); + // Even for Puerto Rico, default is better than International English + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "US", NULL, NULL, &request); + fillIn("en", NULL, NULL, NULL, &config1); + fillIn(NULL, NULL, NULL, NULL, &config2); + // "English" is better than default, since it's a parent of US English + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "PR", NULL, NULL, &request); + fillIn("en", NULL, NULL, NULL, &config1); + fillIn(NULL, NULL, NULL, NULL, &config2); + // "English" is better than default, since it's a parent of Puerto Rico English + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("en", "US", NULL, NULL, &request); + fillIn(NULL, NULL, NULL, NULL, &config1); + fillIn("en", "PR", NULL, NULL, &config2); + // For US English itself, we prefer default to its siblings in the parent tree + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); } -} // namespace android. +} // namespace android diff --git a/libs/androidfw/tests/ResTable_test.cpp b/libs/androidfw/tests/ResTable_test.cpp index dcfe91e3866d..b8b46259f0d1 100644 --- a/libs/androidfw/tests/ResTable_test.cpp +++ b/libs/androidfw/tests/ResTable_test.cpp @@ -39,8 +39,20 @@ namespace { */ #include "data/basic/basic_arsc.h" +/** + * Include a binary library resource table. + * + * Package: com.android.test.basic + */ #include "data/lib/lib_arsc.h" +/** + * Include a system resource table. + * + * Package: android + */ +#include "data/system/system_arsc.h" + TEST(ResTableTest, shouldLoadSuccessfully) { ResTable table; ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); @@ -282,4 +294,67 @@ TEST(ResTableTest, U16StringToInt) { testU16StringToInt(u"0x1ffffffff", 0U, false, true); } +TEST(ResTableTest, ShareButDontModifyResTable) { + ResTable sharedTable; + ASSERT_EQ(NO_ERROR, sharedTable.add(basic_arsc, basic_arsc_len)); + + ResTable_config param; + memset(¶m, 0, sizeof(param)); + param.language[0] = 'v'; + param.language[1] = 's'; + sharedTable.setParameters(¶m); + + // Check that we get the default value for @integer:number1 + Res_value val; + ssize_t block = sharedTable.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + ASSERT_EQ(uint32_t(600), val.data); + + // Create a new table that shares the entries of the shared table. + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(&sharedTable, false)); + + // Set a new configuration on the new table. + memset(¶m, 0, sizeof(param)); + param.language[0] = 's'; + param.language[1] = 'v'; + param.country[0] = 'S'; + param.country[1] = 'E'; + table.setParameters(¶m); + + // Check that we get a new value in the new table. + block = table.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + ASSERT_EQ(uint32_t(400), val.data); + + // Check that we still get the old value in the shared table. + block = sharedTable.getResource(base::R::integer::number1, &val, MAY_NOT_BE_BAG); + ASSERT_GE(block, 0); + ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType); + ASSERT_EQ(uint32_t(600), val.data); +} + +TEST(ResTableTest, GetConfigurationsReturnsUniqueList) { + ResTable table; + ASSERT_EQ(NO_ERROR, table.add(system_arsc, system_arsc_len)); + ASSERT_EQ(NO_ERROR, table.add(basic_arsc, basic_arsc_len)); + + ResTable_config configSv; + memset(&configSv, 0, sizeof(configSv)); + configSv.language[0] = 's'; + configSv.language[1] = 'v'; + + Vector<ResTable_config> configs; + table.getConfigurations(&configs); + + EXPECT_EQ(1, std::count(configs.begin(), configs.end(), configSv)); + + Vector<String8> locales; + table.getLocales(&locales); + + EXPECT_EQ(1, std::count(locales.begin(), locales.end(), String8("sv"))); } + +} // namespace diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h index ac80d8868090..ff9be164dbb0 100644 --- a/libs/androidfw/tests/TestHelpers.h +++ b/libs/androidfw/tests/TestHelpers.h @@ -21,7 +21,7 @@ namespace android { enum { MAY_NOT_BE_BAG = false }; static inline bool operator==(const android::ResTable_config& a, const android::ResTable_config& b) { - return memcmp(&a, &b, sizeof(a)) == 0; + return a.compare(b) == 0; } static inline ::std::ostream& operator<<(::std::ostream& out, const android::ResTable_config& c) { diff --git a/libs/androidfw/tests/ZipUtils_test.cpp b/libs/androidfw/tests/ZipUtils_test.cpp index c6038b597f4e..7293843e066a 100644 --- a/libs/androidfw/tests/ZipUtils_test.cpp +++ b/libs/androidfw/tests/ZipUtils_test.cpp @@ -45,7 +45,7 @@ TEST_F(ZipUtilsTest, ZipTimeConvertSuccess) { EXPECT_EQ(2011, t.tm_year + 1900) << "Year was improperly converted."; - EXPECT_EQ(6, t.tm_mon) + EXPECT_EQ(5, t.tm_mon) << "Month was improperly converted."; EXPECT_EQ(29, t.tm_mday) @@ -59,6 +59,11 @@ TEST_F(ZipUtilsTest, ZipTimeConvertSuccess) { EXPECT_EQ(40, t.tm_sec) << "Second was improperly converted."; + + // We don't have enough information to determine timezone related info. + EXPECT_EQ(-1, t.tm_isdst); + EXPECT_EQ(0, t.tm_gmtoff); + EXPECT_EQ(nullptr, t.tm_zone); } } diff --git a/libs/androidfw/tests/data/appaslib/AndroidManifest.xml b/libs/androidfw/tests/data/appaslib/AndroidManifest.xml new file mode 100644 index 000000000000..e00045b0aa12 --- /dev/null +++ b/libs/androidfw/tests/data/appaslib/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.basic"> + <application> + </application> +</manifest> diff --git a/libs/androidfw/tests/data/appaslib/R.h b/libs/androidfw/tests/data/appaslib/R.h new file mode 100644 index 000000000000..3af921a7ba65 --- /dev/null +++ b/libs/androidfw/tests/data/appaslib/R.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __APPASLIB_R_H +#define __APPASLIB_R_H + +namespace appaslib { +namespace R { +namespace lib { +namespace integer { + enum { + number1 = 0x02020000, // default + }; +} + +namespace array { + enum { + integerArray1 = 0x02030000, // default + }; +} +} // namespace lib + +namespace app { +namespace integer { + enum { + number1 = 0x7f020000, // default + }; +} + +namespace array { + enum { + integerArray1 = 0x7f030000, // default + }; +} +} // namespace app +} // namespace R +} // namespace appaslib + +#endif // __APPASLIB_R_H diff --git a/libs/androidfw/tests/data/appaslib/appaslib_arsc.h b/libs/androidfw/tests/data/appaslib/appaslib_arsc.h new file mode 100644 index 000000000000..be176ab5e63f --- /dev/null +++ b/libs/androidfw/tests/data/appaslib/appaslib_arsc.h @@ -0,0 +1,68 @@ +unsigned char appaslib_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0x04, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xdc, 0x02, 0x00, 0x00, + 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, + 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, + 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, + 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, + 0x72, 0x00, 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00, 0x5c, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x7f, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, + 0x03, 0x00, 0x00, 0x00 +}; +unsigned int appaslib_arsc_len = 772; diff --git a/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h b/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h new file mode 100644 index 000000000000..099285a17aad --- /dev/null +++ b/libs/androidfw/tests/data/appaslib/appaslib_lib_arsc.h @@ -0,0 +1,68 @@ +unsigned char appaslib_lib_arsc[] = { + 0x02, 0x00, 0x0c, 0x00, 0x04, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xdc, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, + 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, + 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, + 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, 0x69, 0x00, 0x63, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x74, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, + 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x61, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, + 0x79, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x00, 0x00, 0x00, 0x07, 0x00, 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, + 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x67, 0x00, 0x65, 0x00, + 0x72, 0x00, 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, + 0x31, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00, 0x5c, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x48, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, + 0x03, 0x00, 0x00, 0x00 +}; +unsigned int appaslib_lib_arsc_len = 772; diff --git a/libs/androidfw/tests/data/appaslib/build b/libs/androidfw/tests/data/appaslib/build new file mode 100755 index 000000000000..e4bd88b4032c --- /dev/null +++ b/libs/androidfw/tests/data/appaslib/build @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Copyright (C) 2015 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. +# + +PATH_TO_FRAMEWORK_RES=$(gettop)/prebuilts/sdk/current/android.jar + +aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES -F bundle.apk -f && \ +unzip bundle.apk resources.arsc && \ +mv resources.arsc appaslib.arsc && \ +xxd -i appaslib.arsc > appaslib_arsc.h && \ +aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES -F bundle.apk -f --shared-lib && \ +unzip bundle.apk resources.arsc && \ +mv resources.arsc appaslib_lib.arsc && \ +xxd -i appaslib_lib.arsc > appaslib_lib_arsc.h \ + diff --git a/libs/androidfw/tests/data/appaslib/res/values/values.xml b/libs/androidfw/tests/data/appaslib/res/values/values.xml new file mode 100644 index 000000000000..39b99a6bfb81 --- /dev/null +++ b/libs/androidfw/tests/data/appaslib/res/values/values.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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. +--> + +<resources> + <integer name="number1">@array/integerArray1</integer> + <integer-array name="integerArray1"> + <item>1</item> + <item>2</item> + <item>3</item> + </integer-array> +</resources> diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h index aaac74059f58..6694dd0c36e1 100644 --- a/libs/androidfw/tests/data/basic/R.h +++ b/libs/androidfw/tests/data/basic/R.h @@ -46,7 +46,7 @@ namespace string { namespace integer { enum { - number1 = 0x7f040000, // default, sv + number1 = 0x7f040000, // default, sv, vs number2 = 0x7f040001, // default test3 = 0x7f090000, // default (in feature) diff --git a/libs/androidfw/tests/data/basic/basic_arsc.h b/libs/androidfw/tests/data/basic/basic_arsc.h index 13ab4fa0d0ea..e497401bdddb 100644 --- a/libs/androidfw/tests/data/basic/basic_arsc.h +++ b/libs/androidfw/tests/data/basic/basic_arsc.h @@ -1,5 +1,5 @@ unsigned char basic_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0x68, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0x0c, 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, @@ -16,7 +16,7 @@ unsigned char basic_arsc[] = { 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x31, 0x00, 0x00, 0x00, 0x05, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, - 0xa0, 0x06, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0x44, 0x07, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, @@ -73,68 +73,81 @@ unsigned char basic_arsc[] = { 0x41, 0x00, 0x72, 0x00, 0x72, 0x00, 0x61, 0x00, 0x79, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, 0x8c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, - 0x05, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x24, 0x00, 0x00, - 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x24, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x58, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x74, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x4c, 0x00, 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, - 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x6c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x4c, 0x00, 0x74, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0xc8, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x44, 0x00, 0x5c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, + 0xc8, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06, 0x7f, 0x01, 0x02, 0x4c, 0x00, + 0x64, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x54, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x76, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x90, 0x01, 0x00, 0x00, - 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x44, 0x00, 0x90, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, 0x58, 0x02, 0x00, 0x00, + 0x01, 0x02, 0x4c, 0x00, 0x64, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x74, 0x6e, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, + 0x90, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, 0x98, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -146,16 +159,17 @@ unsigned char basic_arsc[] = { 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x7f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, 0x08, 0x00, 0x00, 0x10, 0x2c, 0x01, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, - 0x7c, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, + 0x84, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x08, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, + 0x08, 0x00, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, + 0x08, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00 }; -unsigned int basic_arsc_len = 1896; +unsigned int basic_arsc_len = 2060; diff --git a/libs/androidfw/tests/data/basic/res/values-vs/values.xml b/libs/androidfw/tests/data/basic/res/values-vs/values.xml new file mode 100644 index 000000000000..4a5a64016e68 --- /dev/null +++ b/libs/androidfw/tests/data/basic/res/values-vs/values.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2014 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. +--> + +<resources> + <integer name="number1">600</integer> +</resources> diff --git a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h index b742d2811487..a2aa598e5c90 100644 --- a/libs/androidfw/tests/data/basic/split_de_fr_arsc.h +++ b/libs/androidfw/tests/data/basic/split_de_fr_arsc.h @@ -1,5 +1,5 @@ unsigned char split_de_fr_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0xe4, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0xf4, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, @@ -10,7 +10,7 @@ unsigned char split_de_fr_arsc[] = { 0x32, 0x00, 0x00, 0x00, 0x07, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x61, 0x00, 0x69, 0x00, 0x20, 0x00, 0x31, 0x00, 0x00, 0x00, 0x07, 0x00, 0x65, 0x00, 0x73, 0x00, 0x73, 0x00, 0x61, 0x00, 0x69, 0x00, 0x20, 0x00, - 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x5c, 0x03, 0x00, 0x00, + 0x32, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0x6c, 0x03, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, @@ -57,30 +57,32 @@ unsigned char split_de_fr_arsc[] = { 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, - 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x44, 0x00, 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, - 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x74, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x74, 0x6e, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, + 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; -unsigned int split_de_fr_arsc_len = 996; +unsigned int split_de_fr_arsc_len = 1012; diff --git a/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h index e9fb7ea47b75..0cc39154a5ec 100644 --- a/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h +++ b/libs/androidfw/tests/data/basic/split_hdpi_v4_arsc.h @@ -1,10 +1,10 @@ unsigned char split_hdpi_v4_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0x08, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0x10, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x68, 0x00, 0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, - 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, + 0xd8, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x61, 0x00, 0x73, 0x00, @@ -50,19 +50,20 @@ unsigned char split_hdpi_v4_arsc[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x4c, 0x00, 0x68, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; -unsigned int split_hdpi_v4_arsc_len = 776; +unsigned int split_hdpi_v4_arsc_len = 784; diff --git a/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h index 7835f71419f2..d44ba9630aba 100644 --- a/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h +++ b/libs/androidfw/tests/data/basic/split_xhdpi_v4_arsc.h @@ -1,10 +1,10 @@ unsigned char split_xhdpi_v4_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0x14, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x78, 0x00, 0x68, 0x00, 0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x20, 0x01, 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x20, 0x01, 0xd8, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, @@ -50,19 +50,20 @@ unsigned char split_xhdpi_v4_arsc[] = { 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, 0x68, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -unsigned int split_xhdpi_v4_arsc_len = 780; +unsigned int split_xhdpi_v4_arsc_len = 788; diff --git a/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h b/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h index f805db1b009e..2f3f682fb6e4 100644 --- a/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h +++ b/libs/androidfw/tests/data/basic/split_xxhdpi_v4_arsc.h @@ -1,10 +1,10 @@ unsigned char split_xxhdpi_v4_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0x14, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x78, 0x00, 0x78, 0x00, 0x68, 0x00, 0x64, 0x00, 0x70, 0x00, 0x69, 0x00, 0x00, 0x00, - 0x00, 0x02, 0x20, 0x01, 0xd0, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x20, 0x01, 0xd8, 0x02, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x62, 0x00, @@ -50,19 +50,20 @@ unsigned char split_xxhdpi_v4_arsc[] = { 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x44, 0x00, 0x60, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x4c, 0x00, 0x68, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -unsigned int split_xxhdpi_v4_arsc_len = 780; +unsigned int split_xxhdpi_v4_arsc_len = 788; diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h index 27f25fec0d01..6a31fb8ff088 100644 --- a/libs/androidfw/tests/data/system/R.h +++ b/libs/androidfw/tests/data/system/R.h @@ -33,6 +33,12 @@ namespace style { }; } +namespace integer { + enum { + number = 0x01030000, // sv + }; +} + } // namespace R } // namespace android diff --git a/libs/androidfw/tests/data/system/res/values-sv/values.xml b/libs/androidfw/tests/data/system/res/values-sv/values.xml new file mode 100644 index 000000000000..b97bdb68aca7 --- /dev/null +++ b/libs/androidfw/tests/data/system/res/values-sv/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<resources> + <public type="integer" name="number" id="0x01030000" /> + <integer name="number">1</integer> +</resources> diff --git a/libs/androidfw/tests/data/system/system_arsc.h b/libs/androidfw/tests/data/system/system_arsc.h index 215ecae552c5..b0dab6b357e9 100644 --- a/libs/androidfw/tests/data/system/system_arsc.h +++ b/libs/androidfw/tests/data/system/system_arsc.h @@ -1,8 +1,8 @@ unsigned char system_arsc[] = { - 0x02, 0x00, 0x0c, 0x00, 0x18, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x0c, 0x00, 0xf8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xf0, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x20, 0x01, 0xd0, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x69, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -25,26 +25,33 @@ unsigned char system_arsc[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, - 0x72, 0x00, 0x00, 0x00, 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, - 0x6c, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, - 0x70, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x98, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x78, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x73, 0x00, 0x74, 0x00, 0x79, 0x00, 0x6c, 0x00, 0x65, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, + 0x67, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x5e, 0x00, + 0x61, 0x00, 0x74, 0x00, 0x74, 0x00, 0x72, 0x00, 0x2d, 0x00, 0x70, 0x00, + 0x72, 0x00, 0x69, 0x00, 0x76, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x1c, 0x00, 0x84, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x62, 0x00, 0x61, 0x00, 0x63, 0x00, 0x6b, 0x00, 0x67, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x66, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x65, 0x00, 0x67, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x00, 0x00, 0x09, 0x00, 0x54, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x65, 0x00, - 0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, - 0x01, 0x02, 0x44, 0x00, 0x84, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x2e, 0x00, 0x4f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x6e, 0x00, 0x75, 0x00, 0x6d, 0x00, 0x62, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x4c, 0x00, 0x8c, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -55,15 +62,27 @@ unsigned char system_arsc[] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x10, 0x11, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x44, 0x00, - 0x70, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x48, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x4c, 0x00, + 0x78, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0xff, 0xff, - 0x01, 0x00, 0x01, 0x01, 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xff + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, + 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x01, 0x01, + 0x08, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xff, 0x02, 0x02, 0x10, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x01, 0x02, 0x4c, 0x00, 0x60, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x76, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x10, + 0x01, 0x00, 0x00, 0x00, 0x02, 0x02, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; -unsigned int system_arsc_len = 792; +unsigned int system_arsc_len = 1016; diff --git a/libs/common_time/common_time_server.cpp b/libs/common_time/common_time_server.cpp index 01372e00cab8..f72ffaa63712 100644 --- a/libs/common_time/common_time_server.cpp +++ b/libs/common_time/common_time_server.cpp @@ -143,7 +143,7 @@ CommonTimeServer::CommonTimeServer() // Create the eventfd we will use to signal our thread to wake up when // needed. - mWakeupThreadFD = eventfd(0, EFD_NONBLOCK); + mWakeupThreadFD = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); // seed the random number generator (used to generated timeline IDs) srand48(static_cast<unsigned int>(systemTime())); diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp index a4100a2d44fb..20ecda28b22a 100644 --- a/libs/hwui/AmbientShadow.cpp +++ b/libs/hwui/AmbientShadow.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - /** * Extra vertices for the corner for smoother corner. * Only for outer vertices. @@ -54,15 +52,14 @@ // If this is set to negative value, then all the edge will be tessellated. #define ALPHA_THRESHOLD (0.1f / 255.0f) -#include <math.h> -#include <utils/Log.h> -#include <utils/Vector.h> - #include "AmbientShadow.h" + #include "ShadowTessellator.h" #include "Vertex.h" #include "VertexBuffer.h" -#include "utils/MathUtils.h" + +#include <algorithm> +#include <utils/Log.h> namespace android { namespace uirenderer { @@ -81,7 +78,7 @@ inline Vector2 getNormalFromVertices(const Vector3* vertices, int current, int n // The input z value will be converted to be non-negative inside. // The output must be ranged from 0 to 1. inline float getAlphaFromFactoredZ(float factoredZ) { - return 1.0 / (1 + MathUtils::max(factoredZ, 0.0f)); + return 1.0 / (1 + std::max(factoredZ, 0.0f)); } // The shader is using gaussian function e^-(1-x)*(1-x)*4, therefore, we transform diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk deleted file mode 100644 index 38e8be907720..000000000000 --- a/libs/hwui/Android.common.mk +++ /dev/null @@ -1,127 +0,0 @@ -# getConfig in external/skia/include/core/SkBitmap.h is deprecated. -# Allow Gnu extension: in-class initializer of static 'const float' member. -# DeferredLayerUpdater.h: private field 'mRenderThread' is not used. -LOCAL_CLANG_CFLAGS += \ - -Wno-deprecated-declarations \ - -Wno-gnu-static-float-init \ - -Wno-unused-private-field - -LOCAL_SRC_FILES := \ - font/CacheTexture.cpp \ - font/Font.cpp \ - renderstate/Blend.cpp \ - renderstate/MeshState.cpp \ - renderstate/PixelBufferState.cpp \ - renderstate/RenderState.cpp \ - renderstate/Scissor.cpp \ - renderstate/Stencil.cpp \ - renderstate/TextureState.cpp \ - renderthread/CanvasContext.cpp \ - renderthread/DrawFrameTask.cpp \ - renderthread/EglManager.cpp \ - renderthread/RenderProxy.cpp \ - renderthread/RenderTask.cpp \ - renderthread/RenderThread.cpp \ - renderthread/TimeLord.cpp \ - thread/TaskManager.cpp \ - utils/Blur.cpp \ - utils/GLUtils.cpp \ - utils/LinearAllocator.cpp \ - utils/SortedListImpl.cpp \ - AmbientShadow.cpp \ - AnimationContext.cpp \ - Animator.cpp \ - AnimatorManager.cpp \ - AssetAtlas.cpp \ - Caches.cpp \ - CanvasState.cpp \ - ClipArea.cpp \ - DamageAccumulator.cpp \ - DeferredDisplayList.cpp \ - DeferredLayerUpdater.cpp \ - DisplayList.cpp \ - DisplayListCanvas.cpp \ - Dither.cpp \ - Extensions.cpp \ - FboCache.cpp \ - FontRenderer.cpp \ - FrameInfo.cpp \ - FrameInfoVisualizer.cpp \ - GammaFontRenderer.cpp \ - GlopBuilder.cpp \ - GradientCache.cpp \ - Image.cpp \ - Interpolator.cpp \ - JankTracker.cpp \ - Layer.cpp \ - LayerCache.cpp \ - LayerRenderer.cpp \ - Matrix.cpp \ - OpenGLRenderer.cpp \ - Patch.cpp \ - PatchCache.cpp \ - PathCache.cpp \ - PathTessellator.cpp \ - PixelBuffer.cpp \ - Program.cpp \ - ProgramCache.cpp \ - Properties.cpp \ - RenderBufferCache.cpp \ - RenderNode.cpp \ - RenderProperties.cpp \ - ResourceCache.cpp \ - ShadowTessellator.cpp \ - SkiaCanvas.cpp \ - SkiaCanvasProxy.cpp \ - SkiaShader.cpp \ - Snapshot.cpp \ - SpotShadow.cpp \ - TessellationCache.cpp \ - TextDropShadowCache.cpp \ - Texture.cpp \ - TextureCache.cpp - -intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,) - -LOCAL_C_INCLUDES += \ - external/skia/src/core - -LOCAL_CFLAGS += -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES -LOCAL_SHARED_LIBRARIES := liblog libcutils libutils libEGL libGLESv2 libskia libui libgui - -ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) - LOCAL_CFLAGS += -DANDROID_ENABLE_RENDERSCRIPT - LOCAL_SHARED_LIBRARIES += libRS libRScpp - LOCAL_C_INCLUDES += \ - $(intermediates) \ - frameworks/rs/cpp \ - frameworks/rs \ - -endif - -ifndef HWUI_COMPILE_SYMBOLS - LOCAL_CFLAGS += -fvisibility=hidden -endif - -ifdef HWUI_COMPILE_FOR_PERF - # TODO: Non-arm? - LOCAL_CFLAGS += -fno-omit-frame-pointer -marm -mapcs -endif - -ifeq (true, $(HWUI_NULL_GPU)) - LOCAL_SRC_FILES += \ - tests/nullegl.cpp \ - tests/nullgles.cpp - - LOCAL_CFLAGS += -DHWUI_NULL_GPU -endif - -# Defaults for ATRACE_TAG and LOG_TAG for libhwui -LOCAL_CFLAGS += -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" -LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wunreachable-code -LOCAL_CFLAGS += -ffast-math -O3 - -# b/21698669 -ifneq ($(USE_CLANG_PLATFORM_BUILD),true) - LOCAL_CFLAGS += -Werror -endif diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 91e289c32b97..8857c41dc33d 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -2,11 +2,340 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +HWUI_NEW_OPS := true + +# Enables fine-grained GLES error checking +# If set to true, every GLES call is wrapped & error checked +# Has moderate overhead +HWUI_ENABLE_OPENGL_VALIDATION := false + +hwui_src_files := \ + font/CacheTexture.cpp \ + font/Font.cpp \ + hwui/Canvas.cpp \ + hwui/MinikinSkia.cpp \ + hwui/MinikinUtils.cpp \ + hwui/PaintImpl.cpp \ + hwui/Typeface.cpp \ + renderstate/Blend.cpp \ + renderstate/MeshState.cpp \ + renderstate/OffscreenBufferPool.cpp \ + renderstate/PixelBufferState.cpp \ + renderstate/RenderState.cpp \ + renderstate/Scissor.cpp \ + renderstate/Stencil.cpp \ + renderstate/TextureState.cpp \ + renderthread/CanvasContext.cpp \ + renderthread/DrawFrameTask.cpp \ + renderthread/EglManager.cpp \ + renderthread/RenderProxy.cpp \ + renderthread/RenderTask.cpp \ + renderthread/RenderThread.cpp \ + renderthread/TimeLord.cpp \ + thread/TaskManager.cpp \ + utils/Blur.cpp \ + utils/GLUtils.cpp \ + utils/LinearAllocator.cpp \ + utils/NinePatchImpl.cpp \ + utils/StringUtils.cpp \ + utils/TestWindowContext.cpp \ + utils/VectorDrawableUtils.cpp \ + AmbientShadow.cpp \ + AnimationContext.cpp \ + Animator.cpp \ + AnimatorManager.cpp \ + AssetAtlas.cpp \ + Caches.cpp \ + CanvasState.cpp \ + ClipArea.cpp \ + DamageAccumulator.cpp \ + DeferredDisplayList.cpp \ + DeferredLayerUpdater.cpp \ + DeviceInfo.cpp \ + DisplayList.cpp \ + DisplayListCanvas.cpp \ + Dither.cpp \ + Extensions.cpp \ + FboCache.cpp \ + FontRenderer.cpp \ + FrameInfo.cpp \ + FrameInfoVisualizer.cpp \ + GammaFontRenderer.cpp \ + GlopBuilder.cpp \ + GpuMemoryTracker.cpp \ + GradientCache.cpp \ + Image.cpp \ + Interpolator.cpp \ + JankTracker.cpp \ + Layer.cpp \ + LayerCache.cpp \ + LayerRenderer.cpp \ + LayerUpdateQueue.cpp \ + Matrix.cpp \ + OpenGLRenderer.cpp \ + Patch.cpp \ + PatchCache.cpp \ + PathCache.cpp \ + PathTessellator.cpp \ + PathParser.cpp \ + PixelBuffer.cpp \ + Program.cpp \ + ProgramCache.cpp \ + Properties.cpp \ + PropertyValuesHolder.cpp \ + PropertyValuesAnimatorSet.cpp \ + Readback.cpp \ + RenderBufferCache.cpp \ + RenderNode.cpp \ + RenderProperties.cpp \ + ResourceCache.cpp \ + ShadowTessellator.cpp \ + SkiaCanvas.cpp \ + SkiaCanvasProxy.cpp \ + SkiaShader.cpp \ + Snapshot.cpp \ + SpotShadow.cpp \ + TessellationCache.cpp \ + TextDropShadowCache.cpp \ + Texture.cpp \ + TextureCache.cpp \ + VectorDrawable.cpp \ + protos/hwui.proto + +hwui_test_common_src_files := \ + $(call all-cpp-files-under, tests/common/scenes) \ + tests/common/TestContext.cpp \ + tests/common/TestScene.cpp \ + tests/common/TestUtils.cpp + +hwui_cflags := \ + -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES \ + -DATRACE_TAG=ATRACE_TAG_VIEW -DLOG_TAG=\"OpenGLRenderer\" \ + -Wall -Wno-unused-parameter -Wunreachable-code -Werror + +# GCC false-positives on this warning, and since we -Werror that's +# a problem +hwui_cflags += -Wno-free-nonheap-object + +ifeq (true, $(HWUI_NEW_OPS)) + hwui_src_files += \ + BakedOpDispatcher.cpp \ + BakedOpRenderer.cpp \ + BakedOpState.cpp \ + FrameBuilder.cpp \ + LayerBuilder.cpp \ + OpDumper.cpp \ + RecordingCanvas.cpp + + hwui_cflags += -DHWUI_NEW_OPS + +endif + +ifndef HWUI_COMPILE_SYMBOLS + hwui_cflags += -fvisibility=hidden +endif + +ifdef HWUI_COMPILE_FOR_PERF + # TODO: Non-arm? + hwui_cflags += -fno-omit-frame-pointer -marm -mapcs +endif + +# This has to be lazy-resolved because it depends on the LOCAL_MODULE_CLASS +# which varies depending on what is being built +define hwui_proto_include +$(call local-generated-sources-dir)/proto/$(LOCAL_PATH) +endef + +hwui_c_includes += \ + external/skia/include/private \ + external/skia/src/core \ + external/harfbuzz_ng/src \ + external/freetype/include + +ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) + hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT + hwui_c_includes += \ + $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,) \ + frameworks/rs/cpp \ + frameworks/rs +endif + +ifeq (true, $(HWUI_ENABLE_OPENGL_VALIDATION)) + hwui_cflags += -include debug/wrap_gles.h + hwui_src_files += debug/wrap_gles.cpp + hwui_c_includes += frameworks/native/opengl/libs/GLES2 + hwui_cflags += -DDEBUG_OPENGL=3 +endif + + +# ------------------------ +# static library +# ------------------------ + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := libhwui_static +LOCAL_CFLAGS := $(hwui_cflags) +LOCAL_SRC_FILES := $(hwui_src_files) +LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include) +LOCAL_EXPORT_C_INCLUDE_DIRS := \ + $(LOCAL_PATH) \ + $(hwui_c_includes) \ + $(call hwui_proto_include) + +include $(LOCAL_PATH)/hwui_static_deps.mk +include $(BUILD_STATIC_LIBRARY) + +# ------------------------ +# static library null gpu +# ------------------------ + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := libhwui_static_null_gpu +LOCAL_CFLAGS := \ + $(hwui_cflags) \ + -DHWUI_NULL_GPU +LOCAL_SRC_FILES := \ + $(hwui_src_files) \ + debug/nullegl.cpp \ + debug/nullgles.cpp +LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include) +LOCAL_EXPORT_C_INCLUDE_DIRS := \ + $(LOCAL_PATH) \ + $(hwui_c_includes) \ + $(call hwui_proto_include) + +include $(LOCAL_PATH)/hwui_static_deps.mk +include $(BUILD_STATIC_LIBRARY) + +# ------------------------ +# shared library +# ------------------------ + +include $(CLEAR_VARS) + LOCAL_MODULE_CLASS := SHARED_LIBRARIES LOCAL_MODULE := libhwui +LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) -include $(LOCAL_PATH)/Android.common.mk - +include $(LOCAL_PATH)/hwui_static_deps.mk include $(BUILD_SHARED_LIBRARY) -include $(call all-makefiles-under,$(LOCAL_PATH)) +# ------------------------ +# unit tests +# ------------------------ + +include $(CLEAR_VARS) + +LOCAL_MODULE := hwui_unit_tests +LOCAL_MODULE_TAGS := tests +LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu +LOCAL_SHARED_LIBRARIES := libmemunreachable +LOCAL_CFLAGS := \ + $(hwui_cflags) \ + -DHWUI_NULL_GPU + +LOCAL_SRC_FILES += \ + $(hwui_test_common_src_files) \ + tests/unit/main.cpp \ + tests/unit/CanvasStateTests.cpp \ + tests/unit/ClipAreaTests.cpp \ + tests/unit/DamageAccumulatorTests.cpp \ + tests/unit/DeviceInfoTests.cpp \ + tests/unit/FatVectorTests.cpp \ + tests/unit/FontRendererTests.cpp \ + tests/unit/GlopBuilderTests.cpp \ + tests/unit/GpuMemoryTrackerTests.cpp \ + tests/unit/GradientCacheTests.cpp \ + tests/unit/LayerUpdateQueueTests.cpp \ + tests/unit/LinearAllocatorTests.cpp \ + tests/unit/MatrixTests.cpp \ + tests/unit/OffscreenBufferPoolTests.cpp \ + tests/unit/RenderNodeTests.cpp \ + tests/unit/SkiaBehaviorTests.cpp \ + tests/unit/SnapshotTests.cpp \ + tests/unit/StringUtilsTests.cpp \ + tests/unit/TextDropShadowCacheTests.cpp \ + tests/unit/VectorDrawableTests.cpp + +ifeq (true, $(HWUI_NEW_OPS)) + LOCAL_SRC_FILES += \ + tests/unit/BakedOpDispatcherTests.cpp \ + tests/unit/BakedOpRendererTests.cpp \ + tests/unit/BakedOpStateTests.cpp \ + tests/unit/FrameBuilderTests.cpp \ + tests/unit/LeakCheckTests.cpp \ + tests/unit/OpDumperTests.cpp \ + tests/unit/RecordingCanvasTests.cpp \ + tests/unit/SkiaCanvasTests.cpp +endif + +include $(LOCAL_PATH)/hwui_static_deps.mk +include $(BUILD_NATIVE_TEST) + +# ------------------------ +# Macro-bench app +# ------------------------ + +include $(CLEAR_VARS) + +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp +LOCAL_MODULE:= hwuitest +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MULTILIB := both +LOCAL_MODULE_STEM_32 := hwuitest +LOCAL_MODULE_STEM_64 := hwuitest64 +LOCAL_CFLAGS := $(hwui_cflags) + +# set to libhwui_static_null_gpu to skip actual GL commands +LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static + +LOCAL_SRC_FILES += \ + $(hwui_test_common_src_files) \ + tests/macrobench/TestSceneRunner.cpp \ + tests/macrobench/main.cpp + +include $(LOCAL_PATH)/hwui_static_deps.mk +include $(BUILD_EXECUTABLE) + +# ------------------------ +# Micro-bench app +# --------------------- +include $(CLEAR_VARS) + +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local/tmp +LOCAL_MODULE:= hwuimicro +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MULTILIB := both +LOCAL_MODULE_STEM_32 := hwuimicro +LOCAL_MODULE_STEM_64 := hwuimicro64 +LOCAL_CFLAGS := \ + $(hwui_cflags) \ + -DHWUI_NULL_GPU + +LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu +LOCAL_STATIC_LIBRARIES := libgoogle-benchmark + +LOCAL_SRC_FILES += \ + $(hwui_test_common_src_files) \ + tests/microbench/main.cpp \ + tests/microbench/DisplayListCanvasBench.cpp \ + tests/microbench/FontBench.cpp \ + tests/microbench/LinearAllocatorBench.cpp \ + tests/microbench/PathParserBench.cpp \ + tests/microbench/ShadowBench.cpp \ + tests/microbench/TaskManagerBench.cpp + +ifeq (true, $(HWUI_NEW_OPS)) + LOCAL_SRC_FILES += \ + tests/microbench/FrameBuilderBench.cpp +endif + +include $(LOCAL_PATH)/hwui_static_deps.mk +include $(BUILD_EXECUTABLE) diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index 512e0e24aa93..4d65782f684b 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -33,16 +33,18 @@ namespace uirenderer { BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue) : mTarget(nullptr) + , mStagingTarget(nullptr) , mFinalValue(finalValue) , mDeltaValue(0) , mFromValue(0) - , mStagingPlayState(NOT_STARTED) - , mPlayState(NOT_STARTED) + , mStagingPlayState(PlayState::NotStarted) + , mPlayState(PlayState::NotStarted) , mHasStartValue(false) , mStartTime(0) , mDuration(300) , mStartDelay(0) - , mMayRunAsync(true) { + , mMayRunAsync(true) + , mPlayTime(0) { } BaseRenderNodeAnimator::~BaseRenderNodeAnimator() { @@ -50,7 +52,7 @@ BaseRenderNodeAnimator::~BaseRenderNodeAnimator() { void BaseRenderNodeAnimator::checkMutable() { // Should be impossible to hit as the Java-side also has guards for this - LOG_ALWAYS_FATAL_IF(mStagingPlayState != NOT_STARTED, + LOG_ALWAYS_FATAL_IF(mStagingPlayState != PlayState::NotStarted, "Animator has already been started!"); } @@ -81,23 +83,133 @@ void BaseRenderNodeAnimator::setStartDelay(nsecs_t startDelay) { } void BaseRenderNodeAnimator::attach(RenderNode* target) { - mTarget = target; + mStagingTarget = target; onAttached(); } +void BaseRenderNodeAnimator::start() { + mStagingPlayState = PlayState::Running; + mStagingRequests.push_back(Request::Start); + onStagingPlayStateChanged(); +} + +void BaseRenderNodeAnimator::cancel() { + mStagingPlayState = PlayState::Finished; + mStagingRequests.push_back(Request::Cancel); + onStagingPlayStateChanged(); +} + +void BaseRenderNodeAnimator::reset() { + mStagingPlayState = PlayState::Finished; + mStagingRequests.push_back(Request::Reset); + onStagingPlayStateChanged(); +} + +void BaseRenderNodeAnimator::reverse() { + mStagingPlayState = PlayState::Reversing; + mStagingRequests.push_back(Request::Reverse); + onStagingPlayStateChanged(); +} + +void BaseRenderNodeAnimator::end() { + mStagingPlayState = PlayState::Finished; + mStagingRequests.push_back(Request::End); + onStagingPlayStateChanged(); +} + +void BaseRenderNodeAnimator::resolveStagingRequest(Request request) { + switch (request) { + case Request::Start: + mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ? + mPlayTime : 0; + mPlayState = PlayState::Running; + break; + case Request::Reverse: + mPlayTime = (mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) ? + mPlayTime : mDuration; + mPlayState = PlayState::Reversing; + break; + case Request::Reset: + mPlayTime = 0; + mPlayState = PlayState::Finished; + break; + case Request::Cancel: + mPlayState = PlayState::Finished; + break; + case Request::End: + mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration; + mPlayState = PlayState::Finished; + break; + default: + LOG_ALWAYS_FATAL("Invalid staging request: %d", static_cast<int>(request)); + }; +} + void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { + if (mStagingTarget) { + RenderNode* oldTarget = mTarget; + mTarget = mStagingTarget; + mStagingTarget = nullptr; + if (oldTarget && oldTarget != mTarget) { + oldTarget->onAnimatorTargetChanged(this); + } + } + if (!mHasStartValue) { doSetStartValue(getValue(mTarget)); } - if (mStagingPlayState > mPlayState) { - mPlayState = mStagingPlayState; - // Oh boy, we're starting! Man the battle stations! - if (mPlayState == RUNNING) { - transitionToRunning(context); - } else if (mPlayState == FINISHED) { + + if (!mStagingRequests.empty()) { + // No interpolator was set, use the default + if (mPlayState == PlayState::NotStarted && !mInterpolator) { + mInterpolator.reset(Interpolator::createDefaultInterpolator()); + } + // Keep track of the play state and play time before they are changed when + // staging requests are resolved. + nsecs_t currentPlayTime = mPlayTime; + PlayState prevFramePlayState = mPlayState; + + // Resolve staging requests one by one. + for (Request request : mStagingRequests) { + resolveStagingRequest(request); + } + mStagingRequests.clear(); + + if (mStagingPlayState == PlayState::Finished) { + // Set the staging play time and end the animation + updatePlayTime(mPlayTime); callOnFinishedListener(context); + } else if (mStagingPlayState == PlayState::Running + || mStagingPlayState == PlayState::Reversing) { + bool changed = currentPlayTime != mPlayTime || prevFramePlayState != mStagingPlayState; + if (prevFramePlayState != mStagingPlayState) { + transitionToRunning(context); + } + if (changed) { + // Now we need to seek to the stagingPlayTime (i.e. the animation progress that was + // requested from UI thread). It is achieved by modifying mStartTime, such that + // current time - mStartTime = stagingPlayTime (or mDuration -stagingPlayTime in the + // case of reversing) + nsecs_t currentFrameTime = context.frameTimeMs(); + if (mPlayState == PlayState::Reversing) { + // Reverse is not supported for animations with a start delay, so here we + // assume no start delay. + mStartTime = currentFrameTime - (mDuration - mPlayTime); + } else { + // Animation should play forward + if (mPlayTime == 0) { + // If the request is to start from the beginning, include start delay. + mStartTime = currentFrameTime + mStartDelay; + } else { + // If the request is to seek to a non-zero play time, then we skip start + // delay. + mStartTime = currentFrameTime - mPlayTime; + } + } + } } } + onPushStaging(); } void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) { @@ -114,55 +226,57 @@ void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) { // Set to 0 so that the animate() basically instantly finishes mStartTime = 0; } - // No interpolator was set, use the default - if (!mInterpolator) { - mInterpolator.reset(Interpolator::createDefaultInterpolator()); - } - if (mDuration < 0 || mDuration > 50000) { + if (mDuration < 0) { ALOGW("Your duration is strange and confusing: %" PRId64, mDuration); } } bool BaseRenderNodeAnimator::animate(AnimationContext& context) { - if (mPlayState < RUNNING) { + if (mPlayState < PlayState::Running) { return false; } - if (mPlayState == FINISHED) { + if (mPlayState == PlayState::Finished) { return true; } + // This should be set before setValue() so animators can query this time when setValue + // is called. + nsecs_t currentPlayTime = context.frameTimeMs() - mStartTime; + bool finished = updatePlayTime(currentPlayTime); + if (finished && mPlayState != PlayState::Finished) { + mPlayState = PlayState::Finished; + callOnFinishedListener(context); + } + return finished; +} + +bool BaseRenderNodeAnimator::updatePlayTime(nsecs_t playTime) { + mPlayTime = mPlayState == PlayState::Reversing ? mDuration - playTime : playTime; + onPlayTimeChanged(mPlayTime); // If BaseRenderNodeAnimator is handling the delay (not typical), then // because the staging properties reflect the final value, we always need // to call setValue even if the animation isn't yet running or is still // being delayed as we need to override the staging value - if (mStartTime > context.frameTimeMs()) { + if (playTime < 0) { setValue(mTarget, mFromValue); return false; } float fraction = 1.0f; - if (mPlayState == RUNNING && mDuration > 0) { - fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration; - } - if (fraction >= 1.0f) { - fraction = 1.0f; - mPlayState = FINISHED; + if ((mPlayState == PlayState::Running || mPlayState == PlayState::Reversing) && mDuration > 0) { + fraction = mPlayTime / (float) mDuration; } + fraction = MathUtils::clamp(fraction, 0.0f, 1.0f); fraction = mInterpolator->interpolate(fraction); setValue(mTarget, mFromValue + (mDeltaValue * fraction)); - if (mPlayState == FINISHED) { - callOnFinishedListener(context); - return true; - } - - return false; + return playTime >= mDuration; } void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) { - if (mPlayState < FINISHED) { - mPlayState = FINISHED; + if (mPlayState < PlayState::Finished) { + mPlayState = PlayState::Finished; callOnFinishedListener(context); } } @@ -206,18 +320,36 @@ RenderPropertyAnimator::RenderPropertyAnimator(RenderProperty property, float fi void RenderPropertyAnimator::onAttached() { if (!mHasStartValue - && mTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) { - setStartValue((mTarget->stagingProperties().*mPropertyAccess->getter)()); + && mStagingTarget->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) { + setStartValue((mStagingTarget->stagingProperties().*mPropertyAccess->getter)()); } } void RenderPropertyAnimator::onStagingPlayStateChanged() { - if (mStagingPlayState == RUNNING) { - (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); - } else if (mStagingPlayState == FINISHED) { + if (mStagingPlayState == PlayState::Running) { + if (mStagingTarget) { + (mStagingTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); + } else { + // In the case of start delay where stagingTarget has been sync'ed over and null'ed + // we delay the properties update to push staging. + mShouldUpdateStagingProperties = true; + } + } else if (mStagingPlayState == PlayState::Finished) { // We're being canceled, so make sure that whatever values the UI thread // is observing for us is pushed over + mShouldSyncPropertyFields = true; + } +} + +void RenderPropertyAnimator::onPushStaging() { + if (mShouldUpdateStagingProperties) { + (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); + mShouldUpdateStagingProperties = false; + } + + if (mShouldSyncPropertyFields) { mTarget->setPropertyFieldsDirty(dirtyMask()); + mShouldSyncPropertyFields = false; } } diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h index 1b3d8e7f4842..fdae0f32d4e6 100644 --- a/libs/hwui/Animator.h +++ b/libs/hwui/Animator.h @@ -24,6 +24,8 @@ #include "utils/Macros.h" +#include <vector> + namespace android { namespace uirenderer { @@ -59,8 +61,14 @@ public: mMayRunAsync = mayRunAsync; } bool mayRunAsync() { return mMayRunAsync; } - ANDROID_API void start() { mStagingPlayState = RUNNING; onStagingPlayStateChanged(); } - ANDROID_API void end() { mStagingPlayState = FINISHED; onStagingPlayStateChanged(); } + ANDROID_API void start(); + ANDROID_API void reset(); + ANDROID_API void reverse(); + // Terminates the animation at its current progress. + ANDROID_API void cancel(); + + // Terminates the animation and skip to the end of the animation. + ANDROID_API void end(); void attach(RenderNode* target); virtual void onAttached() {} @@ -68,33 +76,58 @@ public: void pushStaging(AnimationContext& context); bool animate(AnimationContext& context); - bool isRunning() { return mPlayState == RUNNING; } - bool isFinished() { return mPlayState == FINISHED; } + bool isRunning() { return mPlayState == PlayState::Running + || mPlayState == PlayState::Reversing; } + bool isFinished() { return mPlayState == PlayState::Finished; } float finalValue() { return mFinalValue; } ANDROID_API virtual uint32_t dirtyMask() = 0; void forceEndNow(AnimationContext& context); + RenderNode* target() { return mTarget; } + RenderNode* stagingTarget() { return mStagingTarget; } protected: + // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI + // thread and Render Thread animation state, respectively. + // From the UI thread, mStagingPlayState transition looks like + // NotStarted -> Running/Reversing -> Finished + // ^ | + // | | + // ---------------------- + // Note: For mStagingState, the Finished state (optional) is only set when the animation is + // terminated by user. + // + // On Render Thread, mPlayState transition: + // NotStart -> Running/Reversing-> Finished + // ^ | + // | | + // ------------------ + // Note that if the animation is in Running/Reversing state, calling start or reverse again + // would do nothing if the animation has the same play direction as the request; otherwise, + // the animation would start from where it is and change direction (i.e. Reversing <-> Running) + + enum class PlayState { + NotStarted, + Running, + Reversing, + Finished, + }; + BaseRenderNodeAnimator(float finalValue); virtual ~BaseRenderNodeAnimator(); virtual float getValue(RenderNode* target) const = 0; virtual void setValue(RenderNode* target, float value) = 0; - RenderNode* target() { return mTarget; } void callOnFinishedListener(AnimationContext& context); virtual void onStagingPlayStateChanged() {} - - enum PlayState { - NOT_STARTED, - RUNNING, - FINISHED, - }; + virtual void onPlayTimeChanged(nsecs_t playTime) {} + virtual void onPushStaging() {} RenderNode* mTarget; + RenderNode* mStagingTarget; float mFinalValue; float mDeltaValue; @@ -108,13 +141,28 @@ protected: nsecs_t mDuration; nsecs_t mStartDelay; bool mMayRunAsync; + // Play Time tracks the progress of animation, it should always be [0, mDuration], 0 being + // the beginning of the animation, will reach mDuration at the end of an animation. + nsecs_t mPlayTime; sp<AnimationListener> mListener; private: + enum class Request { + Start, + Reverse, + Reset, + Cancel, + End + }; inline void checkMutable(); virtual void transitionToRunning(AnimationContext& context); void doSetStartValue(float value); + bool updatePlayTime(nsecs_t playTime); + void resolveStagingRequest(Request request); + + std::vector<Request> mStagingRequests; + }; class RenderPropertyAnimator : public BaseRenderNodeAnimator { @@ -143,6 +191,7 @@ protected: virtual void setValue(RenderNode* target, float value) override; virtual void onAttached() override; virtual void onStagingPlayStateChanged() override; + virtual void onPushStaging() override; private: typedef bool (RenderProperties::*SetFloatProperty)(float value); @@ -152,6 +201,8 @@ private: const PropertyAccessors* mPropertyAccess; static const PropertyAccessors PROPERTY_ACCESSOR_LUT[]; + bool mShouldSyncPropertyFields = false; + bool mShouldUpdateStagingProperties = false; }; class CanvasPropertyPrimitiveAnimator : public BaseRenderNodeAnimator { diff --git a/libs/hwui/AnimatorManager.cpp b/libs/hwui/AnimatorManager.cpp index 0dababd774c1..f170e9cda8af 100644 --- a/libs/hwui/AnimatorManager.cpp +++ b/libs/hwui/AnimatorManager.cpp @@ -27,9 +27,8 @@ namespace uirenderer { using namespace std; -static void unref(BaseRenderNodeAnimator* animator) { +static void detach(sp<BaseRenderNodeAnimator>& animator) { animator->detach(); - animator->decStrong(nullptr); } AnimatorManager::AnimatorManager(RenderNode& parent) @@ -38,14 +37,28 @@ AnimatorManager::AnimatorManager(RenderNode& parent) } AnimatorManager::~AnimatorManager() { - for_each(mNewAnimators.begin(), mNewAnimators.end(), unref); - for_each(mAnimators.begin(), mAnimators.end(), unref); + for_each(mNewAnimators.begin(), mNewAnimators.end(), detach); + for_each(mAnimators.begin(), mAnimators.end(), detach); } void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) { - animator->incStrong(nullptr); + RenderNode* stagingTarget = animator->stagingTarget(); + if (stagingTarget == &mParent) { + return; + } + mNewAnimators.emplace_back(animator.get()); + // If the animator is already attached to other RenderNode, remove it from that RenderNode's + // new animator list. This ensures one animator only ends up in one newAnimatorList during one + // frame, even when it's added multiple times to multiple targets. + if (stagingTarget) { + stagingTarget->removeAnimator(animator); + } animator->attach(&mParent); - mNewAnimators.push_back(animator.get()); +} + +void AnimatorManager::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) { + mNewAnimators.erase(std::remove(mNewAnimators.begin(), mNewAnimators.end(), animator), + mNewAnimators.end()); } void AnimatorManager::setAnimationHandle(AnimationHandle* handle) { @@ -56,38 +69,40 @@ void AnimatorManager::setAnimationHandle(AnimationHandle* handle) { &mParent, mParent.getName()); } -template<typename T> -static void move_all(T& source, T& dest) { - dest.reserve(source.size() + dest.size()); - for (typename T::iterator it = source.begin(); it != source.end(); it++) { - dest.push_back(*it); - } - source.clear(); -} - void AnimatorManager::pushStaging() { if (mNewAnimators.size()) { LOG_ALWAYS_FATAL_IF(!mAnimationHandle, "Trying to start new animators on %p (%s) without an animation handle!", &mParent, mParent.getName()); - // Since this is a straight move, we don't need to inc/dec the ref count - move_all(mNewAnimators, mAnimators); + + // Only add new animators that are not already in the mAnimators list + for (auto& anim : mNewAnimators) { + if (anim->target() != &mParent) { + mAnimators.push_back(std::move(anim)); + } + } + mNewAnimators.clear(); } - for (vector<BaseRenderNodeAnimator*>::iterator it = mAnimators.begin(); it != mAnimators.end(); it++) { - (*it)->pushStaging(mAnimationHandle->context()); + for (auto& animator : mAnimators) { + animator->pushStaging(mAnimationHandle->context()); } } +void AnimatorManager::onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) { + LOG_ALWAYS_FATAL_IF(animator->target() == &mParent, "Target has not been changed"); + mAnimators.erase(std::remove(mAnimators.begin(), mAnimators.end(), animator), mAnimators.end()); +} + class AnimateFunctor { public: - AnimateFunctor(TreeInfo& info, AnimationContext& context) - : dirtyMask(0), mInfo(info), mContext(context) {} + AnimateFunctor(TreeInfo& info, AnimationContext& context, uint32_t* outDirtyMask) + : mInfo(info), mContext(context), mDirtyMask(outDirtyMask) {} - bool operator() (BaseRenderNodeAnimator* animator) { - dirtyMask |= animator->dirtyMask(); + bool operator() (sp<BaseRenderNodeAnimator>& animator) { + *mDirtyMask |= animator->dirtyMask(); bool remove = animator->animate(mContext); if (remove) { - animator->decStrong(nullptr); + animator->detach(); } else { if (animator->isRunning()) { mInfo.out.hasAnimations = true; @@ -99,11 +114,10 @@ public: return remove; } - uint32_t dirtyMask; - private: TreeInfo& mInfo; AnimationContext& mContext; + uint32_t* mDirtyMask; }; uint32_t AnimatorManager::animate(TreeInfo& info) { @@ -124,27 +138,24 @@ uint32_t AnimatorManager::animate(TreeInfo& info) { } void AnimatorManager::animateNoDamage(TreeInfo& info) { - if (!mAnimators.size()) return; - animateCommon(info); } uint32_t AnimatorManager::animateCommon(TreeInfo& info) { - AnimateFunctor functor(info, mAnimationHandle->context()); - std::vector< BaseRenderNodeAnimator* >::iterator newEnd; - newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor); + uint32_t dirtyMask; + AnimateFunctor functor(info, mAnimationHandle->context(), &dirtyMask); + auto newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor); mAnimators.erase(newEnd, mAnimators.end()); mAnimationHandle->notifyAnimationsRan(); mParent.mProperties.updateMatrix(); - return functor.dirtyMask; + return dirtyMask; } -static void endStagingAnimator(BaseRenderNodeAnimator* animator) { - animator->end(); +static void endStagingAnimator(sp<BaseRenderNodeAnimator>& animator) { + animator->cancel(); if (animator->listener()) { - animator->listener()->onAnimationFinished(animator); + animator->listener()->onAnimationFinished(animator.get()); } - animator->decStrong(nullptr); } void AnimatorManager::endAllStagingAnimators() { @@ -159,9 +170,8 @@ class EndActiveAnimatorsFunctor { public: EndActiveAnimatorsFunctor(AnimationContext& context) : mContext(context) {} - void operator() (BaseRenderNodeAnimator* animator) { + void operator() (sp<BaseRenderNodeAnimator>& animator) { animator->forceEndNow(mContext); - animator->decStrong(nullptr); } private: @@ -169,7 +179,7 @@ private: }; void AnimatorManager::endAllActiveAnimators() { - ALOGD("endAllStagingAnimators on %p (%s) with handle %p", + ALOGD("endAllActiveAnimators on %p (%s) with handle %p", &mParent, mParent.getName(), mAnimationHandle); EndActiveAnimatorsFunctor functor(mAnimationHandle->context()); for_each(mAnimators.begin(), mAnimators.end(), functor); diff --git a/libs/hwui/AnimatorManager.h b/libs/hwui/AnimatorManager.h index fb75eb8599b4..61f6179d217c 100644 --- a/libs/hwui/AnimatorManager.h +++ b/libs/hwui/AnimatorManager.h @@ -39,11 +39,13 @@ public: ~AnimatorManager(); void addAnimator(const sp<BaseRenderNodeAnimator>& animator); + void removeAnimator(const sp<BaseRenderNodeAnimator>& animator); void setAnimationHandle(AnimationHandle* handle); bool hasAnimationHandle() { return mAnimationHandle; } void pushStaging(); + void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator); // Returns the combined dirty mask of all animators run uint32_t animate(TreeInfo& info); @@ -66,9 +68,8 @@ private: AnimationHandle* mAnimationHandle; // To improve the efficiency of resizing & removing from the vector - // use manual ref counting instead of sp<>. - std::vector<BaseRenderNodeAnimator*> mNewAnimators; - std::vector<BaseRenderNodeAnimator*> mAnimators; + std::vector< sp<BaseRenderNodeAnimator> > mNewAnimators; + std::vector< sp<BaseRenderNodeAnimator> > mAnimators; }; } /* namespace uirenderer */ diff --git a/libs/hwui/AssetAtlas.cpp b/libs/hwui/AssetAtlas.cpp index 2889d2ff0b78..6afff1b7158e 100644 --- a/libs/hwui/AssetAtlas.cpp +++ b/libs/hwui/AssetAtlas.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "AssetAtlas.h" #include "Caches.h" #include "Image.h" @@ -41,54 +39,36 @@ void AssetAtlas::init(sp<GraphicBuffer> buffer, int64_t* map, int count) { if (!mTexture) { Caches& caches = Caches::getInstance(); mTexture = new Texture(caches); - mTexture->width = buffer->getWidth(); - mTexture->height = buffer->getHeight(); + mTexture->wrap(mImage->getTexture(), + buffer->getWidth(), buffer->getHeight(), GL_RGBA); createEntries(caches, map, count); } } else { ALOGW("Could not create atlas image"); - delete mImage; - mImage = nullptr; + terminate(); } - - updateTextureId(); } void AssetAtlas::terminate() { - if (mImage) { - delete mImage; - mImage = nullptr; - updateTextureId(); - } -} - - -void AssetAtlas::updateTextureId() { - mTexture->id = mImage ? mImage->getTexture() : 0; - if (mTexture->id) { - // Texture ID changed, force-set to defaults to sync the wrapper & GL - // state objects - mTexture->setWrap(GL_CLAMP_TO_EDGE, false, true); - mTexture->setFilter(GL_NEAREST, false, true); - } - for (size_t i = 0; i < mEntries.size(); i++) { - AssetAtlas::Entry* entry = mEntries.valueAt(i); - entry->texture->id = mTexture->id; - } + delete mImage; + mImage = nullptr; + delete mTexture; + mTexture = nullptr; + mEntries.clear(); } /////////////////////////////////////////////////////////////////////////////// // Entries /////////////////////////////////////////////////////////////////////////////// -AssetAtlas::Entry* AssetAtlas::getEntry(const SkBitmap* bitmap) const { - ssize_t index = mEntries.indexOfKey(bitmap->pixelRef()); - return index >= 0 ? mEntries.valueAt(index) : nullptr; +AssetAtlas::Entry* AssetAtlas::getEntry(const SkPixelRef* pixelRef) const { + auto result = mEntries.find(pixelRef); + return result != mEntries.end() ? result->second.get() : nullptr; } -Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const { - ssize_t index = mEntries.indexOfKey(bitmap->pixelRef()); - return index >= 0 ? mEntries.valueAt(index)->texture : nullptr; +Texture* AssetAtlas::getEntryTexture(const SkPixelRef* pixelRef) const { + auto result = mEntries.find(pixelRef); + return result != mEntries.end() ? result->second->texture : nullptr; } /** @@ -96,7 +76,8 @@ Texture* AssetAtlas::getEntryTexture(const SkBitmap* bitmap) const { * instead of applying the changes to the virtual textures. */ struct DelegateTexture: public Texture { - DelegateTexture(Caches& caches, Texture* delegate): Texture(caches), mDelegate(delegate) { } + DelegateTexture(Caches& caches, Texture* delegate) + : Texture(caches), mDelegate(delegate) { } virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, bool force = false, GLenum renderTarget = GL_TEXTURE_2D) override { @@ -113,8 +94,8 @@ private: }; // struct DelegateTexture void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { - const float width = float(mTexture->width); - const float height = float(mTexture->height); + const float width = float(mTexture->width()); + const float height = float(mTexture->height()); for (int i = 0; i < count; ) { SkPixelRef* pixelRef = reinterpret_cast<SkPixelRef*>(map[i++]); @@ -135,13 +116,13 @@ void AssetAtlas::createEntries(Caches& caches, int64_t* map, int count) { Texture* texture = new DelegateTexture(caches, mTexture); texture->blend = !SkAlphaTypeIsOpaque(pixelRef->info().alphaType()); - texture->width = pixelRef->info().width(); - texture->height = pixelRef->info().height(); + texture->wrap(mTexture->id(), pixelRef->info().width(), + pixelRef->info().height(), mTexture->format()); - Entry* entry = new Entry(pixelRef, texture, mapper, *this); + std::unique_ptr<Entry> entry(new Entry(pixelRef, texture, mapper, *this)); texture->uvMapper = &entry->uvMapper; - mEntries.add(entry->pixelRef, entry); + mEntries.emplace(entry->pixelRef, std::move(entry)); } } diff --git a/libs/hwui/AssetAtlas.h b/libs/hwui/AssetAtlas.h index f1cd0b4947dc..75400ff494c3 100644 --- a/libs/hwui/AssetAtlas.h +++ b/libs/hwui/AssetAtlas.h @@ -17,18 +17,16 @@ #ifndef ANDROID_HWUI_ASSET_ATLAS_H #define ANDROID_HWUI_ASSET_ATLAS_H -#include <GLES2/gl2.h> - -#include <ui/GraphicBuffer.h> - -#include <utils/KeyedVector.h> +#include "Texture.h" +#include "UvMapper.h" #include <cutils/compiler.h> - +#include <GLES2/gl2.h> +#include <ui/GraphicBuffer.h> #include <SkBitmap.h> -#include "Texture.h" -#include "UvMapper.h" +#include <memory> +#include <unordered_map> namespace android { namespace uirenderer { @@ -71,6 +69,10 @@ public: return texture->blend ? &atlas.mBlendKey : &atlas.mOpaqueKey; } + ~Entry() { + delete texture; + } + private: /** * The pixel ref that generated this atlas entry. @@ -90,10 +92,6 @@ public: , atlas(atlas) { } - ~Entry() { - delete texture; - } - friend class AssetAtlas; }; @@ -127,7 +125,7 @@ public: * Can return 0 if the atlas is not initialized. */ uint32_t getWidth() const { - return mTexture ? mTexture->width : 0; + return mTexture ? mTexture->width() : 0; } /** @@ -135,7 +133,7 @@ public: * Can return 0 if the atlas is not initialized. */ uint32_t getHeight() const { - return mTexture ? mTexture->height : 0; + return mTexture ? mTexture->height() : 0; } /** @@ -143,24 +141,23 @@ public: * Can return 0 if the atlas is not initialized. */ GLuint getTexture() const { - return mTexture ? mTexture->id : 0; + return mTexture ? mTexture->id() : 0; } /** * Returns the entry in the atlas associated with the specified - * bitmap. If the bitmap is not in the atlas, return NULL. + * pixelRef. If the pixelRef is not in the atlas, return NULL. */ - Entry* getEntry(const SkBitmap* bitmap) const; + Entry* getEntry(const SkPixelRef* pixelRef) const; /** * Returns the texture for the atlas entry associated with the - * specified bitmap. If the bitmap is not in the atlas, return NULL. + * specified pixelRef. If the pixelRef is not in the atlas, return NULL. */ - Texture* getEntryTexture(const SkBitmap* bitmap) const; + Texture* getEntryTexture(const SkPixelRef* pixelRef) const; private: void createEntries(Caches& caches, int64_t* map, int count); - void updateTextureId(); Texture* mTexture; Image* mImage; @@ -168,7 +165,7 @@ private: const bool mBlendKey; const bool mOpaqueKey; - KeyedVector<const SkPixelRef*, Entry*> mEntries; + std::unordered_map<const SkPixelRef*, std::unique_ptr<Entry>> mEntries; }; // class AssetAtlas }; // namespace uirenderer diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp new file mode 100644 index 000000000000..0f670a822899 --- /dev/null +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -0,0 +1,850 @@ +/* + * Copyright (C) 2015 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 "BakedOpDispatcher.h" + +#include "BakedOpRenderer.h" +#include "Caches.h" +#include "Glop.h" +#include "GlopBuilder.h" +#include "Patch.h" +#include "PathTessellator.h" +#include "renderstate/OffscreenBufferPool.h" +#include "renderstate/RenderState.h" +#include "utils/GLUtils.h" +#include "VertexBuffer.h" + +#include <algorithm> +#include <math.h> +#include <SkPaintDefaults.h> +#include <SkPathOps.h> + +namespace android { +namespace uirenderer { + +static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds, const Rect& texCoord) { + vertices[0] = { bounds.left, bounds.top, texCoord.left, texCoord.top }; + vertices[1] = { bounds.right, bounds.top, texCoord.right, texCoord.top }; + vertices[2] = { bounds.left, bounds.bottom, texCoord.left, texCoord.bottom }; + vertices[3] = { bounds.right, bounds.bottom, texCoord.right, texCoord.bottom }; +} + +void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, + const MergedBakedOpList& opList) { + + const BakedOpState& firstState = *(opList.states[0]); + const SkBitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap; + + AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(bitmap->pixelRef()); + Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(bitmap); + if (!texture) return; + const AutoTexture autoCleanup(texture); + + TextureVertex vertices[opList.count * 4]; + Rect texCoords(0, 0, 1, 1); + if (entry) { + entry->uvMapper.map(texCoords); + } + for (size_t i = 0; i < opList.count; i++) { + const BakedOpState& state = *(opList.states[i]); + TextureVertex* rectVerts = &vertices[i * 4]; + + // calculate unclipped bounds, since they'll determine texture coordinates + Rect opBounds = state.op->unmappedBounds; + state.computedState.transform.mapRect(opBounds); + if (CC_LIKELY(state.computedState.transform.isPureTranslate())) { + // pure translate, so snap (same behavior as onBitmapOp) + opBounds.snapToPixelBoundaries(); + } + storeTexturedRect(rectVerts, opBounds, texCoords); + renderer.dirtyRenderTarget(opBounds); + } + + const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType) + ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(firstState.roundRectClipState) + .setMeshTexturedIndexedQuads(vertices, opList.count * 6) + .setFillTexturePaint(*texture, textureFillFlags, firstState.op->paint, firstState.alpha) + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewIdentityEmptyBounds() + .build(); + ClipRect renderTargetClip(opList.clip); + const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr; + renderer.renderGlop(nullptr, clip, glop); +} + +void BakedOpDispatcher::onMergedPatchOps(BakedOpRenderer& renderer, + const MergedBakedOpList& opList) { + const PatchOp& firstOp = *(static_cast<const PatchOp*>(opList.states[0]->op)); + const BakedOpState& firstState = *(opList.states[0]); + AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry( + firstOp.bitmap->pixelRef()); + + // Batches will usually contain a small number of items so it's + // worth performing a first iteration to count the exact number + // of vertices we need in the new mesh + uint32_t totalVertices = 0; + + for (size_t i = 0; i < opList.count; i++) { + const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op)); + + // TODO: cache mesh lookups + const Patch* opMesh = renderer.caches().patchCache.get( + entry, op.bitmap->width(), op.bitmap->height(), + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); + totalVertices += opMesh->verticesCount; + } + + const bool dirtyRenderTarget = renderer.offscreenRenderTarget(); + + uint32_t indexCount = 0; + + TextureVertex vertices[totalVertices]; + TextureVertex* vertex = &vertices[0]; + // Create a mesh that contains the transformed vertices for all the + // 9-patch objects that are part of the batch. Note that onDefer() + // enforces ops drawn by this function to have a pure translate or + // identity matrix + for (size_t i = 0; i < opList.count; i++) { + const PatchOp& op = *(static_cast<const PatchOp*>(opList.states[i]->op)); + const BakedOpState& state = *opList.states[i]; + + // TODO: cache mesh lookups + const Patch* opMesh = renderer.caches().patchCache.get( + entry, op.bitmap->width(), op.bitmap->height(), + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); + + + uint32_t vertexCount = opMesh->verticesCount; + if (vertexCount == 0) continue; + + // We use the bounds to know where to translate our vertices + // Using patchOp->state.mBounds wouldn't work because these + // bounds are clipped + const float tx = floorf(state.computedState.transform.getTranslateX() + + op.unmappedBounds.left + 0.5f); + const float ty = floorf(state.computedState.transform.getTranslateY() + + op.unmappedBounds.top + 0.5f); + + // Copy & transform all the vertices for the current operation + TextureVertex* opVertices = opMesh->vertices.get(); + for (uint32_t j = 0; j < vertexCount; j++, opVertices++) { + TextureVertex::set(vertex++, + opVertices->x + tx, opVertices->y + ty, + opVertices->u, opVertices->v); + } + + // Dirty the current layer if possible. When the 9-patch does not + // contain empty quads we can take a shortcut and simply set the + // dirty rect to the object's bounds. + if (dirtyRenderTarget) { + if (!opMesh->hasEmptyQuads) { + renderer.dirtyRenderTarget(Rect(tx, ty, + tx + op.unmappedBounds.getWidth(), ty + op.unmappedBounds.getHeight())); + } else { + const size_t count = opMesh->quads.size(); + for (size_t i = 0; i < count; i++) { + const Rect& quadBounds = opMesh->quads[i]; + const float x = tx + quadBounds.left; + const float y = ty + quadBounds.top; + renderer.dirtyRenderTarget(Rect(x, y, + x + quadBounds.getWidth(), y + quadBounds.getHeight())); + } + } + } + + indexCount += opMesh->indexCount; + } + + + Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(firstOp.bitmap); + if (!texture) return; + const AutoTexture autoCleanup(texture); + + // 9 patches are built for stretching - always filter + int textureFillFlags = TextureFillFlags::ForceFilter; + if (firstOp.bitmap->colorType() == kAlpha_8_SkColorType) { + textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture; + } + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(firstState.roundRectClipState) + .setMeshTexturedIndexedQuads(vertices, indexCount) + .setFillTexturePaint(*texture, textureFillFlags, firstOp.paint, firstState.alpha) + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewIdentityEmptyBounds() + .build(); + ClipRect renderTargetClip(opList.clip); + const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr; + renderer.renderGlop(nullptr, clip, glop); +} + +static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer, + const TextOp& op, const BakedOpState& textOpState) { + renderer.caches().textureState().activateTexture(0); + + PaintUtils::TextShadow textShadow; + if (!PaintUtils::getTextShadow(op.paint, &textShadow)) { + LOG_ALWAYS_FATAL("failed to query shadow attributes"); + } + + renderer.caches().dropShadowCache.setFontRenderer(fontRenderer); + ShadowTexture* texture = renderer.caches().dropShadowCache.get( + op.paint, op.glyphs, op.glyphCount, textShadow.radius, op.positions); + // If the drop shadow exceeds the max texture size or couldn't be + // allocated, skip drawing + if (!texture) return; + const AutoTexture autoCleanup(texture); + + const float sx = op.x - texture->left + textShadow.dx; + const float sy = op.y - texture->top + textShadow.dy; + + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(textOpState.roundRectClipState) + .setMeshTexturedUnitQuad(nullptr) + .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, textOpState.alpha) + .setTransform(textOpState.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height())) + .build(); + + // Compute damage bounds and clip (since may differ from those in textOpState). + // Bounds should be same as text op, but with dx/dy offset and radius outset + // applied in local space. + auto& transform = textOpState.computedState.transform; + Rect shadowBounds = op.unmappedBounds; // STROKE + const bool expandForStroke = op.paint->getStyle() != SkPaint::kFill_Style; + if (expandForStroke) { + shadowBounds.outset(op.paint->getStrokeWidth() * 0.5f); + } + shadowBounds.translate(textShadow.dx, textShadow.dy); + shadowBounds.outset(textShadow.radius, textShadow.radius); + transform.mapRect(shadowBounds); + if (CC_UNLIKELY(expandForStroke && + (!transform.isPureTranslate() || op.paint->getStrokeWidth() < 1.0f))) { + shadowBounds.outset(0.5f); + } + + auto clipState = textOpState.computedState.clipState; + if (clipState->mode != ClipMode::Rectangle + || !clipState->rect.contains(shadowBounds)) { + // need clip, so pass it and clip bounds + shadowBounds.doIntersect(clipState->rect); + } else { + // don't need clip, ignore + clipState = nullptr; + } + + renderer.renderGlop(&shadowBounds, clipState, glop); +} + +enum class TextRenderType { + Defer, + Flush +}; + +static void renderTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state, + const ClipBase* renderClip, TextRenderType renderType) { + FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer(); + + if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) { + fontRenderer.setFont(op.paint, SkMatrix::I()); + renderTextShadow(renderer, fontRenderer, op, state); + } + + float x = op.x; + float y = op.y; + const Matrix4& transform = state.computedState.transform; + const bool pureTranslate = transform.isPureTranslate(); + if (CC_LIKELY(pureTranslate)) { + x = floorf(x + transform.getTranslateX() + 0.5f); + y = floorf(y + transform.getTranslateY() + 0.5f); + fontRenderer.setFont(op.paint, SkMatrix::I()); + fontRenderer.setTextureFiltering(false); + } else if (CC_UNLIKELY(transform.isPerspective())) { + fontRenderer.setFont(op.paint, SkMatrix::I()); + fontRenderer.setTextureFiltering(true); + } else { + // We only pass a partial transform to the font renderer. That partial + // matrix defines how glyphs are rasterized. Typically we want glyphs + // to be rasterized at their final size on screen, which means the partial + // matrix needs to take the scale factor into account. + // When a partial matrix is used to transform glyphs during rasterization, + // the mesh is generated with the inverse transform (in the case of scale, + // the mesh is generated at 1.0 / scale for instance.) This allows us to + // apply the full transform matrix at draw time in the vertex shader. + // Applying the full matrix in the shader is the easiest way to handle + // rotation and perspective and allows us to always generated quads in the + // font renderer which greatly simplifies the code, clipping in particular. + float sx, sy; + transform.decomposeScale(sx, sy); + fontRenderer.setFont(op.paint, SkMatrix::MakeScale( + roundf(std::max(1.0f, sx)), + roundf(std::max(1.0f, sy)))); + fontRenderer.setTextureFiltering(true); + } + Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); + + int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint); + TextDrawFunctor functor(&renderer, &state, renderClip, + x, y, pureTranslate, alpha, mode, op.paint); + + bool forceFinish = (renderType == TextRenderType::Flush); + bool mustDirtyRenderTarget = renderer.offscreenRenderTarget(); + const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr; + fontRenderer.renderPosText(op.paint, localOpClip, op.glyphs, op.glyphCount, x, y, + op.positions, mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, forceFinish); + + if (mustDirtyRenderTarget) { + if (!pureTranslate) { + transform.mapRect(layerBounds); + } + renderer.dirtyRenderTarget(layerBounds); + } +} + +void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer, + const MergedBakedOpList& opList) { + ClipRect renderTargetClip(opList.clip); + const ClipBase* clip = opList.clipSideFlags ? &renderTargetClip : nullptr; + for (size_t i = 0; i < opList.count; i++) { + const BakedOpState& state = *(opList.states[i]); + const TextOp& op = *(static_cast<const TextOp*>(state.op)); + TextRenderType renderType = (i + 1 == opList.count) + ? TextRenderType::Flush : TextRenderType::Defer; + renderTextOp(renderer, op, state, clip, renderType); + } +} + +namespace VertexBufferRenderFlags { + enum { + Offset = 0x1, + ShadowInterp = 0x2, + }; +} + +static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state, + const VertexBuffer& vertexBuffer, float translateX, float translateY, + const SkPaint& paint, int vertexBufferRenderFlags) { + if (CC_LIKELY(vertexBuffer.getVertexCount())) { + bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp; + const int transformFlags = vertexBufferRenderFlags & VertexBufferRenderFlags::Offset + ? TransformFlags::OffsetByFudgeFactor : 0; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshVertexBuffer(vertexBuffer, shadowInterp) + .setFillPaint(paint, state.alpha) + .setTransform(state.computedState.transform, transformFlags) + .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds()) + .build(); + renderer.renderGlop(state, glop); + } +} + +static void renderConvexPath(BakedOpRenderer& renderer, const BakedOpState& state, + const SkPath& path, const SkPaint& paint) { + VertexBuffer vertexBuffer; + // TODO: try clipping large paths to viewport + PathTessellator::tessellatePath(path, &paint, state.computedState.transform, vertexBuffer); + renderVertexBuffer(renderer, state, vertexBuffer, 0.0f, 0.0f, paint, 0); +} + +static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state, + float xOffset, float yOffset, PathTexture& texture, const SkPaint& paint) { + Rect dest(texture.width(), texture.height()); + dest.translate(xOffset + texture.left - texture.offset, + yOffset + texture.top - texture.offset); + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUnitQuad(nullptr) + .setFillPathTexturePaint(texture, paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(dest) + .build(); + renderer.renderGlop(state, glop); +} + +SkRect getBoundsOfFill(const RecordedOp& op) { + SkRect bounds = op.unmappedBounds.toSkRect(); + if (op.paint->getStyle() == SkPaint::kStrokeAndFill_Style) { + float outsetDistance = op.paint->getStrokeWidth() / 2; + bounds.outset(outsetDistance, outsetDistance); + } + return bounds; +} + +void BakedOpDispatcher::onArcOp(BakedOpRenderer& renderer, const ArcOp& op, const BakedOpState& state) { + // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180) + if (op.paint->getStyle() != SkPaint::kStroke_Style + || op.paint->getPathEffect() != nullptr + || op.useCenter) { + PathTexture* texture = renderer.caches().pathCache.getArc( + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), + op.startAngle, op.sweepAngle, op.useCenter, op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, + *texture, *(op.paint)); + } + } else { + SkRect rect = getBoundsOfFill(op); + SkPath path; + if (op.useCenter) { + path.moveTo(rect.centerX(), rect.centerY()); + } + path.arcTo(rect, op.startAngle, op.sweepAngle, !op.useCenter); + if (op.useCenter) { + path.close(); + } + renderConvexPath(renderer, state, path, *(op.paint)); + } +} + +void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, const BakedOpState& state) { + Texture* texture = renderer.getTexture(op.bitmap); + if (!texture) return; + const AutoTexture autoCleanup(texture); + + const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType) + ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUnitQuad(texture->uvMapper) + .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height())) + .build(); + renderer.renderGlop(state, glop); +} + +void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, const BakedOpState& state) { + const static UvMapper defaultUvMapper; + const uint32_t elementCount = op.meshWidth * op.meshHeight * 6; + + std::unique_ptr<ColorTextureVertex[]> mesh(new ColorTextureVertex[elementCount]); + ColorTextureVertex* vertex = &mesh[0]; + + const int* colors = op.colors; + std::unique_ptr<int[]> tempColors; + if (!colors) { + uint32_t colorsCount = (op.meshWidth + 1) * (op.meshHeight + 1); + tempColors.reset(new int[colorsCount]); + memset(tempColors.get(), 0xff, colorsCount * sizeof(int)); + colors = tempColors.get(); + } + + Texture* texture = renderer.renderState().assetAtlas().getEntryTexture(op.bitmap->pixelRef()); + const UvMapper& mapper(texture && texture->uvMapper ? *texture->uvMapper : defaultUvMapper); + + for (int32_t y = 0; y < op.meshHeight; y++) { + for (int32_t x = 0; x < op.meshWidth; x++) { + uint32_t i = (y * (op.meshWidth + 1) + x) * 2; + + float u1 = float(x) / op.meshWidth; + float u2 = float(x + 1) / op.meshWidth; + float v1 = float(y) / op.meshHeight; + float v2 = float(y + 1) / op.meshHeight; + + mapper.map(u1, v1, u2, v2); + + int ax = i + (op.meshWidth + 1) * 2; + int ay = ax + 1; + int bx = i; + int by = bx + 1; + int cx = i + 2; + int cy = cx + 1; + int dx = i + (op.meshWidth + 1) * 2 + 2; + int dy = dx + 1; + + const float* vertices = op.vertices; + ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]); + ColorTextureVertex::set(vertex++, vertices[ax], vertices[ay], u1, v2, colors[ax / 2]); + ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]); + + ColorTextureVertex::set(vertex++, vertices[dx], vertices[dy], u2, v2, colors[dx / 2]); + ColorTextureVertex::set(vertex++, vertices[bx], vertices[by], u1, v1, colors[bx / 2]); + ColorTextureVertex::set(vertex++, vertices[cx], vertices[cy], u2, v1, colors[cx / 2]); + } + } + + if (!texture) { + texture = renderer.caches().textureCache.get(op.bitmap); + if (!texture) { + return; + } + } + const AutoTexture autoCleanup(texture); + + /* + * TODO: handle alpha_8 textures correctly by applying paint color, but *not* + * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh. + */ + const int textureFillFlags = TextureFillFlags::None; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshColoredTexturedMesh(mesh.get(), elementCount) + .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewOffsetRect(0, 0, op.unmappedBounds) + .build(); + renderer.renderGlop(state, glop); +} + +void BakedOpDispatcher::onBitmapRectOp(BakedOpRenderer& renderer, const BitmapRectOp& op, const BakedOpState& state) { + Texture* texture = renderer.getTexture(op.bitmap); + if (!texture) return; + const AutoTexture autoCleanup(texture); + + Rect uv(std::max(0.0f, op.src.left / texture->width()), + std::max(0.0f, op.src.top / texture->height()), + std::min(1.0f, op.src.right / texture->width()), + std::min(1.0f, op.src.bottom / texture->height())); + + const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType) + ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; + const bool tryToSnap = MathUtils::areEqual(op.src.getWidth(), op.unmappedBounds.getWidth()) + && MathUtils::areEqual(op.src.getHeight(), op.unmappedBounds.getHeight()); + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUvQuad(texture->uvMapper, uv) + .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds) + .build(); + renderer.renderGlop(state, glop); +} + +void BakedOpDispatcher::onColorOp(BakedOpRenderer& renderer, const ColorOp& op, const BakedOpState& state) { + SkPaint paint; + paint.setColor(op.color); + paint.setXfermodeMode(op.mode); + + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshUnitQuad() + .setFillPaint(paint, state.alpha) + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewMapUnitToRect(state.computedState.clipState->rect) + .build(); + renderer.renderGlop(state, glop); +} + +void BakedOpDispatcher::onFunctorOp(BakedOpRenderer& renderer, const FunctorOp& op, const BakedOpState& state) { + renderer.renderFunctor(op, state); +} + +void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) { + VertexBuffer buffer; + PathTessellator::tessellateLines(op.points, op.floatCount, op.paint, + state.computedState.transform, buffer); + int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset; + renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags); +} + +void BakedOpDispatcher::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op, const BakedOpState& state) { + if (op.paint->getPathEffect() != nullptr) { + PathTexture* texture = renderer.caches().pathCache.getOval( + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, + *texture, *(op.paint)); + } + } else { + SkPath path; + SkRect rect = getBoundsOfFill(op); + path.addOval(rect); + + if (state.computedState.localProjectionPathMask != nullptr) { + // Mask the ripple path by the local space projection mask in local space. + // Note that this can create CCW paths. + Op(path, *state.computedState.localProjectionPathMask, kIntersect_SkPathOp, &path); + } + renderConvexPath(renderer, state, path, *(op.paint)); + } +} + +void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op, const BakedOpState& state) { + // 9 patches are built for stretching - always filter + int textureFillFlags = TextureFillFlags::ForceFilter; + if (op.bitmap->colorType() == kAlpha_8_SkColorType) { + textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture; + } + + // TODO: avoid redoing the below work each frame: + AssetAtlas::Entry* entry = renderer.renderState().assetAtlas().getEntry(op.bitmap->pixelRef()); + const Patch* mesh = renderer.caches().patchCache.get( + entry, op.bitmap->width(), op.bitmap->height(), + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.patch); + + Texture* texture = entry ? entry->texture : renderer.caches().textureCache.get(op.bitmap); + if (CC_LIKELY(texture)) { + const AutoTexture autoCleanup(texture); + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshPatchQuads(*mesh) + .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top, + Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight())) + .build(); + renderer.renderGlop(state, glop); + } +} + +void BakedOpDispatcher::onPathOp(BakedOpRenderer& renderer, const PathOp& op, const BakedOpState& state) { + PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + // Unlike other callers to renderPathTexture, no offsets are used because PathOp doesn't + // have any translate built in, other than what's in the SkPath itself + renderPathTexture(renderer, state, 0, 0, *texture, *(op.paint)); + } +} + +void BakedOpDispatcher::onPointsOp(BakedOpRenderer& renderer, const PointsOp& op, const BakedOpState& state) { + VertexBuffer buffer; + PathTessellator::tessellatePoints(op.points, op.floatCount, op.paint, + state.computedState.transform, buffer); + int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset; + renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags); +} + +// See SkPaintDefaults.h +#define SkPaintDefaults_MiterLimit SkIntToScalar(4) + +void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) { + if (op.paint->getStyle() != SkPaint::kFill_Style) { + // only fill + default miter is supported by drawConvexPath, since others must handle joins + static_assert(SkPaintDefaults_MiterLimit == 4.0f, "Miter limit has changed"); + if (CC_UNLIKELY(op.paint->getPathEffect() != nullptr + || op.paint->getStrokeJoin() != SkPaint::kMiter_Join + || op.paint->getStrokeMiter() != SkPaintDefaults_MiterLimit)) { + PathTexture* texture = renderer.caches().pathCache.getRect( + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, + *texture, *(op.paint)); + } + } else { + SkPath path; + path.addRect(getBoundsOfFill(op)); + renderConvexPath(renderer, state, path, *(op.paint)); + } + } else { + if (op.paint->isAntiAlias() && !state.computedState.transform.isSimple()) { + SkPath path; + path.addRect(op.unmappedBounds.toSkRect()); + renderConvexPath(renderer, state, path, *(op.paint)); + } else { + // render simple unit quad, no tessellation required + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshUnitQuad() + .setFillPaint(*op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(op.unmappedBounds) + .build(); + renderer.renderGlop(state, glop); + } + } +} + +void BakedOpDispatcher::onRoundRectOp(BakedOpRenderer& renderer, const RoundRectOp& op, const BakedOpState& state) { + if (op.paint->getPathEffect() != nullptr) { + PathTexture* texture = renderer.caches().pathCache.getRoundRect( + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), + op.rx, op.ry, op.paint); + const AutoTexture holder(texture); + if (CC_LIKELY(holder.texture)) { + renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, + *texture, *(op.paint)); + } + } else { + const VertexBuffer* buffer = renderer.caches().tessellationCache.getRoundRect( + state.computedState.transform, *(op.paint), + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry); + renderVertexBuffer(renderer, state, *buffer, + op.unmappedBounds.left, op.unmappedBounds.top, *(op.paint), 0); + } +} + +static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha, + const VertexBuffer* ambientShadowVertexBuffer, const VertexBuffer* spotShadowVertexBuffer) { + SkPaint paint; + paint.setAntiAlias(true); // want to use AlphaVertex + + // The caller has made sure casterAlpha > 0. + uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha; + if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) { + ambientShadowAlpha = Properties::overrideAmbientShadowStrength; + } + if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) { + paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha)); + renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0, + paint, VertexBufferRenderFlags::ShadowInterp); + } + + uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha; + if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) { + spotShadowAlpha = Properties::overrideSpotShadowStrength; + } + if (spotShadowVertexBuffer && spotShadowAlpha > 0) { + paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha)); + renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0, + paint, VertexBufferRenderFlags::ShadowInterp); + } +} + +void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, const BakedOpState& state) { + TessellationCache::vertexBuffer_pair_t buffers = op.shadowTask->getResult(); + renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second); +} + +void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, const BakedOpState& state) { + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4) + .setFillPaint(*op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewOffsetRect(0, 0, op.unmappedBounds) + .build(); + renderer.renderGlop(state, glop); +} + +void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) { + renderTextOp(renderer, op, state, state.computedState.getClipIfNeeded(), TextRenderType::Flush); +} + +void BakedOpDispatcher::onTextOnPathOp(BakedOpRenderer& renderer, const TextOnPathOp& op, const BakedOpState& state) { + // Note: can't trust clipSideFlags since we record with unmappedBounds == clip. + // TODO: respect clipSideFlags, once we record with bounds + auto renderTargetClip = state.computedState.clipState; + + FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer(); + fontRenderer.setFont(op.paint, SkMatrix::I()); + fontRenderer.setTextureFiltering(true); + + Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); + + int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint); + TextDrawFunctor functor(&renderer, &state, renderTargetClip, + 0.0f, 0.0f, false, alpha, mode, op.paint); + + bool mustDirtyRenderTarget = renderer.offscreenRenderTarget(); + const Rect localSpaceClip = state.computedState.computeLocalSpaceClip(); + if (fontRenderer.renderTextOnPath(op.paint, &localSpaceClip, op.glyphs, op.glyphCount, + op.path, op.hOffset, op.vOffset, + mustDirtyRenderTarget ? &layerBounds : nullptr, &functor)) { + if (mustDirtyRenderTarget) { + // manually dirty render target, since TextDrawFunctor won't + state.computedState.transform.mapRect(layerBounds); + renderer.dirtyRenderTarget(layerBounds); + } + } +} + +void BakedOpDispatcher::onTextureLayerOp(BakedOpRenderer& renderer, const TextureLayerOp& op, const BakedOpState& state) { + const bool tryToSnap = !op.layer->getForceFilter(); + float alpha = (op.layer->getAlpha() / 255.0f) * state.alpha; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUvQuad(nullptr, Rect(0, 1, 1, 0)) // TODO: simplify with VBO + .setFillTextureLayer(*(op.layer), alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRectOptionalSnap(tryToSnap, Rect(op.layer->getWidth(), op.layer->getHeight())) + .build(); + renderer.renderGlop(state, glop); +} + +void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) { + // Note that we don't use op->paint in this function - it's never set on a LayerOp + OffscreenBuffer* buffer = *op.layerHandle; + + if (CC_UNLIKELY(!buffer)) { + // Layer was not allocated, which can occur if there were no draw ops inside. We draw the + // equivalent by drawing a rect with the same layer properties (alpha/xfer/filter). + SkPaint paint; + paint.setAlpha(op.alpha * 255); + paint.setXfermodeMode(op.mode); + paint.setColorFilter(op.colorFilter); + RectOp rectOp(op.unmappedBounds, op.localMatrix, op.localClip, &paint); + BakedOpDispatcher::onRectOp(renderer, rectOp, state); + } else { + float layerAlpha = op.alpha * state.alpha; + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount) + .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top, + Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight())) + .build(); + renderer.renderGlop(state, glop); + } +} + +void BakedOpDispatcher::onCopyToLayerOp(BakedOpRenderer& renderer, const CopyToLayerOp& op, const BakedOpState& state) { + LOG_ALWAYS_FATAL_IF(*(op.layerHandle) != nullptr, "layer already exists!"); + *(op.layerHandle) = renderer.copyToLayer(state.computedState.clippedBounds); + LOG_ALWAYS_FATAL_IF(*op.layerHandle == nullptr, "layer copy failed"); +} + +void BakedOpDispatcher::onCopyFromLayerOp(BakedOpRenderer& renderer, const CopyFromLayerOp& op, const BakedOpState& state) { + LOG_ALWAYS_FATAL_IF(*op.layerHandle == nullptr, "no layer to draw underneath!"); + if (!state.computedState.clippedBounds.isEmpty()) { + if (op.paint && op.paint->getAlpha() < 255) { + SkPaint layerPaint; + layerPaint.setAlpha(op.paint->getAlpha()); + layerPaint.setXfermodeMode(SkXfermode::kDstIn_Mode); + layerPaint.setColorFilter(op.paint->getColorFilter()); + RectOp rectOp(state.computedState.clippedBounds, Matrix4::identity(), nullptr, &layerPaint); + BakedOpDispatcher::onRectOp(renderer, rectOp, state); + } + + OffscreenBuffer& layer = **(op.layerHandle); + auto mode = PaintUtils::getXfermodeDirect(op.paint); + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUvQuad(nullptr, layer.getTextureCoordinates()) + .setFillLayer(layer.texture, nullptr, 1.0f, mode, Blend::ModeOrderSwap::Swap) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(state.computedState.clippedBounds) + .build(); + renderer.renderGlop(state, glop); + } + renderer.renderState().layerPool().putOrDelete(*op.layerHandle); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h new file mode 100644 index 000000000000..4dfdd3ff619a --- /dev/null +++ b/libs/hwui/BakedOpDispatcher.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_BAKED_OP_DISPATCHER_H +#define ANDROID_HWUI_BAKED_OP_DISPATCHER_H + +#include "BakedOpState.h" +#include "RecordedOp.h" + +namespace android { +namespace uirenderer { + +/** + * Provides all "onBitmapOp(...)" style static methods for every op type, which convert the + * RecordedOps and their state to Glops, and renders them with the provided BakedOpRenderer. + * + * onXXXOp methods must either render directly with the renderer, or call a static renderYYY + * method to render content. There should never be draw content rejection in BakedOpDispatcher - + * it must happen at a higher level (except in error-ish cases, like texture-too-big). + */ +class BakedOpDispatcher { +public: + // Declares all "onMergedBitmapOps(...)" style methods for mergeable op types +#define X(Type) \ + static void onMerged##Type##s(BakedOpRenderer& renderer, const MergedBakedOpList& opList); + MAP_MERGEABLE_OPS(X) +#undef X + + // Declares all "onBitmapOp(...)" style methods for every op type +#define X(Type) \ + static void on##Type(BakedOpRenderer& renderer, const Type& op, const BakedOpState& state); + MAP_RENDERABLE_OPS(X) +#undef X + +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_BAKED_OP_DISPATCHER_H diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp new file mode 100644 index 000000000000..3c302b3f6c9f --- /dev/null +++ b/libs/hwui/BakedOpRenderer.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2015 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 "BakedOpRenderer.h" + +#include "Caches.h" +#include "Glop.h" +#include "GlopBuilder.h" +#include "renderstate/OffscreenBufferPool.h" +#include "renderstate/RenderState.h" +#include "utils/GLUtils.h" +#include "VertexBuffer.h" + +#include <algorithm> + +namespace android { +namespace uirenderer { + +OffscreenBuffer* BakedOpRenderer::startTemporaryLayer(uint32_t width, uint32_t height) { + LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); + + OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, width, height); + startRepaintLayer(buffer, Rect(width, height)); + return buffer; +} + +void BakedOpRenderer::recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) { + mRenderState.layerPool().putOrDelete(offscreenBuffer); +} + +void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) { + LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); + + // subtract repaintRect from region, since it will be regenerated + if (repaintRect.contains(0, 0, + offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight)) { + // repaint full layer, so throw away entire region + offscreenBuffer->region.clear(); + } else { + offscreenBuffer->region.subtractSelf(android::Rect(repaintRect.left, repaintRect.top, + repaintRect.right, repaintRect.bottom)); + } + + mRenderTarget.offscreenBuffer = offscreenBuffer; + + // create and bind framebuffer + mRenderTarget.frameBufferId = mRenderState.createFramebuffer(); + mRenderState.bindFramebuffer(mRenderTarget.frameBufferId); + + // attach the texture to the FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + offscreenBuffer->texture.id(), 0); + GL_CHECKPOINT(LOW); + + LOG_ALWAYS_FATAL_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE, + "framebuffer incomplete!"); + + // Change the viewport & ortho projection + setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight); + + clearColorBuffer(repaintRect); +} + +void BakedOpRenderer::endLayer() { + if (mRenderTarget.stencil) { + // if stencil was used for clipping, detach it and return it to pool + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0); + GL_CHECKPOINT(MODERATE); + mCaches.renderBufferCache.put(mRenderTarget.stencil); + mRenderTarget.stencil = nullptr; + } + mRenderTarget.lastStencilClip = nullptr; + + mRenderTarget.offscreenBuffer->updateMeshFromRegion(); + mRenderTarget.offscreenBuffer = nullptr; // It's in drawLayerOp's hands now. + + // Detach the texture from the FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + GL_CHECKPOINT(LOW); + mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId); + mRenderTarget.frameBufferId = 0; +} + +OffscreenBuffer* BakedOpRenderer::copyToLayer(const Rect& area) { + OffscreenBuffer* buffer = mRenderState.layerPool().get(mRenderState, + area.getWidth(), area.getHeight()); + if (!area.isEmpty()) { + mCaches.textureState().activateTexture(0); + mCaches.textureState().bindTexture(buffer->texture.id()); + + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + area.left, mRenderTarget.viewportHeight - area.bottom, + area.getWidth(), area.getHeight()); + } + return buffer; +} + +void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) { + LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "primary framebufferId must be 0"); + mRenderState.bindFramebuffer(0); + setViewport(width, height); + + if (!mOpaque) { + clearColorBuffer(repaintRect); + } + + mRenderState.debugOverdraw(true, true); +} + +void BakedOpRenderer::endFrame(const Rect& repaintRect) { + if (CC_UNLIKELY(Properties::debugOverdraw)) { + ClipRect overdrawClip(repaintRect); + Rect viewportRect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight); + // overdraw visualization + for (int i = 1; i <= 4; i++) { + if (i < 4) { + // nth level of overdraw tests for n+1 draws per pixel + mRenderState.stencil().enableDebugTest(i + 1, false); + } else { + // 4th level tests for 4 or higher draws per pixel + mRenderState.stencil().enableDebugTest(4, true); + } + + SkPaint paint; + paint.setColor(mCaches.getOverdrawColor(i)); + Glop glop; + GlopBuilder(mRenderState, mCaches, &glop) + .setRoundRectClipState(nullptr) + .setMeshUnitQuad() + .setFillPaint(paint, 1.0f) + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewMapUnitToRect(viewportRect) + .build(); + renderGlop(nullptr, &overdrawClip, glop); + } + mRenderState.stencil().disable(); + } + + // Note: we leave FBO 0 renderable here, for post-frame-content decoration +} + +void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) { + mRenderTarget.viewportWidth = width; + mRenderTarget.viewportHeight = height; + mRenderTarget.orthoMatrix.loadOrtho(width, height); + + mRenderState.setViewport(width, height); + mRenderState.blend().syncEnabled(); +} + +void BakedOpRenderer::clearColorBuffer(const Rect& rect) { + if (rect.contains(Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight))) { + // Full viewport is being cleared - disable scissor + mRenderState.scissor().setEnabled(false); + } else { + // Requested rect is subset of viewport - scissor to it to avoid over-clearing + mRenderState.scissor().setEnabled(true); + mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom, + rect.getWidth(), rect.getHeight()); + } + glClear(GL_COLOR_BUFFER_BIT); + if (!mRenderTarget.frameBufferId) mHasDrawn = true; +} + +Texture* BakedOpRenderer::getTexture(const SkBitmap* bitmap) { + Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef()); + if (!texture) { + return mCaches.textureCache.get(bitmap); + } + return texture; +} + +void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* paint) { + std::vector<Vertex> vertices; + vertices.reserve(count); + Vertex* vertex = vertices.data(); + + for (int index = 0; index < count; index += 4) { + float l = rects[index + 0]; + float t = rects[index + 1]; + float r = rects[index + 2]; + float b = rects[index + 3]; + + Vertex::set(vertex++, l, t); + Vertex::set(vertex++, r, t); + Vertex::set(vertex++, l, b); + Vertex::set(vertex++, r, b); + } + + LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "decoration only supported for FBO 0"); + // TODO: Currently assume full FBO damage, due to FrameInfoVisualizer::unionDirty. + // Should should scissor/set mHasDrawn safely. + mRenderState.scissor().setEnabled(false); + mHasDrawn = true; + Glop glop; + GlopBuilder(mRenderState, mCaches, &glop) + .setRoundRectClipState(nullptr) + .setMeshIndexedQuads(vertices.data(), count / 4) + .setFillPaint(*paint, 1.0f) + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewIdentityEmptyBounds() + .build(); + mRenderState.render(glop, mRenderTarget.orthoMatrix); +} + +// clears and re-fills stencil with provided rendertarget space quads, +// and then put stencil into test mode +void BakedOpRenderer::setupStencilQuads(std::vector<Vertex>& quadVertices, + int incrementThreshold) { + mRenderState.stencil().enableWrite(incrementThreshold); + mRenderState.stencil().clear(); + Glop glop; + GlopBuilder(mRenderState, mCaches, &glop) + .setRoundRectClipState(nullptr) + .setMeshIndexedQuads(quadVertices.data(), quadVertices.size() / 4) + .setFillBlack() + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewIdentityEmptyBounds() + .build(); + mRenderState.render(glop, mRenderTarget.orthoMatrix); + mRenderState.stencil().enableTest(incrementThreshold); +} + +void BakedOpRenderer::setupStencilRectList(const ClipBase* clip) { + LOG_ALWAYS_FATAL_IF(clip->mode != ClipMode::RectangleList, "can't rectlist clip without rectlist"); + auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList; + int quadCount = rectList.getTransformedRectanglesCount(); + std::vector<Vertex> rectangleVertices; + rectangleVertices.reserve(quadCount * 4); + for (int i = 0; i < quadCount; i++) { + const TransformedRectangle& tr(rectList.getTransformedRectangle(i)); + const Matrix4& transform = tr.getTransform(); + Rect bounds = tr.getBounds(); + if (transform.rectToRect()) { + // If rectToRect, can simply map bounds before storing verts + transform.mapRect(bounds); + bounds.doIntersect(clip->rect); + if (bounds.isEmpty()) { + continue; // will be outside of scissor, skip + } + } + + rectangleVertices.push_back(Vertex{bounds.left, bounds.top}); + rectangleVertices.push_back(Vertex{bounds.right, bounds.top}); + rectangleVertices.push_back(Vertex{bounds.left, bounds.bottom}); + rectangleVertices.push_back(Vertex{bounds.right, bounds.bottom}); + + if (!transform.rectToRect()) { + // If not rectToRect, must map each point individually + for (auto cur = rectangleVertices.end() - 4; cur < rectangleVertices.end(); cur++) { + transform.mapPoint(cur->x, cur->y); + } + } + } + setupStencilQuads(rectangleVertices, rectList.getTransformedRectanglesCount()); +} + +void BakedOpRenderer::setupStencilRegion(const ClipBase* clip) { + LOG_ALWAYS_FATAL_IF(clip->mode != ClipMode::Region, "can't region clip without region"); + auto&& region = reinterpret_cast<const ClipRegion*>(clip)->region; + + std::vector<Vertex> regionVertices; + SkRegion::Cliperator it(region, clip->rect.toSkIRect()); + while (!it.done()) { + const SkIRect& r = it.rect(); + regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fTop}); + regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fTop}); + regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fBottom}); + regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fBottom}); + it.next(); + } + setupStencilQuads(regionVertices, 0); +} + +void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* clip) { + // Prepare scissor (done before stencil, to simplify filling stencil) + mRenderState.scissor().setEnabled(clip != nullptr); + if (clip) { + mRenderState.scissor().set(mRenderTarget.viewportHeight, clip->rect); + } + + // If stencil may be used for clipping, enable it, fill it, or disable it as appropriate + if (CC_LIKELY(!Properties::debugOverdraw)) { + // only modify stencil mode and content when it's not used for overdraw visualization + if (CC_UNLIKELY(clip && clip->mode != ClipMode::Rectangle)) { + // NOTE: this pointer check is only safe for non-rect clips, + // since rect clips may be created on the stack + if (mRenderTarget.lastStencilClip != clip) { + // Stencil needed, but current stencil isn't up to date + mRenderTarget.lastStencilClip = clip; + + if (mRenderTarget.frameBufferId != 0 && !mRenderTarget.stencil) { + OffscreenBuffer* layer = mRenderTarget.offscreenBuffer; + mRenderTarget.stencil = mCaches.renderBufferCache.get( + Stencil::getLayerStencilFormat(), + layer->texture.width(), layer->texture.height()); + // stencil is bound + allocated - associate it with current FBO + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, mRenderTarget.stencil->getName()); + } + + if (clip->mode == ClipMode::RectangleList) { + setupStencilRectList(clip); + } else { + setupStencilRegion(clip); + } + } else { + // stencil is up to date - just need to ensure it's enabled (since an unclipped + // or scissor-only clipped op may have been drawn, disabling the stencil) + int incrementThreshold = 0; + if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) { + auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList; + incrementThreshold = rectList.getTransformedRectanglesCount(); + } + mRenderState.stencil().enableTest(incrementThreshold); + } + } else { + // either scissor or no clip, so disable stencil test + mRenderState.stencil().disable(); + } + } + + if (dirtyBounds) { + // dirty offscreenbuffer if present + dirtyRenderTarget(*dirtyBounds); + } +} + +void BakedOpRenderer::renderGlopImpl(const Rect* dirtyBounds, const ClipBase* clip, + const Glop& glop) { + prepareRender(dirtyBounds, clip); + mRenderState.render(glop, mRenderTarget.orthoMatrix); + if (!mRenderTarget.frameBufferId) mHasDrawn = true; +} + +void BakedOpRenderer::renderFunctor(const FunctorOp& op, const BakedOpState& state) { + prepareRender(&state.computedState.clippedBounds, state.computedState.getClipIfNeeded()); + + DrawGlInfo info; + auto&& clip = state.computedState.clipRect(); + info.clipLeft = clip.left; + info.clipTop = clip.top; + info.clipRight = clip.right; + info.clipBottom = clip.bottom; + info.isLayer = offscreenRenderTarget(); + info.width = mRenderTarget.viewportWidth; + info.height = mRenderTarget.viewportHeight; + state.computedState.transform.copyTo(&info.transform[0]); + + mRenderState.invokeFunctor(op.functor, DrawGlInfo::kModeDraw, &info); +} + +void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) { + if (mRenderTarget.offscreenBuffer) { + mRenderTarget.offscreenBuffer->dirty(uiDirty); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h new file mode 100644 index 000000000000..62bc564a4a2a --- /dev/null +++ b/libs/hwui/BakedOpRenderer.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 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 "BakedOpState.h" +#include "Matrix.h" +#include "utils/Macros.h" + +namespace android { +namespace uirenderer { + +class Caches; +struct Glop; +class Layer; +class RenderState; +struct ClipBase; + +/** + * Main rendering manager for a collection of work - one frame + any contained FBOs. + * + * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only + * place where FBOs are bound, created, and destroyed. + * + * All rendering operations will be sent by the Dispatcher, a collection of static methods, + * which has intentionally limited access to the renderer functionality. + */ +class BakedOpRenderer { +public: + typedef void (*GlopReceiver)(BakedOpRenderer&, const Rect*, const ClipBase*, const Glop&); + /** + * Position agnostic shadow lighting info. Used with all shadow ops in scene. + */ + struct LightInfo { + LightInfo() : LightInfo(0, 0) {} + LightInfo(uint8_t ambientShadowAlpha, + uint8_t spotShadowAlpha) + : ambientShadowAlpha(ambientShadowAlpha) + , spotShadowAlpha(spotShadowAlpha) {} + uint8_t ambientShadowAlpha; + uint8_t spotShadowAlpha; + }; + + BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, + const LightInfo& lightInfo) + : mGlopReceiver(DefaultGlopReceiver) + , mRenderState(renderState) + , mCaches(caches) + , mOpaque(opaque) + , mLightInfo(lightInfo) { + } + + RenderState& renderState() { return mRenderState; } + Caches& caches() { return mCaches; } + + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect); + void endFrame(const Rect& repaintRect); + WARN_UNUSED_RESULT OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height); + void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer); + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect); + void endLayer(); + WARN_UNUSED_RESULT OffscreenBuffer* copyToLayer(const Rect& area); + + Texture* getTexture(const SkBitmap* bitmap); + const LightInfo& getLightInfo() const { return mLightInfo; } + + void renderGlop(const BakedOpState& state, const Glop& glop) { + renderGlop(&state.computedState.clippedBounds, + state.computedState.getClipIfNeeded(), + glop); + } + void renderFunctor(const FunctorOp& op, const BakedOpState& state); + + void renderGlop(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop) { + mGlopReceiver(*this, dirtyBounds, clip, glop); + } + bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; } + void dirtyRenderTarget(const Rect& dirtyRect); + bool didDraw() const { return mHasDrawn; } + + uint32_t getViewportWidth() const { return mRenderTarget.viewportWidth; } + uint32_t getViewportHeight() const { return mRenderTarget.viewportHeight; } + + // simple draw methods, to be used for end frame decoration + void drawRect(float left, float top, float right, float bottom, const SkPaint* paint) { + float ltrb[4] = { left, top, right, bottom }; + drawRects(ltrb, 4, paint); + } + void drawRects(const float* rects, int count, const SkPaint* paint); +protected: + GlopReceiver mGlopReceiver; +private: + static void DefaultGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds, + const ClipBase* clip, const Glop& glop) { + renderer.renderGlopImpl(dirtyBounds, clip, glop); + } + void renderGlopImpl(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop); + void setViewport(uint32_t width, uint32_t height); + void clearColorBuffer(const Rect& clearRect); + void prepareRender(const Rect* dirtyBounds, const ClipBase* clip); + void setupStencilRectList(const ClipBase* clip); + void setupStencilRegion(const ClipBase* clip); + void setupStencilQuads(std::vector<Vertex>& quadVertices, int incrementThreshold); + + RenderState& mRenderState; + Caches& mCaches; + bool mOpaque; + bool mHasDrawn = false; + + // render target state - setup by start/end layer/frame + // only valid to use in between start/end pairs. + struct { + // If not drawing to a layer: fbo = 0, offscreenBuffer = null, + // Otherwise these refer to currently painting layer's state + GLuint frameBufferId = 0; + OffscreenBuffer* offscreenBuffer = nullptr; + + // Used when drawing to a layer and using stencil clipping. otherwise null. + RenderBuffer* stencil = nullptr; + + // value representing the ClipRectList* or ClipRegion* currently stored in + // the stencil of the current render target + const ClipBase* lastStencilClip = nullptr; + + // Size of renderable region in current render target - for layers, may not match actual + // bounds of FBO texture. offscreenBuffer->texture has this information. + uint32_t viewportWidth = 0; + uint32_t viewportHeight = 0; + + Matrix4 orthoMatrix; + } mRenderTarget; + + const LightInfo mLightInfo; +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp new file mode 100644 index 000000000000..9f98241c6caa --- /dev/null +++ b/libs/hwui/BakedOpState.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2015 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 "BakedOpState.h" + +#include "ClipArea.h" + +namespace android { +namespace uirenderer { + +static int computeClipSideFlags(const Rect& clip, const Rect& bounds) { + int clipSideFlags = 0; + if (clip.left > bounds.left) clipSideFlags |= OpClipSideFlags::Left; + if (clip.top > bounds.top) clipSideFlags |= OpClipSideFlags::Top; + if (clip.right < bounds.right) clipSideFlags |= OpClipSideFlags::Right; + if (clip.bottom < bounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom; + return clipSideFlags; +} + +ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, + const RecordedOp& recordedOp, bool expandForStroke) { + // resolvedMatrix = parentMatrix * localMatrix + transform.loadMultiply(*snapshot.transform, recordedOp.localMatrix); + + // resolvedClippedBounds = intersect(resolvedMatrix * opBounds, resolvedClipRect) + clippedBounds = recordedOp.unmappedBounds; + if (CC_UNLIKELY(expandForStroke)) { + // account for non-hairline stroke + clippedBounds.outset(recordedOp.paint->getStrokeWidth() * 0.5f); + } + transform.mapRect(clippedBounds); + if (CC_UNLIKELY(expandForStroke + && (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) { + // account for hairline stroke when stroke may be < 1 scaled pixel + // Non translate || strokeWidth < 1 is conservative, but will cover all cases + clippedBounds.outset(0.5f); + } + + // resolvedClipRect = intersect(parentMatrix * localClip, parentClip) + clipState = snapshot.serializeIntersectedClip(allocator, + recordedOp.localClip, *(snapshot.transform)); + LOG_ALWAYS_FATAL_IF(!clipState, "must clip!"); + + const Rect& clipRect = clipState->rect; + if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) { + // Rejected based on either empty clip, or bounds not intersecting with clip + + // Note: we could rewind the clipState object in situations where the clipRect is empty, + // but *only* if the caching logic within ClipArea was aware of the rewind. + clipState = nullptr; + clippedBounds.setEmpty(); + } else { + // Not rejected! compute true clippedBounds, clipSideFlags, and path mask + clipSideFlags = computeClipSideFlags(clipRect, clippedBounds); + clippedBounds.doIntersect(clipRect); + + if (CC_UNLIKELY(snapshot.projectionPathMask)) { + // map projection path mask from render target space into op space, + // so intersection with op geometry is possible + Matrix4 inverseTransform; + inverseTransform.loadInverse(transform); + SkMatrix skInverseTransform; + inverseTransform.copyTo(skInverseTransform); + + auto localMask = allocator.create<SkPath>(); + snapshot.projectionPathMask->transform(skInverseTransform, localMask); + localProjectionPathMask = localMask; + } + } +} + +ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, + const Matrix4& localTransform, const ClipBase* localClip) { + transform.loadMultiply(*snapshot.transform, localTransform); + clipState = snapshot.serializeIntersectedClip(allocator, localClip, *(snapshot.transform)); + clippedBounds = clipState->rect; + clipSideFlags = OpClipSideFlags::Full; + localProjectionPathMask = nullptr; +} + +ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot) + : transform(*snapshot.transform) + , clipState(snapshot.mutateClipArea().serializeClip(allocator)) + , clippedBounds(clipState->rect) + , clipSideFlags(OpClipSideFlags::Full) + , localProjectionPathMask(nullptr) {} + +ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect) + : transform(Matrix4::identity()) + , clipState(clipRect) + , clippedBounds(dstRect) + , clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect)) + , localProjectionPathMask(nullptr) { + clippedBounds.doIntersect(clipRect->rect); +} + +BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( + allocator, snapshot, recordedOp, false); + if (bakedState->computedState.clippedBounds.isEmpty()) { + // bounds are empty, so op is rejected + allocator.rewindIfLastAlloc(bakedState); + return nullptr; + } + return bakedState; +} + +BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp); +} + +BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + bool expandForStroke = (strokeBehavior == StrokeBehavior::StyleDefined) + ? (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style) + : true; + + BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( + allocator, snapshot, recordedOp, expandForStroke); + if (bakedState->computedState.clippedBounds.isEmpty()) { + // bounds are empty, so op is rejected + // NOTE: this won't succeed if a clip was allocated + allocator.rewindIfLastAlloc(bakedState); + return nullptr; + } + return bakedState; +} + +BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const ShadowOp* shadowOpPtr) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + + // clip isn't empty, so construct the op + return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr); +} + +BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator, + const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) { + return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp); +} + +void BakedOpState::setupOpacity(const SkPaint* paint) { + computedState.opaqueOverClippedBounds = computedState.transform.isSimple() + && computedState.clipState->mode == ClipMode::Rectangle + && MathUtils::areEqual(alpha, 1.0f) + && !roundRectClipState + && PaintUtils::isOpaquePaint(paint); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h new file mode 100644 index 000000000000..e1441fca5ee2 --- /dev/null +++ b/libs/hwui/BakedOpState.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_BAKED_OP_STATE_H +#define ANDROID_HWUI_BAKED_OP_STATE_H + +#include "Matrix.h" +#include "RecordedOp.h" +#include "Rect.h" +#include "Snapshot.h" + +namespace android { +namespace uirenderer { + +namespace OpClipSideFlags { + enum { + None = 0x0, + Left = 0x1, + Top = 0x2, + Right = 0x4, + Bottom = 0x8, + Full = 0xF, + // ConservativeFull = 0x1F needed? + }; +} + +/** + * Holds a list of BakedOpStates of ops that can be drawn together + */ +struct MergedBakedOpList { + const BakedOpState*const* states; + size_t count; + int clipSideFlags; + Rect clip; +}; + +/** + * Holds the resolved clip, transform, and bounds of a recordedOp, when replayed with a snapshot + */ +class ResolvedRenderState { +public: + ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, + const RecordedOp& recordedOp, bool expandForStroke); + + // Constructor for unbounded ops *with* transform/clip + ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, + const Matrix4& localTransform, const ClipBase* localClip); + + // Constructor for unbounded ops without transform/clip (namely shadows) + ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot); + + // Constructor for primitive ops provided clip, and no transform + ResolvedRenderState(const ClipRect* viewportRect, const Rect& dstRect); + + Rect computeLocalSpaceClip() const { + Matrix4 inverse; + inverse.loadInverse(transform); + + Rect outClip(clipRect()); + inverse.mapRect(outClip); + return outClip; + } + + const Rect& clipRect() const { + return clipState->rect; + } + + bool requiresClip() const { + return clipSideFlags != OpClipSideFlags::None + || CC_UNLIKELY(clipState->mode != ClipMode::Rectangle); + } + + // returns the clip if it's needed to draw the operation, otherwise nullptr + const ClipBase* getClipIfNeeded() const { + return requiresClip() ? clipState : nullptr; + } + + Matrix4 transform; + const ClipBase* clipState = nullptr; + Rect clippedBounds; + int clipSideFlags = 0; + const SkPath* localProjectionPathMask = nullptr; + bool opaqueOverClippedBounds = false; +}; + +/** + * Self-contained op wrapper, containing all resolved state required to draw the op. + * + * Stashed pointers within all point to longer lived objects, with no ownership implied. + */ +class BakedOpState { +public: + static BakedOpState* tryConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp); + + static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp); + + enum class StrokeBehavior { + // stroking is forced, regardless of style on paint (such as for lines) + Forced, + // stroking is defined by style on paint + StyleDefined, + }; + + static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const RecordedOp& recordedOp, StrokeBehavior strokeBehavior); + + static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator, + Snapshot& snapshot, const ShadowOp* shadowOpPtr); + + static BakedOpState* directConstruct(LinearAllocator& allocator, + const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp); + + // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent. + void setupOpacity(const SkPaint* paint); + + // computed state: + ResolvedRenderState computedState; + + // simple state (straight pointer/value storage): + const float alpha; + const RoundRectClipState* roundRectClipState; + const RecordedOp* op; + +private: + friend class LinearAllocator; + + BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, + const RecordedOp& recordedOp, bool expandForStroke) + : computedState(allocator, snapshot, recordedOp, expandForStroke) + , alpha(snapshot.alpha) + , roundRectClipState(snapshot.roundRectClipState) + , op(&recordedOp) {} + + // TODO: fix this brittleness + BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const RecordedOp& recordedOp) + : computedState(allocator, snapshot, recordedOp.localMatrix, recordedOp.localClip) + , alpha(snapshot.alpha) + , roundRectClipState(snapshot.roundRectClipState) + , op(&recordedOp) {} + + BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr) + : computedState(allocator, snapshot) + , alpha(snapshot.alpha) + , roundRectClipState(snapshot.roundRectClipState) + , op(shadowOpPtr) {} + + BakedOpState(const ClipRect* clipRect, const Rect& dstRect, const RecordedOp& recordedOp) + : computedState(clipRect, dstRect) + , alpha(1.0f) + , roundRectClipState(nullptr) + , op(&recordedOp) {} +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_BAKED_OP_STATE_H diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp index 2763e89e19c7..eaa1c330d5dd 100644 --- a/libs/hwui/Caches.cpp +++ b/libs/hwui/Caches.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "Caches.h" #include "GammaFontRenderer.h" @@ -25,6 +23,7 @@ #include "ShadowTessellator.h" #include "utils/GLUtils.h" +#include <cutils/properties.h> #include <utils/Log.h> #include <utils/String8.h> @@ -56,7 +55,6 @@ Caches::Caches(RenderState& renderState) , mInitialized(false) { INIT_LOGD("Creating OpenGL renderer caches"); init(); - initFont(); initConstraints(); initStaticProperties(); initExtensions(); @@ -70,8 +68,6 @@ bool Caches::init() { mRegionMesh = nullptr; mProgram = nullptr; - mFunctorsCount = 0; - patchCache.init(); mInitialized = true; @@ -82,10 +78,6 @@ bool Caches::init() { return true; } -void Caches::initFont() { - fontRenderer = GammaFontRenderer::createRenderer(); -} - void Caches::initExtensions() { if (mExtensions.hasDebugMarker()) { eventMark = glInsertEventMarkerEXT; @@ -104,15 +96,9 @@ void Caches::initConstraints() { } void Caches::initStaticProperties() { - gpuPixelBuffersEnabled = false; - // OpenGL ES 3.0+ specific features - if (mExtensions.hasPixelBufferObjects()) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, property, "true") > 0) { - gpuPixelBuffersEnabled = !strcmp(property, "true"); - } - } + gpuPixelBuffersEnabled = mExtensions.hasPixelBufferObjects() + && property_get_bool(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, true); } void Caches::terminate() { @@ -207,14 +193,14 @@ void Caches::dumpMemoryUsage(String8 &log) { dropShadowCache.getMaxSize()); log.appendFormat(" PatchCache %8d / %8d\n", patchCache.getSize(), patchCache.getMaxSize()); - for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) { - const uint32_t sizeA8 = fontRenderer->getFontRendererSize(i, GL_ALPHA); - const uint32_t sizeRGBA = fontRenderer->getFontRendererSize(i, GL_RGBA); - log.appendFormat(" FontRenderer %d A8 %8d / %8d\n", i, sizeA8, sizeA8); - log.appendFormat(" FontRenderer %d RGBA %8d / %8d\n", i, sizeRGBA, sizeRGBA); - log.appendFormat(" FontRenderer %d total %8d / %8d\n", i, sizeA8 + sizeRGBA, - sizeA8 + sizeRGBA); - } + + const uint32_t sizeA8 = fontRenderer.getFontRendererSize(GL_ALPHA); + const uint32_t sizeRGBA = fontRenderer.getFontRendererSize(GL_RGBA); + log.appendFormat(" FontRenderer A8 %8d / %8d\n", sizeA8, sizeA8); + log.appendFormat(" FontRenderer RGBA %8d / %8d\n", sizeRGBA, sizeRGBA); + log.appendFormat(" FontRenderer total %8d / %8d\n", sizeA8 + sizeRGBA, + sizeA8 + sizeRGBA); + log.appendFormat("Other:\n"); log.appendFormat(" FboCache %8d / %8d\n", fboCache.getSize(), fboCache.getMaxSize()); @@ -226,10 +212,8 @@ void Caches::dumpMemoryUsage(String8 &log) { total += tessellationCache.getSize(); total += dropShadowCache.getSize(); total += patchCache.getSize(); - for (uint32_t i = 0; i < fontRenderer->getFontRendererCount(); i++) { - total += fontRenderer->getFontRendererSize(i, GL_ALPHA); - total += fontRenderer->getFontRendererSize(i, GL_RGBA); - } + total += fontRenderer.getFontRendererSize(GL_ALPHA); + total += fontRenderer.getFontRendererSize(GL_RGBA); log.appendFormat("Total memory usage:\n"); log.appendFormat(" %d bytes, %.2f MB\n", total, total / 1024.0f / 1024.0f); @@ -249,22 +233,22 @@ void Caches::flush(FlushMode mode) { FLUSH_LOGD("Flushing caches (mode %d)", mode); switch (mode) { - case kFlushMode_Full: + case FlushMode::Full: textureCache.clear(); patchCache.clear(); dropShadowCache.clear(); gradientCache.clear(); - fontRenderer->clear(); + fontRenderer.clear(); fboCache.clear(); dither.clear(); // fall through - case kFlushMode_Moderate: - fontRenderer->flush(); + case FlushMode::Moderate: + fontRenderer.flush(); textureCache.flush(); pathCache.clear(); tessellationCache.clear(); // fall through - case kFlushMode_Layers: + case FlushMode::Layers: layerCache.clear(); renderBufferCache.clear(); break; @@ -278,38 +262,6 @@ void Caches::flush(FlushMode mode) { } /////////////////////////////////////////////////////////////////////////////// -// Tiling -/////////////////////////////////////////////////////////////////////////////// - -void Caches::startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard) { - if (mExtensions.hasTiledRendering() && !Properties::debugOverdraw) { - glStartTilingQCOM(x, y, width, height, (discard ? GL_NONE : GL_COLOR_BUFFER_BIT0_QCOM)); - } -} - -void Caches::endTiling() { - if (mExtensions.hasTiledRendering() && !Properties::debugOverdraw) { - glEndTilingQCOM(GL_COLOR_BUFFER_BIT0_QCOM); - } -} - -bool Caches::hasRegisteredFunctors() { - return mFunctorsCount > 0; -} - -void Caches::registerFunctors(uint32_t functorCount) { - mFunctorsCount += functorCount; -} - -void Caches::unregisterFunctors(uint32_t functorCount) { - if (functorCount > mFunctorsCount) { - mFunctorsCount = 0; - } else { - mFunctorsCount -= functorCount; - } -} - -/////////////////////////////////////////////////////////////////////////////// // Regions /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index 804f609e9a0f..eac9359beab3 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -17,15 +17,11 @@ #ifndef ANDROID_HWUI_CACHES_H #define ANDROID_HWUI_CACHES_H -#ifndef LOG_TAG - #define LOG_TAG "OpenGLRenderer" -#endif - - #include "AssetAtlas.h" #include "Dither.h" #include "Extensions.h" #include "FboCache.h" +#include "GammaFontRenderer.h" #include "GradientCache.h" #include "LayerCache.h" #include "PatchCache.h" @@ -47,18 +43,16 @@ #include <GLES3/gl3.h> #include <utils/KeyedVector.h> -#include <utils/Singleton.h> -#include <utils/Vector.h> #include <cutils/compiler.h> #include <SkPath.h> +#include <vector> + namespace android { namespace uirenderer { -class GammaFontRenderer; - /////////////////////////////////////////////////////////////////////////////// // Caches /////////////////////////////////////////////////////////////////////////////// @@ -87,10 +81,10 @@ private: static Caches* sInstance; public: - enum FlushMode { - kFlushMode_Layers = 0, - kFlushMode_Moderate, - kFlushMode_Full + enum class FlushMode { + Layers = 0, + Moderate, + Full }; /** @@ -98,6 +92,8 @@ public: */ bool init(); + bool isInitialized() { return mInitialized; } + /** * Flush the cache. * @@ -107,7 +103,7 @@ public: /** * Destroys all resources associated with this cache. This should - * be called after a flush(kFlushMode_Full). + * be called after a flush(FlushMode::Full). */ void terminate(); @@ -128,10 +124,6 @@ public: */ void deleteLayerDeferred(Layer* layer); - - void startTiling(GLuint x, GLuint y, GLuint width, GLuint height, bool discard); - void endTiling(); - /** * Returns the mesh used to draw regions. Calling this method will * bind a VBO of type GL_ELEMENT_ARRAY_BUFFER that contains the @@ -145,10 +137,6 @@ public: void dumpMemoryUsage(); void dumpMemoryUsage(String8& log); - bool hasRegisteredFunctors(); - void registerFunctors(uint32_t functorCount); - void unregisterFunctors(uint32_t functorCount); - // Misc GLint maxTextureSize; @@ -168,7 +156,7 @@ public: TextDropShadowCache dropShadowCache; FboCache fboCache; - GammaFontRenderer* fontRenderer; + GammaFontRenderer fontRenderer; TaskManager tasks; @@ -190,8 +178,6 @@ public: TextureState& textureState() { return *mTextureState; } private: - - void initFont(); void initExtensions(); void initConstraints(); void initStaticProperties(); @@ -206,12 +192,10 @@ private: std::unique_ptr<TextureVertex[]> mRegionMesh; mutable Mutex mGarbageLock; - Vector<Layer*> mLayerGarbage; + std::vector<Layer*> mLayerGarbage; bool mInitialized; - uint32_t mFunctorsCount; - // TODO: move below to RenderState PixelBufferState* mPixelBufferState = nullptr; TextureState* mTextureState = nullptr; diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp index e22b0d3084ab..e2149d1e4a69 100644 --- a/libs/hwui/CanvasState.cpp +++ b/libs/hwui/CanvasState.cpp @@ -14,9 +14,8 @@ * limitations under the License. */ -#include <SkCanvas.h> - #include "CanvasState.h" +#include "hwui/Canvas.h" #include "utils/MathUtils.h" namespace android { @@ -28,38 +27,86 @@ CanvasState::CanvasState(CanvasStateClient& renderer) , mWidth(-1) , mHeight(-1) , mSaveCount(1) - , mFirstSnapshot(new Snapshot) , mCanvas(renderer) - , mSnapshot(mFirstSnapshot) { - + , mSnapshot(&mFirstSnapshot) { } CanvasState::~CanvasState() { + // First call freeSnapshot on all but mFirstSnapshot + // to invoke all the dtors + freeAllSnapshots(); + + // Now actually release the memory + while (mSnapshotPool) { + void* temp = mSnapshotPool; + mSnapshotPool = mSnapshotPool->previous; + free(temp); + } +} + +void CanvasState::initializeRecordingSaveStack(int viewportWidth, int viewportHeight) { + if (mWidth != viewportWidth || mHeight != viewportHeight) { + mWidth = viewportWidth; + mHeight = viewportHeight; + mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight); + mCanvas.onViewportInitialized(); + } + freeAllSnapshots(); + mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip); + mSnapshot->setRelativeLightCenter(Vector3()); + mSaveCount = 1; } -void CanvasState::initializeSaveStack(float clipLeft, float clipTop, +void CanvasState::initializeSaveStack( + int viewportWidth, int viewportHeight, + float clipLeft, float clipTop, float clipRight, float clipBottom, const Vector3& lightCenter) { - mSnapshot = new Snapshot(mFirstSnapshot, - SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + if (mWidth != viewportWidth || mHeight != viewportHeight) { + mWidth = viewportWidth; + mHeight = viewportHeight; + mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight); + mCanvas.onViewportInitialized(); + } + + freeAllSnapshots(); + mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip); mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); mSnapshot->fbo = mCanvas.getTargetFbo(); mSnapshot->setRelativeLightCenter(lightCenter); mSaveCount = 1; } -void CanvasState::setViewport(int width, int height) { - mWidth = width; - mHeight = height; - mFirstSnapshot->initializeViewport(width, height); - mCanvas.onViewportInitialized(); - - // create a temporary 1st snapshot, so old snapshots are released, - // and viewport can be queried safely. - // TODO: remove, combine viewport + save stack initialization - mSnapshot = new Snapshot(mFirstSnapshot, - SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); - mSaveCount = 1; +Snapshot* CanvasState::allocSnapshot(Snapshot* previous, int savecount) { + void* memory; + if (mSnapshotPool) { + memory = mSnapshotPool; + mSnapshotPool = mSnapshotPool->previous; + mSnapshotPoolCount--; + } else { + memory = malloc(sizeof(Snapshot)); + } + return new (memory) Snapshot(previous, savecount); +} + +void CanvasState::freeSnapshot(Snapshot* snapshot) { + snapshot->~Snapshot(); + // Arbitrary number, just don't let this grown unbounded + if (mSnapshotPoolCount > 10) { + free((void*) snapshot); + } else { + snapshot->previous = mSnapshotPool; + mSnapshotPool = snapshot; + mSnapshotPoolCount++; + } +} + +void CanvasState::freeAllSnapshots() { + while (mSnapshot != &mFirstSnapshot) { + Snapshot* temp = mSnapshot; + mSnapshot = mSnapshot->previous; + freeSnapshot(temp); + } } /////////////////////////////////////////////////////////////////////////////// @@ -73,7 +120,7 @@ void CanvasState::setViewport(int width, int height) { * stack, and ensures restoreToCount() doesn't call back into subclass overrides. */ int CanvasState::saveSnapshot(int flags) { - mSnapshot = new Snapshot(mSnapshot, flags); + mSnapshot = allocSnapshot(mSnapshot, flags); return mSaveCount++; } @@ -85,14 +132,16 @@ int CanvasState::save(int flags) { * Guaranteed to restore without side-effects. */ void CanvasState::restoreSnapshot() { - sp<Snapshot> toRemove = mSnapshot; - sp<Snapshot> toRestore = mSnapshot->previous; + Snapshot* toRemove = mSnapshot; + Snapshot* toRestore = mSnapshot->previous; mSaveCount--; mSnapshot = toRestore; // subclass handles restore implementation mCanvas.onSnapshotRestored(*toRemove, *toRestore); + + freeSnapshot(toRemove); } void CanvasState::restore() { @@ -138,7 +187,7 @@ void CanvasState::setMatrix(const SkMatrix& matrix) { } void CanvasState::setMatrix(const Matrix4& matrix) { - mSnapshot->transform->load(matrix); + *(mSnapshot->transform) = matrix; } void CanvasState::concatMatrix(const SkMatrix& matrix) { @@ -155,17 +204,20 @@ void CanvasState::concatMatrix(const Matrix4& matrix) { /////////////////////////////////////////////////////////////////////////////// bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { - mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op); + mSnapshot->clip(Rect(left, top, right, bottom), op); + mDirtyClip = true; return !mSnapshot->clipIsEmpty(); } bool CanvasState::clipPath(const SkPath* path, SkRegion::Op op) { - mDirtyClip |= mSnapshot->clipPath(*path, op); + mSnapshot->clipPath(*path, op); + mDirtyClip = true; return !mSnapshot->clipIsEmpty(); } bool CanvasState::clipRegion(const SkRegion* region, SkRegion::Op op) { - mDirtyClip |= mSnapshot->clipRegionTransformed(*region, op); + mSnapshot->clipRegionTransformed(*region, op); + mDirtyClip = true; return !mSnapshot->clipIsEmpty(); } @@ -219,7 +271,7 @@ bool CanvasState::calculateQuickRejectForScissor(float left, float top, currentTransform()->mapRect(r); r.snapGeometryToPixelBoundaries(snapOut); - Rect clipRect(currentClipRect()); + Rect clipRect(currentRenderTargetClip()); clipRect.snapToPixelBoundaries(); if (!clipRect.intersects(r)) return true; @@ -247,7 +299,7 @@ bool CanvasState::quickRejectConservative(float left, float top, currentTransform()->mapRect(r); r.roundOut(); // rounded out to be conservative - Rect clipRect(currentClipRect()); + Rect clipRect(currentRenderTargetClip()); clipRect.snapToPixelBoundaries(); if (!clipRect.intersects(r)) return true; diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h index b35db28eaf82..b9e87ae5595d 100644 --- a/libs/hwui/CanvasState.h +++ b/libs/hwui/CanvasState.h @@ -17,12 +17,12 @@ #ifndef ANDROID_HWUI_CANVAS_STATE_H #define ANDROID_HWUI_CANVAS_STATE_H +#include "Snapshot.h" + #include <SkMatrix.h> #include <SkPath.h> #include <SkRegion.h> -#include "Snapshot.h" - namespace android { namespace uirenderer { @@ -71,7 +71,7 @@ public: * (getClip/Matrix), but so that quickRejection can also be used. */ -class ANDROID_API CanvasState { +class CanvasState { public: CanvasState(CanvasStateClient& renderer); ~CanvasState(); @@ -80,10 +80,15 @@ public: * Initializes the first snapshot, computing the projection matrix, * and stores the dimensions of the render target. */ - void initializeSaveStack(float clipLeft, float clipTop, float clipRight, float clipBottom, - const Vector3& lightCenter); + void initializeRecordingSaveStack(int viewportWidth, int viewportHeight); - void setViewport(int width, int height); + /** + * Initializes the first snapshot, computing the projection matrix, + * and stores the dimensions of the render target. + */ + void initializeSaveStack(int viewportWidth, int viewportHeight, + float clipLeft, float clipTop, float clipRight, float clipBottom, + const Vector3& lightCenter); bool hasRectToRectTransform() const { return CC_LIKELY(currentTransform()->rectToRect()); @@ -148,7 +153,7 @@ public: void setInvisible(bool value) { mSnapshot->invisible = value; } inline const mat4* currentTransform() const { return currentSnapshot()->transform; } - inline const Rect& currentClipRect() const { return currentSnapshot()->getClipRect(); } + inline const Rect& currentRenderTargetClip() const { return currentSnapshot()->getRenderTargetClip(); } inline Region* currentRegion() const { return currentSnapshot()->region; } inline int currentFlags() const { return currentSnapshot()->flags; } const Vector3& currentLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); } @@ -159,17 +164,17 @@ public: int getHeight() const { return mHeight; } bool clipIsSimple() const { return currentSnapshot()->clipIsSimple(); } - inline const Snapshot* currentSnapshot() const { - return mSnapshot != nullptr ? mSnapshot.get() : mFirstSnapshot.get(); - } - inline Snapshot* writableSnapshot() { return mSnapshot.get(); } - inline const Snapshot* firstSnapshot() const { return mFirstSnapshot.get(); } + inline const Snapshot* currentSnapshot() const { return mSnapshot; } + inline Snapshot* writableSnapshot() { return mSnapshot; } + inline const Snapshot* firstSnapshot() const { return &mFirstSnapshot; } private: - /// No default constructor - must supply a CanvasStateClient (mCanvas). - CanvasState(); + Snapshot* allocSnapshot(Snapshot* previous, int savecount); + void freeSnapshot(Snapshot* snapshot); + void freeAllSnapshots(); /// indicates that the clip has been changed since the last time it was consumed + // TODO: delete when switching to HWUI_NEW_OPS bool mDirtyClip; /// Dimensions of the drawing surface @@ -179,13 +184,18 @@ private: int mSaveCount; /// Base state - sp<Snapshot> mFirstSnapshot; + Snapshot mFirstSnapshot; /// Host providing callbacks CanvasStateClient& mCanvas; /// Current state - sp<Snapshot> mSnapshot; + Snapshot* mSnapshot; + + // Pool of allocated snapshots to re-use + // NOTE: The dtors have already been invoked! + Snapshot* mSnapshotPool = nullptr; + int mSnapshotPoolCount = 0; }; // class CanvasState diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp index b1a68447fc21..fe6823925083 100644 --- a/libs/hwui/ClipArea.cpp +++ b/libs/hwui/ClipArea.cpp @@ -15,28 +15,19 @@ */ #include "ClipArea.h" +#include "utils/LinearAllocator.h" + #include <SkPath.h> #include <limits> - -#include "Rect.h" +#include <type_traits> namespace android { namespace uirenderer { -static bool intersect(Rect& r, const Rect& r2) { - bool hasIntersection = r.intersect(r2); - if (!hasIntersection) { - r.setEmpty(); - } - return hasIntersection; -} - static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) { - Vertex v; - v.x = x; - v.y = y; + Vertex v = {x, y}; transform.mapPoint(v.x, v.y); - transformedBounds.expandToCoverVertex(v.x, v.y); + transformedBounds.expandToCover(v.x, v.y); } Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) { @@ -50,6 +41,10 @@ Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) { return transformedBounds; } +void ClipBase::dump() const { + ALOGD("mode %d" RECT_STRING, mode, RECT_ARGS(rect)); +} + /* * TransformedRectangle */ @@ -69,9 +64,8 @@ bool TransformedRectangle::canSimplyIntersectWith( return mTransform == other.mTransform; } -bool TransformedRectangle::intersectWith(const TransformedRectangle& other) { - Rect translatedBounds(other.mBounds); - return intersect(mBounds, translatedBounds); +void TransformedRectangle::intersectWith(const TransformedRectangle& other) { + mBounds.doIntersect(other.mBounds); } bool TransformedRectangle::isEmpty() const { @@ -148,7 +142,7 @@ Rect RectangleList::calculateBounds() const { if (index == 0) { bounds = tr.transformedBounds(); } else { - bounds.intersect(tr.transformedBounds()); + bounds.doIntersect(tr.transformedBounds()); } } return bounds; @@ -182,12 +176,18 @@ SkRegion RectangleList::convertToRegion(const SkRegion& clip) const { return rectangleListAsRegion; } +void RectangleList::transform(const Matrix4& transform) { + for (int index = 0; index < mTransformedRectanglesCount; index++) { + mTransformedRectangles[index].transform(transform); + } +} + /* * ClipArea */ ClipArea::ClipArea() - : mMode(kModeRectangle) { + : mMode(ClipMode::Rectangle) { } /* @@ -195,58 +195,67 @@ ClipArea::ClipArea() */ void ClipArea::setViewportDimensions(int width, int height) { + mPostViewportClipObserved = false; mViewportBounds.set(0, 0, width, height); mClipRect = mViewportBounds; } void ClipArea::setEmpty() { - mMode = kModeRectangle; + onClipUpdated(); + mMode = ClipMode::Rectangle; mClipRect.setEmpty(); mClipRegion.setEmpty(); mRectangleList.setEmpty(); } void ClipArea::setClip(float left, float top, float right, float bottom) { - mMode = kModeRectangle; + onClipUpdated(); + mMode = ClipMode::Rectangle; mClipRect.set(left, top, right, bottom); mClipRegion.setEmpty(); } -bool ClipArea::clipRectWithTransform(float left, float top, float right, - float bottom, const mat4* transform, SkRegion::Op op) { - Rect r(left, top, right, bottom); - return clipRectWithTransform(r, transform, op); -} - -bool ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, +void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op) { + if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; + if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; + onClipUpdated(); switch (mMode) { - case kModeRectangle: - return rectangleModeClipRectWithTransform(r, transform, op); - case kModeRectangleList: - return rectangleListModeClipRectWithTransform(r, transform, op); - case kModeRegion: - return regionModeClipRectWithTransform(r, transform, op); + case ClipMode::Rectangle: + rectangleModeClipRectWithTransform(r, transform, op); + break; + case ClipMode::RectangleList: + rectangleListModeClipRectWithTransform(r, transform, op); + break; + case ClipMode::Region: + regionModeClipRectWithTransform(r, transform, op); + break; } - return false; } -bool ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) { +void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) { + if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; + if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; + onClipUpdated(); enterRegionMode(); mClipRegion.op(region, op); onClipRegionUpdated(); - return true; } -bool ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform, +void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform, SkRegion::Op op) { + if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; + if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; + onClipUpdated(); SkMatrix skTransform; transform->copyTo(skTransform); SkPath transformed; path.transform(skTransform, &transformed); SkRegion region; regionFromPath(transformed, region); - return clipRegion(region, op); + enterRegionMode(); + mClipRegion.op(region, op); + onClipRegionUpdated(); } /* @@ -257,41 +266,31 @@ void ClipArea::enterRectangleMode() { // Entering rectangle mode discards any // existing clipping information from the other modes. // The only way this occurs is by a clip setting operation. - mMode = kModeRectangle; + mMode = ClipMode::Rectangle; } -bool ClipArea::rectangleModeClipRectWithTransform(const Rect& r, +void ClipArea::rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op) { if (op == SkRegion::kReplace_Op && transform->rectToRect()) { mClipRect = r; transform->mapRect(mClipRect); - return true; + return; } else if (op != SkRegion::kIntersect_Op) { enterRegionMode(); - return regionModeClipRectWithTransform(r, transform, op); + regionModeClipRectWithTransform(r, transform, op); + return; } if (transform->rectToRect()) { Rect transformed(r); transform->mapRect(transformed); - bool hasIntersection = mClipRect.intersect(transformed); - if (!hasIntersection) { - mClipRect.setEmpty(); - } - return true; + mClipRect.doIntersect(transformed); + return; } enterRectangleListMode(); - return rectangleListModeClipRectWithTransform(r, transform, op); -} - -bool ClipArea::rectangleModeClipRectWithTransform(float left, float top, - float right, float bottom, const mat4* transform, SkRegion::Op op) { - Rect r(left, top, right, bottom); - bool result = rectangleModeClipRectWithTransform(r, transform, op); - mClipRect = mRectangleList.calculateBounds(); - return result; + rectangleListModeClipRectWithTransform(r, transform, op); } /* @@ -302,25 +301,18 @@ void ClipArea::enterRectangleListMode() { // Is is only legal to enter rectangle list mode from // rectangle mode, since rectangle list mode cannot represent // all clip areas that can be represented by a region. - ALOG_ASSERT(mMode == kModeRectangle); - mMode = kModeRectangleList; + ALOG_ASSERT(mMode == ClipMode::Rectangle); + mMode = ClipMode::RectangleList; mRectangleList.set(mClipRect, Matrix4::identity()); } -bool ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, +void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op) { if (op != SkRegion::kIntersect_Op || !mRectangleList.intersectWith(r, *transform)) { enterRegionMode(); - return regionModeClipRectWithTransform(r, transform, op); + regionModeClipRectWithTransform(r, transform, op); } - return true; -} - -bool ClipArea::rectangleListModeClipRectWithTransform(float left, float top, - float right, float bottom, const mat4* transform, SkRegion::Op op) { - Rect r(left, top, right, bottom); - return rectangleListModeClipRectWithTransform(r, transform, op); } /* @@ -328,12 +320,11 @@ bool ClipArea::rectangleListModeClipRectWithTransform(float left, float top, */ void ClipArea::enterRegionMode() { - Mode oldMode = mMode; - mMode = kModeRegion; - if (oldMode != kModeRegion) { - if (oldMode == kModeRectangle) { - mClipRegion.setRect(mClipRect.left, mClipRect.top, - mClipRect.right, mClipRect.bottom); + ClipMode oldMode = mMode; + mMode = ClipMode::Region; + if (oldMode != ClipMode::Region) { + if (oldMode == ClipMode::Rectangle) { + mClipRegion.setRect(mClipRect.toSkIRect()); } else { mClipRegion = mRectangleList.convertToRegion(createViewportRegion()); onClipRegionUpdated(); @@ -341,20 +332,13 @@ void ClipArea::enterRegionMode() { } } -bool ClipArea::regionModeClipRectWithTransform(const Rect& r, +void ClipArea::regionModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op) { SkPath transformedRect = pathFromTransformedRectangle(r, *transform); SkRegion transformedRectRegion; regionFromPath(transformedRect, transformedRectRegion); mClipRegion.op(transformedRectRegion, op); onClipRegionUpdated(); - return true; -} - -bool ClipArea::regionModeClipRectWithTransform(float left, float top, - float right, float bottom, const mat4* transform, SkRegion::Op op) { - return regionModeClipRectWithTransform(Rect(left, top, right, bottom), - transform, op); } void ClipArea::onClipRegionUpdated() { @@ -370,5 +354,184 @@ void ClipArea::onClipRegionUpdated() { } } +/** + * Clip serialization + */ + +const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) { + if (!mPostViewportClipObserved) { + // Only initial clip-to-viewport observed, so no serialization of clip necessary + return nullptr; + } + + static_assert(std::is_trivially_destructible<Rect>::value, + "expect Rect to be trivially destructible"); + static_assert(std::is_trivially_destructible<RectangleList>::value, + "expect RectangleList to be trivially destructible"); + + if (mLastSerialization == nullptr) { + ClipBase* serialization = nullptr; + switch (mMode) { + case ClipMode::Rectangle: + serialization = allocator.create<ClipRect>(mClipRect); + break; + case ClipMode::RectangleList: + serialization = allocator.create<ClipRectList>(mRectangleList); + serialization->rect = mRectangleList.calculateBounds(); + break; + case ClipMode::Region: + serialization = allocator.create<ClipRegion>(mClipRegion); + serialization->rect.set(mClipRegion.getBounds()); + break; + } + serialization->intersectWithRoot = mReplaceOpObserved; + // TODO: this is only done for draw time, should eventually avoid for record time + serialization->rect.snapToPixelBoundaries(); + mLastSerialization = serialization; + } + return mLastSerialization; +} + +inline static const RectangleList& getRectList(const ClipBase* scb) { + return reinterpret_cast<const ClipRectList*>(scb)->rectList; +} + +inline static const SkRegion& getRegion(const ClipBase* scb) { + return reinterpret_cast<const ClipRegion*>(scb)->region; +} + +// Conservative check for too many rectangles to fit in rectangle list. +// For simplicity, doesn't account for rect merging +static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) { + int currentRectCount = clipArea.isRectangleList() + ? clipArea.getRectangleList().getTransformedRectanglesCount() + : 1; + int recordedRectCount = (scb->mode == ClipMode::RectangleList) + ? getRectList(scb).getTransformedRectanglesCount() + : 1; + return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles; +} + +static const ClipRect sEmptyClipRect(Rect(0, 0)); + +const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator, + const ClipBase* recordedClip, const Matrix4& recordedClipTransform) { + + // if no recordedClip passed, just serialize current state + if (!recordedClip) return serializeClip(allocator); + + // if either is empty, clip is empty + if (CC_UNLIKELY(recordedClip->rect.isEmpty())|| mClipRect.isEmpty()) return &sEmptyClipRect; + + if (!mLastResolutionResult + || recordedClip != mLastResolutionClip + || recordedClipTransform != mLastResolutionTransform) { + mLastResolutionClip = recordedClip; + mLastResolutionTransform = recordedClipTransform; + + if (CC_LIKELY(mMode == ClipMode::Rectangle + && recordedClip->mode == ClipMode::Rectangle + && recordedClipTransform.rectToRect())) { + // common case - result is a single rectangle + auto rectClip = allocator.create<ClipRect>(recordedClip->rect); + recordedClipTransform.mapRect(rectClip->rect); + rectClip->rect.doIntersect(mClipRect); + rectClip->rect.snapToPixelBoundaries(); + mLastResolutionResult = rectClip; + } else if (CC_UNLIKELY(mMode == ClipMode::Region + || recordedClip->mode == ClipMode::Region + || cannotFitInRectangleList(*this, recordedClip))) { + // region case + SkRegion other; + switch (recordedClip->mode) { + case ClipMode::Rectangle: + if (CC_LIKELY(recordedClipTransform.rectToRect())) { + // simple transform, skip creating SkPath + Rect resultClip(recordedClip->rect); + recordedClipTransform.mapRect(resultClip); + other.setRect(resultClip.toSkIRect()); + } else { + SkPath transformedRect = pathFromTransformedRectangle(recordedClip->rect, + recordedClipTransform); + other.setPath(transformedRect, createViewportRegion()); + } + break; + case ClipMode::RectangleList: { + RectangleList transformedList(getRectList(recordedClip)); + transformedList.transform(recordedClipTransform); + other = transformedList.convertToRegion(createViewportRegion()); + break; + } + case ClipMode::Region: + other = getRegion(recordedClip); + + // TODO: handle non-translate transforms properly! + other.translate(recordedClipTransform.getTranslateX(), + recordedClipTransform.getTranslateY()); + } + + ClipRegion* regionClip = allocator.create<ClipRegion>(); + switch (mMode) { + case ClipMode::Rectangle: + regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op); + break; + case ClipMode::RectangleList: + regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()), + other, SkRegion::kIntersect_Op); + break; + case ClipMode::Region: + regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op); + break; + } + // Don't need to snap, since region's in int bounds + regionClip->rect.set(regionClip->region.getBounds()); + mLastResolutionResult = regionClip; + } else { + auto rectListClip = allocator.create<ClipRectList>(mRectangleList); + auto&& rectList = rectListClip->rectList; + if (mMode == ClipMode::Rectangle) { + rectList.set(mClipRect, Matrix4::identity()); + } + + if (recordedClip->mode == ClipMode::Rectangle) { + rectList.intersectWith(recordedClip->rect, recordedClipTransform); + } else { + const RectangleList& other = getRectList(recordedClip); + for (int i = 0; i < other.getTransformedRectanglesCount(); i++) { + auto&& tr = other.getTransformedRectangle(i); + Matrix4 totalTransform(recordedClipTransform); + totalTransform.multiply(tr.getTransform()); + rectList.intersectWith(tr.getBounds(), totalTransform); + } + } + rectListClip->rect = rectList.calculateBounds(); + rectListClip->rect.snapToPixelBoundaries(); + mLastResolutionResult = rectListClip; + } + } + return mLastResolutionResult; +} + +void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) { + if (!clip) return; // nothing to do + + if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) { + clipRectWithTransform(clip->rect, &transform, SkRegion::kIntersect_Op); + } else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) { + auto&& rectList = getRectList(clip); + for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) { + auto&& tr = rectList.getTransformedRectangle(i); + Matrix4 totalTransform(transform); + totalTransform.multiply(tr.getTransform()); + clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op); + } + } else { + SkRegion region(getRegion(clip)); + // TODO: handle non-translate transforms properly! + region.translate(transform.getTranslateX(), transform.getTranslateY()); + clipRegion(region, SkRegion::kIntersect_Op); + } +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h index 51ef27b4e9cc..6eb2eef5a5f9 100644 --- a/libs/hwui/ClipArea.h +++ b/libs/hwui/ClipArea.h @@ -16,15 +16,17 @@ #ifndef CLIPAREA_H #define CLIPAREA_H -#include <SkRegion.h> - #include "Matrix.h" #include "Rect.h" #include "utils/Pair.h" +#include <SkRegion.h> + namespace android { namespace uirenderer { +class LinearAllocator; + Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform); class TransformedRectangle { @@ -33,7 +35,7 @@ public: TransformedRectangle(const Rect& bounds, const Matrix4& transform); bool canSimplyIntersectWith(const TransformedRectangle& other) const; - bool intersectWith(const TransformedRectangle& other); + void intersectWith(const TransformedRectangle& other); bool isEmpty() const; @@ -50,6 +52,12 @@ public: return mTransform; } + void transform(const Matrix4& transform) { + Matrix4 t; + t.loadMultiply(transform, mTransform); + mTransform = t; + } + private: Rect mBounds; Matrix4 mTransform; @@ -66,19 +74,64 @@ public: void setEmpty(); void set(const Rect& bounds, const Matrix4& transform); bool intersectWith(const Rect& bounds, const Matrix4& transform); + void transform(const Matrix4& transform); SkRegion convertToRegion(const SkRegion& clip) const; Rect calculateBounds() const; -private: enum { kMaxTransformedRectangles = 5 }; +private: int mTransformedRectanglesCount; TransformedRectangle mTransformedRectangles[kMaxTransformedRectangles]; }; +enum class ClipMode { + Rectangle, + RectangleList, + + // region and path - intersected. if either is empty, don't use + Region +}; + +struct ClipBase { + ClipBase(ClipMode mode) + : mode(mode) {} + ClipBase(const Rect& rect) + : mode(ClipMode::Rectangle) + , rect(rect) {} + const ClipMode mode; + bool intersectWithRoot = false; + // Bounds of the clipping area, used to define the scissor, and define which + // portion of the stencil is updated/used + Rect rect; + + void dump() const; +}; + +struct ClipRect : ClipBase { + ClipRect(const Rect& rect) + : ClipBase(rect) {} +}; + +struct ClipRectList : ClipBase { + ClipRectList(const RectangleList& rectList) + : ClipBase(ClipMode::RectangleList) + , rectList(rectList) {} + RectangleList rectList; +}; + +struct ClipRegion : ClipBase { + ClipRegion(const SkRegion& region) + : ClipBase(ClipMode::Region) + , region(region) {} + ClipRegion() + : ClipBase(ClipMode::Region) {} + SkRegion region; +}; + class ClipArea { public: ClipArea(); @@ -91,12 +144,10 @@ public: void setEmpty(); void setClip(float left, float top, float right, float bottom); - bool clipRectWithTransform(float left, float top, float right, float bottom, - const mat4* transform, SkRegion::Op op = SkRegion::kIntersect_Op); - bool clipRectWithTransform(const Rect& r, const mat4* transform, - SkRegion::Op op = SkRegion::kIntersect_Op); - bool clipRegion(const SkRegion& region, SkRegion::Op op = SkRegion::kIntersect_Op); - bool clipPathWithTransform(const SkPath& path, const mat4* transform, + void clipRectWithTransform(const Rect& r, const mat4* transform, + SkRegion::Op op); + void clipRegion(const SkRegion& region, SkRegion::Op op); + void clipPathWithTransform(const SkPath& path, const mat4* transform, SkRegion::Op op); const Rect& getClipRect() const { @@ -112,41 +163,45 @@ public: } bool isRegion() const { - return kModeRegion == mMode; + return ClipMode::Region == mMode; } bool isSimple() const { - return mMode == kModeRectangle; + return mMode == ClipMode::Rectangle; } bool isRectangleList() const { - return mMode == kModeRectangleList; + return mMode == ClipMode::RectangleList; } + WARN_UNUSED_RESULT const ClipBase* serializeClip(LinearAllocator& allocator); + WARN_UNUSED_RESULT const ClipBase* serializeIntersectedClip(LinearAllocator& allocator, + const ClipBase* recordedClip, const Matrix4& recordedClipTransform); + void applyClip(const ClipBase* recordedClip, const Matrix4& recordedClipTransform); + private: void enterRectangleMode(); - bool rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); - bool rectangleModeClipRectWithTransform(float left, float top, float right, - float bottom, const mat4* transform, SkRegion::Op op); + void rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); void enterRectangleListMode(); - bool rectangleListModeClipRectWithTransform(float left, float top, - float right, float bottom, const mat4* transform, SkRegion::Op op); - bool rectangleListModeClipRectWithTransform(const Rect& r, + void rectangleListModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); void enterRegionModeFromRectangleMode(); void enterRegionModeFromRectangleListMode(); void enterRegionMode(); - bool regionModeClipRectWithTransform(const Rect& r, const mat4* transform, + void regionModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); - bool regionModeClipRectWithTransform(float left, float top, float right, - float bottom, const mat4* transform, SkRegion::Op op); void ensureClipRegion(); void onClipRegionUpdated(); - bool clipRegionOp(float left, float top, float right, float bottom, - SkRegion::Op op); + + // Called by every state modifying public method. + void onClipUpdated() { + mPostViewportClipObserved = true; + mLastSerialization = nullptr; + mLastResolutionResult = nullptr; + } SkRegion createViewportRegion() { return SkRegion(mViewportBounds.toSkIRect()); @@ -158,13 +213,23 @@ private: pathAsRegion.setPath(path, createViewportRegion()); } - enum Mode { - kModeRectangle, - kModeRegion, - kModeRectangleList - }; + ClipMode mMode; + bool mPostViewportClipObserved = false; + bool mReplaceOpObserved = false; + + /** + * If mLastSerialization is non-null, it represents an already serialized copy + * of the current clip state. If null, it has not been computed. + */ + const ClipBase* mLastSerialization = nullptr; + + /** + * This pair of pointers is a single entry cache of most recently seen + */ + const ClipBase* mLastResolutionResult = nullptr; + const ClipBase* mLastResolutionClip = nullptr; + Matrix4 mLastResolutionTransform; - Mode mMode; Rect mViewportBounds; Rect mClipRect; SkRegion mClipRegion; diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp index 9bd3bdc5617e..6d5833b3be86 100644 --- a/libs/hwui/DamageAccumulator.cpp +++ b/libs/hwui/DamageAccumulator.cpp @@ -45,7 +45,7 @@ struct DirtyStack { }; DamageAccumulator::DamageAccumulator() { - mHead = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack)); + mHead = mAllocator.create_trivial<DirtyStack>(); memset(mHead, 0, sizeof(DirtyStack)); // Create a root that we will not pop off mHead->prev = mHead; @@ -78,7 +78,7 @@ void DamageAccumulator::computeCurrentTransform(Matrix4* outMatrix) const { void DamageAccumulator::pushCommon() { if (!mHead->next) { - DirtyStack* nextFrame = (DirtyStack*) mAllocator.alloc(sizeof(DirtyStack)); + DirtyStack* nextFrame = mAllocator.create_trivial<DirtyStack>(); nextFrame->next = nullptr; nextFrame->prev = mHead; mHead->next = nextFrame; @@ -121,7 +121,14 @@ void DamageAccumulator::popTransform() { static inline void mapRect(const Matrix4* matrix, const SkRect& in, SkRect* out) { if (in.isEmpty()) return; Rect temp(in); - matrix->mapRect(temp); + if (CC_LIKELY(!matrix->isPerspective())) { + matrix->mapRect(temp); + } else { + // Don't attempt to calculate damage for a perspective transform + // as the numbers this works with can break the perspective + // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX + temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); + } out->join(RECT_ARGS(temp)); } @@ -134,7 +141,14 @@ static inline void mapRect(const RenderProperties& props, const SkRect& in, SkRe const SkMatrix* transform = props.getTransformMatrix(); SkRect temp(in); if (transform && !transform->isIdentity()) { - transform->mapRect(&temp); + if (CC_LIKELY(!transform->hasPerspective())) { + transform->mapRect(&temp); + } else { + // Don't attempt to calculate damage for a perspective transform + // as the numbers this works with can break the perspective + // calculations. Just give up and expand to DIRTY_MIN/DIRTY_MAX + temp.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); + } } temp.offset(props.getLeft(), props.getTop()); out->join(temp); diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h index dd3365a49d78..250296ecc89f 100644 --- a/libs/hwui/DamageAccumulator.h +++ b/libs/hwui/DamageAccumulator.h @@ -24,6 +24,11 @@ #include "utils/Macros.h" +// Smaller than INT_MIN/INT_MAX because we offset these values +// and thus don't want to be adding offsets to INT_MAX, that's bad +#define DIRTY_MIN (-0x7ffffff-1) +#define DIRTY_MAX (0x7ffffff) + namespace android { namespace uirenderer { @@ -52,7 +57,7 @@ public: // Returns the current dirty area, *NOT* transformed by pushed transforms void peekAtDirty(SkRect* dest) const; - void computeCurrentTransform(Matrix4* outMatrix) const; + ANDROID_API void computeCurrentTransform(Matrix4* outMatrix) const; void finish(SkRect* totalDirty); diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h index 5808aaca76be..748edef730b7 100644 --- a/libs/hwui/Debug.h +++ b/libs/hwui/Debug.h @@ -17,11 +17,18 @@ #ifndef ANDROID_HWUI_DEBUG_H #define ANDROID_HWUI_DEBUG_H -// Turn on to check for OpenGL errors on each frame -#define DEBUG_OPENGL 1 +#define DEBUG_LEVEL_HIGH 3 +#define DEBUG_LEVEL_MODERATE 2 +#define DEBUG_LEVEL_LOW 1 +#define DEBUG_LEVEL_NONE 0 -// Turn on to display informations about the GPU -#define DEBUG_EXTENSIONS 0 +// Turn on to check for OpenGL errors on each frame +// Note DEBUG_LEVEL_HIGH for DEBUG_OPENGL is only setable by enabling +// HWUI_ENABLE_OPENGL_VALIDATION when building HWUI. Similarly if +// HWUI_ENABLE_OPENGL_VALIDATION is set then this is always DEBUG_LEVEL_HIGH +#ifndef DEBUG_OPENGL +#define DEBUG_OPENGL DEBUG_LEVEL_LOW +#endif // Turn on to enable initialization information #define DEBUG_INIT 0 diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index f05857c43a35..1b0f42466bff 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -14,11 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - -#include <SkCanvas.h> - #include <utils/Trace.h> #include <ui/Rect.h> #include <ui/Region.h> @@ -47,6 +42,12 @@ namespace uirenderer { #define DEBUG_COLOR_MERGEDBATCH 0x5f7f7fff #define DEBUG_COLOR_MERGEDBATCH_SOLO 0x5f7fff7f +static bool avoidOverdraw() { + // Don't avoid overdraw when visualizing it, since that makes it harder to + // debug where it's coming from, and when the problem occurs. + return !Properties::debugOverdraw; +}; + ///////////////////////////////////////////////////////////////////////////////// // Operation Batches ///////////////////////////////////////////////////////////////////////////////// @@ -72,7 +73,7 @@ public: // NOTE: ignore empty bounds special case, since we don't merge across those ops mBounds.unionWith(state->mBounds); mAllOpsOpaque &= opaqueOverBounds; - mOps.add(OpStatePair(op, state)); + mOps.push_back(OpStatePair(op, state)); } bool intersects(const Rect& rect) { @@ -136,7 +137,7 @@ public: inline int count() const { return mOps.size(); } protected: - Vector<OpStatePair> mOps; + std::vector<OpStatePair> mOps; Rect mBounds; // union of bounds of contained ops private: bool mAllOpsOpaque; @@ -221,7 +222,10 @@ public: // if paints are equal, then modifiers + paint attribs don't need to be compared if (op->mPaint == mOps[0].op->mPaint) return true; - if (op->getPaintAlpha() != mOps[0].op->getPaintAlpha()) return false; + if (PaintUtils::getAlphaDirect(op->mPaint) + != PaintUtils::getAlphaDirect(mOps[0].op->mPaint)) { + return false; + } if (op->mPaint && mOps[0].op->mPaint && op->mPaint->getColorFilter() != mOps[0].op->mPaint->getColorFilter()) { @@ -413,7 +417,7 @@ void DeferredDisplayList::addClip(OpenGLRenderer& renderer, ClipOp* op) { * beginning of the frame. This would avoid targetting and removing an FBO in the middle of a frame. * * saveLayer operations should be pulled to the beginning of the frame if the canvas doesn't have a - * complex clip, and if the flags (kClip_SaveFlag & kClipToLayer_SaveFlag) are set. + * complex clip, and if the flags (SaveFlags::Clip & SaveFlags::ClipToLayer) are set. */ void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer, SaveLayerOp* op, int newSaveCount) { @@ -421,7 +425,7 @@ void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer, this, op, op->getFlags(), newSaveCount); storeStateOpBarrier(renderer, op); - mSaveStack.push(newSaveCount); + mSaveStack.push_back(newSaveCount); } /** @@ -432,11 +436,11 @@ void DeferredDisplayList::addSave(OpenGLRenderer& renderer, SaveOp* op, int newS int saveFlags = op->getFlags(); DEFER_LOGD("%p adding saveOp %p, flags %x, new count %d", this, op, saveFlags, newSaveCount); - if (recordingComplexClip() && (saveFlags & SkCanvas::kClip_SaveFlag)) { + if (recordingComplexClip() && (saveFlags & SaveFlags::Clip)) { // store and replay the save operation, as it may be needed to correctly playback the clip DEFER_LOGD(" adding save barrier with new save count %d", newSaveCount); storeStateOpBarrier(renderer, op); - mSaveStack.push(newSaveCount); + mSaveStack.push_back(newSaveCount); } } @@ -459,11 +463,11 @@ void DeferredDisplayList::addRestoreToCount(OpenGLRenderer& renderer, StateOp* o resetBatchingState(); } - if (mSaveStack.isEmpty() || newSaveCount > mSaveStack.top()) { + if (mSaveStack.empty() || newSaveCount > mSaveStack.back()) { return; } - while (!mSaveStack.isEmpty() && mSaveStack.top() >= newSaveCount) mSaveStack.pop(); + while (!mSaveStack.empty() && mSaveStack.back() >= newSaveCount) mSaveStack.pop_back(); storeRestoreToCountBarrier(renderer, op, mSaveStack.size() + FLUSH_SAVE_STACK_DEPTH); } @@ -495,10 +499,10 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { // the merge path in those cases deferInfo.mergeable &= !recordingComplexClip(); deferInfo.opaqueOverBounds &= !recordingComplexClip() - && mSaveStack.isEmpty() + && mSaveStack.empty() && !state->mRoundRectClipState; - if (CC_LIKELY(mAvoidOverdraw) && mBatches.size() && + if (CC_LIKELY(avoidOverdraw()) && mBatches.size() && state->mClipSideFlags != kClipSide_ConservativeFull && deferInfo.opaqueOverBounds && state->mBounds.contains(mBounds)) { // avoid overdraw by resetting drawing state + discarding drawing ops @@ -510,7 +514,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { // TODO: elegant way to reuse batches? DrawBatch* b = new DrawBatch(deferInfo); b->add(op, state, deferInfo.opaqueOverBounds); - mBatches.add(b); + mBatches.push_back(b); return; } @@ -520,12 +524,12 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { // insertion point of a new batch, will hopefully be immediately after similar batch // (eventually, should be similar shader) int insertBatchIndex = mBatches.size(); - if (!mBatches.isEmpty()) { + if (!mBatches.empty()) { if (state->mBounds.isEmpty()) { - // don't know the bounds for op, so add to last batch and start from scratch on next op + // don't know the bounds for op, so create new batch and start from scratch on next op DrawBatch* b = new DrawBatch(deferInfo); b->add(op, state, deferInfo.opaqueOverBounds); - mBatches.add(b); + mBatches.push_back(b); resetBatchingState(); #if DEBUG_DEFER DEFER_LOGD("Warning: Encountered op with empty bounds, resetting batches"); @@ -536,7 +540,11 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { if (deferInfo.mergeable) { // Try to merge with any existing batch with same mergeId. - if (mMergingBatches[deferInfo.batchId].get(deferInfo.mergeId, targetBatch)) { + std::unordered_map<mergeid_t, DrawBatch*>& mergingBatch + = mMergingBatches[deferInfo.batchId]; + auto getResult = mergingBatch.find(deferInfo.mergeId); + if (getResult != mergingBatch.end()) { + targetBatch = getResult->second; if (!((MergingDrawBatch*) targetBatch)->canMergeWith(op, state)) { targetBatch = nullptr; } @@ -580,7 +588,8 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { if (deferInfo.mergeable) { targetBatch = new MergingDrawBatch(deferInfo, renderer.getViewportWidth(), renderer.getViewportHeight()); - mMergingBatches[deferInfo.batchId].put(deferInfo.mergeId, targetBatch); + mMergingBatches[deferInfo.batchId].insert( + std::make_pair(deferInfo.mergeId, targetBatch)); } else { targetBatch = new DrawBatch(deferInfo); mBatchLookup[deferInfo.batchId] = targetBatch; @@ -589,7 +598,7 @@ void DeferredDisplayList::addDrawOp(OpenGLRenderer& renderer, DrawOp* op) { DEFER_LOGD("creating %singBatch %p, bid %x, at %d", deferInfo.mergeable ? "Merg" : "Draw", targetBatch, deferInfo.batchId, insertBatchIndex); - mBatches.insertAt(targetBatch, insertBatchIndex); + mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); } targetBatch->add(op, state, deferInfo.opaqueOverBounds); @@ -600,7 +609,7 @@ void DeferredDisplayList::storeStateOpBarrier(OpenGLRenderer& renderer, StateOp* DeferredDisplayState* state = createState(); renderer.storeDisplayState(*state, getStateOpDeferFlags()); - mBatches.add(new StateOpBatch(op, state)); + mBatches.push_back(new StateOpBatch(op, state)); resetBatchingState(); } @@ -610,10 +619,10 @@ void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, S this, newSaveCount, mBatches.size()); // store displayState for the restore operation, as it may be associated with a saveLayer that - // doesn't have kClip_SaveFlag set + // doesn't have SaveFlags::Clip set DeferredDisplayState* state = createState(); renderer.storeDisplayState(*state, getStateOpDeferFlags()); - mBatches.add(new RestoreToCountBatch(op, state, newSaveCount)); + mBatches.push_back(new RestoreToCountBatch(op, state, newSaveCount)); resetBatchingState(); } @@ -621,7 +630,7 @@ void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, S // Replay / flush ///////////////////////////////////////////////////////////////////////////////// -static void replayBatchList(const Vector<Batch*>& batchList, +static void replayBatchList(const std::vector<Batch*>& batchList, OpenGLRenderer& renderer, Rect& dirty) { for (unsigned int i = 0; i < batchList.size(); i++) { @@ -634,7 +643,7 @@ static void replayBatchList(const Vector<Batch*>& batchList, void DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { ATRACE_NAME("flush drawing commands"); - Caches::getInstance().fontRenderer->endPrecaching(); + Caches::getInstance().fontRenderer.endPrecaching(); if (isEmpty()) return; // nothing to flush renderer.restoreToCount(1); @@ -643,9 +652,9 @@ void DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { renderer.eventMark("Flush"); // save and restore so that reordering doesn't affect final state - renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + renderer.save(SaveFlags::MatrixClip); - if (CC_LIKELY(mAvoidOverdraw)) { + if (CC_LIKELY(avoidOverdraw())) { for (unsigned int i = 1; i < mBatches.size(); i++) { if (mBatches[i] && mBatches[i]->coversBounds(mBounds)) { discardDrawingBatches(i - 1); @@ -667,7 +676,7 @@ void DeferredDisplayList::discardDrawingBatches(const unsigned int maxIndex) { // leave deferred state ops alone for simplicity (empty save restore pairs may now exist) if (mBatches[i] && mBatches[i]->purelyDrawBatch()) { delete mBatches[i]; - mBatches.replaceAt(nullptr, i); + mBatches[i] = nullptr; } } mEarliestUnclearedIndex = maxIndex + 1; diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h index 160c1ad2d1f6..98ccf11b1c2a 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -17,15 +17,17 @@ #ifndef ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H #define ANDROID_HWUI_DEFERRED_DISPLAY_LIST_H +#include <unordered_map> + #include <utils/Errors.h> #include <utils/LinearAllocator.h> -#include <utils/Vector.h> -#include <utils/TinyHashMap.h> #include "Matrix.h" #include "OpenGLRenderer.h" #include "Rect.h" +#include <vector> + class SkBitmap; namespace android { @@ -47,11 +49,6 @@ typedef const void* mergeid_t; class DeferredDisplayState { public: - /** static void* operator new(size_t size); PURPOSELY OMITTED **/ - static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); - } - // global op bounds, mapped by mMatrix to be in screen space coordinates, clipped Rect mBounds; @@ -59,7 +56,6 @@ public: bool mClipValid; Rect mClip; int mClipSideFlags; // specifies which sides of the bounds are clipped, unclipped if cleared - bool mClipped; mat4 mMatrix; float mAlpha; const RoundRectClipState* mRoundRectClipState; @@ -81,8 +77,8 @@ public: class DeferredDisplayList { friend struct DeferStateStruct; // used to give access to allocator public: - DeferredDisplayList(const Rect& bounds, bool avoidOverdraw = true) : - mBounds(bounds), mAvoidOverdraw(avoidOverdraw) { + DeferredDisplayList(const Rect& bounds) + : mBounds(bounds) { clear(); } ~DeferredDisplayList() { clear(); } @@ -100,7 +96,7 @@ public: kOpBatch_Count, // Add other batch ids before this }; - bool isEmpty() { return mBatches.isEmpty(); } + bool isEmpty() { return mBatches.empty(); } /** * Plays back all of the draw ops recorded into batches to the renderer. @@ -123,7 +119,7 @@ private: DeferredDisplayList(const DeferredDisplayList& other); // disallow copy DeferredDisplayState* createState() { - return new (mAllocator) DeferredDisplayState(); + return mAllocator.create_trivial<DeferredDisplayState>(); } void tryRecycleState(DeferredDisplayState* state) { @@ -150,17 +146,16 @@ private: // layer space bounds of rendering Rect mBounds; - const bool mAvoidOverdraw; /** * At defer time, stores the *defer time* savecount of save/saveLayer ops that were deferred, so * that when an associated restoreToCount is deferred, it can be recorded as a * RestoreToCountBatch */ - Vector<int> mSaveStack; + std::vector<int> mSaveStack; int mComplexClipStackStart; - Vector<Batch*> mBatches; + std::vector<Batch*> mBatches; // Maps batch ids to the most recent *non-merging* batch of that id Batch* mBatchLookup[kOpBatch_Count]; @@ -176,7 +171,7 @@ private: * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not * collide, which avoids the need to resolve mergeid collisions. */ - TinyHashMap<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count]; + std::unordered_map<mergeid_t, DrawBatch*> mMergingBatches[kOpBatch_Count]; LinearAllocator mAllocator; }; diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp index a17904e31047..f833a5405a5c 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -24,14 +24,13 @@ namespace android { namespace uirenderer { -DeferredLayerUpdater::DeferredLayerUpdater(renderthread::RenderThread& thread, Layer* layer) +DeferredLayerUpdater::DeferredLayerUpdater(Layer* layer) : mSurfaceTexture(nullptr) , mTransform(nullptr) , mNeedsGLContextAttach(false) , mUpdateTexImage(false) , mLayer(layer) - , mCaches(Caches::getInstance()) - , mRenderThread(thread) { + , mCaches(Caches::getInstance()) { mWidth = mLayer->layer.getWidth(); mHeight = mLayer->layer.getHeight(); mBlend = mLayer->isBlend(); @@ -48,13 +47,13 @@ DeferredLayerUpdater::~DeferredLayerUpdater() { } void DeferredLayerUpdater::setPaint(const SkPaint* paint) { - OpenGLRenderer::getAlphaAndModeDirect(paint, &mAlpha, &mMode); + mAlpha = PaintUtils::getAlphaDirect(paint); + mMode = PaintUtils::getXfermodeDirect(paint); SkColorFilter* colorFilter = (paint) ? paint->getColorFilter() : nullptr; SkRefCnt_SafeAssign(mColorFilter, colorFilter); } -bool DeferredLayerUpdater::apply() { - bool success = true; +void DeferredLayerUpdater::apply() { // These properties are applied the same to both layer types mLayer->setColorFilter(mColorFilter); mLayer->setAlpha(mAlpha, mMode); @@ -73,7 +72,6 @@ bool DeferredLayerUpdater::apply() { setTransform(nullptr); } } - return success; } void DeferredLayerUpdater::doUpdateTexImage() { diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index 82f2741b7478..44a24c840892 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -35,7 +35,7 @@ class DeferredLayerUpdater : public VirtualLightRefBase { public: // Note that DeferredLayerUpdater assumes it is taking ownership of the layer // and will not call incrementRef on it as a result. - ANDROID_API DeferredLayerUpdater(renderthread::RenderThread& thread, Layer* layer); + ANDROID_API DeferredLayerUpdater(Layer* layer); ANDROID_API ~DeferredLayerUpdater(); ANDROID_API bool setSize(int width, int height) { @@ -47,6 +47,9 @@ public: return false; } + int getWidth() { return mWidth; } + int getHeight() { return mHeight; } + ANDROID_API bool setBlend(bool blend) { if (blend != mBlend) { mBlend = blend; @@ -75,15 +78,19 @@ public: mTransform = matrix ? new SkMatrix(*matrix) : nullptr; } + SkMatrix* getTransform() { + return mTransform; + } + ANDROID_API void setPaint(const SkPaint* paint); - ANDROID_API bool apply(); + void apply(); Layer* backingLayer() { return mLayer; } - ANDROID_API void detachSurfaceTexture(); + void detachSurfaceTexture(); private: // Generic properties @@ -101,7 +108,6 @@ private: Layer* mLayer; Caches& mCaches; - renderthread::RenderThread& mRenderThread; void doUpdateTexImage(); }; diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp new file mode 100644 index 000000000000..4cfbb2a43198 --- /dev/null +++ b/libs/hwui/DeviceInfo.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 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 <DeviceInfo.h> + +#include "Extensions.h" + +#include <GLES2/gl2.h> +#include <log/log.h> + +#include <thread> +#include <mutex> + +namespace android { +namespace uirenderer { + +static DeviceInfo* sDeviceInfo = nullptr; +static std::once_flag sInitializedFlag; + +const DeviceInfo* DeviceInfo::get() { + LOG_ALWAYS_FATAL_IF(!sDeviceInfo, "DeviceInfo not yet initialized."); + return sDeviceInfo; +} + +void DeviceInfo::initialize() { + std::call_once(sInitializedFlag, []() { + sDeviceInfo = new DeviceInfo(); + sDeviceInfo->load(); + }); +} + +void DeviceInfo::load() { + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); +} + +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h new file mode 100644 index 000000000000..f576a4f48021 --- /dev/null +++ b/libs/hwui/DeviceInfo.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef DEVICEINFO_H +#define DEVICEINFO_H + +#include "Extensions.h" +#include "utils/Macros.h" + +namespace android { +namespace uirenderer { + +class DeviceInfo { + PREVENT_COPY_AND_ASSIGN(DeviceInfo); +public: + // returns nullptr if DeviceInfo is not initialized yet + // Note this does not have a memory fence so it's up to the caller + // to use one if required. Normally this should not be necessary + static const DeviceInfo* get(); + + // only call this after GL has been initialized, or at any point if compiled + // with HWUI_NULL_GPU + static void initialize(); + + const Extensions& extensions() const { return mExtensions; } + + int maxTextureSize() const { return mMaxTextureSize; } + +private: + DeviceInfo() {} + ~DeviceInfo() {} + + void load(); + + Extensions mExtensions; + int mMaxTextureSize; +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* DEVICEINFO_H */ diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp index e679bff18c86..b572bdaccb86 100644 --- a/libs/hwui/DisplayList.cpp +++ b/libs/hwui/DisplayList.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <SkCanvas.h> #include <algorithm> @@ -23,47 +21,75 @@ #include "Debug.h" #include "DisplayList.h" +#include "RenderNode.h" + +#if HWUI_NEW_OPS +#include "RecordedOp.h" +#else #include "DisplayListOp.h" +#endif namespace android { namespace uirenderer { -DisplayListData::DisplayListData() +DisplayList::DisplayList() : projectionReceiveIndex(-1) + , stdAllocator(allocator) + , chunks(stdAllocator) + , ops(stdAllocator) + , children(stdAllocator) + , bitmapResources(stdAllocator) + , pathResources(stdAllocator) + , patchResources(stdAllocator) + , paints(stdAllocator) + , regions(stdAllocator) + , referenceHolders(stdAllocator) + , functors(stdAllocator) + , pushStagingFunctors(stdAllocator) , hasDrawOps(false) { } -DisplayListData::~DisplayListData() { +DisplayList::~DisplayList() { cleanupResources(); } -void DisplayListData::cleanupResources() { - ResourceCache& resourceCache = ResourceCache::getInstance(); - resourceCache.lock(); +void DisplayList::cleanupResources() { + if (CC_UNLIKELY(patchResources.size())) { + ResourceCache& resourceCache = ResourceCache::getInstance(); + resourceCache.lock(); - for (size_t i = 0; i < patchResources.size(); i++) { - resourceCache.decrementRefcountLocked(patchResources.itemAt(i)); - } + for (size_t i = 0; i < patchResources.size(); i++) { + resourceCache.decrementRefcountLocked(patchResources[i]); + } - resourceCache.unlock(); + resourceCache.unlock(); + } for (size_t i = 0; i < pathResources.size(); i++) { - const SkPath* path = pathResources.itemAt(i); + const SkPath* path = pathResources[i]; if (path->unique() && Caches::hasInstance()) { Caches::getInstance().pathCache.removeDeferred(path); } delete path; } + for (auto& iter : functors) { + if (iter.listener) { + iter.listener->onGlFunctorReleased(iter.functor); + } + } + patchResources.clear(); pathResources.clear(); paints.clear(); regions.clear(); } -size_t DisplayListData::addChild(DrawRenderNodeOp* op) { - mReferenceHolders.push(op->renderNode()); - return mChildren.add(op); +size_t DisplayList::addChild(NodeOpType* op) { + referenceHolders.push_back(op->renderNode); + size_t index = children.size(); + children.push_back(op); + return index; } }; // namespace uirenderer diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 7fbda1f6b192..5b3227b7db97 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -17,10 +17,6 @@ #ifndef ANDROID_HWUI_DISPLAY_LIST_H #define ANDROID_HWUI_DISPLAY_LIST_H -#ifndef LOG_TAG - #define LOG_TAG "OpenGLRenderer" -#endif - #include <SkCamera.h> #include <SkMatrix.h> @@ -31,7 +27,6 @@ #include <utils/RefBase.h> #include <utils/SortedVector.h> #include <utils/String8.h> -#include <utils/Vector.h> #include <cutils/compiler.h> @@ -40,9 +35,12 @@ #include "Debug.h" #include "CanvasProperty.h" #include "DeferredDisplayList.h" +#include "GlFunctorLifecycleListener.h" #include "Matrix.h" #include "RenderProperties.h" +#include <vector> + class SkBitmap; class SkPaint; class SkPath; @@ -58,12 +56,19 @@ class OpenGLRenderer; class Rect; class Layer; -class ClipRectOp; -class SaveLayerOp; -class SaveOp; -class RestoreToCountOp; +#if HWUI_NEW_OPS +struct RecordedOp; +struct RenderNodeOp; + +typedef RecordedOp BaseOpType; +typedef RenderNodeOp NodeOpType; +#else class DrawRenderNodeOp; +typedef DisplayListOp BaseOpType; +typedef DrawRenderNodeOp NodeOpType; +#endif + /** * Holds data used in the playback a tree of DisplayLists. */ @@ -106,70 +111,104 @@ struct ReplayStateStruct : public PlaybackStateStruct { }; /** + * Functor that can be used for objects with data in both UI thread and RT to keep the data + * in sync. This functor, when added to DisplayList, will be call during DisplayList sync. + */ +struct PushStagingFunctor { + PushStagingFunctor() {} + virtual ~PushStagingFunctor() {} + virtual void operator ()() {} +}; + +struct FunctorContainer { + Functor* functor; + GlFunctorLifecycleListener* listener; +}; + +/** * Data structure that holds the list of commands used in display list stream */ -class DisplayListData { +class DisplayList { friend class DisplayListCanvas; + friend class RecordingCanvas; public: struct Chunk { - // range of included ops in DLD::displayListOps + // range of included ops in DisplayList::ops() size_t beginOpIndex; size_t endOpIndex; - // range of included children in DLD::mChildren + // range of included children in DisplayList::children() size_t beginChildIndex; size_t endChildIndex; // whether children with non-zero Z in the chunk should be reordered bool reorderChildren; +#if HWUI_NEW_OPS + const ClipBase* reorderClip; +#endif }; - DisplayListData(); - ~DisplayListData(); - - // pointers to all ops within display list, pointing into allocator data - Vector<DisplayListOp*> displayListOps; + DisplayList(); + ~DisplayList(); - // index of DisplayListOp restore, after which projected descendents should be drawn + // index of DisplayListOp restore, after which projected descendants should be drawn int projectionReceiveIndex; - Vector<const SkBitmap*> bitmapResources; - Vector<const SkPath*> pathResources; - Vector<const Res_png_9patch*> patchResources; + const LsaVector<Chunk>& getChunks() const { return chunks; } + const LsaVector<BaseOpType*>& getOps() const { return ops; } - std::vector<std::unique_ptr<const SkPaint>> paints; - std::vector<std::unique_ptr<const SkRegion>> regions; - Vector<Functor*> functors; + const LsaVector<NodeOpType*>& getChildren() const { return children; } - const Vector<Chunk>& getChunks() const { - return chunks; - } + const LsaVector<const SkBitmap*>& getBitmapResources() const { return bitmapResources; } + const LsaVector<FunctorContainer>& getFunctors() const { return functors; } + const LsaVector<PushStagingFunctor*>& getPushStagingFunctors() { return pushStagingFunctors; } + + size_t addChild(NodeOpType* childOp); - size_t addChild(DrawRenderNodeOp* childOp); - const Vector<DrawRenderNodeOp*>& children() { return mChildren; } void ref(VirtualLightRefBase* prop) { - mReferenceHolders.push(prop); + referenceHolders.push_back(prop); } size_t getUsedSize() { return allocator.usedSize(); } bool isEmpty() { +#if HWUI_NEW_OPS + return ops.empty(); +#else return !hasDrawOps; +#endif } private: - Vector< sp<VirtualLightRefBase> > mReferenceHolders; + // allocator into which all ops and LsaVector arrays allocated + LinearAllocator allocator; + LinearStdAllocator<void*> stdAllocator; - // list of children display lists for quick, non-drawing traversal - Vector<DrawRenderNodeOp*> mChildren; + LsaVector<Chunk> chunks; + LsaVector<BaseOpType*> ops; - Vector<Chunk> chunks; + // list of Ops referring to RenderNode children for quick, non-drawing traversal + LsaVector<NodeOpType*> children; - // allocator into which all ops were allocated - LinearAllocator allocator; - bool hasDrawOps; + // Resources - Skia objects + 9 patches referred to by this DisplayList + LsaVector<const SkBitmap*> bitmapResources; + LsaVector<const SkPath*> pathResources; + LsaVector<const Res_png_9patch*> patchResources; + LsaVector<std::unique_ptr<const SkPaint>> paints; + LsaVector<std::unique_ptr<const SkRegion>> regions; + LsaVector< sp<VirtualLightRefBase> > referenceHolders; + + // List of functors + LsaVector<FunctorContainer> functors; + + // List of functors that need to be notified of pushStaging. Note that this list gets nothing + // but a callback during sync DisplayList, unlike the list of functors defined above, which + // gets special treatment exclusive for webview. + LsaVector<PushStagingFunctor*> pushStagingFunctors; + + bool hasDrawOps; // only used if !HWUI_NEW_OPS void cleanupResources(); }; diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index 2dd52788074d..ca968cef91b2 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -16,11 +16,12 @@ #include "DisplayListCanvas.h" -#include "ResourceCache.h" #include "DeferredDisplayList.h" #include "DeferredLayerUpdater.h" #include "DisplayListOp.h" +#include "ResourceCache.h" #include "RenderNode.h" +#include "VectorDrawable.h" #include "utils/PaintUtils.h" #include <SkCamera.h> @@ -31,70 +32,64 @@ namespace android { namespace uirenderer { -DisplayListCanvas::DisplayListCanvas() +DisplayListCanvas::DisplayListCanvas(int width, int height) : mState(*this) , mResourceCache(ResourceCache::getInstance()) - , mDisplayListData(nullptr) + , mDisplayList(nullptr) , mTranslateX(0.0f) , mTranslateY(0.0f) , mHasDeferredTranslate(false) , mDeferredBarrierType(kBarrier_None) , mHighContrastText(false) , mRestoreSaveCount(-1) { + resetRecording(width, height); } DisplayListCanvas::~DisplayListCanvas() { - LOG_ALWAYS_FATAL_IF(mDisplayListData, + LOG_ALWAYS_FATAL_IF(mDisplayList, "Destroyed a DisplayListCanvas during a record!"); } -/////////////////////////////////////////////////////////////////////////////// -// Operations -/////////////////////////////////////////////////////////////////////////////// - -DisplayListData* DisplayListCanvas::finishRecording() { - mPaintMap.clear(); - mRegionMap.clear(); - mPathMap.clear(); - DisplayListData* data = mDisplayListData; - mDisplayListData = nullptr; - mSkiaCanvasProxy.reset(nullptr); - return data; -} - -void DisplayListCanvas::prepareDirty(float left, float top, - float right, float bottom) { - - LOG_ALWAYS_FATAL_IF(mDisplayListData, +void DisplayListCanvas::resetRecording(int width, int height) { + LOG_ALWAYS_FATAL_IF(mDisplayList, "prepareDirty called a second time during a recording!"); - mDisplayListData = new DisplayListData(); + mDisplayList = new DisplayList(); - mState.initializeSaveStack(0, 0, mState.getWidth(), mState.getHeight(), Vector3()); + mState.initializeSaveStack(width, height, + 0, 0, width, height, Vector3()); mDeferredBarrierType = kBarrier_InOrder; mState.setDirtyClip(false); mRestoreSaveCount = -1; } -bool DisplayListCanvas::finish() { + +/////////////////////////////////////////////////////////////////////////////// +// Operations +/////////////////////////////////////////////////////////////////////////////// + +DisplayList* DisplayListCanvas::finishRecording() { flushRestoreToCount(); flushTranslate(); - return false; -} -void DisplayListCanvas::interrupt() { -} - -void DisplayListCanvas::resume() { + mPaintMap.clear(); + mRegionMap.clear(); + mPathMap.clear(); + DisplayList* displayList = mDisplayList; + mDisplayList = nullptr; + mSkiaCanvasProxy.reset(nullptr); + return displayList; } -void DisplayListCanvas::callDrawGLFunction(Functor *functor) { +void DisplayListCanvas::callDrawGLFunction(Functor* functor, + GlFunctorLifecycleListener* listener) { addDrawOp(new (alloc()) DrawFunctorOp(functor)); - mDisplayListData->functors.add(functor); + mDisplayList->functors.push_back({functor, listener}); + mDisplayList->ref(listener); } SkCanvas* DisplayListCanvas::asSkCanvas() { - LOG_ALWAYS_FATAL_IF(!mDisplayListData, + LOG_ALWAYS_FATAL_IF(!mDisplayList, "attempting to get an SkCanvas when we are not recording!"); if (!mSkiaCanvasProxy) { mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this)); @@ -110,7 +105,7 @@ SkCanvas* DisplayListCanvas::asSkCanvas() { return mSkiaCanvasProxy.get(); } -int DisplayListCanvas::save(SkCanvas::SaveFlags flags) { +int DisplayListCanvas::save(SaveFlags::Flags flags) { addStateOp(new (alloc()) SaveOp((int) flags)); return mState.save((int) flags); } @@ -133,9 +128,9 @@ void DisplayListCanvas::restoreToCount(int saveCount) { } int DisplayListCanvas::saveLayer(float left, float top, float right, float bottom, - const SkPaint* paint, SkCanvas::SaveFlags flags) { + const SkPaint* paint, SaveFlags::Flags flags) { // force matrix/clip isolation for layer - flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag; + flags |= SaveFlags::MatrixClip; paint = refPaint(paint); addStateOp(new (alloc()) SaveLayerOp(left, top, right, bottom, paint, (int) flags)); @@ -176,11 +171,6 @@ void DisplayListCanvas::setMatrix(const SkMatrix& matrix) { mState.setMatrix(matrix); } -void DisplayListCanvas::setLocalMatrix(const SkMatrix& matrix) { - addStateOp(new (alloc()) SetLocalMatrixOp(matrix)); - mState.setMatrix(matrix); -} - void DisplayListCanvas::concat(const SkMatrix& matrix) { addStateOp(new (alloc()) ConcatMatrixOp(matrix)); mState.concatMatrix(matrix); @@ -229,11 +219,11 @@ void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) { addRenderNodeOp(op); } -void DisplayListCanvas::drawLayer(DeferredLayerUpdater* layerHandle, float x, float y) { +void DisplayListCanvas::drawLayer(DeferredLayerUpdater* layerHandle) { // We ref the DeferredLayerUpdater due to its thread-safe ref-counting // semantics. - mDisplayListData->ref(layerHandle); - addDrawOp(new (alloc()) DrawLayerOp(layerHandle->backingLayer(), x, y)); + mDisplayList->ref(layerHandle); + addDrawOp(new (alloc()) DrawLayerOp(layerHandle->backingLayer())); } void DisplayListCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { @@ -245,7 +235,7 @@ void DisplayListCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) { - save(SkCanvas::kMatrix_SaveFlag); + save(SaveFlags::Matrix); translate(left, top); drawBitmap(&bitmap, paint); restore(); @@ -266,7 +256,7 @@ void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matri drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint); } else { - save(SkCanvas::kMatrix_SaveFlag); + save(SaveFlags::Matrix); concat(matrix); drawBitmap(&bitmap, paint); restore(); @@ -282,7 +272,7 @@ void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float && (srcBottom - srcTop == dstBottom - dstTop) && (srcRight - srcLeft == dstRight - dstLeft)) { // transform simple rect to rect drawing case into position bitmap ops, since they merge - save(SkCanvas::kMatrix_SaveFlag); + save(SaveFlags::Matrix); translate(dstLeft, dstTop); drawBitmap(&bitmap, paint); restore(); @@ -296,7 +286,7 @@ void DisplayListCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float // Apply the scale transform on the canvas, so that the shader // effectively calculates positions relative to src rect space - save(SkCanvas::kMatrix_SaveFlag); + save(SaveFlags::Matrix); translate(dstLeft, dstTop); scale(scaleX, scaleY); @@ -330,13 +320,14 @@ void DisplayListCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, in vertices, colors, paint)); } -void DisplayListCanvas::drawPatch(const SkBitmap& bitmap, const Res_png_9patch* patch, - float left, float top, float right, float bottom, const SkPaint* paint) { +void DisplayListCanvas::drawNinePatch(const SkBitmap& bitmap, const Res_png_9patch& patch, + float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) { const SkBitmap* bitmapPtr = refBitmap(bitmap); - patch = refPatch(patch); + const Res_png_9patch* patchPtr = refPatch(&patch); paint = refPaint(paint); - addDrawOp(new (alloc()) DrawPatchOp(bitmapPtr, patch, left, top, right, bottom, paint)); + addDrawOp(new (alloc()) DrawPatchOp(bitmapPtr, patchPtr, + dstLeft, dstTop, dstRight, dstBottom, paint)); } void DisplayListCanvas::drawColor(int color, SkXfermode::Mode mode) { @@ -366,13 +357,13 @@ void DisplayListCanvas::drawRoundRect( CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom, CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry, CanvasPropertyPaint* paint) { - mDisplayListData->ref(left); - mDisplayListData->ref(top); - mDisplayListData->ref(right); - mDisplayListData->ref(bottom); - mDisplayListData->ref(rx); - mDisplayListData->ref(ry); - mDisplayListData->ref(paint); + mDisplayList->ref(left); + mDisplayList->ref(top); + mDisplayList->ref(right); + mDisplayList->ref(bottom); + mDisplayList->ref(rx); + mDisplayList->ref(ry); + mDisplayList->ref(paint); refBitmapsInShader(paint->value.getShader()); addDrawOp(new (alloc()) DrawRoundRectPropsOp(&left->value, &top->value, &right->value, &bottom->value, &rx->value, &ry->value, &paint->value)); @@ -384,10 +375,10 @@ void DisplayListCanvas::drawCircle(float x, float y, float radius, const SkPaint void DisplayListCanvas::drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y, CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) { - mDisplayListData->ref(x); - mDisplayListData->ref(y); - mDisplayListData->ref(radius); - mDisplayListData->ref(paint); + mDisplayList->ref(x); + mDisplayList->ref(y); + mDisplayList->ref(radius); + mDisplayList->ref(paint); refBitmapsInShader(paint->value.getShader()); addDrawOp(new (alloc()) DrawCirclePropsOp(&x->value, &y->value, &radius->value, &paint->value)); @@ -424,40 +415,24 @@ void DisplayListCanvas::drawPoints(const float* points, int count, const SkPaint addDrawOp(new (alloc()) DrawPointsOp(points, count, refPaint(&paint))); } -void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count, +void DisplayListCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { + mDisplayList->ref(tree); + mDisplayList->pushStagingFunctors.push_back(tree->getFunctor()); + addDrawOp(new (alloc()) DrawVectorDrawableOp(tree, tree->stagingProperties()->getBounds())); +} + +void DisplayListCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) { if (!glyphs || count <= 0) return; int bytesCount = 2 * count; - DrawOp* op = new (alloc()) DrawTextOnPathOp(refText((const char*) glyphs, bytesCount), + DrawOp* op = new (alloc()) DrawTextOnPathOp(refBuffer<glyph_t>(glyphs, count), bytesCount, count, refPath(&path), hOffset, vOffset, refPaint(&paint)); addDrawOp(op); } -void DisplayListCanvas::drawPosText(const uint16_t* text, const float* positions, - int count, int posCount, const SkPaint& paint) { - if (!text || count <= 0) return; - - int bytesCount = 2 * count; - positions = refBuffer<float>(positions, count * 2); - - DrawOp* op = new (alloc()) DrawPosTextOp(refText((const char*) text, bytesCount), - bytesCount, count, positions, refPaint(&paint)); - addDrawOp(op); -} - -static void simplifyPaint(int color, SkPaint* paint) { - paint->setColor(color); - paint->setShader(nullptr); - paint->setColorFilter(nullptr); - paint->setLooper(nullptr); - paint->setStrokeWidth(4 + 0.04 * paint->getTextSize()); - paint->setStrokeJoin(SkPaint::kRound_Join); - paint->setLooper(nullptr); -} - -void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions, +void DisplayListCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) { @@ -465,34 +440,38 @@ void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions, if (!glyphs || count <= 0 || PaintUtils::paintWillNotDrawText(paint)) return; int bytesCount = count * 2; - const char* text = refText((const char*) glyphs, bytesCount); positions = refBuffer<float>(positions, count * 2); Rect bounds(boundsLeft, boundsTop, boundsRight, boundsBottom); - if (CC_UNLIKELY(mHighContrastText)) { - // high contrast draw path - int color = paint.getColor(); - int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color); - bool darken = channelSum < (128 * 3); - - // outline - SkPaint* outlinePaint = copyPaint(&paint); - simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, outlinePaint); - outlinePaint->setStyle(SkPaint::kStrokeAndFill_Style); - addDrawOp(new (alloc()) DrawTextOp(text, bytesCount, count, - x, y, positions, outlinePaint, totalAdvance, bounds)); // bounds? - - // inner - SkPaint* innerPaint = copyPaint(&paint); - simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, innerPaint); - innerPaint->setStyle(SkPaint::kFill_Style); - addDrawOp(new (alloc()) DrawTextOp(text, bytesCount, count, - x, y, positions, innerPaint, totalAdvance, bounds)); + DrawOp* op = new (alloc()) DrawTextOp(refBuffer<glyph_t>(glyphs, count), bytesCount, count, + x, y, positions, refPaint(&paint), totalAdvance, bounds); + addDrawOp(op); + drawTextDecorations(x, y, totalAdvance, paint); +} + +void DisplayListCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { + if (paint.getStyle() != SkPaint::kFill_Style || + (paint.isAntiAlias() && !mState.currentTransform()->isSimple())) { + SkRegion::Iterator it(region); + while (!it.done()) { + const SkIRect& r = it.rect(); + drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint); + it.next(); + } } else { - // standard draw path - DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count, - x, y, positions, refPaint(&paint), totalAdvance, bounds); - addDrawOp(op); + int count = 0; + Vector<float> rects; + SkRegion::Iterator it(region); + while (!it.done()) { + const SkIRect& r = it.rect(); + rects.push(r.fLeft); + rects.push(r.fTop); + rects.push(r.fRight); + rects.push(r.fBottom); + count += 4; + it.next(); + } + drawRects(rects.array(), count, &paint); } } @@ -532,21 +511,26 @@ void DisplayListCanvas::flushTranslate() { } size_t DisplayListCanvas::addOpAndUpdateChunk(DisplayListOp* op) { - int insertIndex = mDisplayListData->displayListOps.add(op); + int insertIndex = mDisplayList->ops.size(); +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("unsupported"); +#else + mDisplayList->ops.push_back(op); +#endif if (mDeferredBarrierType != kBarrier_None) { // op is first in new chunk - mDisplayListData->chunks.push(); - DisplayListData::Chunk& newChunk = mDisplayListData->chunks.editTop(); + mDisplayList->chunks.emplace_back(); + DisplayList::Chunk& newChunk = mDisplayList->chunks.back(); newChunk.beginOpIndex = insertIndex; newChunk.endOpIndex = insertIndex + 1; newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder); - int nextChildIndex = mDisplayListData->children().size(); + int nextChildIndex = mDisplayList->children.size(); newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex; mDeferredBarrierType = kBarrier_None; } else { // standard case - append to existing chunk - mDisplayListData->chunks.editTop().endOpIndex = insertIndex + 1; + mDisplayList->chunks.back().endOpIndex = insertIndex + 1; } return insertIndex; } @@ -569,22 +553,24 @@ size_t DisplayListCanvas::addDrawOp(DrawOp* op) { op->setQuickRejected(rejected); } - mDisplayListData->hasDrawOps = true; + mDisplayList->hasDrawOps = true; return flushAndAddOp(op); } size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) { int opIndex = addDrawOp(op); - int childIndex = mDisplayListData->addChild(op); +#if !HWUI_NEW_OPS + int childIndex = mDisplayList->addChild(op); // update the chunk's child indices - DisplayListData::Chunk& chunk = mDisplayListData->chunks.editTop(); + DisplayList::Chunk& chunk = mDisplayList->chunks.back(); chunk.endChildIndex = childIndex + 1; - if (op->renderNode()->stagingProperties().isProjectionReceiver()) { + if (op->renderNode->stagingProperties().isProjectionReceiver()) { // use staging property, since recording on UI thread - mDisplayListData->projectionReceiveIndex = opIndex; + mDisplayList->projectionReceiveIndex = opIndex; } +#endif return opIndex; } @@ -595,7 +581,7 @@ void DisplayListCanvas::refBitmapsInShader(const SkShader* shader) { // it to the bitmap pile SkBitmap bitmap; SkShader::TileMode xy[2]; - if (shader->asABitmap(&bitmap, nullptr, xy) == SkShader::kDefault_BitmapType) { + if (shader->isABitmap(&bitmap, nullptr, xy)) { refBitmap(bitmap); return; } diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h index 4982cc919b5e..664f79e283b6 100644 --- a/libs/hwui/DisplayListCanvas.h +++ b/libs/hwui/DisplayListCanvas.h @@ -17,6 +17,14 @@ #ifndef ANDROID_HWUI_DISPLAY_LIST_RENDERER_H #define ANDROID_HWUI_DISPLAY_LIST_RENDERER_H +#include "CanvasState.h" +#include "DisplayList.h" +#include "RenderNode.h" +#include "ResourceCache.h" +#include "SkiaCanvasProxy.h" +#include "hwui/Canvas.h" +#include "utils/Macros.h" + #include <SkDrawFilter.h> #include <SkMatrix.h> #include <SkPaint.h> @@ -25,13 +33,6 @@ #include <SkTLazy.h> #include <cutils/compiler.h> -#include "Canvas.h" -#include "CanvasState.h" -#include "DisplayList.h" -#include "SkiaCanvasProxy.h" -#include "RenderNode.h" -#include "ResourceCache.h" - namespace android { namespace uirenderer { @@ -54,6 +55,7 @@ class DeferredDisplayList; class DeferredLayerUpdater; class DisplayListOp; class DrawOp; +class DrawRenderNodeOp; class RenderNode; class StateOp; @@ -62,67 +64,37 @@ class StateOp; */ class ANDROID_API DisplayListCanvas: public Canvas, public CanvasStateClient { public: - DisplayListCanvas(); + DisplayListCanvas(int width, int height); virtual ~DisplayListCanvas(); - void insertReorderBarrier(bool enableReorder); - - DisplayListData* finishRecording(); - -// ---------------------------------------------------------------------------- -// HWUI Frame state operations -// ---------------------------------------------------------------------------- - - void prepareDirty(float left, float top, float right, float bottom); - void prepare() { prepareDirty(0.0f, 0.0f, width(), height()); } - bool finish(); - void interrupt(); - void resume(); + virtual void resetRecording(int width, int height) override; + virtual WARN_UNUSED_RESULT DisplayList* finishRecording() override; // ---------------------------------------------------------------------------- // HWUI Canvas state operations // ---------------------------------------------------------------------------- - void setViewport(int width, int height) { mState.setViewport(width, height); } - - const Rect& getRenderTargetClipBounds() const { return mState.getRenderTargetClipBounds(); } - - bool isCurrentTransformSimple() { - return mState.currentTransform()->isSimple(); - } + virtual void insertReorderBarrier(bool enableReorder) override; // ---------------------------------------------------------------------------- // HWUI Canvas draw operations // ---------------------------------------------------------------------------- - // Bitmap-based - void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint); - // TODO: move drawPatch() to Canvas.h - void drawPatch(const SkBitmap& bitmap, const Res_png_9patch* patch, - float left, float top, float right, float bottom, const SkPaint* paint); - // Shapes - void drawRects(const float* rects, int count, const SkPaint* paint); - void drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top, + virtual void drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top, CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom, CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry, - CanvasPropertyPaint* paint); - void drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y, - CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint); - + CanvasPropertyPaint* paint) override; + virtual void drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y, + CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) override; // ---------------------------------------------------------------------------- // HWUI Canvas draw operations - special // ---------------------------------------------------------------------------- - void drawLayer(DeferredLayerUpdater* layerHandle, float x, float y); - void drawRenderNode(RenderNode* renderNode); - - // TODO: rename for consistency - void callDrawGLFunction(Functor* functor); - - void setHighContrastText(bool highContrastText) { - mHighContrastText = highContrastText; - } + virtual void drawLayer(DeferredLayerUpdater* layerHandle) override; + virtual void drawRenderNode(RenderNode* renderNode) override; + virtual void callDrawGLFunction(Functor* functor, + GlFunctorLifecycleListener* listener) override; // ---------------------------------------------------------------------------- // CanvasStateClient interface @@ -144,19 +116,24 @@ public: virtual int width() override { return mState.getWidth(); } virtual int height() override { return mState.getHeight(); } + virtual void setHighContrastText(bool highContrastText) override { + mHighContrastText = highContrastText; + } + virtual bool isHighContrastText() override { return mHighContrastText; } + // ---------------------------------------------------------------------------- // android/graphics/Canvas state operations // ---------------------------------------------------------------------------- // Save (layer) virtual int getSaveCount() const override { return mState.getSaveCount(); } - virtual int save(SkCanvas::SaveFlags flags) override; + virtual int save(SaveFlags::Flags flags) override; virtual void restore() override; virtual void restoreToCount(int saveCount) override; virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, - SkCanvas::SaveFlags flags) override; + SaveFlags::Flags flags) override; virtual int saveLayerAlpha(float left, float top, float right, float bottom, - int alpha, SkCanvas::SaveFlags flags) override { + int alpha, SaveFlags::Flags flags) override { SkPaint paint; paint.setAlpha(alpha); return saveLayer(left, top, right, bottom, &paint, flags); @@ -165,7 +142,6 @@ public: // Matrix virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); } virtual void setMatrix(const SkMatrix& matrix) override; - virtual void setLocalMatrix(const SkMatrix& matrix) override; virtual void concat(const SkMatrix& matrix) override; virtual void rotate(float degrees) override; @@ -205,6 +181,7 @@ public: } virtual void drawLines(const float* points, int count, const SkPaint& paint) override; virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override; + virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override; virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) override; virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override; @@ -226,18 +203,20 @@ public: float dstRight, float dstBottom, const SkPaint* paint) override; virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, const float* vertices, const int* colors, const SkPaint* paint) override; + virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk, + float dstLeft, float dstTop, float dstRight, float dstBottom, + const SkPaint* paint) override; + + virtual void drawVectorDrawable(VectorDrawableRoot* tree) override; // Text - virtual void drawText(const uint16_t* glyphs, const float* positions, int count, + virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) override; - virtual void drawPosText(const uint16_t* text, const float* positions, int count, - int posCount, const SkPaint& paint) override; - virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, + virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) override; virtual bool drawTextAbsolutePos() const override { return false; } - private: CanvasState mState; @@ -249,11 +228,14 @@ private: kBarrier_OutOfOrder, }; + void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint); + void drawRects(const float* rects, int count, const SkPaint* paint); + void flushRestoreToCount(); void flushTranslate(); void flushReorderBarrier(); - LinearAllocator& alloc() { return mDisplayListData->allocator; } + LinearAllocator& alloc() { return mDisplayList->allocator; } // Each method returns final index of op size_t addOpAndUpdateChunk(DisplayListOp* op); @@ -270,22 +252,18 @@ private: inline const T* refBuffer(const T* srcBuffer, int32_t count) { if (!srcBuffer) return nullptr; - T* dstBuffer = (T*) mDisplayListData->allocator.alloc(count * sizeof(T)); + T* dstBuffer = (T*) mDisplayList->allocator.alloc<T>(count * sizeof(T)); memcpy(dstBuffer, srcBuffer, count * sizeof(T)); return dstBuffer; } - inline char* refText(const char* text, size_t byteLength) { - return (char*) refBuffer<uint8_t>((uint8_t*)text, byteLength); - } - inline const SkPath* refPath(const SkPath* path) { if (!path) return nullptr; // The points/verbs within the path are refcounted so this copy operation // is inexpensive and maintains the generationID of the original path. const SkPath* cachedPath = new SkPath(*path); - mDisplayListData->pathResources.add(cachedPath); + mDisplayList->pathResources.push_back(cachedPath); return cachedPath; } @@ -309,7 +287,7 @@ private: if (cachedPaint == nullptr || *cachedPaint != *paint) { cachedPaint = new SkPaint(*paint); std::unique_ptr<const SkPaint> copy(cachedPaint); - mDisplayListData->paints.push_back(std::move(copy)); + mDisplayList->paints.push_back(std::move(copy)); // replaceValueFor() performs an add if the entry doesn't exist mPaintMap.replaceValueFor(key, cachedPaint); @@ -319,16 +297,6 @@ private: return cachedPaint; } - inline SkPaint* copyPaint(const SkPaint* paint) { - if (!paint) return nullptr; - - SkPaint* returnPaint = new SkPaint(*paint); - std::unique_ptr<const SkPaint> copy(returnPaint); - mDisplayListData->paints.push_back(std::move(copy)); - - return returnPaint; - } - inline const SkRegion* refRegion(const SkRegion* region) { if (!region) { return region; @@ -339,7 +307,7 @@ private: if (cachedRegion == nullptr) { std::unique_ptr<const SkRegion> copy(new SkRegion(*region)); cachedRegion = copy.get(); - mDisplayListData->regions.push_back(std::move(copy)); + mDisplayList->regions.push_back(std::move(copy)); // replaceValueFor() performs an add if the entry doesn't exist mRegionMap.replaceValueFor(region, cachedRegion); @@ -353,14 +321,13 @@ private: // correctly, such as creating the bitmap from scratch, drawing with it, changing its // contents, and drawing again. The only fix would be to always copy it the first time, // which doesn't seem worth the extra cycles for this unlikely case. - SkBitmap* localBitmap = new (alloc()) SkBitmap(bitmap); - alloc().autoDestroy(localBitmap); - mDisplayListData->bitmapResources.push_back(localBitmap); + SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap); + mDisplayList->bitmapResources.push_back(localBitmap); return localBitmap; } inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) { - mDisplayListData->patchResources.add(patch); + mDisplayList->patchResources.push_back(patch); mResourceCache.incrementRefcount(patch); return patch; } @@ -370,7 +337,7 @@ private: DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap; ResourceCache& mResourceCache; - DisplayListData* mDisplayListData; + DisplayList* mDisplayList; float mTranslateX; float mTranslateY; diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index 8b4b4ba2b79e..59f073ff593c 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -28,6 +28,7 @@ #include "UvMapper.h" #include "utils/LinearAllocator.h" #include "utils/PaintUtils.h" +#include "VectorDrawable.h" #include <algorithm> @@ -64,7 +65,9 @@ public: static void operator delete(void* ptr) { LOG_ALWAYS_FATAL("delete not supported"); } static void* operator new(size_t size) = delete; /** PURPOSELY OMITTED **/ static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); + // FIXME: Quick hack to keep old pipeline working, delete this when + // we no longer need to support HWUI_NEWOPS := false + return allocator.alloc<char>(size); } enum OpLogFlag { @@ -139,7 +142,7 @@ public: * reducing which operations are tagged as mergeable. */ virtual void multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<OpStatePair>& ops, const Rect& bounds) { + const std::vector<OpStatePair>& ops, const Rect& bounds) { for (unsigned int i = 0; i < ops.size(); i++) { renderer.restoreDisplayState(*(ops[i].state), true); ops[i].op->applyDraw(renderer, dirty); @@ -172,10 +175,6 @@ public: void setQuickRejected(bool quickRejected) { mQuickRejected = quickRejected; } bool getQuickRejected() { return mQuickRejected; } - inline int getPaintAlpha() const { - return OpenGLRenderer::getAlphaDirect(mPaint); - } - virtual bool hasTextShadow() const { return false; } @@ -213,7 +212,7 @@ protected: if (state.mAlpha != 1.0f) return false; - SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint); + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(mPaint); return (mode == SkXfermode::kSrcOver_Mode || mode == SkXfermode::kSrc_Mode); @@ -249,8 +248,8 @@ public: virtual bool getLocalBounds(Rect& localBounds) override { localBounds.set(mLocalBounds); - OpenGLRenderer::TextShadow textShadow; - if (OpenGLRenderer::getTextShadow(mPaint, &textShadow)) { + PaintUtils::TextShadow textShadow; + if (PaintUtils::getTextShadow(mPaint, &textShadow)) { Rect shadow(mLocalBounds); shadow.translate(textShadow.dx, textShadow.dx); shadow.outset(textShadow.radius); @@ -372,8 +371,8 @@ public: private: bool isSaveLayerAlpha() const { - SkXfermode::Mode mode = OpenGLRenderer::getXfermodeDirect(mPaint); - int alpha = OpenGLRenderer::getAlphaDirect(mPaint); + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(mPaint); + int alpha = PaintUtils::getAlphaDirect(mPaint); return alpha < 255 && mode == SkXfermode::kSrcOver_Mode; } @@ -472,7 +471,9 @@ public: : mMatrix(matrix) {} virtual void applyState(OpenGLRenderer& renderer, int saveCount) const override { - renderer.setMatrix(mMatrix); + // Setting a matrix on a Canvas isn't equivalent to setting a total matrix on the scene. + // Set a canvas-relative matrix on the renderer instead. + renderer.setLocalMatrix(mMatrix); } virtual void output(int level, uint32_t logFlags) const override { @@ -489,25 +490,6 @@ private: const SkMatrix mMatrix; }; -class SetLocalMatrixOp : public StateOp { -public: - SetLocalMatrixOp(const SkMatrix& matrix) - : mMatrix(matrix) {} - - virtual void applyState(OpenGLRenderer& renderer, int saveCount) const override { - renderer.setLocalMatrix(mMatrix); - } - - virtual void output(int level, uint32_t logFlags) const override { - OP_LOG("SetLocalMatrix " SK_MATRIX_STRING, SK_MATRIX_ARGS(&mMatrix)); - } - - virtual const char* name() override { return "SetLocalMatrix"; } - -private: - const SkMatrix mMatrix; -}; - class ConcatMatrixOp : public StateOp { public: ConcatMatrixOp(const SkMatrix& matrix) @@ -633,7 +615,7 @@ public: AssetAtlas::Entry* getAtlasEntry(OpenGLRenderer& renderer) { if (!mEntryValid) { mEntryValid = true; - mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap); + mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap->pixelRef()); } return mEntry; } @@ -648,7 +630,7 @@ public: * the current layer, if any. */ virtual void multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<OpStatePair>& ops, const Rect& bounds) override { + const std::vector<OpStatePair>& ops, const Rect& bounds) override { const DeferredDisplayState& firstState = *(ops[0].state); renderer.restoreDisplayState(firstState, true); // restore all but the clip @@ -708,7 +690,7 @@ public: // TODO: support clipped bitmaps by handling them in SET_TEXTURE deferInfo.mergeable = state.mMatrix.isSimple() && state.mMatrix.positiveScale() && !state.mClipSideFlags && - OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode && + PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode && (mBitmap->colorType() != kAlpha_8_SkColorType); } @@ -798,7 +780,7 @@ public: AssetAtlas::Entry* getAtlasEntry(OpenGLRenderer& renderer) { if (!mEntryValid) { mEntryValid = true; - mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap); + mEntry = renderer.renderState().assetAtlas().getEntry(mBitmap->pixelRef()); } return mEntry; } @@ -819,7 +801,7 @@ public: * is also responsible for dirtying the current layer, if any. */ virtual void multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<OpStatePair>& ops, const Rect& bounds) override { + const std::vector<OpStatePair>& ops, const Rect& bounds) override { const DeferredDisplayState& firstState = *(ops[0].state); renderer.restoreDisplayState(firstState, true); // restore all but the clip @@ -912,7 +894,7 @@ public: deferInfo.batchId = DeferredDisplayList::kOpBatch_Patch; deferInfo.mergeId = getAtlasEntry(renderer) ? (mergeid_t) mEntry->getMergeId() : (mergeid_t) mBitmap; deferInfo.mergeable = state.mMatrix.isPureTranslate() && - OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; + PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; deferInfo.opaqueOverBounds = isOpaqueOverBounds(state) && mBitmap->isOpaque(); } @@ -1126,6 +1108,30 @@ private: float* mRadius; }; +class DrawVectorDrawableOp : public DrawOp { +public: + DrawVectorDrawableOp(VectorDrawableRoot* tree, const SkRect& bounds) + : DrawOp(nullptr), mTree(tree), mDst(bounds) {} + + virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override { + const SkBitmap& bitmap = mTree->getBitmapUpdateIfDirty(); + SkPaint* paint = mTree->getPaint(); + renderer.drawBitmap(&bitmap, Rect(0, 0, bitmap.width(), bitmap.height()), + mDst, paint); + } + + virtual void output(int level, uint32_t logFlags) const override { + OP_LOG("Draw Vector Drawable %p", mTree); + } + + virtual const char* name() override { return "DrawVectorDrawable"; } + +private: + VectorDrawableRoot* mTree; + SkRect mDst; + +}; + class DrawOvalOp : public DrawStrokableOp { public: DrawOvalOp(float left, float top, float right, float bottom, const SkPaint* paint) @@ -1250,7 +1256,7 @@ public: class DrawSomeTextOp : public DrawOp { public: - DrawSomeTextOp(const char* text, int bytesCount, int count, const SkPaint* paint) + DrawSomeTextOp(const glyph_t* text, int bytesCount, int count, const SkPaint* paint) : DrawOp(paint), mText(text), mBytesCount(bytesCount), mCount(count) {}; virtual void output(int level, uint32_t logFlags) const override { @@ -1258,12 +1264,12 @@ public: } virtual bool hasTextShadow() const override { - return OpenGLRenderer::hasTextShadow(mPaint); + return PaintUtils::hasTextShadow(mPaint); } virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, const DeferredDisplayState& state) override { - FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(mPaint); + FontRenderer& fontRenderer = renderer.getCaches().fontRenderer.getFontRenderer(); fontRenderer.precache(mPaint, mText, mCount, SkMatrix::I()); deferInfo.batchId = mPaint->getColor() == SK_ColorBLACK ? @@ -1272,14 +1278,14 @@ public: } protected: - const char* mText; + const glyph_t* mText; int mBytesCount; int mCount; }; class DrawTextOnPathOp : public DrawSomeTextOp { public: - DrawTextOnPathOp(const char* text, int bytesCount, int count, + DrawTextOnPathOp(const glyph_t* text, int bytesCount, int count, const SkPath* path, float hOffset, float vOffset, const SkPaint* paint) : DrawSomeTextOp(text, bytesCount, count, paint), mPath(path), mHOffset(hOffset), mVOffset(vOffset) { @@ -1299,27 +1305,9 @@ private: float mVOffset; }; -class DrawPosTextOp : public DrawSomeTextOp { -public: - DrawPosTextOp(const char* text, int bytesCount, int count, - const float* positions, const SkPaint* paint) - : DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) { - /* TODO: inherit from DrawBounded and init mLocalBounds */ - } - - virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override { - renderer.drawPosText(mText, mBytesCount, mCount, mPositions, mPaint); - } - - virtual const char* name() override { return "DrawPosText"; } - -private: - const float* mPositions; -}; - class DrawTextOp : public DrawStrokableOp { public: - DrawTextOp(const char* text, int bytesCount, int count, float x, float y, + DrawTextOp(const glyph_t* text, int bytesCount, int count, float x, float y, const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds) : DrawStrokableOp(bounds, paint), mText(text), mBytesCount(bytesCount), mCount(count), mX(x), mY(y), mPositions(positions), mTotalAdvance(totalAdvance) { @@ -1328,7 +1316,7 @@ public: virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo, const DeferredDisplayState& state) override { - FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(mPaint); + FontRenderer& fontRenderer = renderer.getCaches().fontRenderer.getFontRenderer(); SkMatrix transform; renderer.findBestFontTransform(state.mMatrix, &transform); if (mPrecacheTransform != transform) { @@ -1347,7 +1335,7 @@ public: deferInfo.mergeable = state.mMatrix.isPureTranslate() && !hasDecorations - && OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; + && PaintUtils::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode; } virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override { @@ -1358,7 +1346,7 @@ public: } virtual void multiDraw(OpenGLRenderer& renderer, Rect& dirty, - const Vector<OpStatePair>& ops, const Rect& bounds) override { + const std::vector<OpStatePair>& ops, const Rect& bounds) override { for (unsigned int i = 0; i < ops.size(); i++) { const DeferredDisplayState& state = *(ops[i].state); DrawOpMode drawOpMode = (i == ops.size() - 1) ? DrawOpMode::kFlush : DrawOpMode::kDefer; @@ -1380,7 +1368,7 @@ public: virtual const char* name() override { return "DrawText"; } private: - const char* mText; + const glyph_t* mText; int mBytesCount; int mCount; float mX; @@ -1417,26 +1405,31 @@ private: class DrawRenderNodeOp : public DrawBoundedOp { friend class RenderNode; // grant RenderNode access to info of child - friend class DisplayListData; // grant DisplayListData access to info of child + friend class DisplayList; // grant DisplayList access to info of child + friend class DisplayListCanvas; + friend class TestUtils; public: DrawRenderNodeOp(RenderNode* renderNode, const mat4& transformFromParent, bool clipIsSimple) - : DrawBoundedOp(0, 0, renderNode->getWidth(), renderNode->getHeight(), nullptr) - , mRenderNode(renderNode) + : DrawBoundedOp(0, 0, + renderNode->stagingProperties().getWidth(), + renderNode->stagingProperties().getHeight(), + nullptr) + , renderNode(renderNode) , mRecordedWithPotentialStencilClip(!clipIsSimple || !transformFromParent.isSimple()) - , mTransformFromParent(transformFromParent) - , mSkipInOrderDraw(false) {} + , localMatrix(transformFromParent) + , skipInOrderDraw(false) {} virtual void defer(DeferStateStruct& deferStruct, int saveCount, int level, bool useQuickReject) override { - if (mRenderNode->isRenderable() && !mSkipInOrderDraw) { - mRenderNode->defer(deferStruct, level + 1); + if (renderNode->isRenderable() && !skipInOrderDraw) { + renderNode->defer(deferStruct, level + 1); } } virtual void replay(ReplayStateStruct& replayStruct, int saveCount, int level, bool useQuickReject) override { - if (mRenderNode->isRenderable() && !mSkipInOrderDraw) { - mRenderNode->replay(replayStruct, level + 1); + if (renderNode->isRenderable() && !skipInOrderDraw) { + renderNode->replay(replayStruct, level + 1); } } @@ -1445,18 +1438,16 @@ public: } virtual void output(int level, uint32_t logFlags) const override { - OP_LOG("Draw RenderNode %p %s", mRenderNode, mRenderNode->getName()); - if (mRenderNode && (logFlags & kOpLogFlag_Recurse)) { - mRenderNode->output(level + 1); + OP_LOG("Draw RenderNode %p %s", renderNode, renderNode->getName()); + if (renderNode && (logFlags & kOpLogFlag_Recurse)) { + renderNode->output(level + 1); } } virtual const char* name() override { return "DrawRenderNode"; } - RenderNode* renderNode() { return mRenderNode; } - private: - RenderNode* mRenderNode; + RenderNode* renderNode; /** * This RenderNode was drawn into a DisplayList with the canvas in a state that will likely @@ -1478,7 +1469,7 @@ private: /** * Records transform vs parent, used for computing total transform without rerunning DL contents */ - const mat4 mTransformFromParent; + const mat4 localMatrix; /** * Holds the transformation between the projection surface ViewGroup and this RenderNode @@ -1488,8 +1479,8 @@ private: * * Note: doesn't include transformation within the RenderNode, or its properties. */ - mat4 mTransformFromCompositingAncestor; - bool mSkipInOrderDraw; + mat4 transformFromCompositingAncestor; + bool skipInOrderDraw; }; /** @@ -1541,23 +1532,21 @@ private: class DrawLayerOp : public DrawOp { public: - DrawLayerOp(Layer* layer, float x, float y) - : DrawOp(nullptr), mLayer(layer), mX(x), mY(y) {} + DrawLayerOp(Layer* layer) + : DrawOp(nullptr), mLayer(layer) {} virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override { - renderer.drawLayer(mLayer, mX, mY); + renderer.drawLayer(mLayer); } virtual void output(int level, uint32_t logFlags) const override { - OP_LOG("Draw Layer %p at %f %f", mLayer, mX, mY); + OP_LOG("Draw Layer %p", mLayer); } virtual const char* name() override { return "DrawLayer"; } private: Layer* mLayer; - float mX; - float mY; }; }; // namespace uirenderer diff --git a/libs/hwui/Dither.cpp b/libs/hwui/Dither.cpp index 1ba651174c8a..ec2013e27401 100644 --- a/libs/hwui/Dither.cpp +++ b/libs/hwui/Dither.cpp @@ -54,7 +54,6 @@ void Dither::bindDitherTexture() { 15 * dither, 7 * dither, 13 * dither, 5 * dither }; - glPixelStorei(GL_UNPACK_ALIGNMENT, sizeof(GLfloat)); glTexImage2D(GL_TEXTURE_2D, 0, GL_R16F, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, GL_RED, GL_FLOAT, &pattern); } else { @@ -65,7 +64,6 @@ void Dither::bindDitherTexture() { 15, 7, 13, 5 }; - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, DITHER_KERNEL_SIZE, DITHER_KERNEL_SIZE, 0, GL_ALPHA, GL_UNSIGNED_BYTE, &pattern); } diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp index 2a82216eb145..02caaa49e99c 100644 --- a/libs/hwui/Extensions.cpp +++ b/libs/hwui/Extensions.cpp @@ -14,29 +14,19 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "Extensions.h" #include "Debug.h" #include "Properties.h" +#include "utils/StringUtils.h" -#include <EGL/egl.h> -#include <EGL/eglext.h> +#include <GLES2/gl2.h> #include <GLES2/gl2ext.h> #include <utils/Log.h> namespace android { - -using namespace uirenderer; -ANDROID_SINGLETON_STATIC_INSTANCE(Extensions); - namespace uirenderer { -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - // Debug #if DEBUG_EXTENSIONS #define EXT_LOGD(...) ALOGD(__VA_ARGS__) @@ -44,30 +34,16 @@ namespace uirenderer { #define EXT_LOGD(...) #endif -/////////////////////////////////////////////////////////////////////////////// -// Constructors -/////////////////////////////////////////////////////////////////////////////// Extensions::Extensions() { - // Query GL extensions - findExtensions((const char*) glGetString(GL_EXTENSIONS), mGlExtensionList); - mHasNPot = hasGlExtension("GL_OES_texture_npot"); - mHasFramebufferFetch = hasGlExtension("GL_NV_shader_framebuffer_fetch"); - mHasDiscardFramebuffer = hasGlExtension("GL_EXT_discard_framebuffer"); - mHasDebugMarker = hasGlExtension("GL_EXT_debug_marker"); - mHasTiledRendering = hasGlExtension("GL_QCOM_tiled_rendering"); - mHas1BitStencil = hasGlExtension("GL_OES_stencil1"); - mHas4BitStencil = hasGlExtension("GL_OES_stencil4"); - - // Query EGL extensions - findExtensions(eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS), mEglExtensionList); - - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_DEBUG_NV_PROFILING, property, nullptr) > 0) { - mHasNvSystemTime = !strcmp(property, "true") && hasEglExtension("EGL_NV_system_time"); - } else { - mHasNvSystemTime = false; - } + auto extensions = StringUtils::split((const char*) glGetString(GL_EXTENSIONS)); + mHasNPot = extensions.has("GL_OES_texture_npot"); + mHasFramebufferFetch = extensions.has("GL_NV_shader_framebuffer_fetch"); + mHasDiscardFramebuffer = extensions.has("GL_EXT_discard_framebuffer"); + mHasDebugMarker = extensions.has("GL_EXT_debug_marker"); + mHas1BitStencil = extensions.has("GL_OES_stencil1"); + mHas4BitStencil = extensions.has("GL_OES_stencil4"); + mHasUnpackSubImage = extensions.has("GL_EXT_unpack_subimage"); const char* version = (const char*) glGetString(GL_VERSION); @@ -90,40 +66,5 @@ Extensions::Extensions() { } } -/////////////////////////////////////////////////////////////////////////////// -// Methods -/////////////////////////////////////////////////////////////////////////////// - -bool Extensions::hasGlExtension(const char* extension) const { - const String8 s(extension); - return mGlExtensionList.indexOf(s) >= 0; -} - -bool Extensions::hasEglExtension(const char* extension) const { - const String8 s(extension); - return mEglExtensionList.indexOf(s) >= 0; -} - -void Extensions::findExtensions(const char* extensions, SortedVector<String8>& list) const { - const char* current = extensions; - const char* head = current; - EXT_LOGD("Available extensions:"); - do { - head = strchr(current, ' '); - String8 s(current, head ? head - current : strlen(current)); - if (s.length()) { - list.add(s); - EXT_LOGD(" %s", s.string()); - } - current = head + 1; - } while (head); -} - -void Extensions::dump() const { - ALOGD("%s", (const char*) glGetString(GL_VERSION)); - ALOGD("Supported GL extensions:\n%s", (const char*) glGetString(GL_EXTENSIONS)); - ALOGD("Supported EGL extensions:\n%s", eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS)); -} - }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h index e7d317d21941..67cc747015e0 100644 --- a/libs/hwui/Extensions.h +++ b/libs/hwui/Extensions.h @@ -19,11 +19,8 @@ #include <cutils/compiler.h> -#include <utils/Singleton.h> -#include <utils/SortedVector.h> -#include <utils/String8.h> - -#include <GLES2/gl2.h> +#include <string> +#include <unordered_set> namespace android { namespace uirenderer { @@ -32,7 +29,7 @@ namespace uirenderer { // Classes /////////////////////////////////////////////////////////////////////////////// -class ANDROID_API Extensions { +class Extensions { public: Extensions(); @@ -40,11 +37,9 @@ public: inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; } inline bool hasDebugMarker() const { return mHasDebugMarker; } - inline bool hasTiledRendering() const { return mHasTiledRendering; } inline bool has1BitStencil() const { return mHas1BitStencil; } inline bool has4BitStencil() const { return mHas4BitStencil; } - inline bool hasNvSystemTime() const { return mHasNvSystemTime; } - inline bool hasUnpackRowLength() const { return mVersionMajor >= 3; } + inline bool hasUnpackRowLength() const { return mVersionMajor >= 3 || mHasUnpackSubImage; } inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; } inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; } inline bool hasFloatTextures() const { return mVersionMajor >= 3; } @@ -52,25 +47,14 @@ public: inline int getMajorGlVersion() const { return mVersionMajor; } inline int getMinorGlVersion() const { return mVersionMinor; } - bool hasGlExtension(const char* extension) const; - bool hasEglExtension(const char* extension) const; - - void dump() const; - private: - void findExtensions(const char* extensions, SortedVector<String8>& list) const; - - SortedVector<String8> mGlExtensionList; - SortedVector<String8> mEglExtensionList; - bool mHasNPot; bool mHasFramebufferFetch; bool mHasDiscardFramebuffer; bool mHasDebugMarker; - bool mHasTiledRendering; bool mHas1BitStencil; bool mHas4BitStencil; - bool mHasNvSystemTime; + bool mHasUnpackSubImage; int mVersionMajor; int mVersionMinor; diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp index b54d53233a65..b2181b60054f 100644 --- a/libs/hwui/FboCache.cpp +++ b/libs/hwui/FboCache.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <stdlib.h> #include "Debug.h" @@ -29,15 +27,8 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -FboCache::FboCache(): mMaxSize(DEFAULT_FBO_CACHE_SIZE) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_FBO_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting fbo cache size to %s", property); - mMaxSize = atoi(property); - } else { - INIT_LOGD(" Using default fbo cache size of %d", DEFAULT_FBO_CACHE_SIZE); - } -} +FboCache::FboCache() + : mMaxSize(Properties::fboCacheSize) {} FboCache::~FboCache() { clear(); diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h index 97dec88e709b..9a39ec28aa3d 100644 --- a/libs/hwui/FloatColor.h +++ b/libs/hwui/FloatColor.h @@ -17,6 +17,7 @@ #define FLOATCOLOR_H #include "utils/Macros.h" +#include "utils/MathUtils.h" #include <stdint.h> @@ -38,6 +39,17 @@ struct FloatColor { || b > 0.0f; } + bool operator==(const FloatColor& other) const { + return MathUtils::areEqual(r, other.r) + && MathUtils::areEqual(g, other.g) + && MathUtils::areEqual(b, other.b) + && MathUtils::areEqual(a, other.a); + } + + bool operator!=(const FloatColor& other) const { + return !(*this == other); + } + float r; float g; float b; diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 35051b7cccea..276c18d0d3f9 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -21,19 +21,25 @@ #include "Extensions.h" #include "Glop.h" #include "GlopBuilder.h" -#include "OpenGLRenderer.h" #include "PixelBuffer.h" #include "Rect.h" #include "renderstate/RenderState.h" #include "utils/Blur.h" -#include "utils/MathUtils.h" #include "utils/Timing.h" -#include <SkGlyph.h> -#include <SkUtils.h> -#include <cutils/properties.h> +#if HWUI_NEW_OPS +#include "BakedOpDispatcher.h" +#include "BakedOpRenderer.h" +#include "BakedOpState.h" +#else +#include "OpenGLRenderer.h" +#endif +#include <algorithm> +#include <cutils/properties.h> +#include <SkGlyph.h> +#include <SkUtils.h> #include <utils/Log.h> #ifdef ANDROID_ENABLE_RENDERSCRIPT @@ -61,14 +67,26 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { int transformFlags = pureTranslate ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None; Glop glop; +#if HWUI_NEW_OPS + GlopBuilder(renderer->renderState(), renderer->caches(), &glop) + .setRoundRectClipState(bakedState->roundRectClipState) + .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount()) + .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha) + .setTransform(bakedState->computedState.transform, transformFlags) + .setModelViewIdentityEmptyBounds() + .build(); + // Note: don't pass dirty bounds here, so user must manage passing dirty bounds to renderer + renderer->renderGlop(nullptr, clip, glop); +#else GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop) + .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState) .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount()) .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, renderer->currentSnapshot()->alpha) .setTransform(*(renderer->currentSnapshot()), transformFlags) - .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0)) - .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState) + .setModelViewOffsetRect(0, 0, Rect()) .build(); renderer->renderGlop(glop); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -77,8 +95,8 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { static bool sLogFontRendererCreate = true; -FontRenderer::FontRenderer() - : mGammaTable(nullptr) +FontRenderer::FontRenderer(const uint8_t* gammaTable) + : mGammaTable(gammaTable) , mCurrentFont(nullptr) , mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) , mCurrentCacheTexture(nullptr) @@ -94,34 +112,22 @@ FontRenderer::FontRenderer() INIT_LOGD("Creating FontRenderer"); } - mSmallCacheWidth = DEFAULT_TEXT_SMALL_CACHE_WIDTH; - mSmallCacheHeight = DEFAULT_TEXT_SMALL_CACHE_HEIGHT; - mLargeCacheWidth = DEFAULT_TEXT_LARGE_CACHE_WIDTH; - mLargeCacheHeight = DEFAULT_TEXT_LARGE_CACHE_HEIGHT; - - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_TEXT_SMALL_CACHE_WIDTH, property, nullptr) > 0) { - mSmallCacheWidth = atoi(property); - } + mSmallCacheWidth = property_get_int32(PROPERTY_TEXT_SMALL_CACHE_WIDTH, + DEFAULT_TEXT_SMALL_CACHE_WIDTH); + mSmallCacheHeight = property_get_int32(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, + DEFAULT_TEXT_SMALL_CACHE_HEIGHT); - if (property_get(PROPERTY_TEXT_SMALL_CACHE_HEIGHT, property, nullptr) > 0) { - mSmallCacheHeight = atoi(property); - } - - if (property_get(PROPERTY_TEXT_LARGE_CACHE_WIDTH, property, nullptr) > 0) { - mLargeCacheWidth = atoi(property); - } - - if (property_get(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, property, nullptr) > 0) { - mLargeCacheHeight = atoi(property); - } + mLargeCacheWidth = property_get_int32(PROPERTY_TEXT_LARGE_CACHE_WIDTH, + DEFAULT_TEXT_LARGE_CACHE_WIDTH); + mLargeCacheHeight = property_get_int32(PROPERTY_TEXT_LARGE_CACHE_HEIGHT, + DEFAULT_TEXT_LARGE_CACHE_HEIGHT); uint32_t maxTextureSize = (uint32_t) Caches::getInstance().maxTextureSize; - mSmallCacheWidth = MathUtils::min(mSmallCacheWidth, maxTextureSize); - mSmallCacheHeight = MathUtils::min(mSmallCacheHeight, maxTextureSize); - mLargeCacheWidth = MathUtils::min(mLargeCacheWidth, maxTextureSize); - mLargeCacheHeight = MathUtils::min(mLargeCacheHeight, maxTextureSize); + mSmallCacheWidth = std::min(mSmallCacheWidth, maxTextureSize); + mSmallCacheHeight = std::min(mSmallCacheHeight, maxTextureSize); + mLargeCacheWidth = std::min(mLargeCacheWidth, maxTextureSize); + mLargeCacheHeight = std::min(mLargeCacheHeight, maxTextureSize); if (sLogFontRendererCreate) { INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i", @@ -134,7 +140,7 @@ FontRenderer::FontRenderer() sLogFontRendererCreate = false; } -void clearCacheTextures(Vector<CacheTexture*>& cacheTextures) { +void clearCacheTextures(std::vector<CacheTexture*>& cacheTextures) { for (uint32_t i = 0; i < cacheTextures.size(); i++) { delete cacheTextures[i]; } @@ -171,7 +177,7 @@ void FontRenderer::flushAllAndInvalidate() { mDrawn = false; } -void FontRenderer::flushLargeCaches(Vector<CacheTexture*>& cacheTextures) { +void FontRenderer::flushLargeCaches(std::vector<CacheTexture*>& cacheTextures) { // Start from 1; don't deallocate smallest/default texture for (uint32_t i = 1; i < cacheTextures.size(); i++) { CacheTexture* cacheTexture = cacheTextures[i]; @@ -191,7 +197,7 @@ void FontRenderer::flushLargeCaches() { flushLargeCaches(mRGBACacheTextures); } -CacheTexture* FontRenderer::cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, +CacheTexture* FontRenderer::cacheBitmapInTexture(std::vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph, uint32_t* startX, uint32_t* startY) { for (uint32_t i = 0; i < cacheTextures.size(); i++) { if (cacheTextures[i]->fitBitmap(glyph, startX, startY)) { @@ -218,7 +224,7 @@ void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyp // choose an appropriate cache texture list for this glyph format SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); - Vector<CacheTexture*>* cacheTextures = nullptr; + std::vector<CacheTexture*>* cacheTextures = nullptr; switch (format) { case SkMask::kA8_Format: case SkMask::kBW_Format: @@ -399,17 +405,17 @@ void FontRenderer::initTextTexture() { clearCacheTextures(mRGBACacheTextures); mUploadTexture = false; - mACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, + mACacheTextures.push_back(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, GL_ALPHA, true)); - mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL_ALPHA, false)); - mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL_ALPHA, false)); - mACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, + mACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, GL_ALPHA, false)); - mRGBACacheTextures.push(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, + mRGBACacheTextures.push_back(createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, GL_RGBA, false)); - mRGBACacheTextures.push(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, + mRGBACacheTextures.push_back(createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL_RGBA, false)); mCurrentCacheTexture = mACacheTextures[0]; } @@ -425,7 +431,7 @@ void FontRenderer::checkInit() { mInitialized = true; } -void checkTextureUpdateForCache(Caches& caches, Vector<CacheTexture*>& cacheTextures, +void checkTextureUpdateForCache(Caches& caches, std::vector<CacheTexture*>& cacheTextures, bool& resetPixelStore, GLuint& lastTextureId) { for (uint32_t i = 0; i < cacheTextures.size(); i++) { CacheTexture* cacheTexture = cacheTextures[i]; @@ -452,7 +458,6 @@ void FontRenderer::checkTextureUpdate() { GLuint lastTextureId = 0; bool resetPixelStore = false; - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Iterate over all the cache textures and see which ones need to be updated checkTextureUpdateForCache(caches, mACacheTextures, resetPixelStore, lastTextureId); @@ -470,11 +475,10 @@ void FontRenderer::checkTextureUpdate() { mUploadTexture = false; } -void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) { +void FontRenderer::issueDrawCommand(std::vector<CacheTexture*>& cacheTextures) { if (!mFunctor) return; bool first = true; - bool forceRebind = false; for (uint32_t i = 0; i < cacheTextures.size(); i++) { CacheTexture* texture = cacheTextures[i]; if (texture->canDraw()) { @@ -487,7 +491,6 @@ void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) { mFunctor->draw(*texture, mLinearFiltering); texture->resetMesh(); - forceRebind = false; } } } @@ -554,8 +557,8 @@ void FontRenderer::setFont(const SkPaint* paint, const SkMatrix& matrix) { mCurrentFont = Font::create(this, paint, matrix); } -FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) { +FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const glyph_t *glyphs, + int numGlyphs, float radius, const float* positions) { checkInit(); DropShadow image; @@ -574,7 +577,7 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co mBounds = nullptr; Rect bounds; - mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions); + mCurrentFont->measure(paint, glyphs, numGlyphs, &bounds, positions); uint32_t intRadius = Blur::convertRadiusToInt(radius); uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius; @@ -606,7 +609,7 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co // text has non-whitespace, so draw and blur to create the shadow // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted // TODO: don't draw pure whitespace in the first place, and avoid needing this check - mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, + mCurrentFont->render(paint, glyphs, numGlyphs, penX, penY, Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions); // Unbind any PBO we might have used @@ -640,26 +643,26 @@ void FontRenderer::finishRender() { issueDrawCommand(); } -void FontRenderer::precache(const SkPaint* paint, const char* text, int numGlyphs, +void FontRenderer::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkMatrix& matrix) { Font* font = Font::create(this, paint, matrix); - font->precache(paint, text, numGlyphs); + font->precache(paint, glyphs, numGlyphs); } void FontRenderer::endPrecaching() { checkTextureUpdate(); } -bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, - const float* positions, Rect* bounds, TextDrawFunctor* functor, bool forceFinish) { +bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, + int numGlyphs, int x, int y, const float* positions, + Rect* bounds, TextDrawFunctor* functor, bool forceFinish) { if (!mCurrentFont) { ALOGE("No font set"); return false; } initRender(clip, bounds, functor); - mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); + mCurrentFont->render(paint, glyphs, numGlyphs, x, y, positions); if (forceFinish) { finishRender(); @@ -668,33 +671,25 @@ bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const c return mDrawn; } -bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path, - float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor) { +bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, + int numGlyphs, const SkPath* path, float hOffset, float vOffset, + Rect* bounds, TextDrawFunctor* functor) { if (!mCurrentFont) { ALOGE("No font set"); return false; } initRender(clip, bounds, functor); - mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); + mCurrentFont->render(paint, glyphs, numGlyphs, path, hOffset, vOffset); finishRender(); return mDrawn; } -void FontRenderer::removeFont(const Font* font) { - mActiveFonts.remove(font->getDescription()); - - if (mCurrentFont == font) { - mCurrentFont = nullptr; - } -} - void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, float radius) { uint32_t intRadius = Blur::convertRadiusToInt(radius); #ifdef ANDROID_ENABLE_RENDERSCRIPT - if (width * height * intRadius >= RS_MIN_INPUT_CUTOFF) { + if (width * height * intRadius >= RS_MIN_INPUT_CUTOFF && radius <= 25.0f) { uint8_t* outImage = (uint8_t*) memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); if (mRs == nullptr) { @@ -734,14 +729,14 @@ void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, flo #endif std::unique_ptr<float[]> gaussian(new float[2 * intRadius + 1]); - Blur::generateGaussianWeights(gaussian.get(), intRadius); + Blur::generateGaussianWeights(gaussian.get(), radius); std::unique_ptr<uint8_t[]> scratch(new uint8_t[width * height]); Blur::horizontal(gaussian.get(), intRadius, *image, scratch.get(), width, height); Blur::vertical(gaussian.get(), intRadius, scratch.get(), *image, width, height); } -static uint32_t calculateCacheSize(const Vector<CacheTexture*>& cacheTextures) { +static uint32_t calculateCacheSize(const std::vector<CacheTexture*>& cacheTextures) { uint32_t size = 0; for (uint32_t i = 0; i < cacheTextures.size(); i++) { CacheTexture* cacheTexture = cacheTextures[i]; diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index dfb107c99bc5..e10a81b8ccd8 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -21,16 +21,16 @@ #include "font/CacheTexture.h" #include "font/CachedGlyphInfo.h" #include "font/Font.h" -#include "utils/SortedList.h" #include <utils/LruCache.h> -#include <utils/Vector.h> #include <utils/StrongPointer.h> #include <SkPaint.h> #include <GLES2/gl2.h> +#include <vector> + #ifdef ANDROID_ENABLE_RENDERSCRIPT #include "RenderScript.h" namespace RSC { @@ -44,13 +44,31 @@ namespace RSC { namespace android { namespace uirenderer { +#if HWUI_NEW_OPS +class BakedOpState; +class BakedOpRenderer; +struct ClipBase; +#else class OpenGLRenderer; +#endif class TextDrawFunctor { public: - TextDrawFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate, + TextDrawFunctor( +#if HWUI_NEW_OPS + BakedOpRenderer* renderer, + const BakedOpState* bakedState, + const ClipBase* clip, +#else + OpenGLRenderer* renderer, +#endif + float x, float y, bool pureTranslate, int alpha, SkXfermode::Mode mode, const SkPaint* paint) : renderer(renderer) +#if HWUI_NEW_OPS + , bakedState(bakedState) + , clip(clip) +#endif , x(x) , y(y) , pureTranslate(pureTranslate) @@ -61,7 +79,13 @@ public: void draw(CacheTexture& texture, bool linearFiltering); +#if HWUI_NEW_OPS + BakedOpRenderer* renderer; + const BakedOpState* bakedState; + const ClipBase* clip; +#else OpenGLRenderer* renderer; +#endif float x; float y; bool pureTranslate; @@ -72,30 +96,24 @@ public: class FontRenderer { public: - FontRenderer(); + FontRenderer(const uint8_t* gammaTable); ~FontRenderer(); - void flushLargeCaches(Vector<CacheTexture*>& cacheTextures); + void flushLargeCaches(std::vector<CacheTexture*>& cacheTextures); void flushLargeCaches(); - void setGammaTable(const uint8_t* gammaTable) { - mGammaTable = gammaTable; - } - void setFont(const SkPaint* paint, const SkMatrix& matrix); - void precache(const SkPaint* paint, const char* text, int numGlyphs, const SkMatrix& matrix); + void precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkMatrix& matrix); void endPrecaching(); - // bounds is an out parameter - bool renderPosText(const SkPaint* paint, const Rect* clip, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions, - Rect* bounds, TextDrawFunctor* functor, bool forceFinish = true); + bool renderPosText(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, + int numGlyphs, int x, int y, const float* positions, + Rect* outBounds, TextDrawFunctor* functor, bool forceFinish = true); - // bounds is an out parameter - bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path, - float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor); + bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, + int numGlyphs, const SkPath* path, + float hOffset, float vOffset, Rect* outBounds, TextDrawFunctor* functor); struct DropShadow { uint32_t width; @@ -107,8 +125,8 @@ public: // After renderDropShadow returns, the called owns the memory in DropShadow.image // and is responsible for releasing it when it's done with it - DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex, - uint32_t len, int numGlyphs, float radius, const float* positions); + DropShadow renderDropShadow(const SkPaint* paint, const glyph_t *glyphs, int numGlyphs, + float radius, const float* positions); void setTextureFiltering(bool linearFiltering) { mLinearFiltering = linearFiltering; @@ -127,7 +145,7 @@ private: CacheTexture* createCacheTexture(int width, int height, GLenum format, bool allocate); void cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, uint32_t *retOriginX, uint32_t *retOriginY, bool precaching); - CacheTexture* cacheBitmapInTexture(Vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph, + CacheTexture* cacheBitmapInTexture(std::vector<CacheTexture*>& cacheTextures, const SkGlyph& glyph, uint32_t* startX, uint32_t* startY); void flushAllAndInvalidate(); @@ -136,7 +154,7 @@ private: void initRender(const Rect* clip, Rect* bounds, TextDrawFunctor* functor); void finishRender(); - void issueDrawCommand(Vector<CacheTexture*>& cacheTextures); + void issueDrawCommand(std::vector<CacheTexture*>& cacheTextures); void issueDrawCommand(); void appendMeshQuadNoClip(float x1, float y1, float u1, float v1, float x2, float y2, float u2, float v2, @@ -151,8 +169,6 @@ private: float x3, float y3, float u3, float v3, float x4, float y4, float u4, float v4, CacheTexture* texture); - void removeFont(const Font* font); - void checkTextureUpdate(); void setTextureDirty() { @@ -164,8 +180,8 @@ private: uint32_t mLargeCacheWidth; uint32_t mLargeCacheHeight; - Vector<CacheTexture*> mACacheTextures; - Vector<CacheTexture*> mRGBACacheTextures; + std::vector<CacheTexture*> mACacheTextures; + std::vector<CacheTexture*> mRGBACacheTextures; Font* mCurrentFont; LruCache<Font::FontDescription, Font*> mActiveFonts; diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp new file mode 100644 index 000000000000..502f027029a7 --- /dev/null +++ b/libs/hwui/FrameBuilder.cpp @@ -0,0 +1,954 @@ +/* + * Copyright (C) 2016 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 "FrameBuilder.h" + +#include "LayerUpdateQueue.h" +#include "RenderNode.h" +#include "VectorDrawable.h" +#include "renderstate/OffscreenBufferPool.h" +#include "hwui/Canvas.h" +#include "utils/FatVector.h" +#include "utils/PaintUtils.h" +#include "utils/TraceUtils.h" + +#include <SkPathOps.h> +#include <utils/TypeHelpers.h> + +namespace android { +namespace uirenderer { + +FrameBuilder::FrameBuilder(const SkRect& clip, + uint32_t viewportWidth, uint32_t viewportHeight, + const LightGeometry& lightGeometry, Caches& caches) + : mStdAllocator(mAllocator) + , mLayerBuilders(mStdAllocator) + , mLayerStack(mStdAllocator) + , mCanvasState(*this) + , mCaches(caches) + , mLightRadius(lightGeometry.radius) + , mDrawFbo0(true) { + + // Prepare to defer Fbo0 + auto fbo0 = mAllocator.create<LayerBuilder>(viewportWidth, viewportHeight, Rect(clip)); + mLayerBuilders.push_back(fbo0); + mLayerStack.push_back(0); + mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, + clip.fLeft, clip.fTop, clip.fRight, clip.fBottom, + lightGeometry.center); +} + +FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, + const LightGeometry& lightGeometry, Caches& caches) + : mStdAllocator(mAllocator) + , mLayerBuilders(mStdAllocator) + , mLayerStack(mStdAllocator) + , mCanvasState(*this) + , mCaches(caches) + , mLightRadius(lightGeometry.radius) + , mDrawFbo0(false) { + // TODO: remove, with each layer on its own save stack + + // Prepare to defer Fbo0 (which will be empty) + auto fbo0 = mAllocator.create<LayerBuilder>(1, 1, Rect(1, 1)); + mLayerBuilders.push_back(fbo0); + mLayerStack.push_back(0); + mCanvasState.initializeSaveStack(1, 1, + 0, 0, 1, 1, + lightGeometry.center); + + deferLayers(layers); +} + +void FrameBuilder::deferLayers(const LayerUpdateQueue& layers) { + // Render all layers to be updated, in order. Defer in reverse order, so that they'll be + // updated in the order they're passed in (mLayerBuilders are issued to Renderer in reverse) + for (int i = layers.entries().size() - 1; i >= 0; i--) { + RenderNode* layerNode = layers.entries()[i].renderNode; + // only schedule repaint if node still on layer - possible it may have been + // removed during a dropped frame, but layers may still remain scheduled so + // as not to lose info on what portion is damaged + if (CC_LIKELY(layerNode->getLayer() != nullptr)) { + const Rect& layerDamage = layers.entries()[i].damage; + layerNode->computeOrdering(); + + // map current light center into RenderNode's coordinate space + Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter(); + layerNode->getLayer()->inverseTransformInWindow.mapPoint3d(lightCenter); + + saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0, + layerDamage, lightCenter, nullptr, layerNode); + + if (layerNode->getDisplayList()) { + deferNodeOps(*layerNode); + } + restoreForLayer(); + } + } +} + +void FrameBuilder::deferRenderNode(RenderNode& renderNode) { + renderNode.computeOrdering(); + + mCanvasState.save(SaveFlags::MatrixClip); + deferNodePropsAndOps(renderNode); + mCanvasState.restore(); +} + +void FrameBuilder::deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode) { + renderNode.computeOrdering(); + + mCanvasState.save(SaveFlags::MatrixClip); + mCanvasState.translate(tx, ty); + mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, + SkRegion::kIntersect_Op); + deferNodePropsAndOps(renderNode); + mCanvasState.restore(); +} + +static Rect nodeBounds(RenderNode& node) { + auto& props = node.properties(); + return Rect(props.getLeft(), props.getTop(), + props.getRight(), props.getBottom()); +} + +void FrameBuilder::deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes, + const Rect& contentDrawBounds) { + if (nodes.size() < 1) return; + if (nodes.size() == 1) { + if (!nodes[0]->nothingToDraw()) { + deferRenderNode(*nodes[0]); + } + return; + } + // It there are multiple render nodes, they are laid out as follows: + // #0 - backdrop (content + caption) + // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop) + // #2 - additional overlay nodes + // Usually the backdrop cannot be seen since it will be entirely covered by the content. While + // resizing however it might become partially visible. The following render loop will crop the + // backdrop against the content and draw the remaining part of it. It will then draw the content + // cropped to the backdrop (since that indicates a shrinking of the window). + // + // Additional nodes will be drawn on top with no particular clipping semantics. + + // Usually the contents bounds should be mContentDrawBounds - however - we will + // move it towards the fixed edge to give it a more stable appearance (for the moment). + // If there is no content bounds we ignore the layering as stated above and start with 2. + + // Backdrop bounds in render target space + const Rect backdrop = nodeBounds(*nodes[0]); + + // Bounds that content will fill in render target space (note content node bounds may be bigger) + Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight()); + content.translate(backdrop.left, backdrop.top); + if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) { + // Content doesn't entirely overlap backdrop, so fill around content (right/bottom) + + // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to + // also fill left/top. Currently, both 2up and freeform position content at the top/left of + // the backdrop, so this isn't necessary. + if (content.right < backdrop.right) { + // draw backdrop to right side of content + deferRenderNode(0, 0, Rect(content.right, backdrop.top, + backdrop.right, backdrop.bottom), *nodes[0]); + } + if (content.bottom < backdrop.bottom) { + // draw backdrop to bottom of content + // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill + deferRenderNode(0, 0, Rect(content.left, content.bottom, + content.right, backdrop.bottom), *nodes[0]); + } + } + + if (!backdrop.isEmpty()) { + // content node translation to catch up with backdrop + float dx = contentDrawBounds.left - backdrop.left; + float dy = contentDrawBounds.top - backdrop.top; + + Rect contentLocalClip = backdrop; + contentLocalClip.translate(dx, dy); + deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]); + } else { + deferRenderNode(*nodes[1]); + } + + // remaining overlay nodes, simply defer + for (size_t index = 2; index < nodes.size(); index++) { + if (!nodes[index]->nothingToDraw()) { + deferRenderNode(*nodes[index]); + } + } +} + +void FrameBuilder::onViewportInitialized() {} + +void FrameBuilder::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + +void FrameBuilder::deferNodePropsAndOps(RenderNode& node) { + const RenderProperties& properties = node.properties(); + const Outline& outline = properties.getOutline(); + if (properties.getAlpha() <= 0 + || (outline.getShouldClip() && outline.isEmpty()) + || properties.getScaleX() == 0 + || properties.getScaleY() == 0) { + return; // rejected + } + + if (properties.getLeft() != 0 || properties.getTop() != 0) { + mCanvasState.translate(properties.getLeft(), properties.getTop()); + } + if (properties.getStaticMatrix()) { + mCanvasState.concatMatrix(*properties.getStaticMatrix()); + } else if (properties.getAnimationMatrix()) { + mCanvasState.concatMatrix(*properties.getAnimationMatrix()); + } + if (properties.hasTransformMatrix()) { + if (properties.isTransformTranslateOnly()) { + mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY()); + } else { + mCanvasState.concatMatrix(*properties.getTransformMatrix()); + } + } + + const int width = properties.getWidth(); + const int height = properties.getHeight(); + + Rect saveLayerBounds; // will be set to non-empty if saveLayer needed + const bool isLayer = properties.effectiveLayerType() != LayerType::None; + int clipFlags = properties.getClippingFlags(); + if (properties.getAlpha() < 1) { + if (isLayer) { + clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer + } + if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) { + // simply scale rendering content's alpha + mCanvasState.scaleAlpha(properties.getAlpha()); + } else { + // schedule saveLayer by initializing saveLayerBounds + saveLayerBounds.set(0, 0, width, height); + if (clipFlags) { + properties.getClippingRectForFlags(clipFlags, &saveLayerBounds); + clipFlags = 0; // all clipping done by savelayer + } + } + + if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) { + // pretend alpha always causes savelayer to warn about + // performance problem affecting old versions + ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height); + } + } + if (clipFlags) { + Rect clipRect; + properties.getClippingRectForFlags(clipFlags, &clipRect); + mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, + SkRegion::kIntersect_Op); + } + + if (properties.getRevealClip().willClip()) { + Rect bounds; + properties.getRevealClip().getBounds(&bounds); + mCanvasState.setClippingRoundRect(mAllocator, + bounds, properties.getRevealClip().getRadius()); + } else if (properties.getOutline().willClip()) { + mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline())); + } + + bool quickRejected = mCanvasState.currentSnapshot()->getRenderTargetClip().isEmpty() + || (properties.getClipToBounds() + && mCanvasState.quickRejectConservative(0, 0, width, height)); + if (!quickRejected) { + // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer) + if (node.getLayer()) { + // HW layer + LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(node); + BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); + if (bakedOpState) { + // Node's layer already deferred, schedule it to render into parent layer + currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap); + } + } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) { + // draw DisplayList contents within temporary, since persisted layer could not be used. + // (temp layers are clipped to viewport, since they don't persist offscreen content) + SkPaint saveLayerPaint; + saveLayerPaint.setAlpha(properties.getAlpha()); + deferBeginLayerOp(*mAllocator.create_trivial<BeginLayerOp>( + saveLayerBounds, + Matrix4::identity(), + nullptr, // no record-time clip - need only respect defer-time one + &saveLayerPaint)); + deferNodeOps(node); + deferEndLayerOp(*mAllocator.create_trivial<EndLayerOp>()); + } else { + deferNodeOps(node); + } + } +} + +typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair; + +template <typename V> +static void buildZSortedChildList(V* zTranslatedNodes, + const DisplayList& displayList, const DisplayList::Chunk& chunk) { + if (chunk.beginChildIndex == chunk.endChildIndex) return; + + for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) { + RenderNodeOp* childOp = displayList.getChildren()[i]; + RenderNode* child = childOp->renderNode; + float childZ = child->properties().getZ(); + + if (!MathUtils::isZero(childZ) && chunk.reorderChildren) { + zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp)); + childOp->skipInOrderDraw = true; + } else if (!child->properties().getProjectBackwards()) { + // regular, in order drawing DisplayList + childOp->skipInOrderDraw = false; + } + } + + // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order) + std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end()); +} + +template <typename V> +static size_t findNonNegativeIndex(const V& zTranslatedNodes) { + for (size_t i = 0; i < zTranslatedNodes.size(); i++) { + if (zTranslatedNodes[i].key >= 0.0f) return i; + } + return zTranslatedNodes.size(); +} + +template <typename V> +void FrameBuilder::defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode, + const V& zTranslatedNodes) { + const int size = zTranslatedNodes.size(); + if (size == 0 + || (mode == ChildrenSelectMode::Negative&& zTranslatedNodes[0].key > 0.0f) + || (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) { + // no 3d children to draw + return; + } + + /** + * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters + * with very similar Z heights to draw together. + * + * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are + * underneath both, and neither's shadow is drawn on top of the other. + */ + const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes); + size_t drawIndex, shadowIndex, endIndex; + if (mode == ChildrenSelectMode::Negative) { + drawIndex = 0; + endIndex = nonNegativeIndex; + shadowIndex = endIndex; // draw no shadows + } else { + drawIndex = nonNegativeIndex; + endIndex = size; + shadowIndex = drawIndex; // potentially draw shadow for each pos Z child + } + + float lastCasterZ = 0.0f; + while (shadowIndex < endIndex || drawIndex < endIndex) { + if (shadowIndex < endIndex) { + const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value; + const float casterZ = zTranslatedNodes[shadowIndex].key; + // attempt to render the shadow if the caster about to be drawn is its caster, + // OR if its caster's Z value is similar to the previous potential caster + if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) { + deferShadow(reorderClip, *casterNodeOp); + + lastCasterZ = casterZ; // must do this even if current caster not casting a shadow + shadowIndex++; + continue; + } + } + + const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value; + deferRenderNodeOpImpl(*childOp); + drawIndex++; + } +} + +void FrameBuilder::deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterNodeOp) { + auto& node = *casterNodeOp.renderNode; + auto& properties = node.properties(); + + if (properties.getAlpha() <= 0.0f + || properties.getOutline().getAlpha() <= 0.0f + || !properties.getOutline().getPath() + || properties.getScaleX() == 0 + || properties.getScaleY() == 0) { + // no shadow to draw + return; + } + + const SkPath* casterOutlinePath = properties.getOutline().getPath(); + const SkPath* revealClipPath = properties.getRevealClip().getPath(); + if (revealClipPath && revealClipPath->isEmpty()) return; + + float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha(); + + // holds temporary SkPath to store the result of intersections + SkPath* frameAllocatedPath = nullptr; + const SkPath* casterPath = casterOutlinePath; + + // intersect the shadow-casting path with the reveal, if present + if (revealClipPath) { + frameAllocatedPath = createFrameAllocatedPath(); + + Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath); + casterPath = frameAllocatedPath; + } + + // intersect the shadow-casting path with the clipBounds, if present + if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) { + if (!frameAllocatedPath) { + frameAllocatedPath = createFrameAllocatedPath(); + } + Rect clipBounds; + properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds); + SkPath clipBoundsPath; + clipBoundsPath.addRect(clipBounds.left, clipBounds.top, + clipBounds.right, clipBounds.bottom); + + Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath); + casterPath = frameAllocatedPath; + } + + // apply reorder clip to shadow, so it respects clip at beginning of reorderable chunk + int restoreTo = mCanvasState.save(SaveFlags::MatrixClip); + mCanvasState.writableSnapshot()->applyClip(reorderClip, + *mCanvasState.currentSnapshot()->transform); + if (CC_LIKELY(!mCanvasState.getRenderTargetClipBounds().isEmpty())) { + Matrix4 shadowMatrixXY(casterNodeOp.localMatrix); + Matrix4 shadowMatrixZ(casterNodeOp.localMatrix); + node.applyViewPropertyTransforms(shadowMatrixXY, false); + node.applyViewPropertyTransforms(shadowMatrixZ, true); + + sp<TessellationCache::ShadowTask> task = mCaches.tessellationCache.getShadowTask( + mCanvasState.currentTransform(), + mCanvasState.getLocalClipBounds(), + casterAlpha >= 1.0f, + casterPath, + &shadowMatrixXY, &shadowMatrixZ, + mCanvasState.currentSnapshot()->getRelativeLightCenter(), + mLightRadius); + ShadowOp* shadowOp = mAllocator.create<ShadowOp>(task, casterAlpha); + BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct( + mAllocator, *mCanvasState.writableSnapshot(), shadowOp); + if (CC_LIKELY(bakedOpState)) { + currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow); + } + } + mCanvasState.restoreToCount(restoreTo); +} + +void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) { + int count = mCanvasState.save(SaveFlags::MatrixClip); + const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath(); + + SkPath transformedMaskPath; // on stack, since BakedOpState makes a deep copy + if (projectionReceiverOutline) { + // transform the mask for this projector into render target space + // TODO: consider combining both transforms by stashing transform instead of applying + SkMatrix skCurrentTransform; + mCanvasState.currentTransform()->copyTo(skCurrentTransform); + projectionReceiverOutline->transform( + skCurrentTransform, + &transformedMaskPath); + mCanvasState.setProjectionPathMask(mAllocator, &transformedMaskPath); + } + + for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) { + RenderNodeOp* childOp = renderNode.mProjectedNodes[i]; + RenderNode& childNode = *childOp->renderNode; + + // Draw child if it has content, but ignore state in childOp - matrix already applied to + // transformFromCompositingAncestor, and record-time clip is ignored when projecting + if (!childNode.nothingToDraw()) { + int restoreTo = mCanvasState.save(SaveFlags::MatrixClip); + + // Apply transform between ancestor and projected descendant + mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor); + + deferNodePropsAndOps(childNode); + + mCanvasState.restoreToCount(restoreTo); + } + } + mCanvasState.restoreToCount(count); +} + +/** + * Used to define a list of lambdas referencing private FrameBuilder::onXX::defer() methods. + * + * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. + * E.g. a BitmapOp op then would be dispatched to FrameBuilder::onBitmapOp(const BitmapOp&) + */ +#define OP_RECEIVER(Type) \ + [](FrameBuilder& frameBuilder, const RecordedOp& op) { frameBuilder.defer##Type(static_cast<const Type&>(op)); }, +void FrameBuilder::deferNodeOps(const RenderNode& renderNode) { + typedef void (*OpDispatcher) (FrameBuilder& frameBuilder, const RecordedOp& op); + static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER); + + // can't be null, since DL=null node rejection happens before deferNodePropsAndOps + const DisplayList& displayList = *(renderNode.getDisplayList()); + for (auto& chunk : displayList.getChunks()) { + FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes; + buildZSortedChildList(&zTranslatedNodes, displayList, chunk); + + defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Negative, zTranslatedNodes); + for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { + const RecordedOp* op = displayList.getOps()[opIndex]; + receivers[op->opId](*this, *op); + + if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty() + && displayList.projectionReceiveIndex >= 0 + && static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) { + deferProjectedChildren(renderNode); + } + } + defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes); + } +} + +void FrameBuilder::deferRenderNodeOpImpl(const RenderNodeOp& op) { + if (op.renderNode->nothingToDraw()) return; + int count = mCanvasState.save(SaveFlags::MatrixClip); + + // apply state from RecordedOp (clip first, since op's clip is transformed by current matrix) + mCanvasState.writableSnapshot()->applyClip(op.localClip, + *mCanvasState.currentSnapshot()->transform); + mCanvasState.concatMatrix(op.localMatrix); + + // then apply state from node properties, and defer ops + deferNodePropsAndOps(*op.renderNode); + + mCanvasState.restoreToCount(count); +} + +void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) { + if (!op.skipInOrderDraw) { + deferRenderNodeOpImpl(op); + } +} + +/** + * Defers an unmergeable, strokeable op, accounting correctly + * for paint's style on the bounds being computed. + */ +BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId, + BakedOpState::StrokeBehavior strokeBehavior) { + // Note: here we account for stroke when baking the op + BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( + mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior); + if (!bakedState) return nullptr; // quick rejected + + if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) { + bakedState->setupOpacity(op.paint); + } + + currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); + return bakedState; +} + +/** + * Returns batch id for tessellatable shapes, based on paint. Checks to see if path effect/AA will + * be used, since they trigger significantly different rendering paths. + * + * Note: not used for lines/points, since they don't currently support path effects. + */ +static batchid_t tessBatchId(const RecordedOp& op) { + const SkPaint& paint = *(op.paint); + return paint.getPathEffect() + ? OpBatchType::AlphaMaskTexture + : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices); +} + +void FrameBuilder::deferArcOp(const ArcOp& op) { + deferStrokeableOp(op, tessBatchId(op)); +} + +static bool hasMergeableClip(const BakedOpState& state) { + return state.computedState.clipState + || state.computedState.clipState->mode == ClipMode::Rectangle; +} + +void FrameBuilder::deferBitmapOp(const BitmapOp& op) { + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + + if (op.bitmap->isOpaque()) { + bakedState->setupOpacity(op.paint); + } + + // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation + // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in + // MergingDrawBatch::canMergeWith() + if (bakedState->computedState.transform.isSimple() + && bakedState->computedState.transform.positiveScale() + && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode + && op.bitmap->colorType() != kAlpha_8_SkColorType + && hasMergeableClip(*bakedState)) { + mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID()); + // TODO: AssetAtlas in mergeId + currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId); + } else { + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); + } +} + +void FrameBuilder::deferBitmapMeshOp(const BitmapMeshOp& op) { + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); +} + +void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) { + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); +} + +void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) { + const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty(); + SkPaint* paint = op.vectorDrawable->getPaint(); + const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>(op.unmappedBounds, + op.localMatrix, + op.localClip, + paint, + &bitmap, + Rect(bitmap.width(), bitmap.height())); + deferBitmapRectOp(*resolvedOp); +} + +void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) { + // allocate a temporary oval op (with mAllocator, so it persists until render), so the + // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. + float x = *(op.x); + float y = *(op.y); + float radius = *(op.radius); + Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius); + const OvalOp* resolvedOp = mAllocator.create_trivial<OvalOp>( + unmappedBounds, + op.localMatrix, + op.localClip, + op.paint); + deferOvalOp(*resolvedOp); +} + +void FrameBuilder::deferColorOp(const ColorOp& op) { + BakedOpState* bakedState = tryBakeUnboundedOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices); +} + +void FrameBuilder::deferFunctorOp(const FunctorOp& op) { + BakedOpState* bakedState = tryBakeUnboundedOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor); +} + +void FrameBuilder::deferLinesOp(const LinesOp& op) { + batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; + deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); +} + +void FrameBuilder::deferOvalOp(const OvalOp& op) { + deferStrokeableOp(op, tessBatchId(op)); +} + +void FrameBuilder::deferPatchOp(const PatchOp& op) { + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + + if (bakedState->computedState.transform.isPureTranslate() + && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode + && hasMergeableClip(*bakedState)) { + mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID()); + // TODO: AssetAtlas in mergeId + + // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together + currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId); + } else { + // Use Bitmap batchId since Bitmap+Patch use same shader + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); + } +} + +void FrameBuilder::deferPathOp(const PathOp& op) { + auto state = deferStrokeableOp(op, OpBatchType::AlphaMaskTexture); + if (CC_LIKELY(state)) { + mCaches.pathCache.precache(op.path, op.paint); + } +} + +void FrameBuilder::deferPointsOp(const PointsOp& op) { + batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; + deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); +} + +void FrameBuilder::deferRectOp(const RectOp& op) { + deferStrokeableOp(op, tessBatchId(op)); +} + +void FrameBuilder::deferRoundRectOp(const RoundRectOp& op) { + auto state = deferStrokeableOp(op, tessBatchId(op)); + if (CC_LIKELY(state && !op.paint->getPathEffect())) { + // TODO: consider storing tessellation task in BakedOpState + mCaches.tessellationCache.precacheRoundRect(state->computedState.transform, *(op.paint), + op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry); + } +} + +void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) { + // allocate a temporary round rect op (with mAllocator, so it persists until render), so the + // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. + const RoundRectOp* resolvedOp = mAllocator.create_trivial<RoundRectOp>( + Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)), + op.localMatrix, + op.localClip, + op.paint, *op.rx, *op.ry); + deferRoundRectOp(*resolvedOp); +} + +void FrameBuilder::deferSimpleRectsOp(const SimpleRectsOp& op) { + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices); +} + +static batchid_t textBatchId(const SkPaint& paint) { + // TODO: better handling of shader (since we won't care about color then) + return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText; +} + +void FrameBuilder::deferTextOp(const TextOp& op) { + BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( + mAllocator, *mCanvasState.writableSnapshot(), op, + BakedOpState::StrokeBehavior::StyleDefined); + if (!bakedState) return; // quick rejected + + batchid_t batchId = textBatchId(*(op.paint)); + if (bakedState->computedState.transform.isPureTranslate() + && PaintUtils::getXfermodeDirect(op.paint) == SkXfermode::kSrcOver_Mode + && hasMergeableClip(*bakedState)) { + mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor()); + currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId); + } else { + currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); + } + + FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(); + auto& totalTransform = bakedState->computedState.transform; + if (totalTransform.isPureTranslate() || totalTransform.isPerspective()) { + fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::I()); + } else { + // Partial transform case, see BakedOpDispatcher::renderTextOp + float sx, sy; + totalTransform.decomposeScale(sx, sy); + fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::MakeScale( + roundf(std::max(1.0f, sx)), + roundf(std::max(1.0f, sy)))); + } +} + +void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) { + BakedOpState* bakedState = tryBakeUnboundedOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint))); + + mCaches.fontRenderer.getFontRenderer().precache( + op.paint, op.glyphs, op.glyphCount, SkMatrix::I()); +} + +void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) { + if (CC_UNLIKELY(!op.layer->isRenderable())) return; + + const TextureLayerOp* textureLayerOp = &op; + // Now safe to access transform (which was potentially unready at record time) + if (!op.layer->getTransform().isIdentity()) { + // non-identity transform present, so 'inject it' into op by copying + replacing matrix + Matrix4 combinedMatrix(op.localMatrix); + combinedMatrix.multiply(op.layer->getTransform()); + textureLayerOp = mAllocator.create<TextureLayerOp>(op, combinedMatrix); + } + BakedOpState* bakedState = tryBakeOpState(*textureLayerOp); + + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer); +} + +void FrameBuilder::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, + float contentTranslateX, float contentTranslateY, + const Rect& repaintRect, + const Vector3& lightCenter, + const BeginLayerOp* beginLayerOp, RenderNode* renderNode) { + mCanvasState.save(SaveFlags::MatrixClip); + mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight); + mCanvasState.writableSnapshot()->roundRectClipState = nullptr; + mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter); + mCanvasState.writableSnapshot()->transform->loadTranslate( + contentTranslateX, contentTranslateY, 0); + mCanvasState.writableSnapshot()->setClip( + repaintRect.left, repaintRect.top, repaintRect.right, repaintRect.bottom); + + // create a new layer repaint, and push its index on the stack + mLayerStack.push_back(mLayerBuilders.size()); + auto newFbo = mAllocator.create<LayerBuilder>(layerWidth, layerHeight, + repaintRect, beginLayerOp, renderNode); + mLayerBuilders.push_back(newFbo); +} + +void FrameBuilder::restoreForLayer() { + // restore canvas, and pop finished layer off of the stack + mCanvasState.restore(); + mLayerStack.pop_back(); +} + +// TODO: defer time rejection (when bounds become empty) + tests +// Option - just skip layers with no bounds at playback + defer? +void FrameBuilder::deferBeginLayerOp(const BeginLayerOp& op) { + uint32_t layerWidth = (uint32_t) op.unmappedBounds.getWidth(); + uint32_t layerHeight = (uint32_t) op.unmappedBounds.getHeight(); + + auto previous = mCanvasState.currentSnapshot(); + Vector3 lightCenter = previous->getRelativeLightCenter(); + + // Combine all transforms used to present saveLayer content: + // parent content transform * canvas transform * bounds offset + Matrix4 contentTransform(*(previous->transform)); + contentTransform.multiply(op.localMatrix); + contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top); + + Matrix4 inverseContentTransform; + inverseContentTransform.loadInverse(contentTransform); + + // map the light center into layer-relative space + inverseContentTransform.mapPoint3d(lightCenter); + + // Clip bounds of temporary layer to parent's clip rect, so: + Rect saveLayerBounds(layerWidth, layerHeight); + // 1) transform Rect(width, height) into parent's space + // note: left/top offsets put in contentTransform above + contentTransform.mapRect(saveLayerBounds); + // 2) intersect with parent's clip + saveLayerBounds.doIntersect(previous->getRenderTargetClip()); + // 3) and transform back + inverseContentTransform.mapRect(saveLayerBounds); + saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight)); + saveLayerBounds.roundOut(); + + // if bounds are reduced, will clip the layer's area by reducing required bounds... + layerWidth = saveLayerBounds.getWidth(); + layerHeight = saveLayerBounds.getHeight(); + // ...and shifting drawing content to account for left/top side clipping + float contentTranslateX = -saveLayerBounds.left; + float contentTranslateY = -saveLayerBounds.top; + + saveForLayer(layerWidth, layerHeight, + contentTranslateX, contentTranslateY, + Rect(layerWidth, layerHeight), + lightCenter, + &op, nullptr); +} + +void FrameBuilder::deferEndLayerOp(const EndLayerOp& /* ignored */) { + const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp; + int finishedLayerIndex = mLayerStack.back(); + + restoreForLayer(); + + // record the draw operation into the previous layer's list of draw commands + // uses state from the associated beginLayerOp, since it has all the state needed for drawing + LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>( + beginLayerOp.unmappedBounds, + beginLayerOp.localMatrix, + beginLayerOp.localClip, + beginLayerOp.paint, + &(mLayerBuilders[finishedLayerIndex]->offscreenBuffer)); + BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); + + if (bakedOpState) { + // Layer will be drawn into parent layer (which is now current, since we popped mLayerStack) + currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap); + } else { + // Layer won't be drawn - delete its drawing batches to prevent it from doing any work + // TODO: need to prevent any render work from being done + // - create layerop earlier for reject purposes? + mLayerBuilders[finishedLayerIndex]->clear(); + return; + } +} + +void FrameBuilder::deferBeginUnclippedLayerOp(const BeginUnclippedLayerOp& op) { + Matrix4 boundsTransform(*(mCanvasState.currentSnapshot()->transform)); + boundsTransform.multiply(op.localMatrix); + + Rect dstRect(op.unmappedBounds); + boundsTransform.mapRect(dstRect); + dstRect.doIntersect(mCanvasState.currentSnapshot()->getRenderTargetClip()); + + if (dstRect.isEmpty()) { + // Unclipped layer rejected - push a null op, so next EndUnclippedLayerOp is ignored + currentLayer().activeUnclippedSaveLayers.push_back(nullptr); + } else { + // Allocate a holding position for the layer object (copyTo will produce, copyFrom will consume) + OffscreenBuffer** layerHandle = mAllocator.create<OffscreenBuffer*>(nullptr); + + /** + * First, defer an operation to copy out the content from the rendertarget into a layer. + */ + auto copyToOp = mAllocator.create_trivial<CopyToLayerOp>(op, layerHandle); + BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator, + &(currentLayer().repaintClip), dstRect, *copyToOp); + currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer); + + /** + * Defer a clear rect, so that clears from multiple unclipped layers can be drawn + * both 1) simultaneously, and 2) as long after the copyToLayer executes as possible + */ + currentLayer().deferLayerClear(dstRect); + + /** + * And stash an operation to copy that layer back under the rendertarget until + * a balanced EndUnclippedLayerOp is seen + */ + auto copyFromOp = mAllocator.create_trivial<CopyFromLayerOp>(op, layerHandle); + bakedState = BakedOpState::directConstruct(mAllocator, + &(currentLayer().repaintClip), dstRect, *copyFromOp); + currentLayer().activeUnclippedSaveLayers.push_back(bakedState); + } +} + +void FrameBuilder::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) { + LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!"); + + BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back(); + currentLayer().activeUnclippedSaveLayers.pop_back(); + if (copyFromLayerOp) { + currentLayer().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer); + } +} + +void FrameBuilder::finishDefer() { + mCaches.fontRenderer.endPrecaching(); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h new file mode 100644 index 000000000000..b9154435c1e5 --- /dev/null +++ b/libs/hwui/FrameBuilder.h @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2016 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 "BakedOpState.h" +#include "CanvasState.h" +#include "DisplayList.h" +#include "LayerBuilder.h" +#include "RecordedOp.h" +#include "utils/GLUtils.h" + +#include <vector> +#include <unordered_map> + +struct SkRect; + +namespace android { +namespace uirenderer { + +class BakedOpState; +class LayerUpdateQueue; +class OffscreenBuffer; +class Rect; + +/** + * Processes, optimizes, and stores rendering commands from RenderNodes and + * LayerUpdateQueue, building content needed to render a frame. + * + * Resolves final drawing state for each operation (including clip, alpha and matrix), and then + * reorder and merge each op as it is resolved for drawing efficiency. Each layer of content (either + * from the LayerUpdateQueue, or temporary layers created by saveLayer operations in the + * draw stream) will create different reorder contexts, each in its own LayerBuilder. + * + * Then the prepared or 'baked' drawing commands can be issued by calling the templated + * replayBakedOps() function, which will dispatch them (including any created merged op collections) + * to a Dispatcher and Renderer. See BakedOpDispatcher for how these baked drawing operations are + * resolved into Glops and rendered via BakedOpRenderer. + * + * This class is also the authoritative source for traversing RenderNodes, both for standard op + * traversal within a DisplayList, and for out of order RenderNode traversal for Z and projection. + */ +class FrameBuilder : public CanvasStateClient { +public: + struct LightGeometry { + Vector3 center; + float radius; + }; + + FrameBuilder(const SkRect& clip, + uint32_t viewportWidth, uint32_t viewportHeight, + const LightGeometry& lightGeometry, Caches& caches); + + FrameBuilder(const LayerUpdateQueue& layerUpdateQueue, + const LightGeometry& lightGeometry, Caches& caches); + + void deferLayers(const LayerUpdateQueue& layers); + + void deferRenderNode(RenderNode& renderNode); + + void deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode); + + void deferRenderNodeScene(const std::vector< sp<RenderNode> >& nodes, + const Rect& contentDrawBounds); + + virtual ~FrameBuilder() {} + + /** + * replayBakedOps() is templated based on what class will receive ops being replayed. + * + * It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use + * state->op->opId to lookup a receiver that will be called when the op is replayed. + */ + template <typename StaticDispatcher, typename Renderer> + void replayBakedOps(Renderer& renderer) { + std::vector<OffscreenBuffer*> temporaryLayers; + finishDefer(); + /** + * Defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to + * dispatch the op via a method on a static dispatcher when the op is replayed. + * + * For example a BitmapOp would resolve, via the lambda lookup, to calling: + * + * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state); + */ + #define X(Type) \ + [](void* renderer, const BakedOpState& state) { \ + StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), \ + static_cast<const Type&>(*(state.op)), state); \ + }, + static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X); + #undef X + + /** + * Defines a LUT of lambdas which allow merged arrays of BakedOpState* to be passed to a + * static dispatcher when the group of merged ops is replayed. + */ + #define X(Type) \ + [](void* renderer, const MergedBakedOpList& opList) { \ + StaticDispatcher::onMerged##Type##s(*(static_cast<Renderer*>(renderer)), opList); \ + }, + static MergedOpReceiver mergedReceivers[] = BUILD_MERGEABLE_OP_LUT(X); + #undef X + + // Relay through layers in reverse order, since layers + // later in the list will be drawn by earlier ones + for (int i = mLayerBuilders.size() - 1; i >= 1; i--) { + GL_CHECKPOINT(MODERATE); + LayerBuilder& layer = *(mLayerBuilders[i]); + if (layer.renderNode) { + // cached HW layer - can't skip layer if empty + renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect); + GL_CHECKPOINT(MODERATE); + layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); + GL_CHECKPOINT(MODERATE); + renderer.endLayer(); + } else if (!layer.empty()) { + // save layer - skip entire layer if empty (in which case, LayerOp has null layer). + layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height); + temporaryLayers.push_back(layer.offscreenBuffer); + GL_CHECKPOINT(MODERATE); + layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); + GL_CHECKPOINT(MODERATE); + renderer.endLayer(); + } + } + + GL_CHECKPOINT(MODERATE); + if (CC_LIKELY(mDrawFbo0)) { + const LayerBuilder& fbo0 = *(mLayerBuilders[0]); + renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect); + GL_CHECKPOINT(MODERATE); + fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); + GL_CHECKPOINT(MODERATE); + renderer.endFrame(fbo0.repaintRect); + } + + for (auto& temporaryLayer : temporaryLayers) { + renderer.recycleTemporaryLayer(temporaryLayer); + } + } + + void dump() const { + for (auto&& layer : mLayerBuilders) { + layer->dump(); + } + } + + /////////////////////////////////////////////////////////////////// + /// CanvasStateClient interface + /////////////////////////////////////////////////////////////////// + virtual void onViewportInitialized() override; + virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override; + virtual GLuint getTargetFbo() const override { return 0; } + +private: + void finishDefer(); + enum class ChildrenSelectMode { + Negative, + Positive + }; + void saveForLayer(uint32_t layerWidth, uint32_t layerHeight, + float contentTranslateX, float contentTranslateY, + const Rect& repaintRect, + const Vector3& lightCenter, + const BeginLayerOp* beginLayerOp, RenderNode* renderNode); + void restoreForLayer(); + + LayerBuilder& currentLayer() { return *(mLayerBuilders[mLayerStack.back()]); } + + BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) { + return BakedOpState::tryConstruct(mAllocator, *mCanvasState.writableSnapshot(), recordedOp); + } + BakedOpState* tryBakeUnboundedOpState(const RecordedOp& recordedOp) { + return BakedOpState::tryConstructUnbounded(mAllocator, *mCanvasState.writableSnapshot(), recordedOp); + } + + + // should always be surrounded by a save/restore pair, and not called if DisplayList is null + void deferNodePropsAndOps(RenderNode& node); + + template <typename V> + void defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode, + const V& zTranslatedNodes); + + void deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterOp); + + void deferProjectedChildren(const RenderNode& renderNode); + + void deferNodeOps(const RenderNode& renderNode); + + void deferRenderNodeOpImpl(const RenderNodeOp& op); + + void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers); + + SkPath* createFrameAllocatedPath() { + return mAllocator.create<SkPath>(); + } + + BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId, + BakedOpState::StrokeBehavior strokeBehavior = BakedOpState::StrokeBehavior::StyleDefined); + + /** + * Declares all FrameBuilder::deferXXXXOp() methods for every RecordedOp type. + * + * These private methods are called from within deferImpl to defer each individual op + * type differently. + */ +#define X(Type) void defer##Type(const Type& op); + MAP_DEFERRABLE_OPS(X) +#undef X + + // contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches + LinearAllocator mAllocator; + LinearStdAllocator<void*> mStdAllocator; + + // List of every deferred layer's render state. Replayed in reverse order to render a frame. + LsaVector<LayerBuilder*> mLayerBuilders; + + /* + * Stack of indices within mLayerBuilders representing currently active layers. If drawing + * layerA within a layerB, will contain, in order: + * - 0 (representing FBO 0, always present) + * - layerB's index + * - layerA's index + * + * Note that this doesn't vector doesn't always map onto all values of mLayerBuilders. When a + * layer is finished deferring, it will still be represented in mLayerBuilders, but it's index + * won't be in mLayerStack. This is because it can be replayed, but can't have any more drawing + * ops added to it. + */ + LsaVector<size_t> mLayerStack; + + CanvasState mCanvasState; + + Caches& mCaches; + + float mLightRadius; + + const bool mDrawFbo0; +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h index f8013ab6b6c4..0baca391be79 100644 --- a/libs/hwui/FrameInfo.h +++ b/libs/hwui/FrameInfo.h @@ -118,6 +118,10 @@ public: set(FrameInfoIndex::Flags) |= static_cast<uint64_t>(frameInfoFlag); } + const int64_t* data() const { + return mFrameInfo; + } + inline int64_t operator[](FrameInfoIndex index) const { return get(index); } diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp index b416615c20e1..adadd32a2fc0 100644 --- a/libs/hwui/FrameInfoVisualizer.cpp +++ b/libs/hwui/FrameInfoVisualizer.cpp @@ -15,7 +15,12 @@ */ #include "FrameInfoVisualizer.h" +#if HWUI_NEW_OPS +#include "BakedOpRenderer.h" +#else #include "OpenGLRenderer.h" +#endif +#include "utils/Color.h" #include <cutils/compiler.h> #include <array> @@ -27,19 +32,19 @@ #define PROFILE_DRAW_THRESHOLD_STROKE_WIDTH 2 #define PROFILE_DRAW_DP_PER_MS 7 +namespace android { +namespace uirenderer { + // Must be NUM_ELEMENTS in size -static const SkColor THRESHOLD_COLOR = 0xff5faa4d; -static const SkColor BAR_FAST_ALPHA = 0x8F000000; -static const SkColor BAR_JANKY_ALPHA = 0xDF000000; +static const SkColor THRESHOLD_COLOR = Color::Green_500; +static const SkColor BAR_FAST_MASK = 0x8FFFFFFF; +static const SkColor BAR_JANKY_MASK = 0xDFFFFFFF; // We could get this from TimeLord and use the actual frame interval, but // this is good enough #define FRAME_THRESHOLD 16 #define FRAME_THRESHOLD_NS 16000000 -namespace android { -namespace uirenderer { - struct BarSegment { FrameInfoIndex start; FrameInfoIndex end; @@ -47,13 +52,13 @@ struct BarSegment { }; static const std::array<BarSegment,7> Bar {{ - { FrameInfoIndex::IntendedVsync, FrameInfoIndex::HandleInputStart, 0x00796B }, - { FrameInfoIndex::HandleInputStart, FrameInfoIndex::PerformTraversalsStart, 0x388E3C }, - { FrameInfoIndex::PerformTraversalsStart, FrameInfoIndex::DrawStart, 0x689F38}, - { FrameInfoIndex::DrawStart, FrameInfoIndex::SyncStart, 0x2196F3}, - { FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart, 0x4FC3F7}, - { FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers, 0xF44336}, - { FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted, 0xFF9800}, + { FrameInfoIndex::IntendedVsync, FrameInfoIndex::HandleInputStart, Color::Teal_700 }, + { FrameInfoIndex::HandleInputStart, FrameInfoIndex::PerformTraversalsStart, Color::Green_700 }, + { FrameInfoIndex::PerformTraversalsStart, FrameInfoIndex::DrawStart, Color::LightGreen_700 }, + { FrameInfoIndex::DrawStart, FrameInfoIndex::SyncStart, Color::Blue_500 }, + { FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart, Color::LightBlue_300 }, + { FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers, Color::Red_500}, + { FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted, Color::Orange_500}, }}; static int dpToPx(int dp, float density) { @@ -87,7 +92,7 @@ void FrameInfoVisualizer::unionDirty(SkRect* dirty) { } } -void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) { +void FrameInfoVisualizer::draw(ContentRenderer* renderer) { RETURN_IF_DISABLED(); if (mShowDirtyRegions) { @@ -95,7 +100,7 @@ void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) { if (mFlashToggle) { SkPaint paint; paint.setColor(0x7fff0000); - canvas->drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop, + renderer->drawRect(mDirtyRegion.fLeft, mDirtyRegion.fTop, mDirtyRegion.fRight, mDirtyRegion.fBottom, &paint); } } @@ -110,9 +115,9 @@ void FrameInfoVisualizer::draw(OpenGLRenderer* canvas) { info.markSwapBuffers(); info.markFrameCompleted(); - initializeRects(canvas->getViewportHeight(), canvas->getViewportWidth()); - drawGraph(canvas); - drawThreshold(canvas); + initializeRects(renderer->getViewportHeight(), renderer->getViewportWidth()); + drawGraph(renderer); + drawThreshold(renderer); } } @@ -193,27 +198,26 @@ void FrameInfoVisualizer::nextBarSegment(FrameInfoIndex start, FrameInfoIndex en } } -void FrameInfoVisualizer::drawGraph(OpenGLRenderer* canvas) { +void FrameInfoVisualizer::drawGraph(ContentRenderer* renderer) { SkPaint paint; for (size_t i = 0; i < Bar.size(); i++) { nextBarSegment(Bar[i].start, Bar[i].end); - paint.setColor(Bar[i].color | BAR_FAST_ALPHA); - canvas->drawRects(mFastRects.get(), mNumFastRects * 4, &paint); - paint.setColor(Bar[i].color | BAR_JANKY_ALPHA); - canvas->drawRects(mJankyRects.get(), mNumJankyRects * 4, &paint); + paint.setColor(Bar[i].color & BAR_FAST_MASK); + renderer->drawRects(mFastRects.get(), mNumFastRects * 4, &paint); + paint.setColor(Bar[i].color & BAR_JANKY_MASK); + renderer->drawRects(mJankyRects.get(), mNumJankyRects * 4, &paint); } } -void FrameInfoVisualizer::drawThreshold(OpenGLRenderer* canvas) { +void FrameInfoVisualizer::drawThreshold(ContentRenderer* renderer) { SkPaint paint; paint.setColor(THRESHOLD_COLOR); - paint.setStrokeWidth(mThresholdStroke); - - float pts[4]; - pts[0] = 0.0f; - pts[1] = pts[3] = canvas->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit); - pts[2] = canvas->getViewportWidth(); - canvas->drawLines(pts, 4, &paint); + float yLocation = renderer->getViewportHeight() - (FRAME_THRESHOLD * mVerticalUnit); + renderer->drawRect(0.0f, + yLocation - mThresholdStroke/2, + renderer->getViewportWidth(), + yLocation + mThresholdStroke/2, + &paint); } bool FrameInfoVisualizer::consumeProperties() { diff --git a/libs/hwui/FrameInfoVisualizer.h b/libs/hwui/FrameInfoVisualizer.h index cf877c4d8827..83adf1985c72 100644 --- a/libs/hwui/FrameInfoVisualizer.h +++ b/libs/hwui/FrameInfoVisualizer.h @@ -28,7 +28,13 @@ namespace android { namespace uirenderer { +#if HWUI_NEW_OPS +class BakedOpRenderer; +typedef BakedOpRenderer ContentRenderer; +#else class OpenGLRenderer; +typedef OpenGLRenderer ContentRenderer; +#endif // TODO: This is a bit awkward as it needs to match the thing in CanvasContext // A better abstraction here would be nice but iterators are painful @@ -46,7 +52,7 @@ public: void setDensity(float density); void unionDirty(SkRect* dirty); - void draw(OpenGLRenderer* canvas); + void draw(ContentRenderer* renderer); void dumpData(int fd); @@ -56,8 +62,8 @@ private: void initializeRects(const int baseline, const int width); void nextBarSegment(FrameInfoIndex start, FrameInfoIndex end); - void drawGraph(OpenGLRenderer* canvas); - void drawThreshold(OpenGLRenderer* canvas); + void drawGraph(ContentRenderer* renderer); + void drawThreshold(ContentRenderer* renderer); inline float durationMS(size_t index, FrameInfoIndex start, FrameInfoIndex end) { float duration = mFrameSource[index].duration(start, end) * 0.000001f; diff --git a/libs/hwui/FrameMetricsObserver.h b/libs/hwui/FrameMetricsObserver.h new file mode 100644 index 000000000000..4f81c8681fc8 --- /dev/null +++ b/libs/hwui/FrameMetricsObserver.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 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/RefBase.h> + +namespace android { +namespace uirenderer { + +class FrameMetricsObserver : public VirtualLightRefBase { +public: + virtual void notify(const int64_t* buffer); +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/FrameMetricsReporter.h b/libs/hwui/FrameMetricsReporter.h new file mode 100644 index 000000000000..c1cd0a9224b6 --- /dev/null +++ b/libs/hwui/FrameMetricsReporter.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 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/RefBase.h> +#include <utils/Log.h> + +#include "FrameInfo.h" +#include "FrameMetricsObserver.h" + +#include <string.h> +#include <vector> + +namespace android { +namespace uirenderer { + +class FrameMetricsReporter { +public: + FrameMetricsReporter() {} + + void addObserver(FrameMetricsObserver* observer) { + mObservers.push_back(observer); + } + + bool removeObserver(FrameMetricsObserver* observer) { + for (size_t i = 0; i < mObservers.size(); i++) { + if (mObservers[i].get() == observer) { + mObservers.erase(mObservers.begin() + i); + return true; + } + } + return false; + } + + bool hasObservers() { + return mObservers.size() > 0; + } + + void reportFrameMetrics(const int64_t* stats) { + for (size_t i = 0; i < mObservers.size(); i++) { + mObservers[i]->notify(stats); + } + } + +private: + std::vector< sp<FrameMetricsObserver> > mObservers; +}; + +}; // namespace uirenderer +}; // namespace android + diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp index 070c3d70a069..96cac86386b5 100644 --- a/libs/hwui/GammaFontRenderer.cpp +++ b/libs/hwui/GammaFontRenderer.cpp @@ -21,231 +21,22 @@ namespace android { namespace uirenderer { -/////////////////////////////////////////////////////////////////////////////// -// Utils -/////////////////////////////////////////////////////////////////////////////// - -static int luminance(const SkPaint* paint) { - uint32_t c = paint->getColor(); - const int r = (c >> 16) & 0xFF; - const int g = (c >> 8) & 0xFF; - const int b = (c ) & 0xFF; - return (r * 2 + g * 5 + b) >> 3; -} - -/////////////////////////////////////////////////////////////////////////////// -// Base class GammaFontRenderer -/////////////////////////////////////////////////////////////////////////////// - -GammaFontRenderer* GammaFontRenderer::createRenderer() { - // Choose the best renderer - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_TEXT_GAMMA_METHOD, property, DEFAULT_TEXT_GAMMA_METHOD) > 0) { - if (!strcasecmp(property, "lookup")) { - return new LookupGammaFontRenderer(); - } else if (!strcasecmp(property, "shader")) { - return new ShaderGammaFontRenderer(false); - } else if (!strcasecmp(property, "shader3")) { - return new ShaderGammaFontRenderer(true); - } - } - - return new Lookup3GammaFontRenderer(); -} - GammaFontRenderer::GammaFontRenderer() { - // Get the renderer properties - char property[PROPERTY_VALUE_MAX]; - - // Get the gamma - mGamma = DEFAULT_TEXT_GAMMA; - if (property_get(PROPERTY_TEXT_GAMMA, property, nullptr) > 0) { - INIT_LOGD(" Setting text gamma to %s", property); - mGamma = atof(property); - } else { - INIT_LOGD(" Using default text gamma of %.2f", DEFAULT_TEXT_GAMMA); - } - - // Get the black gamma threshold - mBlackThreshold = DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD; - if (property_get(PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD, property, nullptr) > 0) { - INIT_LOGD(" Setting text black gamma threshold to %s", property); - mBlackThreshold = atoi(property); - } else { - INIT_LOGD(" Using default text black gamma threshold of %d", - DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD); - } - - // Get the white gamma threshold - mWhiteThreshold = DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD; - if (property_get(PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD, property, nullptr) > 0) { - INIT_LOGD(" Setting text white gamma threshold to %s", property); - mWhiteThreshold = atoi(property); - } else { - INIT_LOGD(" Using default white black gamma threshold of %d", - DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD); - } -} - -GammaFontRenderer::~GammaFontRenderer() { -} - -/////////////////////////////////////////////////////////////////////////////// -// Shader-based renderer -/////////////////////////////////////////////////////////////////////////////// - -ShaderGammaFontRenderer::ShaderGammaFontRenderer(bool multiGamma) - : GammaFontRenderer() { - INIT_LOGD("Creating shader gamma font renderer"); - mRenderer = nullptr; - mMultiGamma = multiGamma; -} - -void ShaderGammaFontRenderer::describe(ProgramDescription& description, - const SkPaint* paint) const { - if (paint->getShader() == nullptr) { - if (mMultiGamma) { - const int l = luminance(paint); - - if (l <= mBlackThreshold) { - description.hasGammaCorrection = true; - description.gamma = mGamma; - } else if (l >= mWhiteThreshold) { - description.hasGammaCorrection = true; - description.gamma = 1.0f / mGamma; - } - } else { - description.hasGammaCorrection = true; - description.gamma = 1.0f / mGamma; - } - } -} - -void ShaderGammaFontRenderer::setupProgram(ProgramDescription& description, - Program& program) const { - if (description.hasGammaCorrection) { - glUniform1f(program.getUniform("gamma"), description.gamma); - } -} - -void ShaderGammaFontRenderer::endPrecaching() { - if (mRenderer) { - mRenderer->endPrecaching(); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Lookup-based renderer -/////////////////////////////////////////////////////////////////////////////// - -LookupGammaFontRenderer::LookupGammaFontRenderer() - : GammaFontRenderer() { INIT_LOGD("Creating lookup gamma font renderer"); // Compute the gamma tables - const float gamma = 1.0f / mGamma; + const float gamma = 1.0f / Properties::textGamma; for (uint32_t i = 0; i <= 255; i++) { mGammaTable[i] = uint8_t((float)::floor(pow(i / 255.0f, gamma) * 255.0f + 0.5f)); } - - mRenderer = nullptr; } -void LookupGammaFontRenderer::endPrecaching() { +void GammaFontRenderer::endPrecaching() { if (mRenderer) { mRenderer->endPrecaching(); } } -/////////////////////////////////////////////////////////////////////////////// -// Lookup-based renderer, using 3 different correction tables -/////////////////////////////////////////////////////////////////////////////// - -Lookup3GammaFontRenderer::Lookup3GammaFontRenderer() - : GammaFontRenderer() { - INIT_LOGD("Creating lookup3 gamma font renderer"); - - // Compute the gamma tables - const float blackGamma = mGamma; - const float whiteGamma = 1.0f / mGamma; - - for (uint32_t i = 0; i <= 255; i++) { - const float v = i / 255.0f; - const float black = pow(v, blackGamma); - const float white = pow(v, whiteGamma); - - mGammaTable[i] = i; - mGammaTable[256 + i] = uint8_t((float)::floor(black * 255.0f + 0.5f)); - mGammaTable[512 + i] = uint8_t((float)::floor(white * 255.0f + 0.5f)); - } - - memset(mRenderers, 0, sizeof(FontRenderer*) * kGammaCount); - memset(mRenderersUsageCount, 0, sizeof(uint32_t) * kGammaCount); -} - -void Lookup3GammaFontRenderer::endPrecaching() { - for (int i = 0; i < kGammaCount; i++) { - if (mRenderers[i]) { - mRenderers[i]->endPrecaching(); - } - } -} - -void Lookup3GammaFontRenderer::clear() { - for (int i = 0; i < kGammaCount; i++) { - mRenderers[i].reset(nullptr); - } -} - -void Lookup3GammaFontRenderer::flush() { - int count = 0; - int min = -1; - uint32_t minCount = UINT_MAX; - - for (int i = 0; i < kGammaCount; i++) { - if (mRenderers[i]) { - count++; - if (mRenderersUsageCount[i] < minCount) { - minCount = mRenderersUsageCount[i]; - min = i; - } - } - } - - if (count <= 1 || min < 0) return; - - mRenderers[min].reset(nullptr); - - // Also eliminate the caches for large glyphs, as they consume significant memory - for (int i = 0; i < kGammaCount; ++i) { - if (mRenderers[i]) { - mRenderers[i]->flushLargeCaches(); - } - } -} - -FontRenderer* Lookup3GammaFontRenderer::getRenderer(Gamma gamma) { - if (!mRenderers[gamma]) { - mRenderers[gamma].reset(new FontRenderer()); - mRenderers[gamma]->setGammaTable(&mGammaTable[gamma * 256]); - } - mRenderersUsageCount[gamma]++; - return mRenderers[gamma].get(); -} - -FontRenderer& Lookup3GammaFontRenderer::getFontRenderer(const SkPaint* paint) { - if (paint->getShader() == nullptr) { - const int l = luminance(paint); - - if (l <= mBlackThreshold) { - return *getRenderer(kGammaBlack); - } else if (l >= mWhiteThreshold) { - return *getRenderer(kGammaWhite); - } - } - return *getRenderer(kGammaDefault); -} - }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h index ca55bf1e74e0..5813e7f717ee 100644 --- a/libs/hwui/GammaFontRenderer.h +++ b/libs/hwui/GammaFontRenderer.h @@ -17,183 +17,44 @@ #ifndef ANDROID_HWUI_GAMMA_FONT_RENDERER_H #define ANDROID_HWUI_GAMMA_FONT_RENDERER_H -#include <SkPaint.h> - #include "FontRenderer.h" #include "Program.h" +#include <SkPaint.h> + namespace android { namespace uirenderer { class GammaFontRenderer { public: - virtual ~GammaFontRenderer(); - - virtual void clear() = 0; - virtual void flush() = 0; - - virtual FontRenderer& getFontRenderer(const SkPaint* paint) = 0; - - virtual uint32_t getFontRendererCount() const = 0; - virtual uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const = 0; - - virtual void describe(ProgramDescription& description, const SkPaint* paint) const = 0; - virtual void setupProgram(ProgramDescription& description, Program& program) const = 0; - - virtual void endPrecaching() = 0; - - static GammaFontRenderer* createRenderer(); - -protected: GammaFontRenderer(); - int mBlackThreshold; - int mWhiteThreshold; - - float mGamma; -}; - -class ShaderGammaFontRenderer: public GammaFontRenderer { -public: - ~ShaderGammaFontRenderer() { - delete mRenderer; - } - - void clear() override { - delete mRenderer; - mRenderer = nullptr; - } - - void flush() override { - if (mRenderer) { - mRenderer->flushLargeCaches(); - } - } - - FontRenderer& getFontRenderer(const SkPaint* paint) override { - if (!mRenderer) { - mRenderer = new FontRenderer; - } - return *mRenderer; - } - - uint32_t getFontRendererCount() const override { - return 1; - } - - uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const override { - return mRenderer ? mRenderer->getCacheSize(format) : 0; - } - - void describe(ProgramDescription& description, const SkPaint* paint) const override; - void setupProgram(ProgramDescription& description, Program& program) const override; - - void endPrecaching() override; - -private: - ShaderGammaFontRenderer(bool multiGamma); - - FontRenderer* mRenderer; - bool mMultiGamma; - - friend class GammaFontRenderer; -}; - -class LookupGammaFontRenderer: public GammaFontRenderer { -public: - ~LookupGammaFontRenderer() { - delete mRenderer; - } - - void clear() override { - delete mRenderer; - mRenderer = nullptr; + void clear() { + mRenderer.reset(nullptr); } - void flush() override { + void flush() { if (mRenderer) { mRenderer->flushLargeCaches(); } } - FontRenderer& getFontRenderer(const SkPaint* paint) override { + FontRenderer& getFontRenderer() { if (!mRenderer) { - mRenderer = new FontRenderer; - mRenderer->setGammaTable(&mGammaTable[0]); + mRenderer.reset(new FontRenderer(&mGammaTable[0])); } return *mRenderer; } - uint32_t getFontRendererCount() const override { - return 1; - } - - uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const override { + uint32_t getFontRendererSize(GLenum format) const { return mRenderer ? mRenderer->getCacheSize(format) : 0; } - void describe(ProgramDescription& description, const SkPaint* paint) const override { - } - - void setupProgram(ProgramDescription& description, Program& program) const override { - } - - void endPrecaching() override; + void endPrecaching(); private: - LookupGammaFontRenderer(); - - FontRenderer* mRenderer; + std::unique_ptr<FontRenderer> mRenderer; uint8_t mGammaTable[256]; - - friend class GammaFontRenderer; -}; - -class Lookup3GammaFontRenderer: public GammaFontRenderer { -public: - void clear() override; - void flush() override; - - FontRenderer& getFontRenderer(const SkPaint* paint) override; - - uint32_t getFontRendererCount() const override { - return kGammaCount; - } - - uint32_t getFontRendererSize(uint32_t fontRenderer, GLenum format) const override { - if (fontRenderer >= kGammaCount) return 0; - - if (!mRenderers[fontRenderer]) return 0; - - return mRenderers[fontRenderer]->getCacheSize(format); - } - - void describe(ProgramDescription& description, const SkPaint* paint) const override { - } - - void setupProgram(ProgramDescription& description, Program& program) const override { - } - - void endPrecaching() override; - -private: - Lookup3GammaFontRenderer(); - - enum Gamma { - kGammaDefault = 0, - kGammaBlack = 1, - kGammaWhite = 2, - kGammaCount = 3 - }; - - FontRenderer* getRenderer(Gamma gamma); - - uint32_t mRenderersUsageCount[kGammaCount]; - std::unique_ptr<FontRenderer> mRenderers[kGammaCount]; - - uint8_t mGammaTable[256 * kGammaCount]; - - friend class GammaFontRenderer; }; }; // namespace uirenderer diff --git a/libs/hwui/GlFunctorLifecycleListener.h b/libs/hwui/GlFunctorLifecycleListener.h new file mode 100644 index 000000000000..357090eb31ca --- /dev/null +++ b/libs/hwui/GlFunctorLifecycleListener.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 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/Functor.h> +#include <utils/RefBase.h> + +namespace android { +namespace uirenderer { + +class GlFunctorLifecycleListener : public VirtualLightRefBase { +public: + virtual ~GlFunctorLifecycleListener() {} + virtual void onGlFunctorReleased(Functor* functor) = 0; +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h index fa20b0807a88..6a9663412a00 100644 --- a/libs/hwui/Glop.h +++ b/libs/hwui/Glop.h @@ -64,7 +64,7 @@ namespace TransformFlags { // Canvas transform isn't applied to the mesh at draw time, //since it's already built in. - MeshIgnoresCanvasTransform = 1 << 1, + MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove for HWUI_NEW_OPS }; }; @@ -81,8 +81,10 @@ namespace TransformFlags { * vertex/index/Texture/RoundRectClipState pointers prevent this from * being safe. */ -// TODO: PREVENT_COPY_AND_ASSIGN(...) or similar struct Glop { + PREVENT_COPY_AND_ASSIGN(Glop); +public: + Glop() { } struct Mesh { GLuint primitiveMode; // GL_TRIANGLES and GL_TRIANGLE_STRIP supported @@ -135,10 +137,6 @@ struct Glop { } fill; struct Transform { - // Orthographic projection matrix for current FBO - // TODO: move out of Glop, since this is static per FBO - Matrix4 ortho; - // modelView transform, accounting for delta between mesh transform and content of the mesh // often represents x/y offsets within command, or scaling for mesh unit size Matrix4 modelView; @@ -153,7 +151,7 @@ struct Glop { } } transform; - const RoundRectClipState* roundRectClipState; + const RoundRectClipState* roundRectClipState = nullptr; /** * Blending to be used by this draw - both GL_NONE if blending is disabled. @@ -165,11 +163,13 @@ struct Glop { GLenum dst; } blend; +#if !HWUI_NEW_OPS /** * Bounds of the drawing command in layer space. Only mapped into layer * space once GlopBuilder::build() is called. */ - Rect bounds; + Rect bounds; // TODO: remove for HWUI_NEW_OPS +#endif /** * Additional render state to enumerate: diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index 288fed360162..0a8e3f3b4db3 100644 --- a/libs/hwui/GlopBuilder.cpp +++ b/libs/hwui/GlopBuilder.cpp @@ -70,6 +70,20 @@ GlopBuilder::GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop // Mesh //////////////////////////////////////////////////////////////////////////////// +GlopBuilder& GlopBuilder::setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount) { + TRIGGER_STAGE(kMeshStage); + + mOutGlop->mesh.primitiveMode = GL_TRIANGLES; + mOutGlop->mesh.indices = { mRenderState.meshState().getQuadListIBO(), nullptr }; + mOutGlop->mesh.vertices = { + vbo, + VertexAttribFlags::TextureCoord, + nullptr, (const void*) kMeshTextureOffset, nullptr, + kTextureVertexStride }; + mOutGlop->mesh.elementCount = elementCount; + return *this; +} + GlopBuilder& GlopBuilder::setMeshUnitQuad() { TRIGGER_STAGE(kMeshStage); @@ -87,7 +101,7 @@ GlopBuilder& GlopBuilder::setMeshUnitQuad() { GlopBuilder& GlopBuilder::setMeshTexturedUnitQuad(const UvMapper* uvMapper) { if (uvMapper) { // can't use unit quad VBO, so build UV vertices manually - return setMeshTexturedUvQuad(uvMapper, Rect(0, 0, 1, 1)); + return setMeshTexturedUvQuad(uvMapper, Rect(1, 1)); } TRIGGER_STAGE(kMeshStage); @@ -274,7 +288,7 @@ void GlopBuilder::setFill(int color, float alphaScale, SkXfermode::Mode mode; SkScalar srcColorMatrix[20]; if (colorFilter->asColorMode(&color, &mode)) { - mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::kColorBlend; + mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Blend; mDescription.colorMode = mode; const float alpha = SkColorGetA(color) / 255.0f; @@ -286,7 +300,7 @@ void GlopBuilder::setFill(int color, float alphaScale, alpha, }; } else if (colorFilter->asColorMatrix(srcColorMatrix)) { - mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::kColorMatrix; + mOutGlop->fill.filterMode = mDescription.colorOp = ProgramDescription::ColorFilterMode::Matrix; float* colorMatrix = mOutGlop->fill.filter.matrix.matrix; memcpy(colorMatrix, srcColorMatrix, 4 * sizeof(float)); @@ -305,7 +319,7 @@ void GlopBuilder::setFill(int color, float alphaScale, LOG_ALWAYS_FATAL("unsupported ColorFilter"); } } else { - mOutGlop->fill.filterMode = ProgramDescription::kColorNone; + mOutGlop->fill.filterMode = ProgramDescription::ColorFilterMode::None; } } @@ -435,7 +449,6 @@ GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* co mOutGlop->fill.texture = { &texture, GL_TEXTURE_2D, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr }; - mOutGlop->fill.color = { alpha, alpha, alpha, alpha }; setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter); @@ -449,7 +462,6 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) { mOutGlop->fill.texture = { &(layer.getTexture()), layer.getRenderTarget(), GL_LINEAR, GL_CLAMP_TO_EDGE, &layer.getTexTransform() }; - mOutGlop->fill.color = { alpha, alpha, alpha, alpha }; setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap, nullptr, layer.getColorFilter()); @@ -459,17 +471,32 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) { return *this; } +GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& textureTransform) { + TRIGGER_STAGE(kFillStage); + REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); + + mOutGlop->fill.texture = { &texture, + GL_TEXTURE_EXTERNAL_OES, GL_LINEAR, GL_CLAMP_TO_EDGE, + &textureTransform }; + + setFill(SK_ColorWHITE, 1.0f, SkXfermode::kSrc_Mode, Blend::ModeOrderSwap::NoSwap, + nullptr, nullptr); + + mDescription.modulate = mOutGlop->fill.color.a < 1.0f; + mDescription.hasTextureTransform = true; + return *this; +} + //////////////////////////////////////////////////////////////////////////////// // Transform //////////////////////////////////////////////////////////////////////////////// -void GlopBuilder::setTransform(const Matrix4& ortho, const Matrix4& canvas, - const int transformFlags) { +GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) { TRIGGER_STAGE(kTransformStage); - mOutGlop->transform.ortho.load(ortho); - mOutGlop->transform.canvas.load(canvas); + mOutGlop->transform.canvas = canvas; mOutGlop->transform.transformFlags = transformFlags; + return *this; } //////////////////////////////////////////////////////////////////////////////// @@ -481,7 +508,9 @@ GlopBuilder& GlopBuilder::setModelViewMapUnitToRect(const Rect destination) { mOutGlop->transform.modelView.loadTranslate(destination.left, destination.top, 0.0f); mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f); +#if !HWUI_NEW_OPS mOutGlop->bounds = destination; +#endif return *this; } @@ -505,7 +534,9 @@ GlopBuilder& GlopBuilder::setModelViewMapUnitToRectSnap(const Rect destination) mOutGlop->transform.modelView.loadTranslate(left, top, 0.0f); mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f); +#if !HWUI_NEW_OPS mOutGlop->bounds = destination; +#endif return *this; } @@ -513,8 +544,10 @@ GlopBuilder& GlopBuilder::setModelViewOffsetRect(float offsetX, float offsetY, c TRIGGER_STAGE(kModelViewStage); mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f); +#if !HWUI_NEW_OPS mOutGlop->bounds = source; mOutGlop->bounds.translate(offsetX, offsetY); +#endif return *this; } @@ -534,8 +567,10 @@ GlopBuilder& GlopBuilder::setModelViewOffsetRectSnap(float offsetX, float offset } mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f); +#if !HWUI_NEW_OPS mOutGlop->bounds = source; mOutGlop->bounds.translate(offsetX, offsetY); +#endif return *this; } @@ -615,7 +650,7 @@ void GlopBuilder::build() { shaderMatrix.loadInverse(mOutGlop->transform.canvas); shaderMatrix.multiply(mOutGlop->transform.modelView); } else { - shaderMatrix.load(mOutGlop->transform.modelView); + shaderMatrix = mOutGlop->transform.modelView; } SkiaShader::store(mCaches, *mShader, shaderMatrix, &textureUnit, &mDescription, &(mOutGlop->fill.skiaShaderData)); @@ -632,7 +667,51 @@ void GlopBuilder::build() { // Final step: populate program and map bounds into render target space mOutGlop->fill.program = mCaches.programCache.get(mDescription); +#if !HWUI_NEW_OPS mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds); +#endif +} + +void GlopBuilder::dump(const Glop& glop) { + ALOGD("Glop Mesh"); + const Glop::Mesh& mesh = glop.mesh; + ALOGD(" primitive mode: %d", mesh.primitiveMode); + ALOGD(" indices: buffer obj %x, indices %p", mesh.indices.bufferObject, mesh.indices.indices); + + const Glop::Mesh::Vertices& vertices = glop.mesh.vertices; + ALOGD(" vertices: buffer obj %x, flags %x, pos %p, tex %p, clr %p, stride %d", + vertices.bufferObject, vertices.attribFlags, + vertices.position, vertices.texCoord, vertices.color, vertices.stride); + ALOGD(" element count: %d", mesh.elementCount); + + ALOGD("Glop Fill"); + const Glop::Fill& fill = glop.fill; + ALOGD(" program %p", fill.program); + if (fill.texture.texture) { + ALOGD(" texture %p, target %d, filter %d, clamp %d", + fill.texture.texture, fill.texture.target, fill.texture.filter, fill.texture.clamp); + if (fill.texture.textureTransform) { + fill.texture.textureTransform->dump("texture transform"); + } + } + ALOGD_IF(fill.colorEnabled, " color (argb) %.2f %.2f %.2f %.2f", + fill.color.a, fill.color.r, fill.color.g, fill.color.b); + ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None, + " filterMode %d", (int)fill.filterMode); + ALOGD_IF(fill.skiaShaderData.skiaShaderType, " shader type %d", + fill.skiaShaderData.skiaShaderType); + + ALOGD("Glop transform"); + glop.transform.modelView.dump(" model view"); + glop.transform.canvas.dump(" canvas"); + ALOGD_IF(glop.transform.transformFlags, " transformFlags 0x%x", glop.transform.transformFlags); + + ALOGD_IF(glop.roundRectClipState, "Glop RRCS %p", glop.roundRectClipState); + + ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst); +#if !HWUI_NEW_OPS + ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds)); +#endif } } /* namespace uirenderer */ diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h index 549bb21e5f8d..a9dd56f385b1 100644 --- a/libs/hwui/GlopBuilder.h +++ b/libs/hwui/GlopBuilder.h @@ -47,12 +47,13 @@ class GlopBuilder { public: GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop); + GlopBuilder& setMeshTexturedIndexedVbo(GLuint vbo, GLsizei elementCount); GlopBuilder& setMeshUnitQuad(); GlopBuilder& setMeshTexturedUnitQuad(const UvMapper* uvMapper); GlopBuilder& setMeshTexturedUvQuad(const UvMapper* uvMapper, const Rect uvs); GlopBuilder& setMeshVertexBuffer(const VertexBuffer& vertexBuffer, bool shadowInterp); GlopBuilder& setMeshIndexedQuads(Vertex* vertexData, int quadCount); - GlopBuilder& setMeshTexturedMesh(TextureVertex* vertexData, int elementCount); // TODO: use indexed quads + GlopBuilder& setMeshTexturedMesh(TextureVertex* vertexData, int elementCount); // TODO: delete GlopBuilder& setMeshColoredTexturedMesh(ColorTextureVertex* vertexData, int elementCount); // TODO: use indexed quads GlopBuilder& setMeshTexturedIndexedQuads(TextureVertex* vertexData, int elementCount); // TODO: take quadCount GlopBuilder& setMeshPatchQuads(const Patch& patch); @@ -69,11 +70,15 @@ public: GlopBuilder& setFillLayer(Texture& texture, const SkColorFilter* colorFilter, float alpha, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage); GlopBuilder& setFillTextureLayer(Layer& layer, float alpha); + // TODO: Texture should probably know and own its target. + // setFillLayer() forces it to GL_TEXTURE which isn't always correct. + // Similarly setFillLayer normally forces its own wrap & filter mode + GlopBuilder& setFillExternalTexture(Texture& texture, Matrix4& textureTransform); GlopBuilder& setTransform(const Snapshot& snapshot, const int transformFlags) { - setTransform(snapshot.getOrthoMatrix(), *snapshot.transform, transformFlags); - return *this; + return setTransform(*snapshot.transform, transformFlags); } + GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags); GlopBuilder& setModelViewMapUnitToRect(const Rect destination); GlopBuilder& setModelViewMapUnitToRectSnap(const Rect destination); @@ -94,16 +99,20 @@ public: return setModelViewOffsetRect(offsetX, offsetY, source); } } + GlopBuilder& setModelViewIdentityEmptyBounds() { + // pass empty rect since not needed for damage / snap + return setModelViewOffsetRect(0, 0, Rect()); + } GlopBuilder& setRoundRectClipState(const RoundRectClipState* roundRectClipState); void build(); + + static void dump(const Glop& glop); private: void setFill(int color, float alphaScale, SkXfermode::Mode mode, Blend::ModeOrderSwap modeUsage, const SkShader* shader, const SkColorFilter* colorFilter); - void setTransform(const Matrix4& ortho, const Matrix4& canvas, - const int transformFlags); enum StageFlags { kInitialStage = 0, diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp new file mode 100644 index 000000000000..4fb57019264d --- /dev/null +++ b/libs/hwui/GpuMemoryTracker.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 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 "utils/StringUtils.h" +#include "Texture.h" + +#include <cutils/compiler.h> +#include <GpuMemoryTracker.h> +#include <utils/Trace.h> +#include <array> +#include <sstream> +#include <unordered_set> +#include <vector> + +namespace android { +namespace uirenderer { + +pthread_t gGpuThread = 0; + +#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount) + +const char* TYPE_NAMES[] = { + "Texture", + "OffscreenBuffer", + "Layer", +}; + +struct TypeStats { + int totalSize = 0; + int count = 0; +}; + +static std::array<TypeStats, NUM_TYPES> gObjectStats; +static std::unordered_set<GpuMemoryTracker*> gObjectSet; + +void GpuMemoryTracker::notifySizeChanged(int newSize) { + int delta = newSize - mSize; + mSize = newSize; + gObjectStats[static_cast<int>(mType)].totalSize += delta; +} + +void GpuMemoryTracker::startTrackingObject() { + auto result = gObjectSet.insert(this); + LOG_ALWAYS_FATAL_IF(!result.second, + "startTrackingObject() on %p failed, already being tracked!", this); + gObjectStats[static_cast<int>(mType)].count++; +} + +void GpuMemoryTracker::stopTrackingObject() { + size_t removed = gObjectSet.erase(this); + LOG_ALWAYS_FATAL_IF(removed != 1, + "stopTrackingObject removed %zd, is %p not being tracked?", + removed, this); + gObjectStats[static_cast<int>(mType)].count--; +} + +void GpuMemoryTracker::onGLContextCreated() { + LOG_ALWAYS_FATAL_IF(gGpuThread != 0, "We already have a GL thread? " + "current = %lu, gl thread = %lu", pthread_self(), gGpuThread); + gGpuThread = pthread_self(); +} + +void GpuMemoryTracker::onGLContextDestroyed() { + gGpuThread = 0; + if (CC_UNLIKELY(gObjectSet.size() > 0)) { + std::stringstream os; + dump(os); + ALOGE("%s", os.str().c_str()); + LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size()); + } +} + +void GpuMemoryTracker::dump() { + std::stringstream strout; + dump(strout); + ALOGD("%s", strout.str().c_str()); +} + +void GpuMemoryTracker::dump(std::ostream& stream) { + for (int type = 0; type < NUM_TYPES; type++) { + const TypeStats& stats = gObjectStats[type]; + stream << TYPE_NAMES[type]; + stream << " is using " << SizePrinter{stats.totalSize}; + stream << ", count = " << stats.count; + stream << std::endl; + } +} + +int GpuMemoryTracker::getInstanceCount(GpuObjectType type) { + return gObjectStats[static_cast<int>(type)].count; +} + +int GpuMemoryTracker::getTotalSize(GpuObjectType type) { + return gObjectStats[static_cast<int>(type)].totalSize; +} + +void GpuMemoryTracker::onFrameCompleted() { + if (ATRACE_ENABLED()) { + char buf[128]; + for (int type = 0; type < NUM_TYPES; type++) { + snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]); + const TypeStats& stats = gObjectStats[type]; + ATRACE_INT(buf, stats.totalSize); + snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]); + ATRACE_INT(buf, stats.count); + } + } + + std::vector<const Texture*> freeList; + for (const auto& obj : gObjectSet) { + if (obj->objectType() == GpuObjectType::Texture) { + const Texture* texture = static_cast<Texture*>(obj); + if (texture->cleanup) { + ALOGE("Leaked texture marked for cleanup! id=%u, size %ux%u", + texture->id(), texture->width(), texture->height()); + freeList.push_back(texture); + } + } + } + for (auto& texture : freeList) { + const_cast<Texture*>(texture)->deleteTexture(); + delete texture; + } +} + +} // namespace uirenderer +} // namespace android; diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h new file mode 100644 index 000000000000..851aeae8352c --- /dev/null +++ b/libs/hwui/GpuMemoryTracker.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 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 <cutils/log.h> +#include <pthread.h> +#include <ostream> + +namespace android { +namespace uirenderer { + +extern pthread_t gGpuThread; + +#define ASSERT_GPU_THREAD() LOG_ALWAYS_FATAL_IF( \ + !pthread_equal(gGpuThread, pthread_self()), \ + "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \ + "!= gpu thread %lu", this, static_cast<int>(mType), mSize, \ + pthread_self(), gGpuThread) + +enum class GpuObjectType { + Texture = 0, + OffscreenBuffer, + Layer, + + TypeCount, +}; + +class GpuMemoryTracker { +public: + GpuObjectType objectType() { return mType; } + int objectSize() { return mSize; } + + static void onGLContextCreated(); + static void onGLContextDestroyed(); + static void dump(); + static void dump(std::ostream& stream); + static int getInstanceCount(GpuObjectType type); + static int getTotalSize(GpuObjectType type); + static void onFrameCompleted(); + +protected: + GpuMemoryTracker(GpuObjectType type) : mType(type) { + ASSERT_GPU_THREAD(); + startTrackingObject(); + } + + ~GpuMemoryTracker() { + notifySizeChanged(0); + stopTrackingObject(); + } + + void notifySizeChanged(int newSize); + +private: + void startTrackingObject(); + void stopTrackingObject(); + + int mSize = 0; + GpuObjectType mType; +}; + +} // namespace uirenderer +} // namespace android; diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index ea93e7f9716b..c8f5e9435594 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <utils/JenkinsHash.h> #include "Caches.h" @@ -23,6 +21,8 @@ #include "GradientCache.h" #include "Properties.h" +#include <cutils/properties.h> + namespace android { namespace uirenderer { @@ -65,17 +65,9 @@ int GradientCacheEntry::compare(const GradientCacheEntry& lhs, const GradientCac GradientCache::GradientCache(Extensions& extensions) : mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity) , mSize(0) - , mMaxSize(MB(DEFAULT_GRADIENT_CACHE_SIZE)) + , mMaxSize(Properties::gradientCacheSize) , mUseFloatTexture(extensions.hasFloatTextures()) , mHasNpot(extensions.hasNPot()){ - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_GRADIENT_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting gradient cache size to %sMB", property); - setMaxSize(MB(atof(property))); - } else { - INIT_LOGD(" Using default gradient cache size of %.2fMB", DEFAULT_GRADIENT_CACHE_SIZE); - } - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); mCache.setOnEntryRemovedListener(this); @@ -97,22 +89,13 @@ uint32_t GradientCache::getMaxSize() { return mMaxSize; } -void GradientCache::setMaxSize(uint32_t maxSize) { - mMaxSize = maxSize; - while (mSize > mMaxSize) { - mCache.removeOldest(); - } -} - /////////////////////////////////////////////////////////////////////////////// // Callbacks /////////////////////////////////////////////////////////////////////////////// void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) { if (texture) { - const uint32_t size = texture->width * texture->height * bytesPerPixel(); - mSize -= size; - + mSize -= texture->objectSize(); texture->deleteTexture(); delete texture; } @@ -167,20 +150,25 @@ Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, getGradientInfo(colors, count, info); Texture* texture = new Texture(Caches::getInstance()); - texture->width = info.width; - texture->height = 2; texture->blend = info.hasAlpha; texture->generation = 1; - // Asume the cache is always big enough - const uint32_t size = texture->width * texture->height * bytesPerPixel(); + // Assume the cache is always big enough + const uint32_t size = info.width * 2 * bytesPerPixel(); while (getSize() + size > mMaxSize) { - mCache.removeOldest(); + LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(), + "Ran out of things to remove from the cache? getSize() = %" PRIu32 + ", size = %" PRIu32 ", mMaxSize = %" PRIu32 ", width = %" PRIu32, + getSize(), size, mMaxSize, info.width); } - generateTexture(colors, positions, texture); + generateTexture(colors, positions, info.width, 2, texture); mSize += size; + LOG_ALWAYS_FATAL_IF((int)size != texture->objectSize(), + "size != texture->objectSize(), size %" PRIu32 ", objectSize %d" + " width = %" PRIu32 " bytesPerPixel() = %zu", + size, texture->objectSize(), info.width, bytesPerPixel()); mCache.put(gradient, texture); return texture; @@ -231,10 +219,10 @@ void GradientCache::mixFloats(GradientColor& start, GradientColor& end, float am dst += 4 * sizeof(float); } -void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* texture) { - const uint32_t width = texture->width; +void GradientCache::generateTexture(uint32_t* colors, float* positions, + const uint32_t width, const uint32_t height, Texture* texture) { const GLsizei rowBytes = width * bytesPerPixel(); - uint8_t pixels[rowBytes * texture->height]; + uint8_t pixels[rowBytes * height]; static ChannelSplitter gSplitters[] = { &android::uirenderer::GradientCache::splitToBytes, @@ -277,17 +265,11 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, Texture* memcpy(pixels + rowBytes, pixels, rowBytes); - glGenTextures(1, &texture->id); - Caches::getInstance().textureState().bindTexture(texture->id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - if (mUseFloatTexture) { // We have to use GL_RGBA16F because GL_RGBA32F does not support filtering - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, texture->height, 0, - GL_RGBA, GL_FLOAT, pixels); + texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels); } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, texture->height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, pixels); + texture->upload(GL_RGBA, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); } texture->setFilter(GL_LINEAR); diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h index 08319ea1ec9b..dccd45072522 100644 --- a/libs/hwui/GradientCache.h +++ b/libs/hwui/GradientCache.h @@ -25,7 +25,6 @@ #include <utils/LruCache.h> #include <utils/Mutex.h> -#include <utils/Vector.h> namespace android { namespace uirenderer { @@ -124,10 +123,6 @@ public: void clear(); /** - * Sets the maximum size of the cache in bytes. - */ - void setMaxSize(uint32_t maxSize); - /** * Returns the maximum size of the cache in bytes. */ uint32_t getMaxSize(); @@ -144,7 +139,8 @@ private: Texture* addLinearGradient(GradientCacheEntry& gradient, uint32_t* colors, float* positions, int count); - void generateTexture(uint32_t* colors, float* positions, Texture* texture); + void generateTexture(uint32_t* colors, float* positions, + const uint32_t width, const uint32_t height, Texture* texture); struct GradientInfo { uint32_t width; @@ -177,13 +173,12 @@ private: LruCache<GradientCacheEntry, Texture*> mCache; uint32_t mSize; - uint32_t mMaxSize; + const uint32_t mMaxSize; GLint mMaxTextureSize; bool mUseFloatTexture; bool mHasNpot; - Vector<SkShader*> mGarbage; mutable Mutex mLock; }; // class GradientCache diff --git a/libs/hwui/Image.cpp b/libs/hwui/Image.cpp index a31c54675f8a..68a356ba1be0 100644 --- a/libs/hwui/Image.cpp +++ b/libs/hwui/Image.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <utils/Log.h> #include "Caches.h" diff --git a/libs/hwui/Interpolator.cpp b/libs/hwui/Interpolator.cpp index e1b0fc3937c5..cc47f0052b73 100644 --- a/libs/hwui/Interpolator.cpp +++ b/libs/hwui/Interpolator.cpp @@ -16,11 +16,11 @@ #include "Interpolator.h" -#include <cmath> -#include <cutils/log.h> - #include "utils/MathUtils.h" +#include <algorithm> +#include <cutils/log.h> + namespace android { namespace uirenderer { @@ -106,7 +106,7 @@ float LUTInterpolator::interpolate(float input) { weight = modff(lutpos, &ipart); int i1 = (int) ipart; - int i2 = MathUtils::min(i1 + 1, (int) mSize - 1); + int i2 = std::min(i1 + 1, (int) mSize - 1); LOG_ALWAYS_FATAL_IF(i1 < 0 || i2 < 0, "negatives in interpolation!" " i1=%d, i2=%d, input=%f, lutpos=%f, size=%zu, values=%p, ipart=%f, weight=%f", diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index eb9b55f196bb..ebe9c4240221 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -15,12 +15,16 @@ */ #include "JankTracker.h" +#include "Properties.h" + #include <algorithm> #include <cutils/ashmem.h> #include <cutils/log.h> #include <cstdio> #include <errno.h> #include <inttypes.h> +#include <limits> +#include <cmath> #include <sys/mman.h> namespace android { @@ -52,32 +56,35 @@ static const Comparison COMPARISONS[] = { static const int64_t IGNORE_EXCEEDING = seconds_to_nanoseconds(10); /* - * Frames that are exempt from jank metrics. - * First-draw frames, for example, are expected to - * be slow, this is hidden from the user with window animations and - * other tricks - * - * Similarly, we don't track direct-drawing via Surface:lockHardwareCanvas() + * We don't track direct-drawing via Surface:lockHardwareCanvas() * for now * * TODO: kSurfaceCanvas can negatively impact other drawing by using up * time on the RenderThread, figure out how to attribute that as a jank-causer */ -static const int64_t EXEMPT_FRAMES_FLAGS - = FrameInfoFlags::WindowLayoutChanged - | FrameInfoFlags::SurfaceCanvas; +static const int64_t EXEMPT_FRAMES_FLAGS = FrameInfoFlags::SurfaceCanvas; // The bucketing algorithm controls so to speak // If a frame is <= to this it goes in bucket 0 -static const uint32_t kBucketMinThreshold = 7; +static const uint32_t kBucketMinThreshold = 5; // If a frame is > this, start counting in increments of 2ms static const uint32_t kBucket2msIntervals = 32; // If a frame is > this, start counting in increments of 4ms static const uint32_t kBucket4msIntervals = 48; +// For testing purposes to try and eliminate test infra overhead we will +// consider any unknown delay of frame start as part of the test infrastructure +// and filter it out of the frame profile data +static FrameInfoIndex sFrameStart = FrameInfoIndex::IntendedVsync; + +// The interval of the slow frame histogram +static const uint32_t kSlowFrameBucketIntervalMs = 50; +// The start point of the slow frame bucket in ms +static const uint32_t kSlowFrameBucketStartMs = 150; + // This will be called every frame, performance sensitive // Uses bit twiddling to avoid branching while achieving the packing desired -static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) { +static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime) { uint32_t index = static_cast<uint32_t>(ns2ms(frameTime)); // If index > kBucketMinThreshold mask will be 0xFFFFFFFF as a result // of negating 1 (twos compliment, yaay) else mask will be 0 @@ -95,7 +102,7 @@ static uint32_t frameCountIndexForFrameTime(nsecs_t frameTime, uint32_t max) { // be a pretty garbage value right now. However, mask is 0 so we'll end // up with the desired result of 0. index = (index - kBucketMinThreshold) & mask; - return index < max ? index : max; + return index; } // Only called when dumping stats, less performance sensitive @@ -205,21 +212,30 @@ void JankTracker::setFrameInterval(nsecs_t frameInterval) { void JankTracker::addFrame(const FrameInfo& frame) { mData->totalFrameCount++; // Fast-path for jank-free frames - int64_t totalDuration = - frame[FrameInfoIndex::FrameCompleted] - frame[FrameInfoIndex::IntendedVsync]; - uint32_t framebucket = frameCountIndexForFrameTime( - totalDuration, mData->frameCounts.size()); + int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::FrameCompleted); + uint32_t framebucket = frameCountIndexForFrameTime(totalDuration); // Keep the fast path as fast as possible. if (CC_LIKELY(totalDuration < mFrameInterval)) { mData->frameCounts[framebucket]++; return; } + // Only things like Surface.lockHardwareCanvas() are exempt from tracking if (frame[FrameInfoIndex::Flags] & EXEMPT_FRAMES_FLAGS) { return; } - mData->frameCounts[framebucket]++; + if (framebucket <= mData->frameCounts.size()) { + mData->frameCounts[framebucket]++; + } else { + framebucket = (ns2ms(totalDuration) - kSlowFrameBucketStartMs) + / kSlowFrameBucketIntervalMs; + framebucket = std::min(framebucket, + static_cast<uint32_t>(mData->slowFrameCounts.size() - 1)); + framebucket = std::max(framebucket, 0u); + mData->slowFrameCounts[framebucket]++; + } + mData->jankFrameCount++; for (int i = 0; i < NUM_BUCKETS; i++) { @@ -239,30 +255,53 @@ void JankTracker::dumpBuffer(const void* buffer, size_t bufsize, int fd) { } void JankTracker::dumpData(const ProfileData* data, int fd) { + if (sFrameStart != FrameInfoIndex::IntendedVsync) { + dprintf(fd, "\nNote: Data has been filtered!"); + } dprintf(fd, "\nStats since: %" PRIu64 "ns", data->statStartTime); dprintf(fd, "\nTotal frames rendered: %u", data->totalFrameCount); dprintf(fd, "\nJanky frames: %u (%.2f%%)", data->jankFrameCount, (float) data->jankFrameCount / (float) data->totalFrameCount * 100.0f); + dprintf(fd, "\n50th percentile: %ums", findPercentile(data, 50)); dprintf(fd, "\n90th percentile: %ums", findPercentile(data, 90)); dprintf(fd, "\n95th percentile: %ums", findPercentile(data, 95)); dprintf(fd, "\n99th percentile: %ums", findPercentile(data, 99)); for (int i = 0; i < NUM_BUCKETS; i++) { dprintf(fd, "\nNumber %s: %u", JANK_TYPE_NAMES[i], data->jankTypeCounts[i]); } + dprintf(fd, "\nHISTOGRAM:"); + for (size_t i = 0; i < data->frameCounts.size(); i++) { + dprintf(fd, " %ums=%u", frameTimeForFrameCountIndex(i), + data->frameCounts[i]); + } + for (size_t i = 0; i < data->slowFrameCounts.size(); i++) { + dprintf(fd, " %zums=%u", (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs, + data->slowFrameCounts[i]); + } dprintf(fd, "\n"); } void JankTracker::reset() { mData->jankTypeCounts.fill(0); mData->frameCounts.fill(0); + mData->slowFrameCounts.fill(0); mData->totalFrameCount = 0; mData->jankFrameCount = 0; mData->statStartTime = systemTime(CLOCK_MONOTONIC); + sFrameStart = Properties::filterOutTestOverhead + ? FrameInfoIndex::HandleInputStart + : FrameInfoIndex::IntendedVsync; } uint32_t JankTracker::findPercentile(const ProfileData* data, int percentile) { int pos = percentile * data->totalFrameCount / 100; int remaining = data->totalFrameCount - pos; + for (int i = data->slowFrameCounts.size() - 1; i >= 0; i--) { + remaining -= data->slowFrameCounts[i]; + if (remaining <= 0) { + return (i * kSlowFrameBucketIntervalMs) + kSlowFrameBucketStartMs; + } + } for (int i = data->frameCounts.size() - 1; i >= 0; i--) { remaining -= data->frameCounts[i]; if (remaining <= 0) { diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h index 3887e5e65d60..84b8c3f3f155 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -44,7 +44,9 @@ enum JankType { struct ProfileData { std::array<uint32_t, NUM_BUCKETS> jankTypeCounts; // See comments on kBucket* constants for what this holds - std::array<uint32_t, 55> frameCounts; + std::array<uint32_t, 57> frameCounts; + // Holds a histogram of frame times in 50ms increments from 150ms to 5s + std::array<uint16_t, 97> slowFrameCounts; uint32_t totalFrameCount; uint32_t jankFrameCount; diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 62eeb43a2e2e..cdbbbab7730d 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "Layer.h" #include "Caches.h" @@ -38,7 +36,8 @@ namespace android { namespace uirenderer { Layer::Layer(Type layerType, RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight) - : state(kState_Uncached) + : GpuMemoryTracker(GpuObjectType::Layer) + , state(State::Uncached) , caches(Caches::getInstance()) , renderState(renderState) , texture(caches) @@ -47,8 +46,8 @@ Layer::Layer(Type layerType, RenderState& renderState, uint32_t layerWidth, uint // preserves the old inc/dec ref locations. This should be changed... incStrong(nullptr); renderTarget = GL_TEXTURE_2D; - texture.width = layerWidth; - texture.height = layerHeight; + texture.mWidth = layerWidth; + texture.mHeight = layerHeight; renderState.registerLayer(this); } @@ -56,10 +55,9 @@ Layer::~Layer() { renderState.unregisterLayer(this); SkSafeUnref(colorFilter); - if (stencil || fbo || texture.id) { - renderState.requireGLContext(); + if (stencil || fbo || texture.mId) { removeFbo(); - deleteTexture(); + texture.deleteTexture(); } delete[] mesh; @@ -67,7 +65,7 @@ Layer::~Layer() { void Layer::onGlContextLost() { removeFbo(); - deleteTexture(); + texture.deleteTexture(); } uint32_t Layer::computeIdealWidth(uint32_t layerWidth) { @@ -157,8 +155,7 @@ void Layer::removeFbo(bool flush) { if (fbo) { if (flush) LayerRenderer::flushLayer(renderState, this); - // If put fails the cache will delete the FBO - caches.fboCache.put(fbo); + renderState.deleteFramebuffer(fbo); fbo = 0; } } @@ -172,7 +169,8 @@ void Layer::updateDeferred(RenderNode* renderNode, int left, int top, int right, } void Layer::setPaint(const SkPaint* paint) { - OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode); + alpha = PaintUtils::getAlphaDirect(paint); + mode = PaintUtils::getXfermodeDirect(paint); setColorFilter((paint) ? paint->getColorFilter() : nullptr); } @@ -181,8 +179,8 @@ void Layer::setColorFilter(SkColorFilter* filter) { } void Layer::bindTexture() const { - if (texture.id) { - caches.textureState().bindTexture(renderTarget, texture.id); + if (texture.mId) { + caches.textureState().bindTexture(renderTarget, texture.mId); } } @@ -193,29 +191,27 @@ void Layer::bindStencilRenderBuffer() const { } void Layer::generateTexture() { - if (!texture.id) { - glGenTextures(1, &texture.id); - } -} - -void Layer::deleteTexture() { - if (texture.id) { - texture.deleteTexture(); - texture.id = 0; + if (!texture.mId) { + glGenTextures(1, &texture.mId); } } void Layer::clearTexture() { - caches.textureState().unbindTexture(texture.id); - texture.id = 0; + // There's a rare possibility that Caches could have been destroyed already + // since this method is queued up as a task. + // Since this is a reset method, treat this as non-fatal. + if (caches.isInitialized()) { + caches.textureState().unbindTexture(texture.mId); + } + texture.mId = 0; } void Layer::allocateTexture() { #if DEBUG_LAYERS ALOGD(" Allocate layer: %dx%d", getWidth(), getHeight()); #endif - if (texture.id) { - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + if (texture.mId) { + texture.updateSize(getWidth(), getHeight(), GL_RGBA); glTexImage2D(renderTarget, 0, GL_RGBA, getWidth(), getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); } @@ -238,8 +234,7 @@ void Layer::defer(const OpenGLRenderer& rootRenderer) { DeferStateStruct deferredState(*deferredList, *renderer, RenderNode::kReplayFlag_ClipChildren); - renderer->setViewport(width, height); - renderer->setupFrameState(dirtyRect.left, dirtyRect.top, + renderer->setupFrameState(width, height, dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); renderNode->computeOrdering(); @@ -260,9 +255,8 @@ void Layer::flush() { ATRACE_LAYER_WORK("Issue"); renderer->startMark((renderNode.get() != nullptr) ? renderNode->getName() : "Layer"); - renderer->setViewport(layer.getWidth(), layer.getHeight()); - renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, - !isBlend()); + renderer->prepareDirty(layer.getWidth(), layer.getHeight(), + dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); deferredList->flush(*renderer, dirtyRect); @@ -279,9 +273,8 @@ void Layer::render(const OpenGLRenderer& rootRenderer) { ATRACE_LAYER_WORK("Direct-Issue"); updateLightPosFromRenderer(rootRenderer); - renderer->setViewport(layer.getWidth(), layer.getHeight()); - renderer->prepareDirty(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, - !isBlend()); + renderer->prepareDirty(layer.getWidth(), layer.getHeight(), + dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); renderer->drawRenderNode(renderNode.get(), dirtyRect, RenderNode::kReplayFlag_ClipChildren); diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index b670870ca55f..1e5498bb3d21 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -24,6 +24,7 @@ #include <memory> #include <GLES2/gl2.h> +#include <GpuMemoryTracker.h> #include <ui/Region.h> @@ -54,21 +55,21 @@ struct DeferStateStruct; /** * A layer has dimensions and is backed by an OpenGL texture or FBO. */ -class Layer : public VirtualLightRefBase { +class Layer : public VirtualLightRefBase, GpuMemoryTracker { public: - enum Type { - kType_Texture, - kType_DisplayList, + enum class Type { + Texture, + DisplayList, }; // layer lifecycle, controlled from outside - enum State { - kState_Uncached = 0, - kState_InCache = 1, - kState_FailedToCache = 2, - kState_RemovedFromCache = 3, - kState_DeletedFromCache = 4, - kState_InGarbageList = 5, + enum class State { + Uncached = 0, + InCache = 1, + FailedToCache = 2, + RemovedFromCache = 3, + DeletedFromCache = 4, + InGarbageList = 5, }; State state; // public for logging/debugging purposes @@ -94,8 +95,8 @@ public: regionRect.set(bounds.leftTop().x, bounds.leftTop().y, bounds.rightBottom().x, bounds.rightBottom().y); - const float texX = 1.0f / float(texture.width); - const float texY = 1.0f / float(texture.height); + const float texX = 1.0f / float(texture.mWidth); + const float texY = 1.0f / float(texture.mHeight); const float height = layer.getHeight(); texCoords.set( regionRect.left * texX, (height - regionRect.top) * texY, @@ -112,11 +113,11 @@ public: void updateDeferred(RenderNode* renderNode, int left, int top, int right, int bottom); inline uint32_t getWidth() const { - return texture.width; + return texture.mWidth; } inline uint32_t getHeight() const { - return texture.height; + return texture.mHeight; } /** @@ -131,8 +132,7 @@ public: bool resize(const uint32_t width, const uint32_t height); void setSize(uint32_t width, uint32_t height) { - texture.width = width; - texture.height = height; + texture.updateSize(width, height, texture.format()); } ANDROID_API void setPaint(const SkPaint* paint); @@ -201,7 +201,7 @@ public: } inline GLuint getTextureId() const { - return texture.id; + return texture.id(); } inline Texture& getTexture() { @@ -216,6 +216,10 @@ public: this->renderTarget = renderTarget; } + inline bool isRenderable() const { + return renderTarget != GL_NONE; + } + void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) { texture.setWrap(wrap, bindTexture, force, renderTarget); } @@ -241,7 +245,7 @@ public: } inline bool isTextureLayer() const { - return type == kType_Texture; + return type == Type::Texture; } inline SkColorFilter* getColorFilter() const { @@ -263,7 +267,6 @@ public: void bindTexture() const; void generateTexture(); void allocateTexture(); - void deleteTexture(); /** * When the caller frees the texture itself, the caller diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp new file mode 100644 index 000000000000..30007772b1bc --- /dev/null +++ b/libs/hwui/LayerBuilder.cpp @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2016 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 "LayerBuilder.h" + +#include "BakedOpState.h" +#include "RenderNode.h" +#include "utils/PaintUtils.h" +#include "utils/TraceUtils.h" + +#include <utils/TypeHelpers.h> + +namespace android { +namespace uirenderer { + +class BatchBase { +public: + BatchBase(batchid_t batchId, BakedOpState* op, bool merging) + : mBatchId(batchId) + , mMerging(merging) { + mBounds = op->computedState.clippedBounds; + mOps.push_back(op); + } + + bool intersects(const Rect& rect) const { + if (!rect.intersects(mBounds)) return false; + + for (const BakedOpState* op : mOps) { + if (rect.intersects(op->computedState.clippedBounds)) { + return true; + } + } + return false; + } + + batchid_t getBatchId() const { return mBatchId; } + bool isMerging() const { return mMerging; } + + const std::vector<BakedOpState*>& getOps() const { return mOps; } + + void dump() const { + ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING, + this, mBatchId, mMerging, (int) mOps.size(), RECT_ARGS(mBounds)); + } +protected: + batchid_t mBatchId; + Rect mBounds; + std::vector<BakedOpState*> mOps; + bool mMerging; +}; + +class OpBatch : public BatchBase { +public: + OpBatch(batchid_t batchId, BakedOpState* op) + : BatchBase(batchId, op, false) { + } + + void batchOp(BakedOpState* op) { + mBounds.unionWith(op->computedState.clippedBounds); + mOps.push_back(op); + } +}; + +class MergingOpBatch : public BatchBase { +public: + MergingOpBatch(batchid_t batchId, BakedOpState* op) + : BatchBase(batchId, op, true) + , mClipSideFlags(op->computedState.clipSideFlags) { + } + + /* + * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds + * and clip side flags. Positive bounds delta means new bounds fit in old. + */ + static inline bool checkSide(const int currentFlags, const int newFlags, const int side, + float boundsDelta) { + bool currentClipExists = currentFlags & side; + bool newClipExists = newFlags & side; + + // if current is clipped, we must be able to fit new bounds in current + if (boundsDelta > 0 && currentClipExists) return false; + + // if new is clipped, we must be able to fit current bounds in new + if (boundsDelta < 0 && newClipExists) return false; + + return true; + } + + static bool paintIsDefault(const SkPaint& paint) { + return paint.getAlpha() == 255 + && paint.getColorFilter() == nullptr + && paint.getShader() == nullptr; + } + + static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) { + // Note: don't check color, since all currently mergeable ops can merge across colors + return a.getAlpha() == b.getAlpha() + && a.getColorFilter() == b.getColorFilter() + && a.getShader() == b.getShader(); + } + + /* + * Checks if a (mergeable) op can be merged into this batch + * + * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is + * important to consider all paint attributes used in the draw calls in deciding both a) if an + * op tries to merge at all, and b) if the op can merge with another set of ops + * + * False positives can lead to information from the paints of subsequent merged operations being + * dropped, so we make simplifying qualifications on the ops that can merge, per op type. + */ + bool canMergeWith(BakedOpState* op) const { + bool isTextBatch = getBatchId() == OpBatchType::Text + || getBatchId() == OpBatchType::ColorText; + + // Overlapping other operations is only allowed for text without shadow. For other ops, + // multiDraw isn't guaranteed to overdraw correctly + if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) { + if (intersects(op->computedState.clippedBounds)) return false; + } + + const BakedOpState* lhs = op; + const BakedOpState* rhs = mOps[0]; + + if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false; + + // Identical round rect clip state means both ops will clip in the same way, or not at all. + // As the state objects are const, we can compare their pointers to determine mergeability + if (lhs->roundRectClipState != rhs->roundRectClipState) return false; + + // Local masks prevent merge, since they're potentially in different coordinate spaces + if (lhs->computedState.localProjectionPathMask + || rhs->computedState.localProjectionPathMask) return false; + + /* Clipping compatibility check + * + * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its + * clip for that side. + */ + const int currentFlags = mClipSideFlags; + const int newFlags = op->computedState.clipSideFlags; + if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) { + const Rect& opBounds = op->computedState.clippedBounds; + float boundsDelta = mBounds.left - opBounds.left; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) return false; + boundsDelta = mBounds.top - opBounds.top; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false; + + // right and bottom delta calculation reversed to account for direction + boundsDelta = opBounds.right - mBounds.right; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) return false; + boundsDelta = opBounds.bottom - mBounds.bottom; + if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) return false; + } + + const SkPaint* newPaint = op->op->paint; + const SkPaint* oldPaint = mOps[0]->op->paint; + + if (newPaint == oldPaint) { + // if paints are equal, then modifiers + paint attribs don't need to be compared + return true; + } else if (newPaint && !oldPaint) { + return paintIsDefault(*newPaint); + } else if (!newPaint && oldPaint) { + return paintIsDefault(*oldPaint); + } + return paintsAreEquivalent(*newPaint, *oldPaint); + } + + void mergeOp(BakedOpState* op) { + mBounds.unionWith(op->computedState.clippedBounds); + mOps.push_back(op); + + // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat + // check, and doesn't extend past a side of the clip that's in use by the merged batch. + // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect. + mClipSideFlags |= op->computedState.clipSideFlags; + } + + int getClipSideFlags() const { return mClipSideFlags; } + const Rect& getClipRect() const { return mBounds; } + +private: + int mClipSideFlags; +}; + +LayerBuilder::LayerBuilder(uint32_t width, uint32_t height, + const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode) + : width(width) + , height(height) + , repaintRect(repaintRect) + , repaintClip(repaintRect) + , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr) + , beginLayerOp(beginLayerOp) + , renderNode(renderNode) {} + +// iterate back toward target to see if anything drawn since should overlap the new op +// if no target, merging ops still iterate to find similar batch to insert after +void LayerBuilder::locateInsertIndex(int batchId, const Rect& clippedBounds, + BatchBase** targetBatch, size_t* insertBatchIndex) const { + for (int i = mBatches.size() - 1; i >= 0; i--) { + BatchBase* overBatch = mBatches[i]; + + if (overBatch == *targetBatch) break; + + // TODO: also consider shader shared between batch types + if (batchId == overBatch->getBatchId()) { + *insertBatchIndex = i + 1; + if (!*targetBatch) break; // found insert position, quit + } + + if (overBatch->intersects(clippedBounds)) { + // NOTE: it may be possible to optimize for special cases where two operations + // of the same batch/paint could swap order, such as with a non-mergeable + // (clipped) and a mergeable text operation + *targetBatch = nullptr; + break; + } + } +} + +void LayerBuilder::deferLayerClear(const Rect& rect) { + mClearRects.push_back(rect); +} + +void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) { + if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) { + // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers, + // and issue them together in one draw. + flushLayerClears(allocator); + + if (CC_UNLIKELY(activeUnclippedSaveLayers.empty() + && bakedState->computedState.opaqueOverClippedBounds + && bakedState->computedState.clippedBounds.contains(repaintRect) + && !Properties::debugOverdraw)) { + // discard all deferred drawing ops, since new one will occlude them + clear(); + } + } +} + +void LayerBuilder::flushLayerClears(LinearAllocator& allocator) { + if (CC_UNLIKELY(!mClearRects.empty())) { + const int vertCount = mClearRects.size() * 4; + // put the verts in the frame allocator, since + // 1) SimpleRectsOps needs verts, not rects + // 2) even if mClearRects stored verts, std::vectors will move their contents + Vertex* const verts = (Vertex*) allocator.create_trivial_array<Vertex>(vertCount); + + Vertex* currentVert = verts; + Rect bounds = mClearRects[0]; + for (auto&& rect : mClearRects) { + bounds.unionWith(rect); + Vertex::set(currentVert++, rect.left, rect.top); + Vertex::set(currentVert++, rect.right, rect.top); + Vertex::set(currentVert++, rect.left, rect.bottom); + Vertex::set(currentVert++, rect.right, rect.bottom); + } + mClearRects.clear(); // discard rects before drawing so this method isn't reentrant + + // One or more unclipped saveLayers have been enqueued, with deferred clears. + // Flush all of these clears with a single draw + SkPaint* paint = allocator.create<SkPaint>(); + paint->setXfermodeMode(SkXfermode::kClear_Mode); + SimpleRectsOp* op = allocator.create_trivial<SimpleRectsOp>(bounds, + Matrix4::identity(), nullptr, paint, + verts, vertCount); + BakedOpState* bakedState = BakedOpState::directConstruct(allocator, + &repaintClip, bounds, *op); + deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices); + } +} + +void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator, + BakedOpState* op, batchid_t batchId) { + onDeferOp(allocator, op); + OpBatch* targetBatch = mBatchLookup[batchId]; + + size_t insertBatchIndex = mBatches.size(); + if (targetBatch) { + locateInsertIndex(batchId, op->computedState.clippedBounds, + (BatchBase**)(&targetBatch), &insertBatchIndex); + } + + if (targetBatch) { + targetBatch->batchOp(op); + } else { + // new non-merging batch + targetBatch = allocator.create<OpBatch>(batchId, op); + mBatchLookup[batchId] = targetBatch; + mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); + } +} + +void LayerBuilder::deferMergeableOp(LinearAllocator& allocator, + BakedOpState* op, batchid_t batchId, mergeid_t mergeId) { + onDeferOp(allocator, op); + MergingOpBatch* targetBatch = nullptr; + + // Try to merge with any existing batch with same mergeId + auto getResult = mMergingBatchLookup[batchId].find(mergeId); + if (getResult != mMergingBatchLookup[batchId].end()) { + targetBatch = getResult->second; + if (!targetBatch->canMergeWith(op)) { + targetBatch = nullptr; + } + } + + size_t insertBatchIndex = mBatches.size(); + locateInsertIndex(batchId, op->computedState.clippedBounds, + (BatchBase**)(&targetBatch), &insertBatchIndex); + + if (targetBatch) { + targetBatch->mergeOp(op); + } else { + // new merging batch + targetBatch = allocator.create<MergingOpBatch>(batchId, op); + mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch)); + + mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); + } +} + +void LayerBuilder::replayBakedOpsImpl(void* arg, + BakedOpReceiver* unmergedReceivers, MergedOpReceiver* mergedReceivers) const { + ATRACE_NAME("flush drawing commands"); + for (const BatchBase* batch : mBatches) { + size_t size = batch->getOps().size(); + if (size > 1 && batch->isMerging()) { + int opId = batch->getOps()[0]->op->opId; + const MergingOpBatch* mergingBatch = static_cast<const MergingOpBatch*>(batch); + MergedBakedOpList data = { + batch->getOps().data(), + size, + mergingBatch->getClipSideFlags(), + mergingBatch->getClipRect() + }; + mergedReceivers[opId](arg, data); + } else { + for (const BakedOpState* op : batch->getOps()) { + unmergedReceivers[op->op->opId](arg, *op); + } + } + } +} + +void LayerBuilder::clear() { + mBatches.clear(); + for (int i = 0; i < OpBatchType::Count; i++) { + mBatchLookup[i] = nullptr; + mMergingBatchLookup[i].clear(); + } +} + +void LayerBuilder::dump() const { + ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)", + this, width, height, offscreenBuffer, beginLayerOp, + renderNode, renderNode ? renderNode->getName() : "-"); + for (const BatchBase* batch : mBatches) { + batch->dump(); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/LayerBuilder.h b/libs/hwui/LayerBuilder.h new file mode 100644 index 000000000000..4de432c5e7be --- /dev/null +++ b/libs/hwui/LayerBuilder.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2016 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 "ClipArea.h" +#include "Rect.h" +#include "utils/Macros.h" + +#include <vector> +#include <unordered_map> + +struct SkRect; + +namespace android { +namespace uirenderer { + +class BakedOpState; +struct BeginLayerOp; +class BatchBase; +class LinearAllocator; +struct MergedBakedOpList; +class MergingOpBatch; +class OffscreenBuffer; +class OpBatch; +class RenderNode; + +typedef int batchid_t; +typedef const void* mergeid_t; + +namespace OpBatchType { + enum { + Bitmap, + MergedPatch, + AlphaVertices, + Vertices, + AlphaMaskTexture, + Text, + ColorText, + Shadow, + TextureLayer, + Functor, + CopyToLayer, + CopyFromLayer, + + Count // must be last + }; +} + +typedef void (*BakedOpReceiver)(void*, const BakedOpState&); +typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList); + +/** + * Stores the deferred render operations and state used to compute ordering + * for a single FBO/layer. + */ +class LayerBuilder { +// Prevent copy/assign because users may stash pointer to offscreenBuffer and viewportClip +PREVENT_COPY_AND_ASSIGN(LayerBuilder); +public: + // Create LayerBuilder for Fbo0 + LayerBuilder(uint32_t width, uint32_t height, const Rect& repaintRect) + : LayerBuilder(width, height, repaintRect, nullptr, nullptr) {}; + + // Create LayerBuilder for an offscreen layer, where beginLayerOp is present for a + // saveLayer, renderNode is present for a HW layer. + LayerBuilder(uint32_t width, uint32_t height, + const Rect& repaintRect, const BeginLayerOp* beginLayerOp, RenderNode* renderNode); + + // iterate back toward target to see if anything drawn since should overlap the new op + // if no target, merging ops still iterate to find similar batch to insert after + void locateInsertIndex(int batchId, const Rect& clippedBounds, + BatchBase** targetBatch, size_t* insertBatchIndex) const; + + void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId); + + // insertion point of a new batch, will hopefully be immediately after similar batch + // (generally, should be similar shader) + void deferMergeableOp(LinearAllocator& allocator, + BakedOpState* op, batchid_t batchId, mergeid_t mergeId); + + void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const; + + void deferLayerClear(const Rect& dstRect); + + bool empty() const { + return mBatches.empty(); + } + + void clear(); + + void dump() const; + + const uint32_t width; + const uint32_t height; + const Rect repaintRect; + const ClipRect repaintClip; + OffscreenBuffer* offscreenBuffer; + const BeginLayerOp* beginLayerOp; + const RenderNode* renderNode; + + // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps + std::vector<BakedOpState*> activeUnclippedSaveLayers; +private: + void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState); + void flushLayerClears(LinearAllocator& allocator); + + std::vector<BatchBase*> mBatches; + + /** + * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen + * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not + * collide, which avoids the need to resolve mergeid collisions. + */ + std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count]; + + // Maps batch ids to the most recent *non-merging* batch of that id + OpBatch* mBatchLookup[OpBatchType::Count] = { nullptr }; + + std::vector<Rect> mClearRects; +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp index bcbd4129b7e7..f5681ce712d5 100644 --- a/libs/hwui/LayerCache.cpp +++ b/libs/hwui/LayerCache.cpp @@ -14,15 +14,14 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" +#include "LayerCache.h" -#include <GLES2/gl2.h> +#include "Caches.h" +#include "Properties.h" #include <utils/Log.h> -#include "Caches.h" -#include "LayerCache.h" -#include "Properties.h" +#include <GLES2/gl2.h> namespace android { namespace uirenderer { @@ -31,15 +30,9 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -LayerCache::LayerCache(): mSize(0), mMaxSize(MB(DEFAULT_LAYER_CACHE_SIZE)) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_LAYER_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting layer cache size to %sMB", property); - setMaxSize(MB(atof(property))); - } else { - INIT_LOGD(" Using default layer cache size of %.2fMB", DEFAULT_LAYER_CACHE_SIZE); - } -} +LayerCache::LayerCache() + : mSize(0) + , mMaxSize(Properties::layerPoolSize) {} LayerCache::~LayerCache() { clear(); @@ -83,15 +76,14 @@ void LayerCache::deleteLayer(Layer* layer) { LAYER_LOGD("Destroying layer %dx%d, fbo %d", layer->getWidth(), layer->getHeight(), layer->getFbo()); mSize -= layer->getWidth() * layer->getHeight() * 4; - layer->state = Layer::kState_DeletedFromCache; + layer->state = Layer::State::DeletedFromCache; layer->decStrong(nullptr); } } void LayerCache::clear() { - size_t count = mCache.size(); - for (size_t i = 0; i < count; i++) { - deleteLayer(mCache.itemAt(i).mLayer); + for (auto entry : mCache) { + deleteLayer(entry.mLayer); } mCache.clear(); } @@ -100,27 +92,26 @@ Layer* LayerCache::get(RenderState& renderState, const uint32_t width, const uin Layer* layer = nullptr; LayerEntry entry(width, height); - ssize_t index = mCache.indexOf(entry); + auto iter = mCache.find(entry); - if (index >= 0) { - entry = mCache.itemAt(index); - mCache.removeAt(index); + if (iter != mCache.end()) { + entry = *iter; + mCache.erase(iter); layer = entry.mLayer; - layer->state = Layer::kState_RemovedFromCache; + layer->state = Layer::State::RemovedFromCache; mSize -= layer->getWidth() * layer->getHeight() * 4; LAYER_LOGD("Reusing layer %dx%d", layer->getWidth(), layer->getHeight()); } else { LAYER_LOGD("Creating new layer %dx%d", entry.mWidth, entry.mHeight); - layer = new Layer(Layer::kType_DisplayList, renderState, entry.mWidth, entry.mHeight); + layer = new Layer(Layer::Type::DisplayList, renderState, entry.mWidth, entry.mHeight); layer->setBlend(true); layer->generateTexture(); layer->bindTexture(); layer->setFilter(GL_NEAREST); layer->setWrap(GL_CLAMP_TO_EDGE, false); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); #if DEBUG_LAYERS dump(); @@ -131,9 +122,7 @@ Layer* LayerCache::get(RenderState& renderState, const uint32_t width, const uin } void LayerCache::dump() { - size_t size = mCache.size(); - for (size_t i = 0; i < size; i++) { - const LayerEntry& entry = mCache.itemAt(i); + for (auto entry : mCache) { ALOGD(" Layer size %dx%d", entry.mWidth, entry.mHeight); } } @@ -146,13 +135,9 @@ bool LayerCache::put(Layer* layer) { if (size < mMaxSize) { // TODO: Use an LRU while (mSize + size > mMaxSize) { - size_t position = 0; -#if LAYER_REMOVE_BIGGEST_FIRST - position = mCache.size() - 1; -#endif - Layer* victim = mCache.itemAt(position).mLayer; + Layer* victim = mCache.begin()->mLayer; deleteLayer(victim); - mCache.removeAt(position); + mCache.erase(mCache.begin()); LAYER_LOGD(" Deleting layer %.2fx%.2f", victim->layer.getWidth(), victim->layer.getHeight()); @@ -162,14 +147,14 @@ bool LayerCache::put(Layer* layer) { LayerEntry entry(layer); - mCache.add(entry); + mCache.insert(entry); mSize += size; - layer->state = Layer::kState_InCache; + layer->state = Layer::State::InCache; return true; } - layer->state = Layer::kState_FailedToCache; + layer->state = Layer::State::FailedToCache; return false; } diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h index 7d17b9ba41aa..6fe7b3aae859 100644 --- a/libs/hwui/LayerCache.h +++ b/libs/hwui/LayerCache.h @@ -19,7 +19,8 @@ #include "Debug.h" #include "Layer.h" -#include "utils/SortedList.h" + +#include <set> namespace android { namespace uirenderer { @@ -118,12 +119,8 @@ private: return compare(*this, other) != 0; } - friend inline int strictly_order_type(const LayerEntry& lhs, const LayerEntry& rhs) { - return LayerEntry::compare(lhs, rhs) < 0; - } - - friend inline int compare_type(const LayerEntry& lhs, const LayerEntry& rhs) { - return LayerEntry::compare(lhs, rhs); + bool operator<(const LayerEntry& other) const { + return LayerEntry::compare(*this, other) < 0; } Layer* mLayer; @@ -133,7 +130,7 @@ private: void deleteLayer(Layer* layer); - SortedList<LayerEntry> mCache; + std::multiset<LayerEntry> mCache; uint32_t mSize; uint32_t mMaxSize; diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index 00add2903371..137316f57725 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -43,8 +43,8 @@ LayerRenderer::LayerRenderer(RenderState& renderState, Layer* layer) LayerRenderer::~LayerRenderer() { } -void LayerRenderer::prepareDirty(float left, float top, float right, float bottom, - bool opaque) { +void LayerRenderer::prepareDirty(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque) { LAYER_RENDERER_LOGD("Rendering into layer, fbo = %d", mLayer->getFbo()); mRenderState.bindFramebuffer(mLayer->getFbo()); @@ -58,13 +58,14 @@ void LayerRenderer::prepareDirty(float left, float top, float right, float botto mLayer->region.clear(); dirty.set(0.0f, 0.0f, width, height); } else { - dirty.intersect(0.0f, 0.0f, width, height); + dirty.doIntersect(0.0f, 0.0f, width, height); android::Rect r(dirty.left, dirty.top, dirty.right, dirty.bottom); mLayer->region.subtractSelf(r); } mLayer->clipRect.set(dirty); - OpenGLRenderer::prepareDirty(dirty.left, dirty.top, dirty.right, dirty.bottom, opaque); + OpenGLRenderer::prepareDirty(viewportWidth, viewportHeight, + dirty.left, dirty.top, dirty.right, dirty.bottom, opaque); } void LayerRenderer::clear(float left, float top, float right, float bottom, bool opaque) { @@ -188,7 +189,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width LAYER_RENDERER_LOGD("Requesting new render layer %dx%d", width, height); Caches& caches = Caches::getInstance(); - GLuint fbo = caches.fboCache.get(); + GLuint fbo = renderState.createFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); return nullptr; @@ -203,7 +204,7 @@ Layer* LayerRenderer::createRenderLayer(RenderState& renderState, uint32_t width // We first obtain a layer before comparing against the max texture size // because layers are not allocated at the exact desired size. They are - // always created slighly larger to improve recycling + // always created slightly larger to improve recycling const uint32_t maxTextureSize = caches.maxTextureSize; if (layer->getWidth() > maxTextureSize || layer->getHeight() > maxTextureSize) { ALOGW("Layer exceeds max. dimensions supported by the GPU (%dx%d, max=%dx%d)", @@ -272,7 +273,7 @@ bool LayerRenderer::resizeLayer(Layer* layer, uint32_t width, uint32_t height) { Layer* LayerRenderer::createTextureLayer(RenderState& renderState) { LAYER_RENDERER_LOGD("Creating new texture layer"); - Layer* layer = new Layer(Layer::kType_Texture, renderState, 0, 0); + Layer* layer = new Layer(Layer::Type::Texture, renderState, 0, 0); layer->setCacheable(false); layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f); layer->texCoords.set(0.0f, 1.0f, 1.0f, 0.0f); @@ -286,7 +287,7 @@ Layer* LayerRenderer::createTextureLayer(RenderState& renderState) { } void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height, - bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform) { + bool isOpaque, bool forceFilter, GLenum renderTarget, const float* textureTransform) { if (layer) { layer->setBlend(!isOpaque); layer->setForceFilter(forceFilter); @@ -352,11 +353,11 @@ void LayerRenderer::flushLayer(RenderState& renderState, Layer* layer) { bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap) { Caches& caches = Caches::getInstance(); - if (layer + if (layer && layer->isRenderable() && bitmap->width() <= caches.maxTextureSize && bitmap->height() <= caches.maxTextureSize) { - GLuint fbo = caches.fboCache.get(); + GLuint fbo = renderState.createFramebuffer(); if (!fbo) { ALOGW("Could not obtain an FBO"); return false; @@ -372,7 +373,6 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* GLenum format; GLenum type; - GLenum error = GL_NO_ERROR; bool status = false; switch (bitmap->colorType()) { @@ -407,7 +407,6 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* renderState.bindFramebuffer(fbo); glGenTextures(1, &texture); - if ((error = glGetError()) != GL_NO_ERROR) goto error; caches.textureState().activateTexture(0); caches.textureState().bindTexture(texture); @@ -422,24 +421,19 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* glTexImage2D(GL_TEXTURE_2D, 0, format, bitmap->width(), bitmap->height(), 0, format, type, nullptr); - if ((error = glGetError()) != GL_NO_ERROR) goto error; glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - if ((error = glGetError()) != GL_NO_ERROR) goto error; { LayerRenderer renderer(renderState, layer); - renderer.setViewport(bitmap->width(), bitmap->height()); - renderer.OpenGLRenderer::prepareDirty(0.0f, 0.0f, - bitmap->width(), bitmap->height(), !layer->isBlend()); + renderer.OpenGLRenderer::prepareDirty(bitmap->width(), bitmap->height(), + 0.0f, 0.0f, bitmap->width(), bitmap->height(), !layer->isBlend()); renderState.scissor().setEnabled(false); renderer.translate(0.0f, bitmap->height()); renderer.scale(1.0f, -1.0f); - if ((error = glGetError()) != GL_NO_ERROR) goto error; - { Rect bounds; bounds.set(0.0f, 0.0f, bitmap->width(), bitmap->height()); @@ -448,26 +442,20 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels()); - if ((error = glGetError()) != GL_NO_ERROR) goto error; } status = true; } -error: -#if DEBUG_OPENGL - if (error != GL_NO_ERROR) { - ALOGD("GL error while copying layer into bitmap = 0x%x", error); - } -#endif - renderState.bindFramebuffer(previousFbo); layer->setAlpha(alpha, mode); layer->setFbo(previousLayerFbo); caches.textureState().deleteTexture(texture); - caches.fboCache.put(fbo); + renderState.deleteFramebuffer(fbo); renderState.setViewport(previousViewportWidth, previousViewportHeight); + GL_CHECKPOINT(MODERATE); + return status; } return false; diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h index 47ded7e7e0bf..38c3705cfa25 100644 --- a/libs/hwui/LayerRenderer.h +++ b/libs/hwui/LayerRenderer.h @@ -50,8 +50,8 @@ public: virtual ~LayerRenderer(); virtual void onViewportInitialized() override { /* do nothing */ } - virtual void prepareDirty(float left, float top, float right, float bottom, - bool opaque) override; + virtual void prepareDirty(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque) override; virtual void clear(float left, float top, float right, float bottom, bool opaque) override; virtual bool finish() override; @@ -59,7 +59,7 @@ public: static Layer* createRenderLayer(RenderState& renderState, uint32_t width, uint32_t height); static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height); static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height, - bool isOpaque, bool forceFilter, GLenum renderTarget, float* textureTransform); + bool isOpaque, bool forceFilter, GLenum renderTarget, const float* textureTransform); static void destroyLayer(Layer* layer); static bool copyLayer(RenderState& renderState, Layer* layer, SkBitmap* bitmap); diff --git a/libs/hwui/LayerUpdateQueue.cpp b/libs/hwui/LayerUpdateQueue.cpp new file mode 100644 index 000000000000..db5f676d09dc --- /dev/null +++ b/libs/hwui/LayerUpdateQueue.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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 "LayerUpdateQueue.h" + +#include "RenderNode.h" + +namespace android { +namespace uirenderer { + +void LayerUpdateQueue::clear() { + mEntries.clear(); +} + +void LayerUpdateQueue::enqueueLayerWithDamage(RenderNode* renderNode, Rect damage) { + damage.doIntersect(0, 0, renderNode->getWidth(), renderNode->getHeight()); + if (!damage.isEmpty()) { + for (Entry& entry : mEntries) { + if (CC_UNLIKELY(entry.renderNode == renderNode)) { + entry.damage.unionWith(damage); + return; + } + } + mEntries.emplace_back(renderNode, damage); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h new file mode 100644 index 000000000000..5b1a8543dd0d --- /dev/null +++ b/libs/hwui/LayerUpdateQueue.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_LAYER_UPDATE_QUEUE_H +#define ANDROID_HWUI_LAYER_UPDATE_QUEUE_H + +#include "Rect.h" +#include "utils/Macros.h" + +#include <vector> +#include <unordered_map> + +namespace android { +namespace uirenderer { + +class RenderNode; + +class LayerUpdateQueue { + PREVENT_COPY_AND_ASSIGN(LayerUpdateQueue); +public: + struct Entry { + Entry(RenderNode* renderNode, const Rect& damage) + : renderNode(renderNode) + , damage(damage) {} + RenderNode* renderNode; + Rect damage; + }; + + LayerUpdateQueue() {} + void enqueueLayerWithDamage(RenderNode* renderNode, Rect dirty); + void clear(); + const std::vector<Entry>& entries() const { return mEntries; } +private: + std::vector<Entry> mEntries; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_LAYER_UPDATE_QUEUE_H diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp index 06e67c0e85ad..709156c4098b 100644 --- a/libs/hwui/Matrix.cpp +++ b/libs/hwui/Matrix.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <math.h> #include <stdlib.h> #include <string.h> @@ -154,10 +152,6 @@ void Matrix4::load(const float* v) { mType = kTypeUnknown; } -void Matrix4::load(const Matrix4& v) { - *this = v; -} - void Matrix4::load(const SkMatrix& v) { memset(data, 0, sizeof(data)); @@ -443,6 +437,14 @@ void Matrix4::mapPoint(float& x, float& y) const { y = dy * dz; } +/** + * Set the contents of the rect to be the bounding rect around each of the corners, mapped by the + * matrix. + * + * NOTE: an empty rect to an arbitrary matrix isn't guaranteed to have an empty output, since that's + * important for conservative bounds estimation (e.g. rotate45Matrix.mapRect of Rect(0, 10) should + * result in non-empty. + */ void Matrix4::mapRect(Rect& r) const { if (isIdentity()) return; diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index ed54a25f3edf..9cde5d6aa04e 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -14,14 +14,14 @@ * limitations under the License. */ -#ifndef ANDROID_HWUI_MATRIX_H -#define ANDROID_HWUI_MATRIX_H +#pragma once -#include <SkMatrix.h> +#include "Rect.h" #include <cutils/compiler.h> - -#include "Rect.h" +#include <iomanip> +#include <ostream> +#include <SkMatrix.h> namespace android { namespace uirenderer { @@ -114,7 +114,6 @@ public: void loadIdentity(); void load(const float* v); - void load(const Matrix4& v); void load(const SkMatrix& v); void loadInverse(const Matrix4& v); @@ -127,6 +126,9 @@ public: void loadMultiply(const Matrix4& u, const Matrix4& v); void loadOrtho(float left, float right, float bottom, float top, float near, float far); + void loadOrtho(int width, int height) { + loadOrtho(0, width, height, 0, -1, 1); + } uint8_t getType() const; @@ -137,9 +139,11 @@ public: } void multiply(const Matrix4& v) { - Matrix4 u; - u.loadMultiply(*this, v); - load(u); + if (!v.isIdentity()) { + Matrix4 u; + u.loadMultiply(*this, v); + *this = u; + } } void multiply(float v); @@ -214,8 +218,26 @@ public: void dump(const char* label = nullptr) const; + friend std::ostream& operator<<(std::ostream& os, const Matrix4& matrix) { + if (matrix.isSimple()) { + os << "offset " << matrix.getTranslateX() << "x" << matrix.getTranslateY(); + if (!matrix.isPureTranslate()) { + os << ", scale " << matrix[kScaleX] << "x" << matrix[kScaleY]; + } + } else { + os << "[" << matrix[0]; + for (int i = 1; i < 16; i++) { + os << ", " << matrix[i]; + } + os << "]"; + } + return os; + } + static const Matrix4& identity(); + void invalidateType() { mType = kTypeUnknown; } + private: mutable uint8_t mType; @@ -240,4 +262,3 @@ typedef Matrix4 mat4; }; // namespace uirenderer }; // namespace android -#endif // ANDROID_HWUI_MATRIX_H diff --git a/libs/hwui/OpDumper.cpp b/libs/hwui/OpDumper.cpp new file mode 100644 index 000000000000..cab93e8faf6a --- /dev/null +++ b/libs/hwui/OpDumper.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 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 "OpDumper.h" + +#include "RecordedOp.h" + +namespace android { +namespace uirenderer { + +#define STRINGIFY(n) #n, +static const char* sOpNameLut[] = BUILD_FULL_OP_LUT(STRINGIFY); + +void OpDumper::dump(const RecordedOp& op, std::ostream& output, int level) { + for (int i = 0; i < level; i++) { + output << " "; + } + + Rect localBounds(op.unmappedBounds); + op.localMatrix.mapRect(localBounds); + output << sOpNameLut[op.opId] << " " << localBounds; + + if (op.localClip + && (!op.localClip->rect.contains(localBounds) || op.localClip->intersectWithRoot)) { + output << std::fixed << std::setprecision(0) + << " clip=" << op.localClip->rect + << " mode=" << (int)op.localClip->mode; + + if (op.localClip->intersectWithRoot) { + output << " iwr"; + } + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/OpDumper.h b/libs/hwui/OpDumper.h new file mode 100644 index 000000000000..c99b51796b5c --- /dev/null +++ b/libs/hwui/OpDumper.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2016 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 <ostream> + +namespace android { +namespace uirenderer { + +struct RecordedOp; + +class OpDumper { +public: + static void dump(const RecordedOp& op, std::ostream& output, int level = 0); +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 2292ef415cfc..53ea7fa6f77d 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include <GpuMemoryTracker.h> #include "OpenGLRenderer.h" #include "DeferredDisplayList.h" @@ -30,6 +31,7 @@ #include "SkiaShader.h" #include "Vector.h" #include "VertexBuffer.h" +#include "hwui/Canvas.h" #include "utils/GLUtils.h" #include "utils/PaintUtils.h" #include "utils/TraceUtils.h" @@ -38,8 +40,8 @@ #include <stdint.h> #include <sys/types.h> -#include <SkCanvas.h> #include <SkColor.h> +#include <SkPaintDefaults.h> #include <SkPathOps.h> #include <SkShader.h> #include <SkTypeface.h> @@ -70,8 +72,6 @@ OpenGLRenderer::OpenGLRenderer(RenderState& renderState) , mRenderState(renderState) , mFrameStarted(false) , mScissorOptimizationDisabled(false) - , mSuppressTiling(false) - , mFirstFrameAfterResize(true) , mDirty(false) , mLightCenter((Vector3){FLT_MIN, FLT_MIN, FLT_MIN}) , mLightRadius(FLT_MIN) @@ -113,13 +113,13 @@ void OpenGLRenderer::setLightCenter(const Vector3& lightCenter) { void OpenGLRenderer::onViewportInitialized() { glDisable(GL_DITHER); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - mFirstFrameAfterResize = true; } -void OpenGLRenderer::setupFrameState(float left, float top, - float right, float bottom, bool opaque) { +void OpenGLRenderer::setupFrameState(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque) { mCaches.clearGarbage(); - mState.initializeSaveStack(left, top, right, bottom, mLightCenter); + mState.initializeSaveStack(viewportWidth, viewportHeight, + left, top, right, bottom, mLightCenter); mOpaque = opaque; mTilingClip.set(left, top, right, bottom); } @@ -134,25 +134,16 @@ void OpenGLRenderer::startFrame() { mRenderState.setViewport(mState.getWidth(), mState.getHeight()); - // Functors break the tiling extension in pretty spectacular ways - // This ensures we don't use tiling when a functor is going to be - // invoked during the frame - mSuppressTiling = mCaches.hasRegisteredFunctors() - || mFirstFrameAfterResize; - mFirstFrameAfterResize = false; - - startTilingCurrentClip(true); - debugOverdraw(true, true); clear(mTilingClip.left, mTilingClip.top, mTilingClip.right, mTilingClip.bottom, mOpaque); } -void OpenGLRenderer::prepareDirty(float left, float top, - float right, float bottom, bool opaque) { +void OpenGLRenderer::prepareDirty(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque) { - setupFrameState(left, top, right, bottom, opaque); + setupFrameState(viewportWidth, viewportHeight, left, top, right, bottom, opaque); // Layer renderers will start the frame immediately // The framebuffer renderer will first defer the display list @@ -192,46 +183,8 @@ void OpenGLRenderer::clear(float left, float top, float right, float bottom, boo mRenderState.scissor().reset(); } -void OpenGLRenderer::startTilingCurrentClip(bool opaque, bool expand) { - if (!mSuppressTiling) { - const Snapshot* snapshot = currentSnapshot(); - - const Rect* clip = &mTilingClip; - if (snapshot->flags & Snapshot::kFlagFboTarget) { - clip = &(snapshot->layer->clipRect); - } - - startTiling(*clip, getViewportHeight(), opaque, expand); - } -} - -void OpenGLRenderer::startTiling(const Rect& clip, int windowHeight, bool opaque, bool expand) { - if (!mSuppressTiling) { - if(expand) { - // Expand the startTiling region by 1 - int leftNotZero = (clip.left > 0) ? 1 : 0; - int topNotZero = (windowHeight - clip.bottom > 0) ? 1 : 0; - - mCaches.startTiling( - clip.left - leftNotZero, - windowHeight - clip.bottom - topNotZero, - clip.right - clip.left + leftNotZero + 1, - clip.bottom - clip.top + topNotZero + 1, - opaque); - } else { - mCaches.startTiling(clip.left, windowHeight - clip.bottom, - clip.right - clip.left, clip.bottom - clip.top, opaque); - } - } -} - -void OpenGLRenderer::endTiling() { - if (!mSuppressTiling) mCaches.endTiling(); -} - bool OpenGLRenderer::finish() { renderOverdraw(); - endTiling(); mTempPaths.clear(); // When finish() is invoked on FBO 0 we've reached the end @@ -242,12 +195,11 @@ bool OpenGLRenderer::finish() { } if (!suppressErrorChecks()) { -#if DEBUG_OPENGL - GLUtils::dumpGLErrors(); -#endif + GL_CHECKPOINT(MODERATE); #if DEBUG_MEMORY_USAGE mCaches.dumpMemoryUsage(); + GPUMemoryTracker::dump(); #else if (Properties::debugLevel & kDebugMemory) { mCaches.dumpMemoryUsage(); @@ -272,7 +224,7 @@ void OpenGLRenderer::resumeAfterLayer() { void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { if (mState.currentlyIgnored()) return; - Rect clip(mState.currentClipRect()); + Rect clip(mState.currentRenderTargetClip()); clip.snapToPixelBoundaries(); // Since we don't know what the functor will draw, let's dirty @@ -381,7 +333,6 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { && layer->renderNode.get() && layer->renderNode->isRenderable()) { if (inFrame) { - endTiling(); debugOverdraw(false, false); } @@ -393,7 +344,6 @@ bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { if (inFrame) { resumeAfterLayer(); - startTilingCurrentClip(); } layer->debugDrawUpdate = Properties::debugLayersUpdates; @@ -419,7 +369,7 @@ void OpenGLRenderer::updateLayers() { // Note: it is very important to update the layers in order for (int i = 0; i < count; i++) { - Layer* layer = mLayerUpdates.itemAt(i).get(); + Layer* layer = mLayerUpdates[i].get(); updateLayer(layer, false); } @@ -438,7 +388,7 @@ void OpenGLRenderer::flushLayers() { // Note: it is very important to update the layers in order for (int i = 0; i < count; i++) { - mLayerUpdates.itemAt(i)->flush(); + mLayerUpdates[i]->flush(); } mLayerUpdates.clear(); @@ -455,7 +405,7 @@ void OpenGLRenderer::pushLayerUpdate(Layer* layer) { // the insertion order. The linear search is not an issue since // this list is usually very short (typically one item, at most a few) for (int i = mLayerUpdates.size() - 1; i >= 0; i--) { - if (mLayerUpdates.itemAt(i) == layer) { + if (mLayerUpdates[i] == layer) { return; } } @@ -466,8 +416,8 @@ void OpenGLRenderer::pushLayerUpdate(Layer* layer) { void OpenGLRenderer::cancelLayerUpdate(Layer* layer) { if (layer) { for (int i = mLayerUpdates.size() - 1; i >= 0; i--) { - if (mLayerUpdates.itemAt(i) == layer) { - mLayerUpdates.removeAt(i); + if (mLayerUpdates[i] == layer) { + mLayerUpdates.erase(mLayerUpdates.begin() + i); break; } } @@ -522,7 +472,7 @@ void OpenGLRenderer::onSnapshotRestored(const Snapshot& removed, const Snapshot& int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, int flags, const SkPath* convexMask) { // force matrix/clip isolation for layer - flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag; + flags |= SaveFlags::MatrixClip; const int count = mState.saveSnapshot(flags); @@ -539,7 +489,8 @@ void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool currentTransform()->mapRect(bounds); // Layers only make sense if they are in the framebuffer's bounds - if (bounds.intersect(mState.currentClipRect())) { + bounds.doIntersect(mState.currentRenderTargetClip()); + if (!bounds.isEmpty()) { // We cannot work with sub-pixels in this case bounds.snapToPixelBoundaries(); @@ -548,23 +499,20 @@ void OpenGLRenderer::calculateLayerBoundsAndClip(Rect& bounds, Rect& clip, bool // of the framebuffer const Snapshot& previous = *(currentSnapshot()->previous); Rect previousViewport(0, 0, previous.getViewportWidth(), previous.getViewportHeight()); - if (!bounds.intersect(previousViewport)) { - bounds.setEmpty(); - } else if (fboLayer) { + + bounds.doIntersect(previousViewport); + if (!bounds.isEmpty() && fboLayer) { clip.set(bounds); mat4 inverse; inverse.loadInverse(*currentTransform()); inverse.mapRect(clip); clip.snapToPixelBoundaries(); - if (clip.intersect(untransformedBounds)) { + clip.doIntersect(untransformedBounds); + if (!clip.isEmpty()) { clip.translate(-untransformedBounds.left, -untransformedBounds.top); bounds.set(untransformedBounds); - } else { - clip.setEmpty(); } } - } else { - bounds.setEmpty(); } } @@ -583,7 +531,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float const SkPaint* paint, int flags) { const int count = mState.saveSnapshot(flags); - if (!mState.currentlyIgnored() && (flags & SkCanvas::kClipToLayer_SaveFlag)) { + if (!mState.currentlyIgnored() && (flags & SaveFlags::ClipToLayer)) { // initialize the snapshot as though it almost represents an FBO layer so deferred draw // operations will be able to store and restore the current clip and transform info, and // quick rejection will be correct (for display lists) @@ -591,7 +539,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float Rect bounds(left, top, right, bottom); Rect clip; calculateLayerBoundsAndClip(bounds, clip, true); - updateSnapshotIgnoreForLayer(bounds, clip, true, getAlphaDirect(paint)); + updateSnapshotIgnoreForLayer(bounds, clip, true, PaintUtils::getAlphaDirect(paint)); if (!mState.currentlyIgnored()) { writableSnapshot()->resetTransform(-bounds.left, -bounds.top, 0.0f); @@ -610,7 +558,7 @@ int OpenGLRenderer::saveLayerDeferred(float left, float top, float right, float * and the frame buffer still receive every drawing command. For instance, if a * layer is created and a shape intersecting the bounds of the layers and the * framebuffer is draw, the shape will be drawn on both (unless the layer was - * created with the SkCanvas::kClipToLayer_SaveFlag flag.) + * created with the SaveFlags::ClipToLayer flag.) * * A way to implement layers is to create an FBO for each layer, backed by an RGBA * texture. Unfortunately, this is inefficient as it requires every primitive to @@ -660,13 +608,13 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto LAYER_LOGD("Requesting layer %.2fx%.2f", right - left, bottom - top); LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize()); - const bool fboLayer = flags & SkCanvas::kClipToLayer_SaveFlag; + const bool fboLayer = flags & SaveFlags::ClipToLayer; // Window coordinates of the layer Rect clip; Rect bounds(left, top, right, bottom); calculateLayerBoundsAndClip(bounds, clip, fboLayer); - updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, getAlphaDirect(paint)); + updateSnapshotIgnoreForLayer(bounds, clip, fboLayer, PaintUtils::getAlphaDirect(paint)); // Bail out if we won't draw in this snapshot if (mState.currentlyIgnored()) { @@ -726,7 +674,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) { layer->clipRect.set(clip); - layer->setFbo(mCaches.fboCache.get()); + layer->setFbo(mRenderState.createFramebuffer()); writableSnapshot()->region = &writableSnapshot()->layer->region; writableSnapshot()->flags |= Snapshot::kFlagFboTarget | Snapshot::kFlagIsFboLayer; @@ -736,7 +684,6 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) { writableSnapshot()->initializeViewport(bounds.getWidth(), bounds.getHeight()); writableSnapshot()->roundRectClipState = nullptr; - endTiling(); debugOverdraw(false, false); // Bind texture to FBO mRenderState.bindFramebuffer(layer->getFbo()); @@ -751,9 +698,6 @@ bool OpenGLRenderer::createFboLayer(Layer* layer, Rect& bounds, Rect& clip) { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, layer->getTextureId(), 0); - // Expand the startTiling region by 1 - startTilingCurrentClip(true, true); - // Clear the FBO, expand the clear region by 1 to get nice bilinear filtering mRenderState.scissor().setEnabled(true); mRenderState.scissor().set(clip.left - 1.0f, bounds.getHeight() - clip.bottom - 1.0f, @@ -786,8 +730,6 @@ void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& resto mRenderState.scissor().setEnabled(mScissorOptimizationDisabled || clipRequired); if (fboLayer) { - endTiling(); - // Detach the texture from the FBO glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); @@ -796,8 +738,6 @@ void OpenGLRenderer::composeLayer(const Snapshot& removed, const Snapshot& resto // Unbind current FBO and restore previous one mRenderState.bindFramebuffer(restored.fbo); debugOverdraw(true, false); - - startTilingCurrentClip(); } if (!fboLayer && layer->getAlpha() < 255) { @@ -950,7 +890,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) { if (CC_UNLIKELY(layer->region.isEmpty())) return; // nothing to draw if (layer->getConvexMask()) { - save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + save(SaveFlags::MatrixClip); // clip to the area of the layer the mask can be larger clipRect(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kIntersect_Op); @@ -1097,7 +1037,8 @@ void OpenGLRenderer::dirtyLayer(const float left, const float top, } void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) { - if (CC_LIKELY(!bounds.isEmpty() && bounds.intersect(mState.currentClipRect()))) { + bounds.doIntersect(mState.currentRenderTargetClip()); + if (!bounds.isEmpty()) { bounds.snapToPixelBoundaries(); android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom); if (!dirty.isEmpty()) { @@ -1144,7 +1085,7 @@ void OpenGLRenderer::clearLayerRegions() { .setMeshIndexedQuads(&mesh[0], quadCount) .setFillClear() .setTransform(*currentSnapshot(), transformFlags) - .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getClipRect())) + .setModelViewOffsetRect(0, 0, Rect(currentSnapshot()->getRenderTargetClip())) .build(); renderGlop(glop, GlopRenderType::LayerClear); @@ -1159,7 +1100,7 @@ void OpenGLRenderer::clearLayerRegions() { /////////////////////////////////////////////////////////////////////////////// bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) { - const Rect& currentClip = mState.currentClipRect(); + const Rect& currentClip = mState.currentRenderTargetClip(); const mat4* currentMatrix = currentTransform(); if (stateDeferFlags & kStateDeferFlag_Draw) { @@ -1171,7 +1112,8 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef // is used, it should more closely duplicate the quickReject logic (in how it uses // snapToPixelBoundaries) - if (!clippedBounds.intersect(currentClip)) { + clippedBounds.doIntersect(currentClip); + if (clippedBounds.isEmpty()) { // quick rejected return true; } @@ -1201,20 +1143,24 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef // Transform and alpha always deferred, since they are used by state operations // (Note: saveLayer/restore use colorFilter and alpha, so we just save restore everything) - state.mMatrix.load(*currentMatrix); + state.mMatrix = *currentMatrix; state.mAlpha = currentSnapshot()->alpha; // always store/restore, since these are just pointers state.mRoundRectClipState = currentSnapshot()->roundRectClipState; +#if !HWUI_NEW_OPS state.mProjectionPathMask = currentSnapshot()->projectionPathMask; +#endif return false; } void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore) { - setMatrix(state.mMatrix); + setGlobalMatrix(state.mMatrix); writableSnapshot()->alpha = state.mAlpha; writableSnapshot()->roundRectClipState = state.mRoundRectClipState; +#if !HWUI_NEW_OPS writableSnapshot()->projectionPathMask = state.mProjectionPathMask; +#endif if (state.mClipValid && !skipClipRestore) { writableSnapshot()->setClip(state.mClip.left, state.mClip.top, @@ -1246,7 +1192,7 @@ void OpenGLRenderer::setupMergedMultiDraw(const Rect* clipRect) { /////////////////////////////////////////////////////////////////////////////// void OpenGLRenderer::setScissorFromClip() { - Rect clip(mState.currentClipRect()); + Rect clip(mState.currentRenderTargetClip()); clip.snapToPixelBoundaries(); if (mRenderState.scissor().set(clip.left, getViewportHeight() - clip.bottom, @@ -1267,17 +1213,10 @@ void OpenGLRenderer::ensureStencilBuffer() { void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) { // The layer's FBO is already bound when we reach this stage if (!layer->getStencilRenderBuffer()) { - // GL_QCOM_tiled_rendering doesn't like it if a renderbuffer - // is attached after we initiated tiling. We must turn it off, - // attach the new render buffer then turn tiling back on - endTiling(); - RenderBuffer* buffer = mCaches.renderBufferCache.get( Stencil::getLayerStencilFormat(), layer->getWidth(), layer->getHeight()); layer->setStencilRenderBuffer(buffer); - - startTiling(layer->clipRect, layer->layer.getHeight()); } } @@ -1308,9 +1247,8 @@ void OpenGLRenderer::drawRectangleList(const RectangleList& rectangleList) { Rect bounds = tr.getBounds(); if (transform.rectToRect()) { transform.mapRect(bounds); - if (!bounds.intersect(scissorBox)) { - bounds.setEmpty(); - } else { + bounds.doIntersect(scissorBox); + if (!bounds.isEmpty()) { handlePointNoTransform(rectangleVertices, bounds.left, bounds.top); handlePointNoTransform(rectangleVertices, bounds.right, bounds.top); handlePointNoTransform(rectangleVertices, bounds.left, bounds.bottom); @@ -1471,11 +1409,13 @@ void OpenGLRenderer::renderGlop(const Glop& glop, GlopRenderType type) { setStencilFromClip(); } - mRenderState.render(glop); + mRenderState.render(glop, currentSnapshot()->getOrthoMatrix()); if (type == GlopRenderType::Standard && !mRenderState.stencil().isWriteEnabled()) { // TODO: specify more clearly when a draw should dirty the layer. // is writing to the stencil the only time we should ignore this? +#if !HWUI_NEW_OPS dirtyLayer(glop.bounds.left, glop.bounds.top, glop.bounds.right, glop.bounds.bottom); +#endif mDirty = true; } } @@ -1497,10 +1437,7 @@ void OpenGLRenderer::drawRenderNode(RenderNode* renderNode, Rect& dirty, int32_t return; } - // Don't avoid overdraw when visualizing, since that makes it harder to - // debug where it's coming from, and when the problem occurs. - bool avoidOverdraw = !Properties::debugOverdraw; - DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw); + DeferredDisplayList deferredList(mState.currentRenderTargetClip()); DeferStateStruct deferStruct(deferredList, *this, replayFlags); renderNode->defer(deferStruct, 0); @@ -1543,7 +1480,7 @@ void OpenGLRenderer::drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entr .setMeshTexturedMesh(vertices, bitmapCount * 6) .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) .setTransform(*currentSnapshot(), transformFlags) - .setModelViewOffsetRectOptionalSnap(snap, x, y, Rect(0, 0, bounds.getWidth(), bounds.getHeight())) + .setModelViewOffsetRectOptionalSnap(snap, x, y, Rect(bounds.getWidth(), bounds.getHeight())) .build(); renderGlop(glop, GlopRenderType::Multi); } @@ -1566,7 +1503,7 @@ void OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { .setMeshTexturedUnitQuad(texture->uvMapper) .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) .setTransform(*currentSnapshot(), TransformFlags::None) - .setModelViewMapUnitToRectSnap(Rect(0, 0, texture->width, texture->height)) + .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height())) .build(); renderGlop(glop); } @@ -1595,7 +1532,7 @@ void OpenGLRenderer::drawBitmapMesh(const SkBitmap* bitmap, int meshWidth, int m colors = tempColors.get(); } - Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); + Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef()); const UvMapper& mapper(getMapper(texture)); for (int32_t y = 0; y < meshHeight; y++) { @@ -1670,10 +1607,10 @@ void OpenGLRenderer::drawBitmap(const SkBitmap* bitmap, Rect src, Rect dst, cons if (!texture) return; const AutoTexture autoCleanup(texture); - Rect uv(std::max(0.0f, src.left / texture->width), - std::max(0.0f, src.top / texture->height), - std::min(1.0f, src.right / texture->width), - std::min(1.0f, src.bottom / texture->height)); + Rect uv(std::max(0.0f, src.left / texture->width()), + std::max(0.0f, src.top / texture->height()), + std::min(1.0f, src.right / texture->width()), + std::min(1.0f, src.bottom / texture->height())); const int textureFillFlags = (bitmap->colorType() == kAlpha_8_SkColorType) ? TextureFillFlags::IsAlphaMaskTexture : TextureFillFlags::None; @@ -1699,6 +1636,7 @@ void OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Patch* mesh, Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap); if (!texture) return; + const AutoTexture autoCleanup(texture); // 9 patches are built for stretching - always filter int textureFillFlags = TextureFillFlags::ForceFilter; @@ -1711,7 +1649,7 @@ void OpenGLRenderer::drawPatch(const SkBitmap* bitmap, const Patch* mesh, .setMeshPatchQuads(*mesh) .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) .setTransform(*currentSnapshot(), TransformFlags::None) - .setModelViewOffsetRectSnap(left, top, Rect(0, 0, right - left, bottom - top)) // TODO: get minimal bounds from patch + .setModelViewOffsetRectSnap(left, top, Rect(right - left, bottom - top)) // TODO: get minimal bounds from patch .build(); renderGlop(glop); } @@ -1741,7 +1679,7 @@ void OpenGLRenderer::drawPatches(const SkBitmap* bitmap, AssetAtlas::Entry* entr .setMeshTexturedIndexedQuads(vertices, elementCount) .setFillTexturePaint(*texture, textureFillFlags, paint, currentSnapshot()->alpha) .setTransform(*currentSnapshot(), transformFlags) - .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0)) + .setModelViewOffsetRect(0, 0, Rect()) .build(); renderGlop(glop, GlopRenderType::Multi); } @@ -1835,7 +1773,7 @@ void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) { // No need to check against the clip, we fill the clip region if (mState.currentlyIgnored()) return; - Rect clip(mState.currentClipRect()); + Rect clip(mState.currentRenderTargetClip()); clip.snapToPixelBoundaries(); SkPaint paint; @@ -1901,6 +1839,7 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p path.addCircle(x, y, radius); } +#if !HWUI_NEW_OPS if (CC_UNLIKELY(currentSnapshot()->projectionPathMask != nullptr)) { // mask ripples with projection mask SkPath maskPath = *(currentSnapshot()->projectionPathMask->projectionMask); @@ -1918,8 +1857,9 @@ void OpenGLRenderer::drawCircle(float x, float y, float radius, const SkPaint* p // Mask the ripple path by the projection mask, now that it's // in local space. Note that this can create CCW paths. - Op(path, maskPath, kIntersect_PathOp, &path); + Op(path, maskPath, kIntersect_SkPathOp, &path); } +#endif drawConvexPath(path, p); } @@ -1978,9 +1918,6 @@ void OpenGLRenderer::drawArc(float left, float top, float right, float bottom, drawConvexPath(path, p); } -// See SkPaintDefaults.h -#define SkPaintDefaults_MiterLimit SkIntToScalar(4) - void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, const SkPaint* p) { if (mState.currentlyIgnored() @@ -1991,6 +1928,7 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, if (p->getStyle() != SkPaint::kFill_Style) { // only fill style is supported by drawConvexPath, since others have to handle joins + static_assert(SkPaintDefaults_MiterLimit == 4.0f, "Miter limit has changed"); if (p->getPathEffect() != nullptr || p->getStrokeJoin() != SkPaint::kMiter_Join || p->getStrokeMiter() != SkPaintDefaults_MiterLimit) { mCaches.textureState().activateTexture(0); @@ -2019,13 +1957,13 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, } } -void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, - int bytesCount, int count, const float* positions, +void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const glyph_t* glyphs, + int count, const float* positions, FontRenderer& fontRenderer, int alpha, float x, float y) { mCaches.textureState().activateTexture(0); - TextShadow textShadow; - if (!getTextShadow(paint, &textShadow)) { + PaintUtils::TextShadow textShadow; + if (!PaintUtils::getTextShadow(paint, &textShadow)) { LOG_ALWAYS_FATAL("failed to query shadow attributes"); } @@ -2033,7 +1971,7 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, // if shader-based correction is enabled mCaches.dropShadowCache.setFontRenderer(fontRenderer); ShadowTexture* texture = mCaches.dropShadowCache.get( - paint, text, bytesCount, count, textShadow.radius, positions); + paint, glyphs, count, textShadow.radius, positions); // If the drop shadow exceeds the max texture size or couldn't be // allocated, skip drawing if (!texture) return; @@ -2048,69 +1986,19 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, .setMeshTexturedUnitQuad(nullptr) .setFillShadowTexturePaint(*texture, textShadow.color, *paint, currentSnapshot()->alpha) .setTransform(*currentSnapshot(), TransformFlags::None) - .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height)) + .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height())) .build(); renderGlop(glop); } +// TODO: remove this, once mState.currentlyIgnored captures snapshot alpha bool OpenGLRenderer::canSkipText(const SkPaint* paint) const { - float alpha = (hasTextShadow(paint) ? 1.0f : paint->getAlpha()) * currentSnapshot()->alpha; + float alpha = (PaintUtils::hasTextShadow(paint) + ? 1.0f : paint->getAlpha()) * currentSnapshot()->alpha; return MathUtils::isZero(alpha) && PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode; } -void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count, - const float* positions, const SkPaint* paint) { - if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) { - return; - } - - // NOTE: Skia does not support perspective transform on drawPosText yet - if (!currentTransform()->isSimple()) { - return; - } - - mRenderState.scissor().setEnabled(true); - - float x = 0.0f; - float y = 0.0f; - const bool pureTranslate = currentTransform()->isPureTranslate(); - if (pureTranslate) { - x = floorf(x + currentTransform()->getTranslateX() + 0.5f); - y = floorf(y + currentTransform()->getTranslateY() + 0.5f); - } - - FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); - fontRenderer.setFont(paint, SkMatrix::I()); - - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); - - if (CC_UNLIKELY(hasTextShadow(paint))) { - drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, - alpha, 0.0f, 0.0f); - } - - // Pick the appropriate texture filtering - bool linearFilter = currentTransform()->changesBounds(); - if (pureTranslate && !linearFilter) { - linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f; - } - fontRenderer.setTextureFiltering(linearFilter); - - const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip()); - Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); - - TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); - if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y, - positions, hasLayer() ? &bounds : nullptr, &functor)) { - dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform()); - mDirty = true; - } - -} - bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const { if (CC_LIKELY(transform.isPureTranslate())) { outMatrix->setIdentity(); @@ -2165,8 +2053,9 @@ void OpenGLRenderer::skew(float sx, float sy) { mState.skew(sx, sy); } -void OpenGLRenderer::setMatrix(const Matrix4& matrix) { - mState.setMatrix(matrix); +void OpenGLRenderer::setLocalMatrix(const Matrix4& matrix) { + mState.setMatrix(mBaseTransform); + mState.concatMatrix(matrix); } void OpenGLRenderer::setLocalMatrix(const SkMatrix& matrix) { @@ -2203,14 +2092,14 @@ void OpenGLRenderer::setProjectionPathMask(LinearAllocator& allocator, const SkP mState.setProjectionPathMask(allocator, path); } -void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float x, float y, +void OpenGLRenderer::drawText(const glyph_t* glyphs, int bytesCount, int count, float x, float y, const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode) { if (drawOpMode == DrawOpMode::kImmediate) { // The checks for corner-case ignorable text and quick rejection is only done for immediate // drawing as ops from DeferredDisplayList are already filtered for these - if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint) || + if (glyphs == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint) || quickRejectSetupScissor(bounds)) { return; } @@ -2227,15 +2116,14 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float y = floorf(y + transform.getTranslateY() + 0.5f); } - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); + int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint); - FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); + FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(); - if (CC_UNLIKELY(hasTextShadow(paint))) { + if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) { fontRenderer.setFont(paint, SkMatrix::I()); - drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, + drawTextShadow(paint, glyphs, count, positions, fontRenderer, alpha, oldX, oldY); } @@ -2260,21 +2148,26 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float fontRenderer.setTextureFiltering(linearFilter); // TODO: Implement better clipping for scaled/rotated text - const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect(); + const Rect* clip = !pureTranslate ? nullptr : &mState.currentRenderTargetClip(); Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); bool status; +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("unsupported"); + TextDrawFunctor functor(nullptr, nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint); +#else TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); +#endif // don't call issuedrawcommand, do it at end of batch bool forceFinish = (drawOpMode != DrawOpMode::kDefer); if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) { SkPaint paintCopy(*paint); paintCopy.setTextAlign(SkPaint::kLeft_Align); - status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y, + status = fontRenderer.renderPosText(&paintCopy, clip, glyphs, count, x, y, positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish); } else { - status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, + status = fontRenderer.renderPosText(paint, clip, glyphs, count, x, y, positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish); } @@ -2285,33 +2178,35 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float dirtyLayerUnchecked(layerBounds, getRegion()); } - drawTextDecorations(totalAdvance, oldX, oldY, paint); - mDirty = true; } -void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, +void OpenGLRenderer::drawTextOnPath(const glyph_t* glyphs, int bytesCount, int count, const SkPath* path, float hOffset, float vOffset, const SkPaint* paint) { - if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) { + if (glyphs == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) { return; } // TODO: avoid scissor by calculating maximum bounds using path bounds + font metrics mRenderState.scissor().setEnabled(true); - FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint); + FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(); fontRenderer.setFont(paint, SkMatrix::I()); fontRenderer.setTextureFiltering(true); - int alpha; - SkXfermode::Mode mode; - getAlphaAndMode(paint, &alpha, &mode); + int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint); +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("unsupported"); + TextDrawFunctor functor(nullptr, nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint); +#else TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint); +#endif const Rect* clip = &writableSnapshot()->getLocalClip(); Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); - if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path, + if (fontRenderer.renderTextOnPath(paint, clip, glyphs, count, path, hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) { dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform()); mDirty = true; @@ -2325,16 +2220,19 @@ void OpenGLRenderer::drawPath(const SkPath* path, const SkPaint* paint) { PathTexture* texture = mCaches.pathCache.get(path, paint); if (!texture) return; - const AutoTexture autoCleanup(texture); const float x = texture->left - texture->offset; const float y = texture->top - texture->offset; drawPathTexture(texture, x, y, paint); + + if (texture->cleanup) { + mCaches.pathCache.remove(path, paint); + } mDirty = true; } -void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { +void OpenGLRenderer::drawLayer(Layer* layer) { if (!layer) { return; } @@ -2343,14 +2241,14 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { if (layer->isTextureLayer()) { transform = &layer->getTransform(); if (!transform->isIdentity()) { - save(SkCanvas::kMatrix_SaveFlag); + save(SaveFlags::Matrix); concatMatrix(*transform); } } bool clipRequired = false; const bool rejected = mState.calculateQuickRejectForScissor( - x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(), + 0, 0, layer->layer.getWidth(), layer->layer.getHeight(), &clipRequired, nullptr, false); if (rejected) { @@ -2379,7 +2277,7 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { .setMeshTexturedIndexedQuads(layer->mesh, layer->meshElementCount) .setFillLayer(layer->getTexture(), layer->getColorFilter(), getLayerAlpha(layer), layer->getMode(), Blend::ModeOrderSwap::NoSwap) .setTransform(*currentSnapshot(), TransformFlags::None) - .setModelViewOffsetRectSnap(x, y, Rect(0, 0, layer->layer.getWidth(), layer->layer.getHeight())) + .setModelViewOffsetRectSnap(0, 0, Rect(layer->layer.getWidth(), layer->layer.getHeight())) .build(); DRAW_DOUBLE_STENCIL_IF(!layer->hasDrawnSinceUpdate, renderGlop(glop)); #if DEBUG_LAYERS_AS_REGIONS @@ -2392,7 +2290,7 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { SkPaint paint; paint.setColor(0x7f00ff00); - drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(), &paint); + drawColorRect(0, 0, layer->layer.getWidth(), layer->layer.getHeight(), &paint); } } layer->hasDrawnSinceUpdate = true; @@ -2418,7 +2316,7 @@ void OpenGLRenderer::setDrawFilter(SkDrawFilter* filter) { /////////////////////////////////////////////////////////////////////////////// Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) { - Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap); + Texture* texture = mRenderState.assetAtlas().getEntryTexture(bitmap->pixelRef()); if (!texture) { return mCaches.textureCache.get(bitmap); } @@ -2427,7 +2325,7 @@ Texture* OpenGLRenderer::getTexture(const SkBitmap* bitmap) { void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y, const SkPaint* paint) { - if (quickRejectSetupScissor(x, y, x + texture->width, y + texture->height)) { + if (quickRejectSetupScissor(x, y, x + texture->width(), y + texture->height())) { return; } @@ -2437,61 +2335,11 @@ void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y, .setMeshTexturedUnitQuad(nullptr) .setFillPathTexturePaint(*texture, *paint, currentSnapshot()->alpha) .setTransform(*currentSnapshot(), TransformFlags::None) - .setModelViewMapUnitToRect(Rect(x, y, x + texture->width, y + texture->height)) + .setModelViewMapUnitToRect(Rect(x, y, x + texture->width(), y + texture->height())) .build(); renderGlop(glop); } -// Same values used by Skia -#define kStdStrikeThru_Offset (-6.0f / 21.0f) -#define kStdUnderline_Offset (1.0f / 9.0f) -#define kStdUnderline_Thickness (1.0f / 18.0f) - -void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y, - const SkPaint* paint) { - // Handle underline and strike-through - uint32_t flags = paint->getFlags(); - if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { - SkPaint paintCopy(*paint); - - if (CC_LIKELY(underlineWidth > 0.0f)) { - const float textSize = paintCopy.getTextSize(); - const float strokeWidth = std::max(textSize * kStdUnderline_Thickness, 1.0f); - - const float left = x; - float top = 0.0f; - - int linesCount = 0; - if (flags & SkPaint::kUnderlineText_Flag) linesCount++; - if (flags & SkPaint::kStrikeThruText_Flag) linesCount++; - - const int pointsCount = 4 * linesCount; - float points[pointsCount]; - int currentPoint = 0; - - if (flags & SkPaint::kUnderlineText_Flag) { - top = y + textSize * kStdUnderline_Offset; - points[currentPoint++] = left; - points[currentPoint++] = top; - points[currentPoint++] = left + underlineWidth; - points[currentPoint++] = top; - } - - if (flags & SkPaint::kStrikeThruText_Flag) { - top = y + textSize * kStdStrikeThru_Offset; - points[currentPoint++] = left; - points[currentPoint++] = top; - points[currentPoint++] = left + underlineWidth; - points[currentPoint++] = top; - } - - paintCopy.setStrokeWidth(strokeWidth); - - drawLines(&points[0], pointsCount, &paintCopy); - } - } -} - void OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) { if (mState.currentlyIgnored()) { return; @@ -2595,12 +2443,6 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot renderGlop(glop); } -void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, - SkXfermode::Mode* mode) const { - getAlphaAndModeDirect(paint, alpha, mode); - *alpha *= currentSnapshot()->alpha; -} - float OpenGLRenderer::getLayerAlpha(const Layer* layer) const { return (layer->getAlpha() / 255.0f) * currentSnapshot()->alpha; } diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 402f6edd475d..dacd8ccaa6ea 100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -35,6 +35,7 @@ #include <SkBitmap.h> #include <SkCanvas.h> #include <SkColorFilter.h> +#include <SkDrawLooper.h> #include <SkMatrix.h> #include <SkPaint.h> #include <SkRegion.h> @@ -44,12 +45,13 @@ #include <utils/Functor.h> #include <utils/RefBase.h> #include <utils/SortedVector.h> -#include <utils/Vector.h> #include <cutils/compiler.h> #include <androidfw/ResourceTypes.h> +#include <vector> + class SkShader; namespace android { @@ -117,15 +119,6 @@ public: OpenGLRenderer(RenderState& renderState); virtual ~OpenGLRenderer(); - /** - * Sets the dimension of the underlying drawing surface. This method must - * be called at least once every time the drawing surface changes size. - * - * @param width The width in pixels of the underlysing surface - * @param height The height in pixels of the underlysing surface - */ - void setViewport(int width, int height) { mState.setViewport(width, height); } - void initProperties(); void initLight(float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); @@ -141,21 +134,8 @@ public: * and will not be cleared. If false, the target surface * will be cleared */ - virtual void prepareDirty(float left, float top, float right, float bottom, - bool opaque); - - /** - * Prepares the renderer to draw a frame. This method must be invoked - * at the beginning of each frame. When this method is invoked, the - * entire drawing surface is assumed to be redrawn. - * - * @param opaque If true, the target surface is considered opaque - * and will not be cleared. If false, the target surface - * will be cleared - */ - void prepare(bool opaque) { - prepareDirty(0.0f, 0.0f, mState.getWidth(), mState.getHeight(), opaque); - } + virtual void prepareDirty(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque); /** * Indicates the end of a frame. This method must be invoked whenever @@ -186,7 +166,7 @@ public: const SkPaint* paint, int flags); void drawRenderNode(RenderNode* displayList, Rect& dirty, int32_t replayFlags = 1); - void drawLayer(Layer* layer, float x, float y); + void drawLayer(Layer* layer); void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint); void drawBitmaps(const SkBitmap* bitmap, AssetAtlas::Entry* entry, int bitmapCount, TextureVertex* vertices, bool pureTranslate, const Rect& bounds, const SkPaint* paint); @@ -211,11 +191,9 @@ public: void drawPath(const SkPath* path, const SkPaint* paint); void drawLines(const float* points, int count, const SkPaint* paint); void drawPoints(const float* points, int count, const SkPaint* paint); - void drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path, + void drawTextOnPath(const glyph_t* glyphs, int bytesCount, int count, const SkPath* path, float hOffset, float vOffset, const SkPaint* paint); - void drawPosText(const char* text, int bytesCount, int count, - const float* positions, const SkPaint* paint); - void drawText(const char* text, int bytesCount, int count, float x, float y, + void drawText(const glyph_t* glyphs, int bytesCount, int count, float x, float y, const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode = DrawOpMode::kImmediate); void drawRects(const float* rects, int count, const SkPaint* paint); @@ -280,57 +258,6 @@ public: void endMark() const; /** - * Gets the alpha and xfermode out of a paint object. If the paint is null - * alpha will be 255 and the xfermode will be SRC_OVER. This method does - * not multiply the paint's alpha by the current snapshot's alpha, and does - * not replace the alpha with the overrideLayerAlpha - * - * @param paint The paint to extract values from - * @param alpha Where to store the resulting alpha - * @param mode Where to store the resulting xfermode - */ - static inline void getAlphaAndModeDirect(const SkPaint* paint, int* alpha, - SkXfermode::Mode* mode) { - *mode = getXfermodeDirect(paint); - *alpha = getAlphaDirect(paint); - } - - static inline SkXfermode::Mode getXfermodeDirect(const SkPaint* paint) { - if (!paint) return SkXfermode::kSrcOver_Mode; - return PaintUtils::getXfermode(paint->getXfermode()); - } - - static inline int getAlphaDirect(const SkPaint* paint) { - if (!paint) return 255; - return paint->getAlpha(); - } - - struct TextShadow { - SkScalar radius; - float dx; - float dy; - SkColor color; - }; - - static inline bool getTextShadow(const SkPaint* paint, TextShadow* textShadow) { - SkDrawLooper::BlurShadowRec blur; - if (paint && paint->getLooper() && paint->getLooper()->asABlurShadow(&blur)) { - if (textShadow) { - textShadow->radius = Blur::convertSigmaToRadius(blur.fSigma); - textShadow->dx = blur.fOffset.fX; - textShadow->dy = blur.fOffset.fY; - textShadow->color = blur.fColor; - } - return true; - } - return false; - } - - static inline bool hasTextShadow(const SkPaint* paint) { - return getTextShadow(paint, nullptr); - } - - /** * Build the best transform to use to rasterize text given a full * transform matrix, and whether filteration is needed. * @@ -366,8 +293,10 @@ public: void restore(); void restoreToCount(int saveCount); - void getMatrix(SkMatrix* outMatrix) const { mState.getMatrix(outMatrix); } - void setMatrix(const SkMatrix& matrix) { mState.setMatrix(matrix); } + void setGlobalMatrix(const Matrix4& matrix) { + mState.setMatrix(matrix); + } + void setLocalMatrix(const Matrix4& matrix); void setLocalMatrix(const SkMatrix& matrix); void concatMatrix(const SkMatrix& matrix) { mState.concatMatrix(matrix); } @@ -426,7 +355,8 @@ protected: * Perform the setup specific to a frame. This method does not * issue any OpenGL commands. */ - void setupFrameState(float left, float top, float right, float bottom, bool opaque); + void setupFrameState(int viewportWidth, int viewportHeight, + float left, float top, float right, float bottom, bool opaque); /** * Indicates the start of rendering. This method will setup the @@ -510,16 +440,6 @@ protected: void drawTextureLayer(Layer* layer, const Rect& rect); /** - * Gets the alpha and xfermode out of a paint object. If the paint is null - * alpha will be 255 and the xfermode will be SRC_OVER. Accounts for snapshot alpha. - * - * @param paint The paint to extract values from - * @param alpha Where to store the resulting alpha - * @param mode Where to store the resulting xfermode - */ - inline void getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) const; - - /** * Gets the alpha from a layer, accounting for snapshot alpha * * @param layer The layer from which the alpha is extracted @@ -554,27 +474,6 @@ private: void discardFramebuffer(float left, float top, float right, float bottom); /** - * Tells the GPU what part of the screen is about to be redrawn. - * This method will use the current layer space clip rect. - * This method needs to be invoked every time getTargetFbo() is - * bound again. - */ - void startTilingCurrentClip(bool opaque = false, bool expand = false); - - /** - * Tells the GPU what part of the screen is about to be redrawn. - * This method needs to be invoked every time getTargetFbo() is - * bound again. - */ - void startTiling(const Rect& clip, int windowHeight, bool opaque = false, bool expand = false); - - /** - * Tells the GPU that we are done drawing the frame or that we - * are switching to another render target. - */ - void endTiling(); - - /** * Sets the clipping rectangle using glScissor. The clip is defined by * the current snapshot's clipRect member. */ @@ -736,24 +635,11 @@ private: */ void drawConvexPath(const SkPath& path, const SkPaint* paint); - /** - * Draws text underline and strike-through if needed. - * - * @param text The text to decor - * @param bytesCount The number of bytes in the text - * @param totalAdvance The total advance in pixels, defines underline/strikethrough length - * @param x The x coordinate where the text will be drawn - * @param y The y coordinate where the text will be drawn - * @param paint The paint to draw the text with - */ - void drawTextDecorations(float totalAdvance, float x, float y, const SkPaint* paint); - /** * Draws shadow layer on text (with optional positions). * * @param paint The paint to draw the shadow with * @param text The text to draw - * @param bytesCount The number of bytes in the text * @param count The number of glyphs in the text * @param positions The x, y positions of individual glyphs (or NULL) * @param fontRenderer The font renderer object @@ -761,7 +647,7 @@ private: * @param x The x coordinate where the shadow will be drawn * @param y The y coordinate where the shadow will be drawn */ - void drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count, + void drawTextShadow(const SkPaint* paint, const glyph_t* glyphs, int count, const float* positions, FontRenderer& fontRenderer, int alpha, float x, float y); @@ -855,16 +741,12 @@ private: // List of rectangles to clear after saveLayer() is invoked std::vector<Rect> mLayers; // List of layers to update at the beginning of a frame - Vector< sp<Layer> > mLayerUpdates; + std::vector< sp<Layer> > mLayerUpdates; // See PROPERTY_DISABLE_SCISSOR_OPTIMIZATION in // Properties.h bool mScissorOptimizationDisabled; - // No-ops start/endTiling when set - bool mSuppressTiling; - bool mFirstFrameAfterResize; - bool mSkipOutlineClip; // True if anything has been drawn since the last call to diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h index c98932cf095e..922ff7caecb8 100644 --- a/libs/hwui/Outline.h +++ b/libs/hwui/Outline.h @@ -26,20 +26,43 @@ namespace uirenderer { class Outline { public: + enum class Type { + None = 0, + Empty = 1, + ConvexPath = 2, + RoundRect = 3 + }; + Outline() : mShouldClip(false) - , mType(kOutlineType_None) + , mType(Type::None) , mRadius(0) , mAlpha(0.0f) {} void setRoundRect(int left, int top, int right, int bottom, float radius, float alpha) { - mType = kOutlineType_RoundRect; + mAlpha = alpha; + if (mType == Type::RoundRect + && left == mBounds.left + && right == mBounds.right + && top == mBounds.top + && bottom == mBounds.bottom + && radius == mRadius) { + // nothing to change, don't do any work + return; + } + + mType = Type::RoundRect; mBounds.set(left, top, right, bottom); mRadius = radius; + + // update mPath to reflect new outline mPath.reset(); - mPath.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom), - radius, radius); - mAlpha = alpha; + if (MathUtils::isPositive(radius)) { + mPath.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom), + radius, radius); + } else { + mPath.addRect(left, top, right, bottom); + } } void setConvexPath(const SkPath* outline, float alpha) { @@ -47,26 +70,26 @@ public: setEmpty(); return; } - mType = kOutlineType_ConvexPath; + mType = Type::ConvexPath; mPath = *outline; mBounds.set(outline->getBounds()); mAlpha = alpha; } void setEmpty() { - mType = kOutlineType_Empty; + mType = Type::Empty; mPath.reset(); mAlpha = 0.0f; } void setNone() { - mType = kOutlineType_None; + mType = Type::None; mPath.reset(); mAlpha = 0.0f; } bool isEmpty() const { - return mType == kOutlineType_Empty; + return mType == Type::Empty; } float getAlpha() const { @@ -83,7 +106,7 @@ public: bool willClip() const { // only round rect outlines can be used for clipping - return mShouldClip && (mType == kOutlineType_RoundRect); + return mShouldClip && (mType == Type::RoundRect); } bool willRoundRectClip() const { @@ -92,7 +115,7 @@ public: } bool getAsRoundRect(Rect* outRect, float* outRadius) const { - if (mType == kOutlineType_RoundRect) { + if (mType == Type::RoundRect) { outRect->set(mBounds); *outRadius = mRadius; return true; @@ -101,21 +124,26 @@ public: } const SkPath* getPath() const { - if (mType == kOutlineType_None || mType == kOutlineType_Empty) return nullptr; + if (mType == Type::None || mType == Type::Empty) return nullptr; return &mPath; } -private: - enum OutlineType { - kOutlineType_None = 0, - kOutlineType_Empty = 1, - kOutlineType_ConvexPath = 2, - kOutlineType_RoundRect = 3 - }; + Type getType() const { + return mType; + } + + const Rect& getBounds() const { + return mBounds; + } + + float getRadius() const { + return mRadius; + } +private: bool mShouldClip; - OutlineType mType; + Type mType; Rect mBounds; float mRadius; float mAlpha; diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp index 6a7dfb3890d3..b471e7850a99 100644 --- a/libs/hwui/Patch.cpp +++ b/libs/hwui/Patch.cpp @@ -14,18 +14,16 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - -#include <cmath> - -#include <utils/Log.h> +#include "Patch.h" #include "Caches.h" -#include "Patch.h" #include "Properties.h" #include "UvMapper.h" #include "utils/MathUtils.h" +#include <algorithm> +#include <utils/Log.h> + namespace android { namespace uirenderer { @@ -191,10 +189,10 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f const uint32_t oldQuadCount = quadCount; quadCount++; - x1 = MathUtils::max(x1, 0.0f); - x2 = MathUtils::max(x2, 0.0f); - y1 = MathUtils::max(y1, 0.0f); - y2 = MathUtils::max(y2, 0.0f); + x1 = std::max(x1, 0.0f); + x2 = std::max(x2, 0.0f); + y1 = std::max(y1, 0.0f); + y2 = std::max(y2, 0.0f); // Skip degenerate and transparent (empty) quads if ((mColors[oldQuadCount] == 0) || x1 >= x2 || y1 >= y2) { @@ -208,8 +206,7 @@ void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, f // Record all non empty quads if (hasEmptyQuads) { - Rect bounds(x1, y1, x2, y2); - quads.add(bounds); + quads.emplace_back(x1, y1, x2, y2); } mUvMapper.map(u1, v1, u2, v2); diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h index b63bd24456d3..f04416ccabf9 100644 --- a/libs/hwui/Patch.h +++ b/libs/hwui/Patch.h @@ -21,13 +21,13 @@ #include <GLES2/gl2.h> -#include <utils/Vector.h> - #include <androidfw/ResourceTypes.h> #include "Rect.h" #include "UvMapper.h" +#include <vector> + namespace android { namespace uirenderer { @@ -52,7 +52,7 @@ public: uint32_t verticesCount = 0; uint32_t indexCount = 0; bool hasEmptyQuads = false; - Vector<Rect> quads; + std::vector<Rect> quads; GLintptr positionOffset = 0; GLintptr textureOffset = 0; diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp index 27652624b498..bd6feb9fc762 100644 --- a/libs/hwui/PatchCache.cpp +++ b/libs/hwui/PatchCache.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <utils/JenkinsHash.h> #include <utils/Log.h> @@ -34,20 +32,12 @@ namespace uirenderer { PatchCache::PatchCache(RenderState& renderState) : mRenderState(renderState) + , mMaxSize(Properties::patchCacheSize) , mSize(0) , mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity) , mMeshBuffer(0) , mFreeBlocks(nullptr) - , mGenerationId(0) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting patch cache size to %skB", property); - mMaxSize = KB(atoi(property)); - } else { - INIT_LOGD(" Using default patch cache size of %.2fkB", DEFAULT_PATCH_CACHE_SIZE); - mMaxSize = KB(DEFAULT_PATCH_CACHE_SIZE); - } -} + , mGenerationId(0) {} PatchCache::~PatchCache() { clear(); diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h index 387f79acf0ec..66ef6a0279ba 100644 --- a/libs/hwui/PatchCache.h +++ b/libs/hwui/PatchCache.h @@ -169,7 +169,7 @@ private: #endif RenderState& mRenderState; - uint32_t mMaxSize; + const uint32_t mMaxSize; uint32_t mSize; LruCache<PatchDescription, Patch*> mCache; diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index 3af640f76365..a8ace8c10edd 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -14,14 +14,12 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <SkBitmap.h> #include <SkCanvas.h> #include <SkColor.h> #include <SkPaint.h> #include <SkPath.h> +#include <SkPathEffect.h> #include <SkRect.h> #include <utils/JenkinsHash.h> @@ -33,21 +31,39 @@ #include "thread/Signal.h" #include "thread/TaskProcessor.h" +#include <cutils/properties.h> + namespace android { namespace uirenderer { +template <class T> +static bool compareWidthHeight(const T& lhs, const T& rhs) { + return (lhs.mWidth == rhs.mWidth) && (lhs.mHeight == rhs.mHeight); +} + +static bool compareRoundRects(const PathDescription::Shape::RoundRect& lhs, + const PathDescription::Shape::RoundRect& rhs) { + return compareWidthHeight(lhs, rhs) && lhs.mRx == rhs.mRx && lhs.mRy == rhs.mRy; +} + +static bool compareArcs(const PathDescription::Shape::Arc& lhs, const PathDescription::Shape::Arc& rhs) { + return compareWidthHeight(lhs, rhs) && lhs.mStartAngle == rhs.mStartAngle && + lhs.mSweepAngle == rhs.mSweepAngle && lhs.mUseCenter == rhs.mUseCenter; +} + /////////////////////////////////////////////////////////////////////////////// // Cache entries /////////////////////////////////////////////////////////////////////////////// PathDescription::PathDescription() - : type(kShapeNone) + : type(ShapeType::None) , join(SkPaint::kDefault_Join) , cap(SkPaint::kDefault_Cap) , style(SkPaint::kFill_Style) , miter(4.0f) , strokeWidth(1.0f) , pathEffect(nullptr) { + // Shape bits should be set to zeroes, because they are used for hash calculation. memset(&shape, 0, sizeof(Shape)); } @@ -59,11 +75,12 @@ PathDescription::PathDescription(ShapeType type, const SkPaint* paint) , miter(paint->getStrokeMiter()) , strokeWidth(paint->getStrokeWidth()) , pathEffect(paint->getPathEffect()) { + // Shape bits should be set to zeroes, because they are used for hash calculation. memset(&shape, 0, sizeof(Shape)); } hash_t PathDescription::hash() const { - uint32_t hash = JenkinsHashMix(0, type); + uint32_t hash = JenkinsHashMix(0, static_cast<int>(type)); hash = JenkinsHashMix(hash, join); hash = JenkinsHashMix(hash, cap); hash = JenkinsHashMix(hash, style); @@ -74,6 +91,32 @@ hash_t PathDescription::hash() const { return JenkinsHashWhiten(hash); } +bool PathDescription::operator==(const PathDescription& rhs) const { + if (type != rhs.type) return false; + if (join != rhs.join) return false; + if (cap != rhs.cap) return false; + if (style != rhs.style) return false; + if (miter != rhs.miter) return false; + if (strokeWidth != rhs.strokeWidth) return false; + if (pathEffect != rhs.pathEffect) return false; + switch (type) { + case ShapeType::None: + return 0; + case ShapeType::Rect: + return compareWidthHeight(shape.rect, rhs.shape.rect); + case ShapeType::RoundRect: + return compareRoundRects(shape.roundRect, rhs.shape.roundRect); + case ShapeType::Circle: + return shape.circle.mRadius == rhs.shape.circle.mRadius; + case ShapeType::Oval: + return compareWidthHeight(shape.oval, rhs.shape.oval); + case ShapeType::Arc: + return compareArcs(shape.arc, rhs.shape.arc); + case ShapeType::Path: + return shape.path.mGenerationID == rhs.shape.path.mGenerationID; + } +} + /////////////////////////////////////////////////////////////////////////////// // Utilities /////////////////////////////////////////////////////////////////////////////// @@ -136,17 +179,10 @@ static void drawPath(const SkPath *path, const SkPaint* paint, SkBitmap& bitmap, // Cache constructor/destructor /////////////////////////////////////////////////////////////////////////////// -PathCache::PathCache(): - mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity), - mSize(0), mMaxSize(MB(DEFAULT_PATH_CACHE_SIZE)) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_PATH_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting %s cache size to %sMB", name, property); - mMaxSize = MB(atof(property)); - } else { - INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_PATH_CACHE_SIZE); - } - +PathCache::PathCache() + : mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity) + , mSize(0) + , mMaxSize(Properties::pathCacheSize) { mCache.setOnEntryRemovedListener(this); GLint maxTextureSize; @@ -186,7 +222,7 @@ void PathCache::operator()(PathDescription& entry, PathTexture*& texture) { void PathCache::removeTexture(PathTexture* texture) { if (texture) { - const uint32_t size = texture->width * texture->height; + const uint32_t size = texture->width() * texture->height(); // If there is a pending task we must wait for it to return // before attempting our cleanup @@ -210,9 +246,7 @@ void PathCache::removeTexture(PathTexture* texture) { ALOGD("Shape deleted, size = %d", size); } - if (texture->id) { - Caches::getInstance().textureState().deleteTexture(texture->id); - } + texture->deleteTexture(); delete texture; } } @@ -249,8 +283,7 @@ PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath *p drawPath(path, paint, bitmap, left, top, offset, width, height); PathTexture* texture = new PathTexture(Caches::getInstance(), - left, top, offset, width, height, - path->getGenerationID()); + left, top, offset, path->getGenerationID()); generateTexture(entry, &bitmap, texture); return texture; @@ -263,7 +296,7 @@ void PathCache::generateTexture(const PathDescription& entry, SkBitmap* bitmap, // Note here that we upload to a texture even if it's bigger than mMaxSize. // Such an entry in mCache will only be temporary, since it will be evicted // immediately on trim, or on any other Path entering the cache. - uint32_t size = texture->width * texture->height; + uint32_t size = texture->width() * texture->height(); mSize += size; PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d", texture->id, size, mSize); @@ -281,24 +314,8 @@ void PathCache::clear() { void PathCache::generateTexture(SkBitmap& bitmap, Texture* texture) { ATRACE_NAME("Upload Path Texture"); - SkAutoLockPixels alp(bitmap); - if (!bitmap.readyToDraw()) { - ALOGE("Cannot generate texture from bitmap"); - return; - } - - glGenTextures(1, &texture->id); - - Caches::getInstance().textureState().bindTexture(texture->id); - // Textures are Alpha8 - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - texture->blend = true; - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0, - GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels()); - + texture->upload(bitmap); texture->setFilter(GL_LINEAR); - texture->setWrap(GL_CLAMP_TO_EDGE); } /////////////////////////////////////////////////////////////////////////////// @@ -321,16 +338,12 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { texture->left = left; texture->top = top; texture->offset = offset; - texture->width = width; - texture->height = height; if (width <= mMaxTextureSize && height <= mMaxTextureSize) { SkBitmap* bitmap = new SkBitmap(); drawPath(&t->path, &t->paint, *bitmap, left, top, offset, width, height); t->setResult(bitmap); } else { - texture->width = 0; - texture->height = 0; t->setResult(nullptr); } } @@ -341,7 +354,7 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { void PathCache::removeDeferred(const SkPath* path) { Mutex::Autolock l(mLock); - mGarbage.push(path->getGenerationID()); + mGarbage.push_back(path->getGenerationID()); } void PathCache::clearGarbage() { @@ -349,14 +362,11 @@ void PathCache::clearGarbage() { { // scope for the mutex Mutex::Autolock l(mLock); - size_t count = mGarbage.size(); - for (size_t i = 0; i < count; i++) { - const uint32_t generationID = mGarbage.itemAt(i); - + for (const uint32_t generationID : mGarbage) { LruCache<PathDescription, PathTexture*>::Iterator iter(mCache); while (iter.next()) { const PathDescription& key = iter.key(); - if (key.type == kShapePath && key.shape.path.mGenerationID == generationID) { + if (key.type == ShapeType::Path && key.shape.path.mGenerationID == generationID) { pathsToRemove.push(key); } } @@ -370,7 +380,7 @@ void PathCache::clearGarbage() { } PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) { - PathDescription entry(kShapePath, paint); + PathDescription entry(ShapeType::Path, paint); entry.shape.path.mGenerationID = path->getGenerationID(); PathTexture* texture = mCache.get(entry); @@ -400,12 +410,18 @@ PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) { return texture; } +void PathCache::remove(const SkPath* path, const SkPaint* paint) { + PathDescription entry(ShapeType::Path, paint); + entry.shape.path.mGenerationID = path->getGenerationID(); + mCache.remove(entry); +} + void PathCache::precache(const SkPath* path, const SkPaint* paint) { if (!Caches::getInstance().tasks.canRunTasks()) { return; } - PathDescription entry(kShapePath, paint); + PathDescription entry(ShapeType::Path, paint); entry.shape.path.mGenerationID = path->getGenerationID(); PathTexture* texture = mCache.get(entry); @@ -444,7 +460,7 @@ void PathCache::precache(const SkPath* path, const SkPaint* paint) { PathTexture* PathCache::getRoundRect(float width, float height, float rx, float ry, const SkPaint* paint) { - PathDescription entry(kShapeRoundRect, paint); + PathDescription entry(ShapeType::RoundRect, paint); entry.shape.roundRect.mWidth = width; entry.shape.roundRect.mHeight = height; entry.shape.roundRect.mRx = rx; @@ -469,7 +485,7 @@ PathTexture* PathCache::getRoundRect(float width, float height, /////////////////////////////////////////////////////////////////////////////// PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) { - PathDescription entry(kShapeCircle, paint); + PathDescription entry(ShapeType::Circle, paint); entry.shape.circle.mRadius = radius; PathTexture* texture = get(entry); @@ -489,7 +505,7 @@ PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) { /////////////////////////////////////////////////////////////////////////////// PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) { - PathDescription entry(kShapeOval, paint); + PathDescription entry(ShapeType::Oval, paint); entry.shape.oval.mWidth = width; entry.shape.oval.mHeight = height; @@ -512,7 +528,7 @@ PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) /////////////////////////////////////////////////////////////////////////////// PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) { - PathDescription entry(kShapeRect, paint); + PathDescription entry(ShapeType::Rect, paint); entry.shape.rect.mWidth = width; entry.shape.rect.mHeight = height; @@ -536,7 +552,7 @@ PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) PathTexture* PathCache::getArc(float width, float height, float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) { - PathDescription entry(kShapeArc, paint); + PathDescription entry(ShapeType::Arc, paint); entry.shape.arc.mWidth = width; entry.shape.arc.mHeight = height; entry.shape.arc.mStartAngle = startAngle; diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index 70148631db34..6368ddd49966 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -25,10 +25,12 @@ #include "utils/Pair.h" #include <GLES2/gl2.h> +#include <SkPaint.h> #include <SkPath.h> #include <utils/LruCache.h> #include <utils/Mutex.h> -#include <utils/Vector.h> + +#include <vector> class SkBitmap; class SkCanvas; @@ -60,13 +62,11 @@ class Caches; */ struct PathTexture: public Texture { PathTexture(Caches& caches, float left, float top, - float offset, int width, int height, int generation) + float offset, int generation) : Texture(caches) , left(left) , top(top) , offset(offset) { - this->width = width; - this->height = height; this->generation = generation; } PathTexture(Caches& caches, int generation) @@ -109,18 +109,18 @@ private: sp<Task<SkBitmap*> > mTask; }; // struct PathTexture -enum ShapeType { - kShapeNone, - kShapeRect, - kShapeRoundRect, - kShapeCircle, - kShapeOval, - kShapeArc, - kShapePath +enum class ShapeType { + None, + Rect, + RoundRect, + Circle, + Oval, + Arc, + Path }; struct PathDescription { - DESCRIPTION_TYPE(PathDescription); + HASHABLE_TYPE(PathDescription); ShapeType type; SkPaint::Join join; SkPaint::Cap cap; @@ -160,8 +160,6 @@ struct PathDescription { PathDescription(); PathDescription(ShapeType shapeType, const SkPaint* paint); - - hash_t hash() const; }; /** @@ -201,6 +199,7 @@ public: PathTexture* getArc(float width, float height, float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint); PathTexture* get(const SkPath* path, const SkPaint* paint); + void remove(const SkPath* path, const SkPaint* paint); /** * Removes the specified path. This is meant to be called from threads @@ -300,14 +299,14 @@ private: LruCache<PathDescription, PathTexture*> mCache; uint32_t mSize; - uint32_t mMaxSize; + const uint32_t mMaxSize; GLuint mMaxTextureSize; bool mDebugEnabled; sp<PathProcessor> mProcessor; - Vector<uint32_t> mGarbage; + std::vector<uint32_t> mGarbage; mutable Mutex mLock; }; // class PathCache diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp new file mode 100644 index 000000000000..2179f146115a --- /dev/null +++ b/libs/hwui/PathParser.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2015 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 "PathParser.h" + +#include "jni.h" + +#include <errno.h> +#include <utils/Log.h> +#include <sstream> +#include <stdlib.h> +#include <string> +#include <vector> + +namespace android { +namespace uirenderer { + +static size_t nextStart(const char* s, size_t length, size_t startIndex) { + size_t index = startIndex; + while (index < length) { + char c = s[index]; + // Note that 'e' or 'E' are not valid path commands, but could be + // used for floating point numbers' scientific notation. + // Therefore, when searching for next command, we should ignore 'e' + // and 'E'. + if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0)) + && c != 'e' && c != 'E') { + return index; + } + index++; + } + return index; +} + +/** + * Calculate the position of the next comma or space or negative sign + * @param s the string to search + * @param start the position to start searching + * @param result the result of the extraction, including the position of the + * the starting position of next number, whether it is ending with a '-'. + */ +static void extract(int* outEndPosition, bool* outEndWithNegOrDot, const char* s, int start, int end) { + // Now looking for ' ', ',', '.' or '-' from the start. + int currentIndex = start; + bool foundSeparator = false; + *outEndWithNegOrDot = false; + bool secondDot = false; + bool isExponential = false; + for (; currentIndex < end; currentIndex++) { + bool isPrevExponential = isExponential; + isExponential = false; + char currentChar = s[currentIndex]; + switch (currentChar) { + case ' ': + case ',': + foundSeparator = true; + break; + case '-': + // The negative sign following a 'e' or 'E' is not a separator. + if (currentIndex != start && !isPrevExponential) { + foundSeparator = true; + *outEndWithNegOrDot = true; + } + break; + case '.': + if (!secondDot) { + secondDot = true; + } else { + // This is the second dot, and it is considered as a separator. + foundSeparator = true; + *outEndWithNegOrDot = true; + } + break; + case 'e': + case 'E': + isExponential = true; + break; + } + if (foundSeparator) { + break; + } + } + // In the case where nothing is found, we put the end position to the end of + // our extract range. Otherwise, end position will be where separator is found. + *outEndPosition = currentIndex; +} + +static float parseFloat(PathParser::ParseResult* result, const char* startPtr, size_t expectedLength) { + char* endPtr = NULL; + float currentValue = strtof(startPtr, &endPtr); + if ((currentValue == HUGE_VALF || currentValue == -HUGE_VALF) && errno == ERANGE) { + result->failureOccurred = true; + result->failureMessage = "Float out of range: "; + result->failureMessage.append(startPtr, expectedLength); + } + if (currentValue == 0 && endPtr == startPtr) { + // No conversion is done. + result->failureOccurred = true; + result->failureMessage = "Float format error when parsing: "; + result->failureMessage.append(startPtr, expectedLength); + } + return currentValue; +} + +/** + * Parse the floats in the string. + * + * @param s the string containing a command and list of floats + * @return true on success + */ +static void getFloats(std::vector<float>* outPoints, PathParser::ParseResult* result, + const char* pathStr, int start, int end) { + + if (pathStr[start] == 'z' || pathStr[start] == 'Z') { + return; + } + int startPosition = start + 1; + int endPosition = start; + + // The startPosition should always be the first character of the + // current number, and endPosition is the character after the current + // number. + while (startPosition < end) { + bool endWithNegOrDot; + extract(&endPosition, &endWithNegOrDot, pathStr, startPosition, end); + + if (startPosition < endPosition) { + float currentValue = parseFloat(result, &pathStr[startPosition], + end - startPosition); + if (result->failureOccurred) { + return; + } + outPoints->push_back(currentValue); + } + + if (endWithNegOrDot) { + // Keep the '-' or '.' sign with next number. + startPosition = endPosition; + } else { + startPosition = endPosition + 1; + } + } + return; +} + +bool PathParser::isVerbValid(char verb) { + verb = tolower(verb); + return verb == 'a' || verb == 'c' || verb == 'h' || verb == 'l' || verb == 'm' || verb == 'q' + || verb == 's' || verb == 't' || verb == 'v' || verb == 'z'; +} + +void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result, + const char* pathStr, size_t strLen) { + if (pathStr == NULL) { + result->failureOccurred = true; + result->failureMessage = "Path string cannot be NULL."; + return; + } + + size_t start = 0; + // Skip leading spaces. + while (isspace(pathStr[start]) && start < strLen) { + start++; + } + if (start == strLen) { + result->failureOccurred = true; + result->failureMessage = "Path string cannot be empty."; + return; + } + size_t end = start + 1; + + while (end < strLen) { + end = nextStart(pathStr, strLen, end); + std::vector<float> points; + getFloats(&points, result, pathStr, start, end); + if (!isVerbValid(pathStr[start])) { + result->failureOccurred = true; + result->failureMessage = "Invalid pathData. Failure occurred at position " + + std::to_string(start) + " of path: " + pathStr; + } + // If either verb or points is not valid, return immediately. + if (result->failureOccurred) { + return; + } + data->verbs.push_back(pathStr[start]); + data->verbSizes.push_back(points.size()); + data->points.insert(data->points.end(), points.begin(), points.end()); + start = end; + end++; + } + + if ((end - start) == 1 && start < strLen) { + if (!isVerbValid(pathStr[start])) { + result->failureOccurred = true; + result->failureMessage = "Invalid pathData. Failure occurred at position " + + std::to_string(start) + " of path: " + pathStr; + return; + } + data->verbs.push_back(pathStr[start]); + data->verbSizes.push_back(0); + } +} + +void PathParser::dump(const PathData& data) { + // Print out the path data. + size_t start = 0; + for (size_t i = 0; i < data.verbs.size(); i++) { + std::ostringstream os; + os << data.verbs[i]; + os << ", verb size: " << data.verbSizes[i]; + for (size_t j = 0; j < data.verbSizes[i]; j++) { + os << " " << data.points[start + j]; + } + start += data.verbSizes[i]; + ALOGD("%s", os.str().c_str()); + } + + std::ostringstream os; + for (size_t i = 0; i < data.points.size(); i++) { + os << data.points[i] << ", "; + } + ALOGD("points are : %s", os.str().c_str()); +} + +void PathParser::parseAsciiStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, size_t strLen) { + PathData pathData; + getPathDataFromAsciiString(&pathData, result, pathStr, strLen); + if (result->failureOccurred) { + return; + } + // Check if there is valid data coming out of parsing the string. + if (pathData.verbs.size() == 0) { + result->failureOccurred = true; + result->failureMessage = "No verbs found in the string for pathData: "; + result->failureMessage += pathStr; + return; + } + VectorDrawableUtils::verbsToPath(skPath, pathData); + return; +} + +}; // namespace uirenderer +}; //namespace android diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h new file mode 100644 index 000000000000..5578e8d42e2f --- /dev/null +++ b/libs/hwui/PathParser.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_PATHPARSER_H +#define ANDROID_HWUI_PATHPARSER_H + +#include "VectorDrawable.h" +#include "utils/VectorDrawableUtils.h" + +#include <jni.h> +#include <android/log.h> +#include <cutils/compiler.h> + +#include <string> + +namespace android { +namespace uirenderer { + + +class PathParser { +public: + struct ANDROID_API ParseResult { + bool failureOccurred = false; + std::string failureMessage; + }; + /** + * Parse the string literal and create a Skia Path. Return true on success. + */ + ANDROID_API static void parseAsciiStringForSkPath(SkPath* outPath, ParseResult* result, + const char* pathStr, size_t strLength); + ANDROID_API static void getPathDataFromAsciiString(PathData* outData, ParseResult* result, + const char* pathStr, size_t strLength); + static void dump(const PathData& data); + static bool isVerbValid(char verb); +}; + +}; // namespace uirenderer +}; // namespace android +#endif //ANDROID_HWUI_PATHPARSER_H diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp index 38f214ab3e5d..9246237aeffb 100644 --- a/libs/hwui/PathTessellator.cpp +++ b/libs/hwui/PathTessellator.cpp @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -#define LOG_TAG "OpenGLRenderer" #define LOG_NDEBUG 1 -#define ATRACE_TAG ATRACE_TAG_VIEW #define VERTEX_DEBUG 0 @@ -35,6 +32,15 @@ #define DEBUG_DUMP_BUFFER() #endif +#include "PathTessellator.h" + +#include "Matrix.h" +#include "Vector.h" +#include "Vertex.h" +#include "utils/MathUtils.h" + +#include <algorithm> + #include <SkPath.h> #include <SkPaint.h> #include <SkPoint.h> @@ -47,12 +53,6 @@ #include <utils/Log.h> #include <utils/Trace.h> -#include "PathTessellator.h" -#include "Matrix.h" -#include "Vector.h" -#include "Vertex.h" -#include "utils/MathUtils.h" - namespace android { namespace uirenderer { @@ -155,7 +155,7 @@ public: // always use 2 points for hairline if (halfStrokeWidth == 0.0f) return 2; - float threshold = MathUtils::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH; + float threshold = std::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH; return MathUtils::divisionsNeededToApproximateArc(halfStrokeWidth, PI, threshold); } return 0; @@ -180,7 +180,8 @@ public: } }; -void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) { +void getFillVerticesFromPerimeter(const std::vector<Vertex>& perimeter, + VertexBuffer& vertexBuffer) { Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size()); int currentIndex = 0; @@ -204,8 +205,8 @@ void getFillVerticesFromPerimeter(const Vector<Vertex>& perimeter, VertexBuffer& * Uses an additional 2 vertices at the end to wrap around, closing the tri-strip * (for a total of perimeter.size() * 2 + 2 vertices) */ -void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter, - VertexBuffer& vertexBuffer) { +void getStrokeVerticesFromPerimeter(const PaintInfo& paintInfo, + const std::vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) { Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2); int currentIndex = 0; @@ -263,7 +264,7 @@ static inline void storeBeginEnd(const PaintInfo& paintInfo, const Vertex& cente * 2 - can zig-zag across 'extra' vertices at either end, to create round caps */ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo, - const Vector<Vertex>& vertices, VertexBuffer& vertexBuffer) { + const std::vector<Vertex>& vertices, VertexBuffer& vertexBuffer) { const int extra = paintInfo.capExtraDivisions(); const int allocSize = (vertices.size() + extra) * 2; Vertex* buffer = vertexBuffer.alloc<Vertex>(allocSize); @@ -342,8 +343,9 @@ void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo, * * 3 - zig zag back and forth inside the shape to fill it (using perimeter.size() vertices) */ -void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter, - VertexBuffer& vertexBuffer, float maxAlpha = 1.0f) { +void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, + const std::vector<Vertex>& perimeter, VertexBuffer& vertexBuffer, + float maxAlpha = 1.0f) { AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(perimeter.size() * 3 + 2); // generate alpha points - fill Alpha vertex gaps in between each point with @@ -401,7 +403,7 @@ void getFillVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Ver * For explanation of constants and general methodoloyg, see comments for * getStrokeVerticesFromUnclosedVerticesAA() below. */ -inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& vertices, +inline static void storeCapAA(const PaintInfo& paintInfo, const std::vector<Vertex>& vertices, AlphaVertex* buffer, bool isFirst, Vector2 normal, int offset) { const int extra = paintInfo.capExtraDivisions(); const int extraOffset = (extra + 1) / 2; @@ -426,8 +428,8 @@ inline static void storeCapAA(const PaintInfo& paintInfo, const Vector<Vertex>& } // determine referencePoint, the center point for the 4 primary cap vertices - const Vertex* point = isFirst ? vertices.begin() : (vertices.end() - 1); - Vector2 referencePoint = {point->x, point->y}; + const Vertex& point = isFirst ? vertices.front() : vertices.back(); + Vector2 referencePoint = {point.x, point.y}; if (paintInfo.cap == SkPaint::kSquare_Cap) { // To account for square cap, move the primary cap vertices (that create the AA edge) by the // stroke offset vector (rotated to be parallel to the stroke) @@ -572,7 +574,7 @@ or, for rounded caps: = 2 + 6 * pts + 6 * roundDivs */ void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo, - const Vector<Vertex>& vertices, VertexBuffer& vertexBuffer) { + const std::vector<Vertex>& vertices, VertexBuffer& vertexBuffer) { const int extra = paintInfo.capExtraDivisions(); const int allocSize = 6 * vertices.size() + 2 + 6 * extra; @@ -645,8 +647,8 @@ void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo, } -void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo, const Vector<Vertex>& perimeter, - VertexBuffer& vertexBuffer) { +void getStrokeVerticesFromPerimeterAA(const PaintInfo& paintInfo, + const std::vector<Vertex>& perimeter, VertexBuffer& vertexBuffer) { AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(6 * perimeter.size() + 8); int offset = 2 * perimeter.size() + 3; @@ -724,7 +726,7 @@ void PathTessellator::tessellatePath(const SkPath &path, const SkPaint* paint, const PaintInfo paintInfo(paint, transform); - Vector<Vertex> tempVertices; + std::vector<Vertex> tempVertices; float threshInvScaleX = paintInfo.inverseScaleX; float threshInvScaleY = paintInfo.inverseScaleY; if (paintInfo.style == SkPaint::kStroke_Style) { @@ -797,7 +799,7 @@ static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer, dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2); for (int i = 0; i < count; i += 2) { - bounds.expandToCoverVertex(points[i + 0], points[i + 1]); + bounds.expandToCover(points[i + 0], points[i + 1]); dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]); } dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint); @@ -819,7 +821,7 @@ void PathTessellator::tessellatePoints(const float* points, int count, const SkP } // calculate outline - Vector<Vertex> outlineVertices; + std::vector<Vertex> outlineVertices; PathApproximationInfo approximationInfo(paintInfo.inverseScaleX, paintInfo.inverseScaleY, OUTLINE_REFINE_THRESHOLD); approximatePathOutlineVertices(path, true, approximationInfo, outlineVertices); @@ -861,10 +863,8 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa vertexBuffer.alloc<Vertex>(numLines * lineAllocSize + (numLines - 1) * 2); } - Vector<Vertex> tempVertices; - tempVertices.push(); - tempVertices.push(); - Vertex* tempVerticesData = tempVertices.editArray(); + std::vector<Vertex> tempVertices(2); + Vertex* tempVerticesData = &tempVertices.front(); Rect bounds; bounds.set(points[0], points[1], points[0], points[1]); for (int i = 0; i < count; i += 4) { @@ -878,8 +878,8 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa } // calculate bounds - bounds.expandToCoverVertex(tempVerticesData[0].x, tempVerticesData[0].y); - bounds.expandToCoverVertex(tempVerticesData[1].x, tempVerticesData[1].y); + bounds.expandToCover(tempVerticesData[0].x, tempVerticesData[0].y); + bounds.expandToCover(tempVerticesData[1].x, tempVerticesData[1].y); } // since multiple objects tessellated into buffer, separate them with degen tris @@ -900,18 +900,11 @@ void PathTessellator::tessellateLines(const float* points, int count, const SkPa /////////////////////////////////////////////////////////////////////////////// bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, float threshold, - Vector<Vertex>& outputVertices) { + std::vector<Vertex>& outputVertices) { PathApproximationInfo approximationInfo(1.0f, 1.0f, threshold); return approximatePathOutlineVertices(path, true, approximationInfo, outputVertices); } -void pushToVector(Vector<Vertex>& vertices, float x, float y) { - // TODO: make this not yuck - vertices.push(); - Vertex* newVertex = &(vertices.editArray()[vertices.size() - 1]); - Vertex::set(newVertex, x, y); -} - class ClockwiseEnforcer { public: void addPoint(const SkPoint& point) { @@ -927,15 +920,15 @@ public: lastX = x; lastY = y; } - void reverseVectorIfNotClockwise(Vector<Vertex>& vertices) { + void reverseVectorIfNotClockwise(std::vector<Vertex>& vertices) { if (sum < 0) { // negative sum implies CounterClockwise const int size = vertices.size(); for (int i = 0; i < size / 2; i++) { Vertex tmp = vertices[i]; int k = size - 1 - i; - vertices.replaceAt(vertices[k], i); - vertices.replaceAt(tmp, k); + vertices[i] = vertices[k]; + vertices[k] = tmp; } } } @@ -947,7 +940,7 @@ private: }; bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose, - const PathApproximationInfo& approximationInfo, Vector<Vertex>& outputVertices) { + const PathApproximationInfo& approximationInfo, std::vector<Vertex>& outputVertices) { ATRACE_CALL(); // TODO: to support joins other than sharp miter, join vertices should be labelled in the @@ -959,7 +952,7 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo while (SkPath::kDone_Verb != (v = iter.next(pts))) { switch (v) { case SkPath::kMove_Verb: - pushToVector(outputVertices, pts[0].x(), pts[0].y()); + outputVertices.push_back(Vertex{pts[0].x(), pts[0].y()}); ALOGV("Move to pos %f %f", pts[0].x(), pts[0].y()); clockwiseEnforcer.addPoint(pts[0]); break; @@ -969,7 +962,7 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo break; case SkPath::kLine_Verb: ALOGV("kLine_Verb %f %f -> %f %f", pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y()); - pushToVector(outputVertices, pts[1].x(), pts[1].y()); + outputVertices.push_back(Vertex{pts[1].x(), pts[1].y()}); clockwiseEnforcer.addPoint(pts[1]); break; case SkPath::kQuad_Verb: @@ -1020,7 +1013,7 @@ bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool fo int size = outputVertices.size(); if (size >= 2 && outputVertices[0].x == outputVertices[size - 1].x && outputVertices[0].y == outputVertices[size - 1].y) { - outputVertices.pop(); + outputVertices.pop_back(); wasClosed = true; } @@ -1048,7 +1041,7 @@ void PathTessellator::recursiveCubicBezierVertices( float p1x, float p1y, float c1x, float c1y, float p2x, float p2y, float c2x, float c2y, const PathApproximationInfo& approximationInfo, - Vector<Vertex>& outputVertices, int depth) { + std::vector<Vertex>& outputVertices, int depth) { float dx = p2x - p1x; float dy = p2y - p1y; float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx); @@ -1058,7 +1051,7 @@ void PathTessellator::recursiveCubicBezierVertices( if (depth >= MAX_DEPTH || d * d <= getThreshold(approximationInfo, dx, dy)) { // below thresh, draw line by adding endpoint - pushToVector(outputVertices, p2x, p2y); + outputVertices.push_back(Vertex{p2x, p2y}); } else { float p1c1x = (p1x + c1x) * 0.5f; float p1c1y = (p1y + c1y) * 0.5f; @@ -1093,7 +1086,7 @@ void PathTessellator::recursiveQuadraticBezierVertices( float bx, float by, float cx, float cy, const PathApproximationInfo& approximationInfo, - Vector<Vertex>& outputVertices, int depth) { + std::vector<Vertex>& outputVertices, int depth) { float dx = bx - ax; float dy = by - ay; // d is the cross product of vector (B-A) and (C-B). @@ -1102,7 +1095,7 @@ void PathTessellator::recursiveQuadraticBezierVertices( if (depth >= MAX_DEPTH || d * d <= getThreshold(approximationInfo, dx, dy)) { // below thresh, draw line by adding endpoint - pushToVector(outputVertices, bx, by); + outputVertices.push_back(Vertex{bx, by}); } else { float acx = (ax + cx) * 0.5f; float bcx = (bx + cx) * 0.5f; diff --git a/libs/hwui/PathTessellator.h b/libs/hwui/PathTessellator.h index 16c8b36a6a9d..cddfb049212c 100644 --- a/libs/hwui/PathTessellator.h +++ b/libs/hwui/PathTessellator.h @@ -17,13 +17,17 @@ #ifndef ANDROID_HWUI_PATH_TESSELLATOR_H #define ANDROID_HWUI_PATH_TESSELLATOR_H -#include <utils/Vector.h> - #include "Matrix.h" #include "Rect.h" #include "Vertex.h" #include "VertexBuffer.h" +#include <algorithm> +#include <vector> + +class SkPath; +class SkPaint; + namespace android { namespace uirenderer { @@ -38,7 +42,7 @@ struct PathApproximationInfo { : thresholdSquared(pixelThreshold * pixelThreshold) , sqrInvScaleX(invScaleX * invScaleX) , sqrInvScaleY(invScaleY * invScaleY) - , thresholdForConicQuads(pixelThreshold * MathUtils::min(invScaleX, invScaleY) / 2.0f) { + , thresholdForConicQuads(pixelThreshold * std::min(invScaleX, invScaleY) / 2.0f) { }; const float thresholdSquared; @@ -109,11 +113,11 @@ public: * @param outputVertices An empty Vector which will be populated with the output */ static bool approximatePathOutlineVertices(const SkPath &path, float threshold, - Vector<Vertex> &outputVertices); + std::vector<Vertex> &outputVertices); private: static bool approximatePathOutlineVertices(const SkPath &path, bool forceClose, - const PathApproximationInfo& approximationInfo, Vector<Vertex> &outputVertices); + const PathApproximationInfo& approximationInfo, std::vector<Vertex> &outputVertices); /* endpoints a & b, @@ -124,7 +128,7 @@ private: float bx, float by, float cx, float cy, const PathApproximationInfo& approximationInfo, - Vector<Vertex> &outputVertices, int depth = 0); + std::vector<Vertex> &outputVertices, int depth = 0); /* endpoints p1, p2 @@ -136,7 +140,7 @@ private: float p2x, float p2y, float c2x, float c2y, const PathApproximationInfo& approximationInfo, - Vector<Vertex> &outputVertices, int depth = 0); + std::vector<Vertex> &outputVertices, int depth = 0); }; }; // namespace uirenderer diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp index 9665a68b0e77..165c7db4c85f 100644 --- a/libs/hwui/PixelBuffer.cpp +++ b/libs/hwui/PixelBuffer.cpp @@ -14,14 +14,13 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "PixelBuffer.h" #include "Debug.h" #include "Extensions.h" #include "Properties.h" #include "renderstate/RenderState.h" +#include "utils/GLUtils.h" #include <utils/Log.h> @@ -37,12 +36,14 @@ public: CpuPixelBuffer(GLenum format, uint32_t width, uint32_t height); uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) override; - void unmap() override; uint8_t* getMappedPointer() const override; void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) override; +protected: + void unmap() override; + private: std::unique_ptr<uint8_t[]> mBuffer; }; @@ -82,12 +83,14 @@ public: ~GpuPixelBuffer(); uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) override; - void unmap() override; uint8_t* getMappedPointer() const override; void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) override; +protected: + void unmap() override; + private: GLuint mBuffer; uint8_t* mMappedPointer; @@ -114,15 +117,12 @@ uint8_t* GpuPixelBuffer::map(AccessMode mode) { if (mAccessMode == kAccessMode_None) { mCaches.pixelBufferState().bind(mBuffer); mMappedPointer = (uint8_t*) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, getSize(), mode); -#if DEBUG_OPENGL - if (!mMappedPointer) { - GLenum status = GL_NO_ERROR; - while ((status = glGetError()) != GL_NO_ERROR) { - ALOGE("Could not map GPU pixel buffer: 0x%x", status); - } + if (CC_UNLIKELY(!mMappedPointer)) { + GLUtils::dumpGLErrors(); + LOG_ALWAYS_FATAL("Failed to map PBO"); } -#endif mAccessMode = mode; + mCaches.pixelBufferState().unbind(); } return mMappedPointer; @@ -152,6 +152,7 @@ void GpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t hei unmap(); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, mFormat, GL_UNSIGNED_BYTE, reinterpret_cast<void*>(offset)); + mCaches.pixelBufferState().unbind(); } /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/PixelBuffer.h b/libs/hwui/PixelBuffer.h index aac5ec4777ee..bbef36b72e4f 100644 --- a/libs/hwui/PixelBuffer.h +++ b/libs/hwui/PixelBuffer.h @@ -91,14 +91,6 @@ public: virtual uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) = 0; /** - * Unmaps this buffer, if needed. After the buffer is unmapped, - * the pointer previously returned by map() becomes invalid and - * should not be used. After calling this method, getMappedPointer() - * will always return NULL. - */ - virtual void unmap() = 0; - - /** * Returns the current access mode for this buffer. If the buffer * is not mapped, this method returns kAccessMode_None. */ @@ -204,6 +196,14 @@ protected: mFormat(format), mWidth(width), mHeight(height), mAccessMode(kAccessMode_None) { } + /** + * Unmaps this buffer, if needed. After the buffer is unmapped, + * the pointer previously returned by map() becomes invalid and + * should not be used. After calling this method, getMappedPointer() + * will always return NULL. + */ + virtual void unmap() = 0; + GLenum mFormat; uint32_t mWidth; diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp index 32713e9b36f3..e43b80d440e7 100644 --- a/libs/hwui/Program.cpp +++ b/libs/hwui/Program.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <utils/Trace.h> #include "Program.h" diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h index af1e4a74d46e..e5200a516777 100644 --- a/libs/hwui/Program.h +++ b/libs/hwui/Program.h @@ -78,14 +78,12 @@ namespace uirenderer { #define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38 #define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 39 -#define PROGRAM_HAS_GAMMA_CORRECTION 40 +#define PROGRAM_IS_SIMPLE_GRADIENT 40 -#define PROGRAM_IS_SIMPLE_GRADIENT 41 +#define PROGRAM_HAS_COLORS 41 -#define PROGRAM_HAS_COLORS 42 - -#define PROGRAM_HAS_DEBUG_HIGHLIGHT 43 -#define PROGRAM_HAS_ROUND_RECT_CLIP 44 +#define PROGRAM_HAS_DEBUG_HIGHLIGHT 42 +#define PROGRAM_HAS_ROUND_RECT_CLIP 43 /////////////////////////////////////////////////////////////////////////////// // Types @@ -103,10 +101,10 @@ typedef uint64_t programid; * A ProgramDescription must be used in conjunction with a ProgramCache. */ struct ProgramDescription { - enum ColorFilterMode { - kColorNone = 0, - kColorMatrix, - kColorBlend + enum class ColorFilterMode { + None = 0, + Matrix, + Blend }; enum Gradient { @@ -157,9 +155,6 @@ struct ProgramDescription { SkXfermode::Mode framebufferMode; bool swapSrcDst; - bool hasGammaCorrection; - float gamma; - bool hasDebugHighlight; bool hasRoundRectClip; @@ -193,15 +188,12 @@ struct ProgramDescription { bitmapWrapS = GL_CLAMP_TO_EDGE; bitmapWrapT = GL_CLAMP_TO_EDGE; - colorOp = kColorNone; + colorOp = ColorFilterMode::None; colorMode = SkXfermode::kClear_Mode; framebufferMode = SkXfermode::kClear_Mode; swapSrcDst = false; - hasGammaCorrection = false; - gamma = 2.2f; - hasDebugHighlight = false; hasRoundRectClip = false; } @@ -249,14 +241,14 @@ struct ProgramDescription { key |= (shadersMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_SHADER_SHIFT; } switch (colorOp) { - case kColorMatrix: + case ColorFilterMode::Matrix: key |= PROGRAM_KEY_COLOR_MATRIX; break; - case kColorBlend: + case ColorFilterMode::Blend: key |= PROGRAM_KEY_COLOR_BLEND; key |= (colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT; break; - case kColorNone: + case ColorFilterMode::None: break; } key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT; @@ -266,7 +258,6 @@ struct ProgramDescription { if (useShadowAlphaInterp) key |= programid(0x1) << PROGRAM_USE_SHADOW_ALPHA_INTERP_SHIFT; if (hasExternalTexture) key |= programid(0x1) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT; if (hasTextureTransform) key |= programid(0x1) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT; - if (hasGammaCorrection) key |= programid(0x1) << PROGRAM_HAS_GAMMA_CORRECTION; if (isSimpleGradient) key |= programid(0x1) << PROGRAM_IS_SIMPLE_GRADIENT; if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS; if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT; diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 41adda15f367..05be48822fb2 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <utils/String8.h> #include "Caches.h" @@ -40,7 +38,8 @@ namespace uirenderer { // Vertex shaders snippets /////////////////////////////////////////////////////////////////////////////// -const char* gVS_Header_Attributes = +const char* gVS_Header_Start = + "#version 100\n" "attribute vec4 position;\n"; const char* gVS_Header_Attributes_TexCoords = "attribute vec2 texCoords;\n"; @@ -134,6 +133,8 @@ const char* gVS_Footer = // Fragment shaders snippets /////////////////////////////////////////////////////////////////////////////// +const char* gFS_Header_Start = + "#version 100\n"; const char* gFS_Header_Extension_FramebufferFetch = "#extension GL_NV_shader_framebuffer_fetch : enable\n\n"; const char* gFS_Header_Extension_ExternalTexture = @@ -166,8 +167,6 @@ const char* gFS_Uniforms_ColorOp[3] = { // PorterDuff "uniform vec4 colorBlend;\n" }; -const char* gFS_Uniforms_Gamma = - "uniform float gamma;\n"; const char* gFS_Uniforms_HasRoundRectClip = "uniform vec4 roundRectInnerRectLTRB;\n" @@ -203,18 +202,10 @@ const char* gFS_Fast_SingleA8Texture = "\nvoid main(void) {\n" " gl_FragColor = texture2D(baseSampler, outTexCoords);\n" "}\n\n"; -const char* gFS_Fast_SingleA8Texture_ApplyGamma = - "\nvoid main(void) {\n" - " gl_FragColor = vec4(0.0, 0.0, 0.0, pow(texture2D(baseSampler, outTexCoords).a, gamma));\n" - "}\n\n"; const char* gFS_Fast_SingleModulateA8Texture = "\nvoid main(void) {\n" " gl_FragColor = color * texture2D(baseSampler, outTexCoords).a;\n" "}\n\n"; -const char* gFS_Fast_SingleModulateA8Texture_ApplyGamma = - "\nvoid main(void) {\n" - " gl_FragColor = color * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n" - "}\n\n"; const char* gFS_Fast_SingleGradient[2] = { "\nvoid main(void) {\n" " gl_FragColor = %s + texture2D(gradientSampler, linear);\n" @@ -249,13 +240,11 @@ const char* gFS_Main_FetchTexture[2] = { // Modulate " fragColor = color * texture2D(baseSampler, outTexCoords);\n" }; -const char* gFS_Main_FetchA8Texture[4] = { +const char* gFS_Main_FetchA8Texture[2] = { // Don't modulate " fragColor = texture2D(baseSampler, outTexCoords);\n", - " fragColor = texture2D(baseSampler, outTexCoords);\n", // Modulate " fragColor = color * texture2D(baseSampler, outTexCoords).a;\n", - " fragColor = color * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n" }; const char* gFS_Main_FetchGradient[6] = { // Linear @@ -283,38 +272,29 @@ const char* gFS_Main_BlendShadersBG = " fragColor = blendShaders(gradientColor, bitmapColor)"; const char* gFS_Main_BlendShadersGB = " fragColor = blendShaders(bitmapColor, gradientColor)"; -const char* gFS_Main_BlendShaders_Modulate[6] = { +const char* gFS_Main_BlendShaders_Modulate[3] = { // Don't modulate ";\n", - ";\n", // Modulate " * color.a;\n", - " * color.a;\n", // Modulate with alpha 8 texture " * texture2D(baseSampler, outTexCoords).a;\n", - " * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n" }; -const char* gFS_Main_GradientShader_Modulate[6] = { +const char* gFS_Main_GradientShader_Modulate[3] = { // Don't modulate " fragColor = gradientColor;\n", - " fragColor = gradientColor;\n", // Modulate " fragColor = gradientColor * color.a;\n", - " fragColor = gradientColor * color.a;\n", // Modulate with alpha 8 texture " fragColor = gradientColor * texture2D(baseSampler, outTexCoords).a;\n", - " fragColor = gradientColor * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n" }; -const char* gFS_Main_BitmapShader_Modulate[6] = { +const char* gFS_Main_BitmapShader_Modulate[3] = { // Don't modulate " fragColor = bitmapColor;\n", - " fragColor = bitmapColor;\n", // Modulate " fragColor = bitmapColor * color.a;\n", - " fragColor = bitmapColor * color.a;\n", // Modulate with alpha 8 texture " fragColor = bitmapColor * texture2D(baseSampler, outTexCoords).a;\n", - " fragColor = bitmapColor * pow(texture2D(baseSampler, outTexCoords).a, gamma);\n" }; const char* gFS_Main_FragColor = " gl_FragColor = fragColor;\n"; @@ -459,7 +439,7 @@ static inline size_t gradientIndex(const ProgramDescription& description) { String8 ProgramCache::generateVertexShader(const ProgramDescription& description) { // Add attributes - String8 shader(gVS_Header_Attributes); + String8 shader(gVS_Header_Start); if (description.hasTexture || description.hasExternalTexture) { shader.append(gVS_Header_Attributes_TexCoords); } @@ -539,13 +519,12 @@ String8 ProgramCache::generateVertexShader(const ProgramDescription& description static bool shaderOp(const ProgramDescription& description, String8& shader, const int modulateOp, const char** snippets) { int op = description.hasAlpha8Texture ? MODULATE_OP_MODULATE_A8 : modulateOp; - op = op * 2 + description.hasGammaCorrection; shader.append(snippets[op]); return description.hasAlpha8Texture; } String8 ProgramCache::generateFragmentShader(const ProgramDescription& description) { - String8 shader; + String8 shader(gFS_Header_Start); const bool blendFramebuffer = description.framebufferMode >= SkXfermode::kPlus_Mode; if (blendFramebuffer) { @@ -595,9 +574,6 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti shader.appendFormat(gFS_Uniforms_GradientSampler[description.isSimpleGradient], gFS_Uniforms_Dither); } - if (description.hasGammaCorrection) { - shader.append(gFS_Uniforms_Gamma); - } if (description.hasRoundRectClip) { shader.append(gFS_Uniforms_HasRoundRectClip); } @@ -606,7 +582,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (!description.hasVertexAlpha && !blendFramebuffer && !description.hasColors - && description.colorOp == ProgramDescription::kColorNone + && description.colorOp == ProgramDescription::ColorFilterMode::None && !description.hasDebugHighlight && !description.hasRoundRectClip) { bool fast = false; @@ -632,17 +608,9 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti fast = true; } else if (singleA8Texture) { if (!description.modulate) { - if (description.hasGammaCorrection) { - shader.append(gFS_Fast_SingleA8Texture_ApplyGamma); - } else { - shader.append(gFS_Fast_SingleA8Texture); - } + shader.append(gFS_Fast_SingleA8Texture); } else { - if (description.hasGammaCorrection) { - shader.append(gFS_Fast_SingleModulateA8Texture_ApplyGamma); - } else { - shader.append(gFS_Fast_SingleModulateA8Texture); - } + shader.append(gFS_Fast_SingleModulateA8Texture); } fast = true; } else if (singleGradient) { @@ -670,13 +638,13 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.hasBitmap) { shader.append(gFS_Uniforms_BitmapSampler); } - shader.append(gFS_Uniforms_ColorOp[description.colorOp]); + shader.append(gFS_Uniforms_ColorOp[static_cast<int>(description.colorOp)]); // Generate required functions if (description.hasGradient && description.hasBitmap) { generateBlend(shader, "blendShaders", description.shadersMode); } - if (description.colorOp == ProgramDescription::kColorBlend) { + if (description.colorOp == ProgramDescription::ColorFilterMode::Blend) { generateBlend(shader, "blendColors", description.colorMode); } if (blendFramebuffer) { @@ -692,8 +660,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti if (description.hasTexture || description.hasExternalTexture) { if (description.hasAlpha8Texture) { if (!description.hasGradient && !description.hasBitmap) { - shader.append(gFS_Main_FetchA8Texture[modulateOp * 2 + - description.hasGammaCorrection]); + shader.append(gFS_Main_FetchA8Texture[modulateOp]); } } else { shader.append(gFS_Main_FetchTexture[modulateOp]); @@ -739,7 +706,7 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti } // Apply the color op if needed - shader.append(gFS_Main_ApplyColorOp[description.colorOp]); + shader.append(gFS_Main_ApplyColorOp[static_cast<int>(description.colorOp)]); if (description.hasVertexAlpha) { if (description.useShadowAlphaInterp) { diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 2e63793f6ffe..6f68c2bdff80 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -17,8 +17,12 @@ #include "Debug.h" -#include <algorithm> +#include <cutils/compiler.h> #include <cutils/log.h> +#include <cutils/properties.h> + +#include <algorithm> +#include <cstdlib> namespace android { namespace uirenderer { @@ -29,7 +33,22 @@ bool Properties::debugLayersUpdates = false; bool Properties::debugOverdraw = false; bool Properties::showDirtyRegions = false; bool Properties::skipEmptyFrames = true; -bool Properties::swapBuffersWithDamage = true; +bool Properties::useBufferAge = true; +bool Properties::enablePartialUpdates = true; + +float Properties::textGamma = DEFAULT_TEXT_GAMMA; + +int Properties::fboCacheSize = DEFAULT_FBO_CACHE_SIZE; +int Properties::gradientCacheSize = MB(DEFAULT_GRADIENT_CACHE_SIZE); +int Properties::layerPoolSize = MB(DEFAULT_LAYER_CACHE_SIZE); +int Properties::patchCacheSize = KB(DEFAULT_PATCH_CACHE_SIZE); +int Properties::pathCacheSize = MB(DEFAULT_PATH_CACHE_SIZE); +int Properties::renderBufferCacheSize = MB(DEFAULT_RENDER_BUFFER_CACHE_SIZE); +int Properties::tessellationCacheSize = MB(DEFAULT_VERTEX_CACHE_SIZE); +int Properties::textDropShadowCacheSize = MB(DEFAULT_DROP_SHADOW_CACHE_SIZE); +int Properties::textureCacheSize = MB(DEFAULT_TEXTURE_CACHE_SIZE); + +float Properties::textureCacheFlushRate = DEFAULT_TEXTURE_CACHE_FLUSH_RATE; DebugLevel Properties::debugLevel = kDebugDisabled; OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default; @@ -45,13 +64,34 @@ int Properties::overrideSpotShadowStrength = -1; ProfileType Properties::sProfileType = ProfileType::None; bool Properties::sDisableProfileBars = false; +bool Properties::waitForGpuCompletion = false; + +bool Properties::filterOutTestOverhead = false; + +static int property_get_int(const char* key, int defaultValue) { + char buf[PROPERTY_VALUE_MAX] = {'\0',}; + + if (property_get(key, buf, "") > 0) { + return atoi(buf); + } + return defaultValue; +} + +static float property_get_float(const char* key, float defaultValue) { + char buf[PROPERTY_VALUE_MAX] = {'\0',}; + + if (property_get(key, buf, "") > 0) { + return atof(buf); + } + return defaultValue; +} + bool Properties::load() { char property[PROPERTY_VALUE_MAX]; bool prevDebugLayersUpdates = debugLayersUpdates; bool prevDebugOverdraw = debugOverdraw; StencilClipDebug prevDebugStencilClip = debugStencilClip; - debugOverdraw = false; if (property_get(PROPERTY_DEBUG_OVERDRAW, property, nullptr) > 0) { INIT_LOGD(" Overdraw debug enabled: %s", property); @@ -98,13 +138,27 @@ bool Properties::load() { showDirtyRegions = property_get_bool(PROPERTY_DEBUG_SHOW_DIRTY_REGIONS, false); - debugLevel = kDebugDisabled; - if (property_get(PROPERTY_DEBUG, property, nullptr) > 0) { - debugLevel = (DebugLevel) atoi(property); - } + debugLevel = (DebugLevel) property_get_int(PROPERTY_DEBUG, kDebugDisabled); skipEmptyFrames = property_get_bool(PROPERTY_SKIP_EMPTY_DAMAGE, true); - swapBuffersWithDamage = property_get_bool(PROPERTY_SWAP_WITH_DAMAGE, true); + useBufferAge = property_get_bool(PROPERTY_USE_BUFFER_AGE, true); + enablePartialUpdates = property_get_bool(PROPERTY_ENABLE_PARTIAL_UPDATES, true); + + textGamma = property_get_float(PROPERTY_TEXT_GAMMA, DEFAULT_TEXT_GAMMA); + + fboCacheSize = property_get_int(PROPERTY_FBO_CACHE_SIZE, DEFAULT_FBO_CACHE_SIZE); + gradientCacheSize = MB(property_get_float(PROPERTY_GRADIENT_CACHE_SIZE, DEFAULT_GRADIENT_CACHE_SIZE)); + layerPoolSize = MB(property_get_float(PROPERTY_LAYER_CACHE_SIZE, DEFAULT_LAYER_CACHE_SIZE)); + patchCacheSize = KB(property_get_float(PROPERTY_PATCH_CACHE_SIZE, DEFAULT_PATCH_CACHE_SIZE)); + pathCacheSize = MB(property_get_float(PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE)); + renderBufferCacheSize = MB(property_get_float(PROPERTY_RENDER_BUFFER_CACHE_SIZE, DEFAULT_RENDER_BUFFER_CACHE_SIZE)); + tessellationCacheSize = MB(property_get_float(PROPERTY_VERTEX_CACHE_SIZE, DEFAULT_VERTEX_CACHE_SIZE)); + textDropShadowCacheSize = MB(property_get_float(PROPERTY_DROP_SHADOW_CACHE_SIZE, DEFAULT_DROP_SHADOW_CACHE_SIZE)); + textureCacheSize = MB(property_get_float(PROPERTY_TEXTURE_CACHE_SIZE, DEFAULT_TEXTURE_CACHE_SIZE)); + textureCacheFlushRate = std::max(0.0f, std::min(1.0f, + property_get_float(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, DEFAULT_TEXTURE_CACHE_FLUSH_RATE))); + + filterOutTestOverhead = property_get_bool(PROPERTY_FILTER_TEST_OVERHEAD, false); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 26d8bf754ddb..171873de1f9a 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -18,8 +18,6 @@ #define ANDROID_HWUI_PROPERTIES_H #include <cutils/properties.h> -#include <stdlib.h> -#include <utils/Singleton.h> /** * This file contains the list of system properties used to configure @@ -33,12 +31,6 @@ namespace uirenderer { // Compile-time properties /////////////////////////////////////////////////////////////////////////////// -// If turned on, text is interpreted as glyphs instead of UTF-16 -#define RENDER_TEXT_AS_GLYPHS 1 - -// Indicates whether to remove the biggest layers first, or the smaller ones -#define LAYER_REMOVE_BIGGEST_FIRST 0 - // Textures used by layers must have dimensions multiples of this number #define LAYER_SIZE 64 @@ -87,12 +79,6 @@ enum DebugLevel { #define PROPERTY_DEBUG_OVERDRAW "debug.hwui.overdraw" /** - * Used to enable/disable PerfHUD ES profiling. The accepted values - * are "true" and "false". The default value is "false". - */ -#define PROPERTY_DEBUG_NV_PROFILING "debug.hwui.nv_profiling" - -/** * System property used to enable or disable hardware rendering profiling. * The default value of this property is assumed to be false. * @@ -151,13 +137,21 @@ enum DebugLevel { #define PROPERTY_SKIP_EMPTY_DAMAGE "debug.hwui.skip_empty_damage" /** - * Setting this property will enable usage of EGL_KHR_swap_buffers_with_damage - * See: https://www.khronos.org/registry/egl/extensions/KHR/EGL_KHR_swap_buffers_with_damage.txt - * Default is "false" temporarily - * TODO: Change to "true", make sure to remove the log in EglManager::swapBuffers - * before changing this to default to true! + * Controls whether or not HWUI will use the EGL_EXT_buffer_age extension + * to do partial invalidates. Setting this to "false" will fall back to + * using BUFFER_PRESERVED instead + * Default is "true" */ -#define PROPERTY_SWAP_WITH_DAMAGE "debug.hwui.swap_with_damage" +#define PROPERTY_USE_BUFFER_AGE "debug.hwui.use_buffer_age" + +/** + * Setting this to "false" will force HWUI to always do full-redraws of the surface. + * This will disable the use of EGL_EXT_buffer_age and BUFFER_PRESERVED. + * Default is "true" + */ +#define PROPERTY_ENABLE_PARTIAL_UPDATES "debug.hwui.enable_partial_updates" + +#define PROPERTY_FILTER_TEST_OVERHEAD "debug.hwui.filter_test_overhead" /////////////////////////////////////////////////////////////////////////////// // Runtime configuration properties @@ -204,30 +198,8 @@ enum DebugLevel { #define PROPERTY_TEXT_LARGE_CACHE_WIDTH "ro.hwui.text_large_cache_width" #define PROPERTY_TEXT_LARGE_CACHE_HEIGHT "ro.hwui.text_large_cache_height" -// Indicates whether gamma correction should be applied in the shaders -// or in lookup tables. Accepted values: -// -// - "lookup3", correction based on lookup tables. Gamma correction -// is different for black and white text (see thresholds below) -// -// - "lookup", correction based on a single lookup table -// -// - "shader3", correction applied by a GLSL shader. Gamma correction -// is different for black and white text (see thresholds below) -// -// - "shader", correction applied by a GLSL shader -// -// See PROPERTY_TEXT_GAMMA, PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD and -// PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD for more control. -#define PROPERTY_TEXT_GAMMA_METHOD "hwui.text_gamma_correction" -#define DEFAULT_TEXT_GAMMA_METHOD "lookup" - // Gamma (>= 1.0, <= 10.0) #define PROPERTY_TEXT_GAMMA "hwui.text_gamma" -// Luminance threshold below which black gamma correction is applied. Range: [0..255] -#define PROPERTY_TEXT_BLACK_GAMMA_THRESHOLD "hwui.text_gamma.black_threshold" -// Lumincance threshold above which white gamma correction is applied. Range: [0..255] -#define PROPERTY_TEXT_WHITE_GAMMA_THRESHOLD "hwui.text_gamma.white_threshold" /////////////////////////////////////////////////////////////////////////////// // Default property values @@ -238,7 +210,7 @@ enum DebugLevel { #define DEFAULT_RENDER_BUFFER_CACHE_SIZE 2.0f #define DEFAULT_PATH_CACHE_SIZE 4.0f #define DEFAULT_VERTEX_CACHE_SIZE 1.0f -#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB +#define DEFAULT_PATCH_CACHE_SIZE 128.0f // in kB #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f #define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f #define DEFAULT_FBO_CACHE_SIZE 0 @@ -246,8 +218,6 @@ enum DebugLevel { #define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f #define DEFAULT_TEXT_GAMMA 1.4f -#define DEFAULT_TEXT_BLACK_GAMMA_THRESHOLD 64 -#define DEFAULT_TEXT_WHITE_GAMMA_THRESHOLD 192 /////////////////////////////////////////////////////////////////////////////// // Misc @@ -291,8 +261,21 @@ public: static bool showDirtyRegions; // TODO: Remove after stabilization period static bool skipEmptyFrames; - // TODO: Remove after stabilization period - static bool swapBuffersWithDamage; + static bool useBufferAge; + static bool enablePartialUpdates; + + static float textGamma; + + static int fboCacheSize; + static int gradientCacheSize; + static int layerPoolSize; + static int patchCacheSize; + static int pathCacheSize; + static int renderBufferCacheSize; + static int tessellationCacheSize; + static int textDropShadowCacheSize; + static int textureCacheSize; + static float textureCacheFlushRate; static DebugLevel debugLevel; static OverdrawColorSet overdrawColorSet; @@ -310,6 +293,13 @@ public: static ProfileType getProfileType(); + // Should be used only by test apps + static bool waitForGpuCompletion; + + // Should only be set by automated tests to try and filter out + // any overhead they add + static bool filterOutTestOverhead; + private: static ProfileType sProfileType; static bool sDisableProfileBars; diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp new file mode 100644 index 000000000000..b29f91ff34aa --- /dev/null +++ b/libs/hwui/PropertyValuesAnimatorSet.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2016 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 "PropertyValuesAnimatorSet.h" +#include "RenderNode.h" + +#include <algorithm> + +namespace android { +namespace uirenderer { + +void PropertyValuesAnimatorSet::addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder, + Interpolator* interpolator, nsecs_t startDelay, + nsecs_t duration, int repeatCount) { + + PropertyAnimator* animator = new PropertyAnimator(propertyValuesHolder, + interpolator, startDelay, duration, repeatCount); + mAnimators.emplace_back(animator); + setListener(new PropertyAnimatorSetListener(this)); +} + +PropertyValuesAnimatorSet::PropertyValuesAnimatorSet() + : BaseRenderNodeAnimator(1.0f) { + setStartValue(0); + mLastFraction = 0.0f; + setInterpolator(new LinearInterpolator()); +} + +void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) { + if (mOneShotListener.get()) { + mOneShotListener->onAnimationFinished(animator); + mOneShotListener = nullptr; + } +} + +float PropertyValuesAnimatorSet::getValue(RenderNode* target) const { + return mLastFraction; +} + +void PropertyValuesAnimatorSet::setValue(RenderNode* target, float value) { + mLastFraction = value; +} + +void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) { + if (playTime == 0 && mDuration > 0) { + // Reset all the animators + for (auto it = mAnimators.rbegin(); it != mAnimators.rend(); it++) { + // Note that this set may containing animators modifying the same property, so when we + // reset the animators, we need to make sure the animators that end the first will + // have the final say on what the property value should be. + (*it)->setFraction(0); + } + } else if (playTime >= mDuration) { + // Skip all the animators to end + for (auto& anim : mAnimators) { + anim->setFraction(1); + } + } else { + for (auto& anim : mAnimators) { + anim->setCurrentPlayTime(playTime); + } + } +} + +void PropertyValuesAnimatorSet::start(AnimationListener* listener) { + init(); + mOneShotListener = listener; + BaseRenderNodeAnimator::start(); +} + +void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) { + init(); + mOneShotListener = listener; + BaseRenderNodeAnimator::reverse(); +} + +void PropertyValuesAnimatorSet::init() { + if (mInitialized) { + return; + } + + // Sort the animators by their total duration. Note that all the animators in the set start at + // the same time, so the ones with longer total duration (which includes start delay) will + // be the ones that end later. + std::sort(mAnimators.begin(), mAnimators.end(), [](auto& a, auto&b) { + return a->getTotalDuration() < b->getTotalDuration(); + }); + mDuration = mAnimators[mAnimators.size() - 1]->getTotalDuration(); + mInitialized = true; +} + +uint32_t PropertyValuesAnimatorSet::dirtyMask() { + return RenderNode::DISPLAY_LIST; +} + +PropertyAnimator::PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, + nsecs_t startDelay, nsecs_t duration, int repeatCount) + : mPropertyValuesHolder(holder), mInterpolator(interpolator), mStartDelay(startDelay), + mDuration(duration) { + if (repeatCount < 0) { + mRepeatCount = UINT32_MAX; + } else { + mRepeatCount = repeatCount; + } + mTotalDuration = ((nsecs_t) mRepeatCount + 1) * mDuration + mStartDelay; +} + +void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) { + if (playTime >= mStartDelay && playTime < mTotalDuration) { + nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration; + float fraction = currentIterationPlayTime / (float) mDuration; + setFraction(fraction); + } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) { + // This makes sure we only set the fraction = 1 once. It is needed because there might + // be another animator modifying the same property after this animator finishes, we need + // to make sure we don't set conflicting values on the same property within one frame. + setFraction(1.0f); + } +} + +void PropertyAnimator::setFraction(float fraction) { + mLatestFraction = fraction; + float interpolatedFraction = mInterpolator->interpolate(fraction); + mPropertyValuesHolder->setFraction(interpolatedFraction); +} + +void PropertyAnimatorSetListener::onAnimationFinished(BaseRenderNodeAnimator* animator) { + mSet->onFinished(animator); +} + +} +} diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h new file mode 100644 index 000000000000..c7ae7c0e8ce1 --- /dev/null +++ b/libs/hwui/PropertyValuesAnimatorSet.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 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 "Animator.h" +#include "PropertyValuesHolder.h" +#include "Interpolator.h" + +namespace android { +namespace uirenderer { + +class PropertyAnimator { +public: + PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, nsecs_t startDelay, + nsecs_t duration, int repeatCount); + void setCurrentPlayTime(nsecs_t playTime); + nsecs_t getTotalDuration() { + return mTotalDuration; + } + void setFraction(float fraction); + +private: + std::unique_ptr<PropertyValuesHolder> mPropertyValuesHolder; + std::unique_ptr<Interpolator> mInterpolator; + nsecs_t mStartDelay; + nsecs_t mDuration; + uint32_t mRepeatCount; + nsecs_t mTotalDuration; + float mLatestFraction = 0.0f; +}; + +class ANDROID_API PropertyValuesAnimatorSet : public BaseRenderNodeAnimator { +public: + friend class PropertyAnimatorSetListener; + PropertyValuesAnimatorSet(); + + void start(AnimationListener* listener); + void reverse(AnimationListener* listener); + + void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder, + Interpolator* interpolators, int64_t startDelays, + nsecs_t durations, int repeatCount); + virtual uint32_t dirtyMask(); + +protected: + virtual float getValue(RenderNode* target) const override; + virtual void setValue(RenderNode* target, float value) override; + virtual void onPlayTimeChanged(nsecs_t playTime) override; + +private: + void init(); + void onFinished(BaseRenderNodeAnimator* animator); + // Listener set from outside + sp<AnimationListener> mOneShotListener; + std::vector< std::unique_ptr<PropertyAnimator> > mAnimators; + float mLastFraction = 0.0f; + bool mInitialized = false; +}; + +class PropertyAnimatorSetListener : public AnimationListener { +public: + PropertyAnimatorSetListener(PropertyValuesAnimatorSet* set) : mSet(set) {} + virtual void onAnimationFinished(BaseRenderNodeAnimator* animator) override; + +private: + PropertyValuesAnimatorSet* mSet; +}; + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp new file mode 100644 index 000000000000..0932d653fd5e --- /dev/null +++ b/libs/hwui/PropertyValuesHolder.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2016 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 "PropertyValuesHolder.h" + +#include "utils/VectorDrawableUtils.h" + +#include <utils/Log.h> + +namespace android { +namespace uirenderer { + +using namespace VectorDrawable; + +float PropertyValuesHolder::getValueFromData(float fraction) { + if (mDataSource.size() == 0) { + LOG_ALWAYS_FATAL("No data source is defined"); + return 0; + } + if (fraction <= 0.0f) { + return mDataSource.front(); + } + if (fraction >= 1.0f) { + return mDataSource.back(); + } + + fraction *= mDataSource.size() - 1; + int lowIndex = floor(fraction); + fraction -= lowIndex; + + float value = mDataSource[lowIndex] * (1.0f - fraction) + + mDataSource[lowIndex + 1] * fraction; + return value; +} + +void GroupPropertyValuesHolder::setFraction(float fraction) { + float animatedValue; + if (mDataSource.size() > 0) { + animatedValue = getValueFromData(fraction); + } else { + animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + } + mGroup->mutateProperties()->setPropertyValue(mPropertyId, animatedValue); +} + +inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) { + return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction); +} + +// TODO: Add a test for this +SkColor FullPathColorPropertyValuesHolder::interpolateColors(SkColor fromColor, SkColor toColor, + float fraction) { + U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction); + U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction); + U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction); + U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction); + return SkColorSetARGB(alpha, red, green, blue); +} + +void FullPathColorPropertyValuesHolder::setFraction(float fraction) { + SkColor animatedValue = interpolateColors(mStartValue, mEndValue, fraction); + mFullPath->mutateProperties()->setColorPropertyValue(mPropertyId, animatedValue); +} + +void FullPathPropertyValuesHolder::setFraction(float fraction) { + float animatedValue; + if (mDataSource.size() > 0) { + animatedValue = getValueFromData(fraction); + } else { + animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + } + mFullPath->mutateProperties()->setPropertyValue(mPropertyId, animatedValue); +} + +void PathDataPropertyValuesHolder::setFraction(float fraction) { + VectorDrawableUtils::interpolatePaths(&mPathData, mStartValue, mEndValue, fraction); + mPath->mutateProperties()->setData(mPathData); +} + +void RootAlphaPropertyValuesHolder::setFraction(float fraction) { + float animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction; + mTree->mutateProperties()->setRootAlpha(animatedValue); +} + +} // namepace uirenderer +} // namespace android diff --git a/libs/hwui/PropertyValuesHolder.h b/libs/hwui/PropertyValuesHolder.h new file mode 100644 index 000000000000..b905faef104c --- /dev/null +++ b/libs/hwui/PropertyValuesHolder.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 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 "VectorDrawable.h" + +#include <SkColor.h> + +namespace android { +namespace uirenderer { + +/** + * PropertyValues holder contains data needed to change a property of a Vector Drawable object. + * When a fraction in [0f, 1f] is provided, the holder will calculate an interpolated value based + * on its start and end value, and set the new value on the VectorDrawble's corresponding property. + */ +class ANDROID_API PropertyValuesHolder { +public: + virtual void setFraction(float fraction) = 0; + void setPropertyDataSource(float* dataSource, int length) { + mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length); + } + float getValueFromData(float fraction); + virtual ~PropertyValuesHolder() {} +protected: + std::vector<float> mDataSource; +}; + +class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolder { +public: + GroupPropertyValuesHolder(VectorDrawable::Group* ptr, int propertyId, float startValue, + float endValue) + : mGroup(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue){ + } + void setFraction(float fraction) override; +private: + VectorDrawable::Group* mGroup; + int mPropertyId; + float mStartValue; + float mEndValue; +}; + +class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolder { +public: + FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, int32_t startValue, + int32_t endValue) + : mFullPath(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue) {}; + void setFraction(float fraction) override; + static SkColor interpolateColors(SkColor fromColor, SkColor toColor, float fraction); +private: + VectorDrawable::FullPath* mFullPath; + int mPropertyId; + int32_t mStartValue; + int32_t mEndValue; +}; + +class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolder { +public: + FullPathPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, float startValue, + float endValue) + : mFullPath(ptr) + , mPropertyId(propertyId) + , mStartValue(startValue) + , mEndValue(endValue) {}; + void setFraction(float fraction) override; +private: + VectorDrawable::FullPath* mFullPath; + int mPropertyId; + float mStartValue; + float mEndValue; +}; + +class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolder { +public: + PathDataPropertyValuesHolder(VectorDrawable::Path* ptr, PathData* startValue, + PathData* endValue) + : mPath(ptr) + , mStartValue(*startValue) + , mEndValue(*endValue) {}; + void setFraction(float fraction) override; +private: + VectorDrawable::Path* mPath; + PathData mPathData; + PathData mStartValue; + PathData mEndValue; +}; + +class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolder { +public: + RootAlphaPropertyValuesHolder(VectorDrawable::Tree* tree, float startValue, float endValue) + : mTree(tree) + , mStartValue(startValue) + , mEndValue(endValue) {} + void setFraction(float fraction) override; +private: + VectorDrawable::Tree* mTree; + float mStartValue; + float mEndValue; +}; +} +} diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp new file mode 100644 index 000000000000..55f823dfe226 --- /dev/null +++ b/libs/hwui/Readback.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2016 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 "Readback.h" + +#include "Caches.h" +#include "Image.h" +#include "GlopBuilder.h" +#include "renderstate/RenderState.h" +#include "renderthread/EglManager.h" +#include "utils/GLUtils.h" + +#include <GLES2/gl2.h> +#include <ui/Fence.h> +#include <ui/GraphicBuffer.h> + +namespace android { +namespace uirenderer { + +CopyResult Readback::copySurfaceInto(renderthread::RenderThread& renderThread, + Surface& surface, SkBitmap* bitmap) { + // TODO: Clean this up and unify it with LayerRenderer::copyLayer, + // of which most of this is copied from. + renderThread.eglManager().initialize(); + + Caches& caches = Caches::getInstance(); + RenderState& renderState = renderThread.renderState(); + int destWidth = bitmap->width(); + int destHeight = bitmap->height(); + if (destWidth > caches.maxTextureSize + || destHeight > caches.maxTextureSize) { + ALOGW("Can't copy surface into bitmap, %dx%d exceeds max texture size %d", + destWidth, destHeight, caches.maxTextureSize); + return CopyResult::DestinationInvalid; + } + GLuint fbo = renderState.createFramebuffer(); + if (!fbo) { + ALOGW("Could not obtain an FBO"); + return CopyResult::UnknownError; + } + + SkAutoLockPixels alp(*bitmap); + + GLuint texture; + + GLenum format; + GLenum type; + + switch (bitmap->colorType()) { + case kAlpha_8_SkColorType: + format = GL_ALPHA; + type = GL_UNSIGNED_BYTE; + break; + case kRGB_565_SkColorType: + format = GL_RGB; + type = GL_UNSIGNED_SHORT_5_6_5; + break; + case kARGB_4444_SkColorType: + format = GL_RGBA; + type = GL_UNSIGNED_SHORT_4_4_4_4; + break; + case kN32_SkColorType: + default: + format = GL_RGBA; + type = GL_UNSIGNED_BYTE; + break; + } + + renderState.bindFramebuffer(fbo); + + // TODO: Use layerPool or something to get this maybe? But since we + // need explicit format control we can't currently. + + // Setup the rendertarget + glGenTextures(1, &texture); + caches.textureState().activateTexture(0); + caches.textureState().bindTexture(texture); + glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, format, destWidth, destHeight, + 0, format, type, nullptr); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, texture, 0); + + // Setup the source + sp<GraphicBuffer> sourceBuffer; + sp<Fence> sourceFence; + Matrix4 texTransform; + status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence, + texTransform.data); + texTransform.invalidateType(); + if (err != NO_ERROR) { + ALOGW("Failed to get last queued buffer, error = %d", err); + return CopyResult::UnknownError; + } + if (!sourceBuffer.get()) { + ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); + return CopyResult::SourceEmpty; + } + if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) { + ALOGW("Surface is protected, unable to copy from it"); + return CopyResult::SourceInvalid; + } + err = sourceFence->wait(500 /* ms */); + if (err != NO_ERROR) { + ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); + return CopyResult::Timeout; + } + + // TODO: Can't use Image helper since it forces GL_TEXTURE_2D usage via + // GL_OES_EGL_image, which doesn't work since we need samplerExternalOES + // to be able to properly sample from the buffer. + + // Create the EGLImage object that maps the GraphicBuffer + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + EGLClientBuffer clientBuffer = (EGLClientBuffer) sourceBuffer->getNativeBuffer(); + EGLint attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; + + EGLImageKHR sourceImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, + EGL_NATIVE_BUFFER_ANDROID, clientBuffer, attrs); + + if (sourceImage == EGL_NO_IMAGE_KHR) { + ALOGW("Error creating image (%#x)", eglGetError()); + return CopyResult::UnknownError; + } + GLuint sourceTexId; + // Create a 2D texture to sample from the EGLImage + glGenTextures(1, &sourceTexId); + Caches::getInstance().textureState().bindTexture(GL_TEXTURE_EXTERNAL_OES, sourceTexId); + glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, sourceImage); + + GLenum status = GL_NO_ERROR; + while ((status = glGetError()) != GL_NO_ERROR) { + ALOGW("Error creating image (%#x)", status); + return CopyResult::UnknownError; + } + + Texture sourceTexture(caches); + sourceTexture.wrap(sourceTexId, + sourceBuffer->getWidth(), sourceBuffer->getHeight(), 0 /* total lie */); + + { + // Draw & readback + renderState.setViewport(destWidth, destHeight); + renderState.scissor().setEnabled(false); + renderState.blend().syncEnabled(); + renderState.stencil().disable(); + + Rect destRect(destWidth, destHeight); + Glop glop; + GlopBuilder(renderState, caches, &glop) + .setRoundRectClipState(nullptr) + .setMeshTexturedUnitQuad(nullptr) + .setFillExternalTexture(sourceTexture, texTransform) + .setTransform(Matrix4::identity(), TransformFlags::None) + .setModelViewMapUnitToRect(destRect) + .build(); + Matrix4 ortho; + ortho.loadOrtho(destWidth, destHeight); + renderState.render(glop, ortho); + + glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, + type, bitmap->getPixels()); + } + + // Cleanup + caches.textureState().deleteTexture(texture); + renderState.deleteFramebuffer(fbo); + + GL_CHECKPOINT(MODERATE); + + return CopyResult::Success; +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h new file mode 100644 index 000000000000..a112c42988c0 --- /dev/null +++ b/libs/hwui/Readback.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2016 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 "renderthread/RenderThread.h" + +#include <SkBitmap.h> +#include <gui/Surface.h> + +namespace android { +namespace uirenderer { + +// Keep in sync with PixelCopy.java codes +enum class CopyResult { + Success = 0, + UnknownError = 1, + Timeout = 2, + SourceEmpty = 3, + SourceInvalid = 4, + DestinationInvalid = 5, +}; + +class Readback { +public: + static CopyResult copySurfaceInto(renderthread::RenderThread& renderThread, + Surface& surface, SkBitmap* bitmap); +}; + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h new file mode 100644 index 000000000000..aee9d6370083 --- /dev/null +++ b/libs/hwui/RecordedOp.h @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_RECORDED_OP_H +#define ANDROID_HWUI_RECORDED_OP_H + +#include "RecordedOp.h" +#include "font/FontUtil.h" +#include "Matrix.h" +#include "Rect.h" +#include "RenderNode.h" +#include "TessellationCache.h" +#include "utils/LinearAllocator.h" +#include "Vector.h" + +#include <androidfw/ResourceTypes.h> +#include <SkXfermode.h> + +class SkBitmap; +class SkPaint; + +namespace android { +namespace uirenderer { + +struct ClipBase; +class OffscreenBuffer; +class RenderNode; +struct Vertex; + +namespace VectorDrawable { +class Tree; +} + +/** + * Authoritative op list, used for generating the op ID enum, ID based LUTS, and + * the functions to which they dispatch. Parameter macros are executed for each op, + * in order, based on the op's type. + * + * There are 4 types of op, which defines dispatch/LUT capability: + * + * | DisplayList | Render | Merge | + * -------------|-------------|-------------|-------------| + * PRE RENDER | Yes | | | + * RENDER ONLY | | Yes | | + * UNMERGEABLE | Yes | Yes | | + * MERGEABLE | Yes | Yes | Yes | + * + * PRE RENDER - These ops are recorded into DisplayLists, but can't be directly rendered. This + * may be because they need to be transformed into other op types (e.g. CirclePropsOp), + * be traversed to access multiple renderable ops within (e.g. RenderNodeOp), or because they + * modify renderbuffer lifecycle, instead of directly rendering content (the various LayerOps). + * + * RENDER ONLY - These ops cannot be recorded into DisplayLists, and are instead implicitly + * constructed from other commands/RenderNode properties. They cannot be merged. + * + * UNMERGEABLE - These ops can be recorded into DisplayLists and rendered directly, but do not + * support merged rendering. + * + * MERGEABLE - These ops can be recorded into DisplayLists and rendered individually, or merged + * under certain circumstances. + */ +#define MAP_OPS_BASED_ON_TYPE(PRE_RENDER_OP_FN, RENDER_ONLY_OP_FN, UNMERGEABLE_OP_FN, MERGEABLE_OP_FN) \ + PRE_RENDER_OP_FN(RenderNodeOp) \ + PRE_RENDER_OP_FN(CirclePropsOp) \ + PRE_RENDER_OP_FN(RoundRectPropsOp) \ + PRE_RENDER_OP_FN(BeginLayerOp) \ + PRE_RENDER_OP_FN(EndLayerOp) \ + PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \ + PRE_RENDER_OP_FN(EndUnclippedLayerOp) \ + PRE_RENDER_OP_FN(VectorDrawableOp) \ + \ + RENDER_ONLY_OP_FN(ShadowOp) \ + RENDER_ONLY_OP_FN(LayerOp) \ + RENDER_ONLY_OP_FN(CopyToLayerOp) \ + RENDER_ONLY_OP_FN(CopyFromLayerOp) \ + \ + UNMERGEABLE_OP_FN(ArcOp) \ + UNMERGEABLE_OP_FN(BitmapMeshOp) \ + UNMERGEABLE_OP_FN(BitmapRectOp) \ + UNMERGEABLE_OP_FN(ColorOp) \ + UNMERGEABLE_OP_FN(FunctorOp) \ + UNMERGEABLE_OP_FN(LinesOp) \ + UNMERGEABLE_OP_FN(OvalOp) \ + UNMERGEABLE_OP_FN(PathOp) \ + UNMERGEABLE_OP_FN(PointsOp) \ + UNMERGEABLE_OP_FN(RectOp) \ + UNMERGEABLE_OP_FN(RoundRectOp) \ + UNMERGEABLE_OP_FN(SimpleRectsOp) \ + UNMERGEABLE_OP_FN(TextOnPathOp) \ + UNMERGEABLE_OP_FN(TextureLayerOp) \ + \ + MERGEABLE_OP_FN(BitmapOp) \ + MERGEABLE_OP_FN(PatchOp) \ + MERGEABLE_OP_FN(TextOp) + +/** + * LUT generators, which will insert nullptr for unsupported ops + */ +#define NULLPTR_OP_FN(Type) nullptr, + +#define BUILD_DEFERRABLE_OP_LUT(OP_FN) \ + { MAP_OPS_BASED_ON_TYPE(OP_FN, NULLPTR_OP_FN, OP_FN, OP_FN) } + +#define BUILD_MERGEABLE_OP_LUT(OP_FN) \ + { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, NULLPTR_OP_FN, NULLPTR_OP_FN, OP_FN) } + +#define BUILD_RENDERABLE_OP_LUT(OP_FN) \ + { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, OP_FN, OP_FN, OP_FN) } + +#define BUILD_FULL_OP_LUT(OP_FN) \ + { MAP_OPS_BASED_ON_TYPE(OP_FN, OP_FN, OP_FN, OP_FN) } + +/** + * Op mapping functions, which skip unsupported ops. + * + * Note: Do not use for LUTS, since these do not preserve ID order. + */ +#define NULL_OP_FN(Type) + +#define MAP_DEFERRABLE_OPS(OP_FN) \ + MAP_OPS_BASED_ON_TYPE(OP_FN, NULL_OP_FN, OP_FN, OP_FN) + +#define MAP_MERGEABLE_OPS(OP_FN) \ + MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, NULL_OP_FN, NULL_OP_FN, OP_FN) + +#define MAP_RENDERABLE_OPS(OP_FN) \ + MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, OP_FN, OP_FN, OP_FN) + +// Generate OpId enum +#define IDENTITY_FN(Type) Type, +namespace RecordedOpId { + enum { + MAP_OPS_BASED_ON_TYPE(IDENTITY_FN, IDENTITY_FN, IDENTITY_FN, IDENTITY_FN) + Count, + }; +} +static_assert(RecordedOpId::RenderNodeOp == 0, + "First index must be zero for LUTs to work"); + +#define BASE_PARAMS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint +#define BASE_PARAMS_PAINTLESS const Rect& unmappedBounds, const Matrix4& localMatrix, const ClipBase* localClip +#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, paint) +#define SUPER_PAINTLESS(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, nullptr) + +struct RecordedOp { + /* ID from RecordedOpId - generally used for jumping into function tables */ + const int opId; + + /* bounds in *local* space, without accounting for DisplayList transformation, or stroke */ + const Rect unmappedBounds; + + /* transform in recording space (vs DisplayList origin) */ + const Matrix4 localMatrix; + + /* clip in recording space - nullptr if not clipped */ + const ClipBase* localClip; + + /* optional paint, stored in base object to simplify merging logic */ + const SkPaint* paint; +protected: + RecordedOp(unsigned int opId, BASE_PARAMS) + : opId(opId) + , unmappedBounds(unmappedBounds) + , localMatrix(localMatrix) + , localClip(localClip) + , paint(paint) {} +}; + +struct RenderNodeOp : RecordedOp { + RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode) + : SUPER_PAINTLESS(RenderNodeOp) + , renderNode(renderNode) {} + RenderNode * renderNode; // not const, since drawing modifies it + + /** + * Holds the transformation between the projection surface ViewGroup and this RenderNode + * drawing instance. Represents any translations / transformations done within the drawing of + * the compositing ancestor ViewGroup's draw, before the draw of the View represented by this + * DisplayList draw instance. + * + * Note: doesn't include transformation within the RenderNode, or its properties. + */ + Matrix4 transformFromCompositingAncestor; + bool skipInOrderDraw = false; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Standard Ops +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct ArcOp : RecordedOp { + ArcOp(BASE_PARAMS, float startAngle, float sweepAngle, bool useCenter) + : SUPER(ArcOp) + , startAngle(startAngle) + , sweepAngle(sweepAngle) + , useCenter(useCenter) {} + const float startAngle; + const float sweepAngle; + const bool useCenter; +}; + +struct BitmapOp : RecordedOp { + BitmapOp(BASE_PARAMS, const SkBitmap* bitmap) + : SUPER(BitmapOp) + , bitmap(bitmap) {} + const SkBitmap* bitmap; + // TODO: asset atlas/texture id lookup? +}; + +struct BitmapMeshOp : RecordedOp { + BitmapMeshOp(BASE_PARAMS, const SkBitmap* bitmap, int meshWidth, int meshHeight, + const float* vertices, const int* colors) + : SUPER(BitmapMeshOp) + , bitmap(bitmap) + , meshWidth(meshWidth) + , meshHeight(meshHeight) + , vertices(vertices) + , colors(colors) {} + const SkBitmap* bitmap; + const int meshWidth; + const int meshHeight; + const float* vertices; + const int* colors; +}; + +struct BitmapRectOp : RecordedOp { + BitmapRectOp(BASE_PARAMS, const SkBitmap* bitmap, const Rect& src) + : SUPER(BitmapRectOp) + , bitmap(bitmap) + , src(src) {} + const SkBitmap* bitmap; + const Rect src; +}; + +struct CirclePropsOp : RecordedOp { + CirclePropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint, + float* x, float* y, float* radius) + : RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClip, paint) + , x(x) + , y(y) + , radius(radius) {} + const float* x; + const float* y; + const float* radius; +}; + +struct ColorOp : RecordedOp { + // Note: unbounded op that will fillclip, so no bounds/matrix needed + ColorOp(const ClipBase* localClip, int color, SkXfermode::Mode mode) + : RecordedOp(RecordedOpId::ColorOp, Rect(), Matrix4::identity(), localClip, nullptr) + , color(color) + , mode(mode) {} + const int color; + const SkXfermode::Mode mode; +}; + +struct FunctorOp : RecordedOp { + // Note: undefined record-time bounds, since this op fills the clip + // TODO: explicitly define bounds + FunctorOp(const Matrix4& localMatrix, const ClipBase* localClip, Functor* functor) + : RecordedOp(RecordedOpId::FunctorOp, Rect(), localMatrix, localClip, nullptr) + , functor(functor) {} + Functor* functor; +}; + +struct LinesOp : RecordedOp { + LinesOp(BASE_PARAMS, const float* points, const int floatCount) + : SUPER(LinesOp) + , points(points) + , floatCount(floatCount) {} + const float* points; + const int floatCount; +}; + +struct OvalOp : RecordedOp { + OvalOp(BASE_PARAMS) + : SUPER(OvalOp) {} +}; + +struct PatchOp : RecordedOp { + PatchOp(BASE_PARAMS, const SkBitmap* bitmap, const Res_png_9patch* patch) + : SUPER(PatchOp) + , bitmap(bitmap) + , patch(patch) {} + const SkBitmap* bitmap; + const Res_png_9patch* patch; +}; + +struct PathOp : RecordedOp { + PathOp(BASE_PARAMS, const SkPath* path) + : SUPER(PathOp) + , path(path) {} + const SkPath* path; +}; + +struct PointsOp : RecordedOp { + PointsOp(BASE_PARAMS, const float* points, const int floatCount) + : SUPER(PointsOp) + , points(points) + , floatCount(floatCount) {} + const float* points; + const int floatCount; +}; + +struct RectOp : RecordedOp { + RectOp(BASE_PARAMS) + : SUPER(RectOp) {} +}; + +struct RoundRectOp : RecordedOp { + RoundRectOp(BASE_PARAMS, float rx, float ry) + : SUPER(RoundRectOp) + , rx(rx) + , ry(ry) {} + const float rx; + const float ry; +}; + +struct RoundRectPropsOp : RecordedOp { + RoundRectPropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint, + float* left, float* top, float* right, float* bottom, float *rx, float *ry) + : RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClip, paint) + , left(left) + , top(top) + , right(right) + , bottom(bottom) + , rx(rx) + , ry(ry) {} + const float* left; + const float* top; + const float* right; + const float* bottom; + const float* rx; + const float* ry; +}; + +struct VectorDrawableOp : RecordedOp { + VectorDrawableOp(VectorDrawable::Tree* tree, BASE_PARAMS_PAINTLESS) + : SUPER_PAINTLESS(VectorDrawableOp) + , vectorDrawable(tree) {} + VectorDrawable::Tree* vectorDrawable; +}; + +/** + * Real-time, dynamic-lit shadow. + * + * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time, + * and are resolved dynamically, and transform isn't needed. + * + * State construction handles these properties specially, ignoring matrix/bounds. + */ +struct ShadowOp : RecordedOp { + ShadowOp(sp<TessellationCache::ShadowTask>& shadowTask, float casterAlpha) + : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), nullptr, nullptr) + , shadowTask(shadowTask) + , casterAlpha(casterAlpha) { + }; + sp<TessellationCache::ShadowTask> shadowTask; + const float casterAlpha; +}; + +struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?) + SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount) + : SUPER(SimpleRectsOp) + , vertices(vertices) + , vertexCount(vertexCount) {} + Vertex* vertices; + const size_t vertexCount; +}; + +struct TextOp : RecordedOp { + TextOp(BASE_PARAMS, const glyph_t* glyphs, const float* positions, int glyphCount, + float x, float y) + : SUPER(TextOp) + , glyphs(glyphs) + , positions(positions) + , glyphCount(glyphCount) + , x(x) + , y(y) {} + const glyph_t* glyphs; + const float* positions; + const int glyphCount; + const float x; + const float y; +}; + +struct TextOnPathOp : RecordedOp { + // TODO: explicitly define bounds + TextOnPathOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint, + const glyph_t* glyphs, int glyphCount, const SkPath* path, float hOffset, float vOffset) + : RecordedOp(RecordedOpId::TextOnPathOp, Rect(), localMatrix, localClip, paint) + , glyphs(glyphs) + , glyphCount(glyphCount) + , path(path) + , hOffset(hOffset) + , vOffset(vOffset) {} + const glyph_t* glyphs; + const int glyphCount; + + const SkPath* path; + const float hOffset; + const float vOffset; +}; + +struct TextureLayerOp : RecordedOp { + TextureLayerOp(BASE_PARAMS_PAINTLESS, Layer* layer) + : SUPER_PAINTLESS(TextureLayerOp) + , layer(layer) {} + + // Copy an existing TextureLayerOp, replacing the underlying matrix + TextureLayerOp(const TextureLayerOp& op, const Matrix4& replacementMatrix) + : RecordedOp(RecordedOpId::TextureLayerOp, op.unmappedBounds, replacementMatrix, + op.localClip, op.paint) + , layer(op.layer) { + + } + Layer* layer; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Layers +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Stateful operation! denotes the creation of an off-screen layer, + * and that commands following will render into it. + */ +struct BeginLayerOp : RecordedOp { + BeginLayerOp(BASE_PARAMS) + : SUPER(BeginLayerOp) {} +}; + +/** + * Stateful operation! Denotes end of off-screen layer, and that + * commands since last BeginLayerOp should be drawn into parent FBO. + * + * State in this op is empty, it just serves to signal that a layer has been finished. + */ +struct EndLayerOp : RecordedOp { + EndLayerOp() + : RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {} +}; + +struct BeginUnclippedLayerOp : RecordedOp { + BeginUnclippedLayerOp(BASE_PARAMS) + : SUPER(BeginUnclippedLayerOp) {} +}; + +struct EndUnclippedLayerOp : RecordedOp { + EndUnclippedLayerOp() + : RecordedOp(RecordedOpId::EndUnclippedLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {} +}; + +struct CopyToLayerOp : RecordedOp { + CopyToLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle) + : RecordedOp(RecordedOpId::CopyToLayerOp, + op.unmappedBounds, + op.localMatrix, + nullptr, // clip intentionally ignored + op.paint) + , layerHandle(layerHandle) {} + + // Records a handle to the Layer object, since the Layer itself won't be + // constructed until after this operation is constructed. + OffscreenBuffer** layerHandle; +}; + + +// draw the parameter layer underneath +struct CopyFromLayerOp : RecordedOp { + CopyFromLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle) + : RecordedOp(RecordedOpId::CopyFromLayerOp, + op.unmappedBounds, + op.localMatrix, + nullptr, // clip intentionally ignored + op.paint) + , layerHandle(layerHandle) {} + + // Records a handle to the Layer object, since the Layer itself won't be + // constructed until after this operation is constructed. + OffscreenBuffer** layerHandle; +}; + +/** + * Draws an OffscreenBuffer. + * + * Alpha, mode, and colorfilter are embedded, since LayerOps are always dynamically generated, + * when creating/tracking a SkPaint* during defer isn't worth the bother. + */ +struct LayerOp : RecordedOp { + // Records a one-use (saveLayer) layer for drawing. + LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle) + : SUPER_PAINTLESS(LayerOp) + , layerHandle(layerHandle) + , alpha(paint ? paint->getAlpha() / 255.0f : 1.0f) + , mode(PaintUtils::getXfermodeDirect(paint)) + , colorFilter(paint ? paint->getColorFilter() : nullptr) {} + + LayerOp(RenderNode& node) + : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), Matrix4::identity(), nullptr, nullptr) + , layerHandle(node.getLayerHandle()) + , alpha(node.properties().layerProperties().alpha() / 255.0f) + , mode(node.properties().layerProperties().xferMode()) + , colorFilter(node.properties().layerProperties().colorFilter()) {} + + // Records a handle to the Layer object, since the Layer itself won't be + // constructed until after this operation is constructed. + OffscreenBuffer** layerHandle; + const float alpha; + const SkXfermode::Mode mode; + + // pointer to object owned by either LayerProperties, or a recorded Paint object in a + // BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used. + SkColorFilter* colorFilter; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_RECORDED_OP_H diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp new file mode 100644 index 000000000000..b49f9b529989 --- /dev/null +++ b/libs/hwui/RecordingCanvas.cpp @@ -0,0 +1,657 @@ +/* + * Copyright (C) 2015 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 "RecordingCanvas.h" + +#include "DeferredLayerUpdater.h" +#include "RecordedOp.h" +#include "RenderNode.h" +#include "VectorDrawable.h" + +namespace android { +namespace uirenderer { + +RecordingCanvas::RecordingCanvas(size_t width, size_t height) + : mState(*this) + , mResourceCache(ResourceCache::getInstance()) { + resetRecording(width, height); +} + +RecordingCanvas::~RecordingCanvas() { + LOG_ALWAYS_FATAL_IF(mDisplayList, + "Destroyed a RecordingCanvas during a record!"); +} + +void RecordingCanvas::resetRecording(int width, int height) { + LOG_ALWAYS_FATAL_IF(mDisplayList, + "prepareDirty called a second time during a recording!"); + mDisplayList = new DisplayList(); + + mState.initializeRecordingSaveStack(width, height); + + mDeferredBarrierType = DeferredBarrierType::InOrder; + mState.setDirtyClip(false); +} + +DisplayList* RecordingCanvas::finishRecording() { + restoreToCount(1); + mPaintMap.clear(); + mRegionMap.clear(); + mPathMap.clear(); + DisplayList* displayList = mDisplayList; + mDisplayList = nullptr; + mSkiaCanvasProxy.reset(nullptr); + return displayList; +} + +void RecordingCanvas::insertReorderBarrier(bool enableReorder) { + if (enableReorder) { + mDeferredBarrierType = DeferredBarrierType::OutOfOrder; + mDeferredBarrierClip = getRecordedClip(); + } else { + mDeferredBarrierType = DeferredBarrierType::InOrder; + mDeferredBarrierClip = nullptr; + } +} + +SkCanvas* RecordingCanvas::asSkCanvas() { + LOG_ALWAYS_FATAL_IF(!mDisplayList, + "attempting to get an SkCanvas when we are not recording!"); + if (!mSkiaCanvasProxy) { + mSkiaCanvasProxy.reset(new SkiaCanvasProxy(this)); + } + + // SkCanvas instances default to identity transform, but should inherit + // the state of this Canvas; if this code was in the SkiaCanvasProxy + // constructor, we couldn't cache mSkiaCanvasProxy. + SkMatrix parentTransform; + getMatrix(&parentTransform); + mSkiaCanvasProxy.get()->setMatrix(parentTransform); + + return mSkiaCanvasProxy.get(); +} + +// ---------------------------------------------------------------------------- +// CanvasStateClient implementation +// ---------------------------------------------------------------------------- + +void RecordingCanvas::onViewportInitialized() { +} + +void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) { + if (removed.flags & Snapshot::kFlagIsFboLayer) { + addOp(alloc().create_trivial<EndLayerOp>()); + } else if (removed.flags & Snapshot::kFlagIsLayer) { + addOp(alloc().create_trivial<EndUnclippedLayerOp>()); + } +} + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas state operations +// ---------------------------------------------------------------------------- +// Save (layer) +int RecordingCanvas::save(SaveFlags::Flags flags) { + return mState.save((int) flags); +} + +void RecordingCanvas::RecordingCanvas::restore() { + mState.restore(); +} + +void RecordingCanvas::restoreToCount(int saveCount) { + mState.restoreToCount(saveCount); +} + +int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, + const SkPaint* paint, SaveFlags::Flags flags) { + // force matrix/clip isolation for layer + flags |= SaveFlags::MatrixClip; + bool clippedLayer = flags & SaveFlags::ClipToLayer; + + const Snapshot& previous = *mState.currentSnapshot(); + + // initialize the snapshot as though it almost represents an FBO layer so deferred draw + // operations will be able to store and restore the current clip and transform info, and + // quick rejection will be correct (for display lists) + + const Rect unmappedBounds(left, top, right, bottom); + + // determine clipped bounds relative to previous viewport. + Rect visibleBounds = unmappedBounds; + previous.transform->mapRect(visibleBounds); + + if (CC_UNLIKELY(!clippedLayer + && previous.transform->rectToRect() + && visibleBounds.contains(previous.getRenderTargetClip()))) { + // unlikely case where an unclipped savelayer is recorded with a clip it can use, + // as none of its unaffected/unclipped area is visible + clippedLayer = true; + flags |= SaveFlags::ClipToLayer; + } + + visibleBounds.doIntersect(previous.getRenderTargetClip()); + visibleBounds.snapToPixelBoundaries(); + visibleBounds.doIntersect(Rect(previous.getViewportWidth(), previous.getViewportHeight())); + + // Map visible bounds back to layer space, and intersect with parameter bounds + Rect layerBounds = visibleBounds; + Matrix4 inverse; + inverse.loadInverse(*previous.transform); + inverse.mapRect(layerBounds); + layerBounds.doIntersect(unmappedBounds); + + int saveValue = mState.save((int) flags); + Snapshot& snapshot = *mState.writableSnapshot(); + + // layerBounds is in original bounds space, but clipped by current recording clip + if (layerBounds.isEmpty() || unmappedBounds.isEmpty()) { + // Don't bother recording layer, since it's been rejected + if (CC_LIKELY(clippedLayer)) { + snapshot.resetClip(0, 0, 0, 0); + } + return saveValue; + } + + if (CC_LIKELY(clippedLayer)) { + auto previousClip = getRecordedClip(); // note: done before new snapshot's clip has changed + + snapshot.flags |= Snapshot::kFlagIsLayer | Snapshot::kFlagIsFboLayer; + snapshot.initializeViewport(unmappedBounds.getWidth(), unmappedBounds.getHeight()); + snapshot.transform->loadTranslate(-unmappedBounds.left, -unmappedBounds.top, 0.0f); + + Rect clip = layerBounds; + clip.translate(-unmappedBounds.left, -unmappedBounds.top); + snapshot.resetClip(clip.left, clip.top, clip.right, clip.bottom); + snapshot.roundRectClipState = nullptr; + + addOp(alloc().create_trivial<BeginLayerOp>( + unmappedBounds, + *previous.transform, // transform to *draw* with + previousClip, // clip to *draw* with + refPaint(paint))); + } else { + snapshot.flags |= Snapshot::kFlagIsLayer; + + addOp(alloc().create_trivial<BeginUnclippedLayerOp>( + unmappedBounds, + *mState.currentSnapshot()->transform, + getRecordedClip(), + refPaint(paint))); + } + + return saveValue; +} + +// Matrix +void RecordingCanvas::rotate(float degrees) { + if (degrees == 0) return; + + mState.rotate(degrees); +} + +void RecordingCanvas::scale(float sx, float sy) { + if (sx == 1 && sy == 1) return; + + mState.scale(sx, sy); +} + +void RecordingCanvas::skew(float sx, float sy) { + mState.skew(sx, sy); +} + +void RecordingCanvas::translate(float dx, float dy) { + if (dx == 0 && dy == 0) return; + + mState.translate(dx, dy, 0); +} + +// Clip +bool RecordingCanvas::getClipBounds(SkRect* outRect) const { + *outRect = mState.getLocalClipBounds().toSkRect(); + return !(outRect->isEmpty()); +} +bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const { + return mState.quickRejectConservative(left, top, right, bottom); +} +bool RecordingCanvas::quickRejectPath(const SkPath& path) const { + SkRect bounds = path.getBounds(); + return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); +} +bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) { + return mState.clipRect(left, top, right, bottom, op); +} +bool RecordingCanvas::clipPath(const SkPath* path, SkRegion::Op op) { + return mState.clipPath(path, op); +} +bool RecordingCanvas::clipRegion(const SkRegion* region, SkRegion::Op op) { + return mState.clipRegion(region, op); +} + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas draw operations +// ---------------------------------------------------------------------------- +void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) { + addOp(alloc().create_trivial<ColorOp>( + getRecordedClip(), + color, + mode)); +} + +void RecordingCanvas::drawPaint(const SkPaint& paint) { + SkRect bounds; + if (getClipBounds(&bounds)) { + drawRect(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, paint); + } +} + +static Rect calcBoundsOfPoints(const float* points, int floatCount) { + Rect unmappedBounds(points[0], points[1], points[0], points[1]); + for (int i = 2; i < floatCount; i += 2) { + unmappedBounds.expandToCover(points[i], points[i + 1]); + } + return unmappedBounds; +} + +// Geometry +void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) { + if (floatCount < 2) return; + floatCount &= ~0x1; // round down to nearest two + + addOp(alloc().create_trivial<PointsOp>( + calcBoundsOfPoints(points, floatCount), + *mState.currentSnapshot()->transform, + getRecordedClip(), + refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); +} + +void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) { + if (floatCount < 4) return; + floatCount &= ~0x3; // round down to nearest four + + addOp(alloc().create_trivial<LinesOp>( + calcBoundsOfPoints(points, floatCount), + *mState.currentSnapshot()->transform, + getRecordedClip(), + refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); +} + +void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) { + addOp(alloc().create_trivial<RectOp>( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint))); +} + +void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) { + if (rects == nullptr) return; + + Vertex* rectData = (Vertex*) mDisplayList->allocator.create_trivial_array<Vertex>(vertexCount); + Vertex* vertex = rectData; + + float left = FLT_MAX; + float top = FLT_MAX; + float right = FLT_MIN; + float bottom = FLT_MIN; + for (int index = 0; index < vertexCount; index += 4) { + float l = rects[index + 0]; + float t = rects[index + 1]; + float r = rects[index + 2]; + float b = rects[index + 3]; + + Vertex::set(vertex++, l, t); + Vertex::set(vertex++, r, t); + Vertex::set(vertex++, l, b); + Vertex::set(vertex++, r, b); + + left = std::min(left, l); + top = std::min(top, t); + right = std::max(right, r); + bottom = std::max(bottom, b); + } + addOp(alloc().create_trivial<SimpleRectsOp>( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(paint), rectData, vertexCount)); +} + +void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { + if (paint.getStyle() == SkPaint::kFill_Style + && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) { + int count = 0; + Vector<float> rects; + SkRegion::Iterator it(region); + while (!it.done()) { + const SkIRect& r = it.rect(); + rects.push(r.fLeft); + rects.push(r.fTop); + rects.push(r.fRight); + rects.push(r.fBottom); + count += 4; + it.next(); + } + drawSimpleRects(rects.array(), count, &paint); + } else { + SkRegion::Iterator it(region); + while (!it.done()) { + const SkIRect& r = it.rect(); + drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint); + it.next(); + } + } +} +void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom, + float rx, float ry, const SkPaint& paint) { + if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) { + addOp(alloc().create_trivial<RoundRectOp>( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), rx, ry)); + } else { + drawRect(left, top, right, bottom, paint); + } +} + +void RecordingCanvas::drawRoundRect( + CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top, + CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom, + CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry, + CanvasPropertyPaint* paint) { + mDisplayList->ref(left); + mDisplayList->ref(top); + mDisplayList->ref(right); + mDisplayList->ref(bottom); + mDisplayList->ref(rx); + mDisplayList->ref(ry); + mDisplayList->ref(paint); + refBitmapsInShader(paint->value.getShader()); + addOp(alloc().create_trivial<RoundRectPropsOp>( + *(mState.currentSnapshot()->transform), + getRecordedClip(), + &paint->value, + &left->value, &top->value, &right->value, &bottom->value, + &rx->value, &ry->value)); +} + +void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { + // TODO: move to Canvas.h + if (radius <= 0) return; + drawOval(x - radius, y - radius, x + radius, y + radius, paint); +} + +void RecordingCanvas::drawCircle( + CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y, + CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) { + mDisplayList->ref(x); + mDisplayList->ref(y); + mDisplayList->ref(radius); + mDisplayList->ref(paint); + refBitmapsInShader(paint->value.getShader()); + addOp(alloc().create_trivial<CirclePropsOp>( + *(mState.currentSnapshot()->transform), + getRecordedClip(), + &paint->value, + &x->value, &y->value, &radius->value)); +} + +void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { + addOp(alloc().create_trivial<OvalOp>( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint))); +} + +void RecordingCanvas::drawArc(float left, float top, float right, float bottom, + float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) { + if (fabs(sweepAngle) >= 360.0f) { + drawOval(left, top, right, bottom, paint); + } else { + addOp(alloc().create_trivial<ArcOp>( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), + startAngle, sweepAngle, useCenter)); + } +} + +void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { + addOp(alloc().create_trivial<PathOp>( + Rect(path.getBounds()), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), refPath(&path))); +} + +void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { + mDisplayList->pushStagingFunctors.push_back(tree->getFunctor()); + mDisplayList->ref(tree); + addOp(alloc().create_trivial<VectorDrawableOp>( + tree, + Rect(tree->stagingProperties()->getBounds()), + *(mState.currentSnapshot()->transform), + getRecordedClip())); +} + +// Bitmap-based +void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) { + save(SaveFlags::Matrix); + translate(left, top); + drawBitmap(&bitmap, paint); + restore(); +} + +void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, + const SkPaint* paint) { + if (matrix.isIdentity()) { + drawBitmap(&bitmap, paint); + } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) + && MathUtils::isPositive(matrix.getScaleX()) + && MathUtils::isPositive(matrix.getScaleY())) { + // SkMatrix::isScaleTranslate() not available in L + SkRect src; + SkRect dst; + bitmap.getBounds(&src); + matrix.mapRect(&dst, src); + drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, + dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint); + } else { + save(SaveFlags::Matrix); + concat(matrix); + drawBitmap(&bitmap, paint); + restore(); + } +} + +void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, + float srcRight, float srcBottom, float dstLeft, float dstTop, + float dstRight, float dstBottom, const SkPaint* paint) { + if (srcLeft == 0 && srcTop == 0 + && srcRight == bitmap.width() + && srcBottom == bitmap.height() + && (srcBottom - srcTop == dstBottom - dstTop) + && (srcRight - srcLeft == dstRight - dstLeft)) { + // transform simple rect to rect drawing case into position bitmap ops, since they merge + save(SaveFlags::Matrix); + translate(dstLeft, dstTop); + drawBitmap(&bitmap, paint); + restore(); + } else { + addOp(alloc().create_trivial<BitmapRectOp>( + Rect(dstLeft, dstTop, dstRight, dstBottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(paint), refBitmap(bitmap), + Rect(srcLeft, srcTop, srcRight, srcBottom))); + } +} + +void RecordingCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, + const float* vertices, const int* colors, const SkPaint* paint) { + int vertexCount = (meshWidth + 1) * (meshHeight + 1); + addOp(alloc().create_trivial<BitmapMeshOp>( + calcBoundsOfPoints(vertices, vertexCount * 2), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight, + refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex + refBuffer<int>(colors, vertexCount))); // 1 color per vertex +} + +void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& patch, + float dstLeft, float dstTop, float dstRight, float dstBottom, + const SkPaint* paint) { + addOp(alloc().create_trivial<PatchOp>( + Rect(dstLeft, dstTop, dstRight, dstBottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(paint), refBitmap(bitmap), refPatch(&patch))); +} + +// Text +void RecordingCanvas::drawGlyphs(const uint16_t* glyphs, const float* positions, int glyphCount, + const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, + float boundsRight, float boundsBottom, float totalAdvance) { + if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return; + glyphs = refBuffer<glyph_t>(glyphs, glyphCount); + positions = refBuffer<float>(positions, glyphCount * 2); + + // TODO: either must account for text shadow in bounds, or record separate ops for text shadows + addOp(alloc().create_trivial<TextOp>( + Rect(boundsLeft, boundsTop, boundsRight, boundsBottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), glyphs, positions, glyphCount, x, y)); + drawTextDecorations(x, y, totalAdvance, paint); +} + +void RecordingCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path, + float hOffset, float vOffset, const SkPaint& paint) { + if (!glyphs || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return; + glyphs = refBuffer<glyph_t>(glyphs, glyphCount); + addOp(alloc().create_trivial<TextOnPathOp>( + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset)); +} + +void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { + addOp(alloc().create_trivial<BitmapOp>( + Rect(bitmap->width(), bitmap->height()), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(paint), refBitmap(*bitmap))); +} + +void RecordingCanvas::drawRenderNode(RenderNode* renderNode) { + auto&& stagingProps = renderNode->stagingProperties(); + RenderNodeOp* op = alloc().create_trivial<RenderNodeOp>( + Rect(stagingProps.getWidth(), stagingProps.getHeight()), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + renderNode); + int opIndex = addOp(op); + if (CC_LIKELY(opIndex >= 0)) { + int childIndex = mDisplayList->addChild(op); + + // update the chunk's child indices + DisplayList::Chunk& chunk = mDisplayList->chunks.back(); + chunk.endChildIndex = childIndex + 1; + + if (renderNode->stagingProperties().isProjectionReceiver()) { + // use staging property, since recording on UI thread + mDisplayList->projectionReceiveIndex = opIndex; + } + } +} + +void RecordingCanvas::drawLayer(DeferredLayerUpdater* layerHandle) { + // We ref the DeferredLayerUpdater due to its thread-safe ref-counting semantics. + mDisplayList->ref(layerHandle); + + // Note that the backing layer has *not* yet been updated, so don't trust + // its width, height, transform, etc...! + addOp(alloc().create_trivial<TextureLayerOp>( + Rect(layerHandle->getWidth(), layerHandle->getHeight()), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + layerHandle->backingLayer())); +} + +void RecordingCanvas::callDrawGLFunction(Functor* functor, + GlFunctorLifecycleListener* listener) { + mDisplayList->functors.push_back({functor, listener}); + mDisplayList->ref(listener); + addOp(alloc().create_trivial<FunctorOp>( + *(mState.currentSnapshot()->transform), + getRecordedClip(), + functor)); +} + +size_t RecordingCanvas::addOp(RecordedOp* op) { + // skip op with empty clip + if (op->localClip && op->localClip->rect.isEmpty()) { + // NOTE: this rejection happens after op construction/content ref-ing, so content ref'd + // and held by renderthread isn't affected by clip rejection. + // Could rewind alloc here if desired, but callers would have to not touch op afterwards. + return -1; + } + + int insertIndex = mDisplayList->ops.size(); + mDisplayList->ops.push_back(op); + if (mDeferredBarrierType != DeferredBarrierType::None) { + // op is first in new chunk + mDisplayList->chunks.emplace_back(); + DisplayList::Chunk& newChunk = mDisplayList->chunks.back(); + newChunk.beginOpIndex = insertIndex; + newChunk.endOpIndex = insertIndex + 1; + newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder); + newChunk.reorderClip = mDeferredBarrierClip; + + int nextChildIndex = mDisplayList->children.size(); + newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex; + mDeferredBarrierType = DeferredBarrierType::None; + } else { + // standard case - append to existing chunk + mDisplayList->chunks.back().endOpIndex = insertIndex + 1; + } + return insertIndex; +} + +void RecordingCanvas::refBitmapsInShader(const SkShader* shader) { + if (!shader) return; + + // If this paint has an SkShader that has an SkBitmap add + // it to the bitmap pile + SkBitmap bitmap; + SkShader::TileMode xy[2]; + if (shader->isABitmap(&bitmap, nullptr, xy)) { + refBitmap(bitmap); + return; + } + SkShader::ComposeRec rec; + if (shader->asACompose(&rec)) { + refBitmapsInShader(rec.fShaderA); + refBitmapsInShader(rec.fShaderB); + return; + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h new file mode 100644 index 000000000000..372be241042a --- /dev/null +++ b/libs/hwui/RecordingCanvas.h @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_RECORDING_CANVAS_H +#define ANDROID_HWUI_RECORDING_CANVAS_H + +#include "CanvasState.h" +#include "DisplayList.h" +#include "ResourceCache.h" +#include "SkiaCanvasProxy.h" +#include "Snapshot.h" +#include "hwui/Canvas.h" +#include "utils/LinearAllocator.h" +#include "utils/Macros.h" +#include "utils/NinePatch.h" + +#include <SkDrawFilter.h> +#include <SkPaint.h> +#include <SkTLazy.h> + +#include <vector> + +namespace android { +namespace uirenderer { + +struct ClipBase; +class DeferredLayerUpdater; +struct RecordedOp; + +class ANDROID_API RecordingCanvas: public Canvas, public CanvasStateClient { + enum class DeferredBarrierType { + None, + InOrder, + OutOfOrder, + }; +public: + RecordingCanvas(size_t width, size_t height); + virtual ~RecordingCanvas(); + + virtual void resetRecording(int width, int height) override; + virtual WARN_UNUSED_RESULT DisplayList* finishRecording() override; +// ---------------------------------------------------------------------------- +// MISC HWUI OPERATIONS - TODO: CATEGORIZE +// ---------------------------------------------------------------------------- + virtual void insertReorderBarrier(bool enableReorder) override; + + virtual void drawLayer(DeferredLayerUpdater* layerHandle) override; + virtual void drawRenderNode(RenderNode* renderNode) override; + virtual void callDrawGLFunction(Functor* functor, + GlFunctorLifecycleListener* listener) override; + +// ---------------------------------------------------------------------------- +// CanvasStateClient interface +// ---------------------------------------------------------------------------- + virtual void onViewportInitialized() override; + virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override; + virtual GLuint getTargetFbo() const override { return -1; } + +// ---------------------------------------------------------------------------- +// HWUI Canvas draw operations +// ---------------------------------------------------------------------------- + + virtual void drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top, + CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom, + CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry, + CanvasPropertyPaint* paint) override; + virtual void drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y, + CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) override; + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas interface +// ---------------------------------------------------------------------------- + virtual SkCanvas* asSkCanvas() override; + + virtual void setBitmap(const SkBitmap& bitmap) override { + LOG_ALWAYS_FATAL("RecordingCanvas is not backed by a bitmap."); + } + + virtual bool isOpaque() override { return false; } + virtual int width() override { return mState.getWidth(); } + virtual int height() override { return mState.getHeight(); } + + virtual void setHighContrastText(bool highContrastText) override { + mHighContrastText = highContrastText; + } + virtual bool isHighContrastText() override { return mHighContrastText; } + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas state operations +// ---------------------------------------------------------------------------- + // Save (layer) + virtual int getSaveCount() const override { return mState.getSaveCount(); } + virtual int save(SaveFlags::Flags flags) override; + virtual void restore() override; + virtual void restoreToCount(int saveCount) override; + + virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, + SaveFlags::Flags flags) override; + virtual int saveLayerAlpha(float left, float top, float right, float bottom, + int alpha, SaveFlags::Flags flags) override { + SkPaint paint; + paint.setAlpha(alpha); + return saveLayer(left, top, right, bottom, &paint, flags); + } + + // Matrix + virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); } + virtual void setMatrix(const SkMatrix& matrix) override { mState.setMatrix(matrix); } + + virtual void concat(const SkMatrix& matrix) override { mState.concatMatrix(matrix); } + virtual void rotate(float degrees) override; + virtual void scale(float sx, float sy) override; + virtual void skew(float sx, float sy) override; + virtual void translate(float dx, float dy) override; + + // Clip + virtual bool getClipBounds(SkRect* outRect) const override; + virtual bool quickRejectRect(float left, float top, float right, float bottom) const override; + virtual bool quickRejectPath(const SkPath& path) const override; + + virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op) override; + virtual bool clipPath(const SkPath* path, SkRegion::Op op) override; + virtual bool clipRegion(const SkRegion* region, SkRegion::Op op) override; + + // Misc + virtual SkDrawFilter* getDrawFilter() override { return mDrawFilter.get(); } + virtual void setDrawFilter(SkDrawFilter* filter) override { + mDrawFilter.reset(SkSafeRef(filter)); + } + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas draw operations +// ---------------------------------------------------------------------------- + virtual void drawColor(int color, SkXfermode::Mode mode) override; + virtual void drawPaint(const SkPaint& paint) override; + + // Geometry + virtual void drawPoint(float x, float y, const SkPaint& paint) override { + float points[2] = { x, y }; + drawPoints(points, 2, paint); + } + virtual void drawPoints(const float* points, int floatCount, const SkPaint& paint) override; + virtual void drawLine(float startX, float startY, float stopX, float stopY, + const SkPaint& paint) override { + float points[4] = { startX, startY, stopX, stopY }; + drawLines(points, 4, paint); + } + virtual void drawLines(const float* points, int floatCount, const SkPaint& paint) override; + virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override; + virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override; + virtual void drawRoundRect(float left, float top, float right, float bottom, + float rx, float ry, const SkPaint& paint) override; + virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override; + virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) override; + virtual void drawArc(float left, float top, float right, float bottom, + float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) override; + virtual void drawPath(const SkPath& path, const SkPaint& paint) override; + virtual void drawVertices(SkCanvas::VertexMode vertexMode, int vertexCount, + const float* verts, const float* tex, const int* colors, + const uint16_t* indices, int indexCount, const SkPaint& paint) override + { /* RecordingCanvas does not support drawVertices(); ignore */ } + + virtual void drawVectorDrawable(VectorDrawableRoot* tree) override; + + // Bitmap-based + virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override; + virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix, + const SkPaint* paint) override; + virtual void drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, + float srcRight, float srcBottom, float dstLeft, float dstTop, + float dstRight, float dstBottom, const SkPaint* paint) override; + virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, + const float* vertices, const int* colors, const SkPaint* paint) override; + virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk, + float dstLeft, float dstTop, float dstRight, float dstBottom, + const SkPaint* paint) override; + + // Text + virtual bool drawTextAbsolutePos() const override { return false; } + +protected: + virtual void drawGlyphs(const uint16_t* text, const float* positions, int count, + const SkPaint& paint, float x, float y, + float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, + float totalAdvance) override; + virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path, + float hOffset, float vOffset, const SkPaint& paint) override; + +private: + const ClipBase* getRecordedClip() { + return mState.writableSnapshot()->mutateClipArea().serializeClip(alloc()); + } + + void drawBitmap(const SkBitmap* bitmap, const SkPaint* paint); + void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint); + + + size_t addOp(RecordedOp* op); +// ---------------------------------------------------------------------------- +// lazy object copy +// ---------------------------------------------------------------------------- + LinearAllocator& alloc() { return mDisplayList->allocator; } + + void refBitmapsInShader(const SkShader* shader); + + template<class T> + inline const T* refBuffer(const T* srcBuffer, int32_t count) { + if (!srcBuffer) return nullptr; + + T* dstBuffer = (T*) mDisplayList->allocator.alloc<T>(count * sizeof(T)); + memcpy(dstBuffer, srcBuffer, count * sizeof(T)); + return dstBuffer; + } + + inline const SkPath* refPath(const SkPath* path) { + if (!path) return nullptr; + + // The points/verbs within the path are refcounted so this copy operation + // is inexpensive and maintains the generationID of the original path. + const SkPath* cachedPath = new SkPath(*path); + mDisplayList->pathResources.push_back(cachedPath); + return cachedPath; + } + + /** + * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in + * (with deduping based on paint hash / equality check) + */ + inline const SkPaint* refPaint(const SkPaint* paint) { + if (!paint) return nullptr; + + // If there is a draw filter apply it here and store the modified paint + // so that we don't need to modify the paint every time we access it. + SkTLazy<SkPaint> filteredPaint; + if (mDrawFilter.get()) { + filteredPaint.set(*paint); + mDrawFilter->filter(filteredPaint.get(), SkDrawFilter::kPaint_Type); + paint = filteredPaint.get(); + } + + // compute the hash key for the paint and check the cache. + const uint32_t key = paint->getHash(); + const SkPaint* cachedPaint = mPaintMap.valueFor(key); + // In the unlikely event that 2 unique paints have the same hash we do a + // object equality check to ensure we don't erroneously dedup them. + if (cachedPaint == nullptr || *cachedPaint != *paint) { + cachedPaint = new SkPaint(*paint); + mDisplayList->paints.emplace_back(cachedPaint); + // replaceValueFor() performs an add if the entry doesn't exist + mPaintMap.replaceValueFor(key, cachedPaint); + refBitmapsInShader(cachedPaint->getShader()); + } + + return cachedPaint; + } + + inline const SkRegion* refRegion(const SkRegion* region) { + if (!region) { + return region; + } + + const SkRegion* cachedRegion = mRegionMap.valueFor(region); + // TODO: Add generation ID to SkRegion + if (cachedRegion == nullptr) { + std::unique_ptr<const SkRegion> copy(new SkRegion(*region)); + cachedRegion = copy.get(); + mDisplayList->regions.push_back(std::move(copy)); + + // replaceValueFor() performs an add if the entry doesn't exist + mRegionMap.replaceValueFor(region, cachedRegion); + } + + return cachedRegion; + } + + inline const SkBitmap* refBitmap(const SkBitmap& bitmap) { + // Note that this assumes the bitmap is immutable. There are cases this won't handle + // correctly, such as creating the bitmap from scratch, drawing with it, changing its + // contents, and drawing again. The only fix would be to always copy it the first time, + // which doesn't seem worth the extra cycles for this unlikely case. + SkBitmap* localBitmap = alloc().create<SkBitmap>(bitmap); + mDisplayList->bitmapResources.push_back(localBitmap); + return localBitmap; + } + + inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) { + mDisplayList->patchResources.push_back(patch); + mResourceCache.incrementRefcount(patch); + return patch; + } + + DefaultKeyedVector<uint32_t, const SkPaint*> mPaintMap; + DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap; + DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap; + + CanvasState mState; + std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy; + ResourceCache& mResourceCache; + DeferredBarrierType mDeferredBarrierType = DeferredBarrierType::None; + const ClipBase* mDeferredBarrierClip = nullptr; + DisplayList* mDisplayList = nullptr; + bool mHighContrastText = false; + SkAutoTUnref<SkDrawFilter> mDrawFilter; +}; // class RecordingCanvas + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_RECORDING_CANVAS_H diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 4c4cd3da3be4..de4fa55bb508 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -14,16 +14,17 @@ * limitations under the License. */ -#ifndef ANDROID_HWUI_RECT_H -#define ANDROID_HWUI_RECT_H +#pragma once -#include <cmath> -#include <algorithm> -#include <SkRect.h> +#include "Vertex.h" #include <utils/Log.h> -#include "Vertex.h" +#include <algorithm> +#include <cmath> +#include <iomanip> +#include <ostream> +#include <SkRect.h> namespace android { namespace uirenderer { @@ -125,25 +126,32 @@ public: } bool intersects(float l, float t, float r, float b) const { - return !intersectWith(l, t, r, b).isEmpty(); + float tempLeft = std::max(left, l); + float tempTop = std::max(top, t); + float tempRight = std::min(right, r); + float tempBottom = std::min(bottom, b); + + return ((tempLeft < tempRight) && (tempTop < tempBottom)); // !isEmpty } bool intersects(const Rect& r) const { return intersects(r.left, r.top, r.right, r.bottom); } - bool intersect(float l, float t, float r, float b) { - Rect tmp(l, t, r, b); - intersectWith(tmp); - if (!tmp.isEmpty()) { - set(tmp); - return true; - } - return false; + /** + * This method is named 'doIntersect' instead of 'intersect' so as not to be confused with + * SkRect::intersect / android.graphics.Rect#intersect behavior, which do not modify the object + * if the intersection of the rects would be empty. + */ + void doIntersect(float l, float t, float r, float b) { + left = std::max(left, l); + top = std::max(top, t); + right = std::min(right, r); + bottom = std::min(bottom, b); } - bool intersect(const Rect& r) { - return intersect(r.left, r.top, r.right, r.bottom); + void doIntersect(const Rect& r) { + doIntersect(r.left, r.top, r.right, r.bottom); } inline bool contains(float l, float t, float r, float b) const { @@ -246,20 +254,24 @@ public: bottom = ceilf(bottom); } - void expandToCoverVertex(float x, float y) { + /* + * Similar to unionWith, except this makes the assumption that both rects are non-empty + * to avoid both emptiness checks. + */ + void expandToCover(const Rect& other) { + left = std::min(left, other.left); + top = std::min(top, other.top); + right = std::max(right, other.right); + bottom = std::max(bottom, other.bottom); + } + + void expandToCover(float x, float y) { left = std::min(left, x); top = std::min(top, y); right = std::max(right, x); bottom = std::max(bottom, y); } - void expandToCoverRect(float otherLeft, float otherTop, float otherRight, float otherBottom) { - left = std::min(left, otherLeft); - top = std::min(top, otherTop); - right = std::max(right, otherRight); - bottom = std::max(bottom, otherBottom); - } - SkRect toSkRect() const { return SkRect::MakeLTRB(left, top, right, bottom); } @@ -269,29 +281,26 @@ public: } void dump(const char* label = nullptr) const { - ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom); + ALOGD("%s[l=%.2f t=%.2f r=%.2f b=%.2f]", label ? label : "Rect", left, top, right, bottom); } -private: - void intersectWith(Rect& tmp) const { - tmp.left = std::max(left, tmp.left); - tmp.top = std::max(top, tmp.top); - tmp.right = std::min(right, tmp.right); - tmp.bottom = std::min(bottom, tmp.bottom); - } + friend std::ostream& operator<<(std::ostream& os, const Rect& rect) { + if (rect.isEmpty()) { + // Print empty, but continue, since empty rects may still have useful coordinate info + os << "(empty)"; + } - Rect intersectWith(float l, float t, float r, float b) const { - Rect tmp; - tmp.left = std::max(left, l); - tmp.top = std::max(top, t); - tmp.right = std::min(right, r); - tmp.bottom = std::min(bottom, b); - return tmp; - } + if (rect.left == 0 && rect.top == 0) { + return os << "[" << rect.right << " x " << rect.bottom << "]"; + } + return os << "[" << rect.left + << " " << rect.top + << " " << rect.right + << " " << rect.bottom << "]"; + } }; // class Rect }; // namespace uirenderer }; // namespace android -#endif // ANDROID_HWUI_RECT_H diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp index d0812c96afd7..1ac57cdbac0c 100644 --- a/libs/hwui/RenderBufferCache.cpp +++ b/libs/hwui/RenderBufferCache.cpp @@ -14,14 +14,14 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - -#include <utils/Log.h> - #include "Debug.h" #include "Properties.h" #include "RenderBufferCache.h" +#include <utils/Log.h> + +#include <cstdlib> + namespace android { namespace uirenderer { @@ -40,16 +40,9 @@ namespace uirenderer { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -RenderBufferCache::RenderBufferCache(): mSize(0), mMaxSize(MB(DEFAULT_RENDER_BUFFER_CACHE_SIZE)) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_RENDER_BUFFER_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting render buffer cache size to %sMB", property); - setMaxSize(MB(atof(property))); - } else { - INIT_LOGD(" Using default render buffer cache size of %.2fMB", - DEFAULT_RENDER_BUFFER_CACHE_SIZE); - } -} +RenderBufferCache::RenderBufferCache() + : mSize(0) + , mMaxSize(Properties::renderBufferCacheSize) {} RenderBufferCache::~RenderBufferCache() { clear(); @@ -67,11 +60,6 @@ uint32_t RenderBufferCache::getMaxSize() { return mMaxSize; } -void RenderBufferCache::setMaxSize(uint32_t maxSize) { - clear(); - mMaxSize = maxSize; -} - /////////////////////////////////////////////////////////////////////////////// // Caching /////////////////////////////////////////////////////////////////////////////// @@ -100,9 +88,8 @@ void RenderBufferCache::deleteBuffer(RenderBuffer* buffer) { } void RenderBufferCache::clear() { - size_t count = mCache.size(); - for (size_t i = 0; i < count; i++) { - deleteBuffer(mCache.itemAt(i).mBuffer); + for (auto entry : mCache) { + deleteBuffer(entry.mBuffer); } mCache.clear(); } @@ -111,11 +98,11 @@ RenderBuffer* RenderBufferCache::get(GLenum format, const uint32_t width, const RenderBuffer* buffer = nullptr; RenderBufferEntry entry(format, width, height); - ssize_t index = mCache.indexOf(entry); + auto iter = mCache.find(entry); - if (index >= 0) { - entry = mCache.itemAt(index); - mCache.removeAt(index); + if (iter != mCache.end()) { + entry = *iter; + mCache.erase(iter); buffer = entry.mBuffer; mSize -= buffer->getSize(); @@ -141,16 +128,14 @@ bool RenderBufferCache::put(RenderBuffer* buffer) { const uint32_t size = buffer->getSize(); if (size < mMaxSize) { while (mSize + size > mMaxSize) { - size_t position = 0; - - RenderBuffer* victim = mCache.itemAt(position).mBuffer; + RenderBuffer* victim = mCache.begin()->mBuffer; deleteBuffer(victim); - mCache.removeAt(position); + mCache.erase(mCache.begin()); } RenderBufferEntry entry(buffer); - mCache.add(entry); + mCache.insert(entry); mSize += size; RENDER_BUFFER_LOGD("Added %s render buffer (%dx%d)", diff --git a/libs/hwui/RenderBufferCache.h b/libs/hwui/RenderBufferCache.h index 6c668b09c40d..f77f4c95b5ba 100644 --- a/libs/hwui/RenderBufferCache.h +++ b/libs/hwui/RenderBufferCache.h @@ -20,7 +20,8 @@ #include <GLES2/gl2.h> #include "RenderBuffer.h" -#include "utils/SortedList.h" + +#include <set> namespace android { namespace uirenderer { @@ -63,10 +64,6 @@ public: void clear(); /** - * Sets the maximum size of the cache in bytes. - */ - void setMaxSize(uint32_t maxSize); - /** * Returns the maximum size of the cache in bytes. */ uint32_t getMaxSize(); @@ -100,14 +97,8 @@ private: return compare(*this, other) != 0; } - friend inline int strictly_order_type(const RenderBufferEntry& lhs, - const RenderBufferEntry& rhs) { - return RenderBufferEntry::compare(lhs, rhs) < 0; - } - - friend inline int compare_type(const RenderBufferEntry& lhs, - const RenderBufferEntry& rhs) { - return RenderBufferEntry::compare(lhs, rhs); + bool operator<(const RenderBufferEntry& other) const { + return RenderBufferEntry::compare(*this, other) < 0; } RenderBuffer* mBuffer; @@ -118,7 +109,7 @@ private: void deleteBuffer(RenderBuffer* buffer); - SortedList<RenderBufferEntry> mCache; + std::multiset<RenderBufferEntry> mCache; uint32_t mSize; uint32_t mMaxSize; diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 642ec25dfe83..6e848fddf48f 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -14,20 +14,15 @@ * limitations under the License. */ -#define ATRACE_TAG ATRACE_TAG_VIEW -#define LOG_TAG "OpenGLRenderer" - #include "RenderNode.h" -#include <algorithm> -#include <string> - -#include <SkCanvas.h> -#include <algorithm> - - #include "DamageAccumulator.h" #include "Debug.h" +#if HWUI_NEW_OPS +#include "BakedOpRenderer.h" +#include "RecordedOp.h" +#include "OpDumper.h" +#endif #include "DisplayListOp.h" #include "LayerRenderer.h" #include "OpenGLRenderer.h" @@ -36,52 +31,99 @@ #include "utils/TraceUtils.h" #include "renderthread/CanvasContext.h" +#include "protos/hwui.pb.h" +#include "protos/ProtoHelpers.h" + +#include <algorithm> +#include <sstream> +#include <string> + namespace android { namespace uirenderer { void RenderNode::debugDumpLayers(const char* prefix) { +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("TODO: dump layer"); +#else if (mLayer) { ALOGD("%sNode %p (%s) has layer %p (fbo = %u, wasBuildLayered = %s)", prefix, this, getName(), mLayer, mLayer->getFbo(), mLayer->wasBuildLayered ? "true" : "false"); } - if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->debugDumpLayers(prefix); +#endif + if (mDisplayList) { + for (auto&& child : mDisplayList->getChildren()) { + child->renderNode->debugDumpLayers(prefix); } } } RenderNode::RenderNode() : mDirtyPropertyFields(0) - , mNeedsDisplayListDataSync(false) - , mDisplayListData(nullptr) - , mStagingDisplayListData(nullptr) + , mNeedsDisplayListSync(false) + , mDisplayList(nullptr) + , mStagingDisplayList(nullptr) , mAnimatorManager(*this) - , mLayer(nullptr) , mParentCount(0) { } RenderNode::~RenderNode() { - deleteDisplayListData(); - delete mStagingDisplayListData; + deleteDisplayList(nullptr); + delete mStagingDisplayList; +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL_IF(mLayer, "layer missed detachment!"); +#else if (mLayer) { ALOGW("Memory Warning: Layer %p missed its detachment, held on to for far too long!", mLayer); mLayer->postDecStrong(); mLayer = nullptr; } +#endif } -void RenderNode::setStagingDisplayList(DisplayListData* data) { - mNeedsDisplayListDataSync = true; - delete mStagingDisplayListData; - mStagingDisplayListData = data; +void RenderNode::setStagingDisplayList(DisplayList* displayList, TreeObserver* observer) { + mNeedsDisplayListSync = true; + delete mStagingDisplayList; + mStagingDisplayList = displayList; + // If mParentCount == 0 we are the sole reference to this RenderNode, + // so immediately free the old display list + if (!mParentCount && !mStagingDisplayList) { + deleteDisplayList(observer); + } } /** * This function is a simplified version of replay(), where we simply retrieve and log the * display list. This function should remain in sync with the replay() function. */ +#if HWUI_NEW_OPS +void RenderNode::output(uint32_t level, const char* label) { + ALOGD("%s (%s %p%s%s%s%s%s)", + label, + getName(), + this, + (MathUtils::isZero(properties().getAlpha()) ? ", zero alpha" : ""), + (properties().hasShadow() ? ", casting shadow" : ""), + (isRenderable() ? "" : ", empty"), + (properties().getProjectBackwards() ? ", projected" : ""), + (mLayer != nullptr ? ", on HW Layer" : "")); + properties().debugOutputProperties(level + 1); + + if (mDisplayList) { + for (auto&& op : mDisplayList->getOps()) { + std::stringstream strout; + OpDumper::dump(*op, strout, level + 1); + if (op->opId == RecordedOpId::RenderNodeOp) { + auto rnOp = reinterpret_cast<const RenderNodeOp*>(op); + rnOp->renderNode->output(level + 1, strout.str().c_str()); + } else { + ALOGD("%s", strout.str().c_str()); + } + } + } + ALOGD("%*s/RenderNode(%s %p)", level * 2, "", getName(), this); +} +#else void RenderNode::output(uint32_t level) { ALOGD("%*sStart display list (%p, %s%s%s%s%s%s)", (level - 1) * 2, "", this, getName(), @@ -90,28 +132,97 @@ void RenderNode::output(uint32_t level) { (isRenderable() ? "" : ", empty"), (properties().getProjectBackwards() ? ", projected" : ""), (mLayer != nullptr ? ", on HW Layer" : "")); - ALOGD("%*s%s %d", level * 2, "", "Save", - SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); - + ALOGD("%*s%s %d", level * 2, "", "Save", SaveFlags::MatrixClip); properties().debugOutputProperties(level); - int flags = DisplayListOp::kOpLogFlag_Recurse; - if (mDisplayListData) { + if (mDisplayList) { // TODO: consider printing the chunk boundaries here - for (unsigned int i = 0; i < mDisplayListData->displayListOps.size(); i++) { - mDisplayListData->displayListOps[i]->output(level, flags); + for (auto&& op : mDisplayList->getOps()) { + op->output(level, DisplayListOp::kOpLogFlag_Recurse); } } - ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName()); + } +#endif + +void RenderNode::copyTo(proto::RenderNode *pnode) { + pnode->set_id(static_cast<uint64_t>( + reinterpret_cast<uintptr_t>(this))); + pnode->set_name(mName.string(), mName.length()); + + proto::RenderProperties* pprops = pnode->mutable_properties(); + pprops->set_left(properties().getLeft()); + pprops->set_top(properties().getTop()); + pprops->set_right(properties().getRight()); + pprops->set_bottom(properties().getBottom()); + pprops->set_clip_flags(properties().getClippingFlags()); + pprops->set_alpha(properties().getAlpha()); + pprops->set_translation_x(properties().getTranslationX()); + pprops->set_translation_y(properties().getTranslationY()); + pprops->set_translation_z(properties().getTranslationZ()); + pprops->set_elevation(properties().getElevation()); + pprops->set_rotation(properties().getRotation()); + pprops->set_rotation_x(properties().getRotationX()); + pprops->set_rotation_y(properties().getRotationY()); + pprops->set_scale_x(properties().getScaleX()); + pprops->set_scale_y(properties().getScaleY()); + pprops->set_pivot_x(properties().getPivotX()); + pprops->set_pivot_y(properties().getPivotY()); + pprops->set_has_overlapping_rendering(properties().getHasOverlappingRendering()); + pprops->set_pivot_explicitly_set(properties().isPivotExplicitlySet()); + pprops->set_project_backwards(properties().getProjectBackwards()); + pprops->set_projection_receiver(properties().isProjectionReceiver()); + set(pprops->mutable_clip_bounds(), properties().getClipBounds()); + + const Outline& outline = properties().getOutline(); + if (outline.getType() != Outline::Type::None) { + proto::Outline* poutline = pprops->mutable_outline(); + poutline->clear_path(); + if (outline.getType() == Outline::Type::Empty) { + poutline->set_type(proto::Outline_Type_Empty); + } else if (outline.getType() == Outline::Type::ConvexPath) { + poutline->set_type(proto::Outline_Type_ConvexPath); + if (const SkPath* path = outline.getPath()) { + set(poutline->mutable_path(), *path); + } + } else if (outline.getType() == Outline::Type::RoundRect) { + poutline->set_type(proto::Outline_Type_RoundRect); + } else { + ALOGW("Uknown outline type! %d", static_cast<int>(outline.getType())); + poutline->set_type(proto::Outline_Type_None); + } + poutline->set_should_clip(outline.getShouldClip()); + poutline->set_alpha(outline.getAlpha()); + poutline->set_radius(outline.getRadius()); + set(poutline->mutable_bounds(), outline.getBounds()); + } else { + pprops->clear_outline(); + } + + const RevealClip& revealClip = properties().getRevealClip(); + if (revealClip.willClip()) { + proto::RevealClip* prevealClip = pprops->mutable_reveal_clip(); + prevealClip->set_x(revealClip.getX()); + prevealClip->set_y(revealClip.getY()); + prevealClip->set_radius(revealClip.getRadius()); + } else { + pprops->clear_reveal_clip(); + } + + pnode->clear_children(); + if (mDisplayList) { + for (auto&& child : mDisplayList->getChildren()) { + child->renderNode->copyTo(pnode->add_children()); + } + } } int RenderNode::getDebugSize() { int size = sizeof(RenderNode); - if (mStagingDisplayListData) { - size += mStagingDisplayListData->getUsedSize(); + if (mStagingDisplayList) { + size += mStagingDisplayList->getUsedSize(); } - if (mDisplayListData && mDisplayListData != mStagingDisplayListData) { - size += mDisplayListData->getUsedSize(); + if (mDisplayList && mDisplayList != mStagingDisplayList) { + size += mDisplayList->getUsedSize(); } return size; } @@ -130,6 +241,10 @@ void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) { mAnimatorManager.addAnimator(animator); } +void RenderNode::removeAnimator(const sp<BaseRenderNodeAnimator>& animator) { + mAnimatorManager.removeAnimator(animator); +} + void RenderNode::damageSelf(TreeInfo& info) { if (isRenderable()) { if (properties().getClipDamageToBounds()) { @@ -137,7 +252,7 @@ void RenderNode::damageSelf(TreeInfo& info) { } else { // Hope this is big enough? // TODO: Get this from the display list ops or something - info.damageAccumulator->dirty(INT_MIN, INT_MIN, INT_MAX, INT_MAX); + info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); } } } @@ -157,13 +272,38 @@ void RenderNode::prepareLayer(TreeInfo& info, uint32_t dirtyMask) { } } +static layer_t* createLayer(RenderState& renderState, uint32_t width, uint32_t height) { +#if HWUI_NEW_OPS + return renderState.layerPool().get(renderState, width, height); +#else + return LayerRenderer::createRenderLayer(renderState, width, height); +#endif +} + +static void destroyLayer(layer_t* layer) { +#if HWUI_NEW_OPS + RenderState& renderState = layer->renderState; + renderState.layerPool().putOrDelete(layer); +#else + LayerRenderer::destroyLayer(layer); +#endif +} + +static bool layerMatchesWidthAndHeight(layer_t* layer, int width, int height) { +#if HWUI_NEW_OPS + return layer->viewportWidth == (uint32_t) width && layer->viewportHeight == (uint32_t)height; +#else + return layer->layer.getWidth() == width && layer->layer.getHeight() == height; +#endif +} + void RenderNode::pushLayerUpdate(TreeInfo& info) { LayerType layerType = properties().effectiveLayerType(); // If we are not a layer OR we cannot be rendered (eg, view was detached) // we need to destroy any Layers we may have had previously if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable())) { if (CC_UNLIKELY(mLayer)) { - LayerRenderer::destroyLayer(mLayer); + destroyLayer(mLayer); mLayer = nullptr; } return; @@ -171,13 +311,22 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { bool transformUpdateNeeded = false; if (!mLayer) { - mLayer = LayerRenderer::createRenderLayer(info.renderState, getWidth(), getHeight()); + mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight()); +#if !HWUI_NEW_OPS applyLayerPropertiesToLayer(info); +#endif damageSelf(info); transformUpdateNeeded = true; - } else if (mLayer->layer.getWidth() != getWidth() || mLayer->layer.getHeight() != getHeight()) { + } else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) { +#if HWUI_NEW_OPS + RenderState& renderState = mLayer->renderState; + if (properties().fitsOnLayer()) { + mLayer = renderState.layerPool().resize(mLayer, getWidth(), getHeight()); + } else { +#else if (!LayerRenderer::resizeLayer(mLayer, getWidth(), getHeight())) { - LayerRenderer::destroyLayer(mLayer); +#endif + destroyLayer(mLayer); mLayer = nullptr; } damageSelf(info); @@ -190,20 +339,30 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { if (!mLayer) { Caches::getInstance().dumpMemoryUsage(); if (info.errorHandler) { - std::string msg = "Unable to create layer for "; - msg += getName(); - info.errorHandler->onError(msg); + std::ostringstream err; + err << "Unable to create layer for " << getName(); + const int maxTextureSize = Caches::getInstance().maxTextureSize; + if (getWidth() > maxTextureSize || getHeight() > maxTextureSize) { + err << ", size " << getWidth() << "x" << getHeight() + << " exceeds max size " << maxTextureSize; + } else { + err << ", see logcat for more info"; + } + info.errorHandler->onError(err.str()); } return; } - if (transformUpdateNeeded) { + if (transformUpdateNeeded && mLayer) { // update the transform in window of the layer to reset its origin wrt light source position Matrix4 windowTransform; info.damageAccumulator->computeCurrentTransform(&windowTransform); mLayer->setWindowTransform(windowTransform); } +#if HWUI_NEW_OPS + info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty); +#else if (dirty.intersect(0, 0, getWidth(), getHeight())) { dirty.roundOut(&dirty); mLayer->updateDeferred(this, dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom); @@ -213,13 +372,12 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { if (info.renderer && mLayer->deferredUpdateScheduled) { info.renderer->pushLayerUpdate(mLayer); } +#endif - if (info.canvasContext) { - // There might be prefetched layers that need to be accounted for. - // That might be us, so tell CanvasContext that this layer is in the - // tree and should not be destroyed. - info.canvasContext->markLayerInUse(this); - } + // There might be prefetched layers that need to be accounted for. + // That might be us, so tell CanvasContext that this layer is in the + // tree and should not be destroyed. + info.canvasContext.markLayerInUse(this); } /** @@ -242,24 +400,32 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) { } bool willHaveFunctor = false; - if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayListData) { - willHaveFunctor = !mStagingDisplayListData->functors.isEmpty(); - } else if (mDisplayListData) { - willHaveFunctor = !mDisplayListData->functors.isEmpty(); + if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) { + willHaveFunctor = !mStagingDisplayList->getFunctors().empty(); + } else if (mDisplayList) { + willHaveFunctor = !mDisplayList->getFunctors().empty(); } bool childFunctorsNeedLayer = mProperties.prepareForFunctorPresence( willHaveFunctor, functorsNeedLayer); + if (CC_UNLIKELY(mPositionListener.get())) { + mPositionListener->onPositionUpdated(*this, info); + } + prepareLayer(info, animatorDirtyMask); if (info.mode == TreeInfo::MODE_FULL) { pushStagingDisplayListChanges(info); } - prepareSubTree(info, childFunctorsNeedLayer, mDisplayListData); + prepareSubTree(info, childFunctorsNeedLayer, mDisplayList); pushLayerUpdate(info); info.damageAccumulator->popTransform(); } +void RenderNode::syncProperties() { + mProperties = mStagingProperties; +} + void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { // Push the animators first so that setupStartValueIfNecessary() is called // before properties() is trampled by stagingProperties(), as they are @@ -271,8 +437,10 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { mDirtyPropertyFields = 0; damageSelf(info); info.damageAccumulator->popTransform(); - mProperties = mStagingProperties; + syncProperties(); +#if !HWUI_NEW_OPS applyLayerPropertiesToLayer(info); +#endif // We could try to be clever and only re-damage if the matrix changed. // However, we don't need to worry about that. The cost of over-damaging // here is only going to be a single additional map rect of this node @@ -283,6 +451,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { } } +#if !HWUI_NEW_OPS void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { if (CC_LIKELY(!mLayer)) return; @@ -291,96 +460,104 @@ void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { mLayer->setColorFilter(props.colorFilter()); mLayer->setBlend(props.needsBlending()); } +#endif -void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) { - if (mNeedsDisplayListDataSync) { - mNeedsDisplayListDataSync = false; - // Make sure we inc first so that we don't fluctuate between 0 and 1, - // which would thrash the layer cache - if (mStagingDisplayListData) { - for (size_t i = 0; i < mStagingDisplayListData->children().size(); i++) { - mStagingDisplayListData->children()[i]->mRenderNode->incParentRefCount(); - } +void RenderNode::syncDisplayList(TreeObserver* observer) { + // Make sure we inc first so that we don't fluctuate between 0 and 1, + // which would thrash the layer cache + if (mStagingDisplayList) { + for (auto&& child : mStagingDisplayList->getChildren()) { + child->renderNode->incParentRefCount(); + } + } + deleteDisplayList(observer); + mDisplayList = mStagingDisplayList; + mStagingDisplayList = nullptr; + if (mDisplayList) { + for (auto& iter : mDisplayList->getFunctors()) { + (*iter.functor)(DrawGlInfo::kModeSync, nullptr); + } + for (size_t i = 0; i < mDisplayList->getPushStagingFunctors().size(); i++) { + (*mDisplayList->getPushStagingFunctors()[i])(); } + } +} + +void RenderNode::pushStagingDisplayListChanges(TreeInfo& info) { + if (mNeedsDisplayListSync) { + mNeedsDisplayListSync = false; // Damage with the old display list first then the new one to catch any // changes in isRenderable or, in the future, bounds damageSelf(info); - deleteDisplayListData(); - // TODO: Remove this caches stuff - if (mStagingDisplayListData && mStagingDisplayListData->functors.size()) { - Caches::getInstance().registerFunctors(mStagingDisplayListData->functors.size()); - } - mDisplayListData = mStagingDisplayListData; - mStagingDisplayListData = nullptr; - if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->functors.size(); i++) { - (*mDisplayListData->functors[i])(DrawGlInfo::kModeSync, nullptr); - } - } + syncDisplayList(info.observer); damageSelf(info); } } -void RenderNode::deleteDisplayListData() { - if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->decParentRefCount(); - } - if (mDisplayListData->functors.size()) { - Caches::getInstance().unregisterFunctors(mDisplayListData->functors.size()); +void RenderNode::deleteDisplayList(TreeObserver* observer) { + if (mDisplayList) { + for (auto&& child : mDisplayList->getChildren()) { + child->renderNode->decParentRefCount(observer); } } - delete mDisplayListData; - mDisplayListData = nullptr; + delete mDisplayList; + mDisplayList = nullptr; } -void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree) { +void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree) { if (subtree) { TextureCache& cache = Caches::getInstance().textureCache; - info.out.hasFunctors |= subtree->functors.size(); - for (size_t i = 0; info.prepareTextures && i < subtree->bitmapResources.size(); i++) { - info.prepareTextures = cache.prefetchAndMarkInUse( - info.canvasContext, subtree->bitmapResources[i]); + info.out.hasFunctors |= subtree->getFunctors().size(); + for (auto&& bitmapResource : subtree->getBitmapResources()) { + void* ownerToken = &info.canvasContext; + info.prepareTextures = cache.prefetchAndMarkInUse(ownerToken, bitmapResource); } - for (size_t i = 0; i < subtree->children().size(); i++) { - DrawRenderNodeOp* op = subtree->children()[i]; - RenderNode* childNode = op->mRenderNode; - info.damageAccumulator->pushTransform(&op->mTransformFromParent); + for (auto&& op : subtree->getChildren()) { + RenderNode* childNode = op->renderNode; +#if HWUI_NEW_OPS + info.damageAccumulator->pushTransform(&op->localMatrix); + bool childFunctorsNeedLayer = functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip; +#else + info.damageAccumulator->pushTransform(&op->localMatrix); bool childFunctorsNeedLayer = functorsNeedLayer // Recorded with non-rect clip, or canvas-rotated by parent || op->mRecordedWithPotentialStencilClip; +#endif childNode->prepareTreeImpl(info, childFunctorsNeedLayer); info.damageAccumulator->popTransform(); } } } -void RenderNode::destroyHardwareResources() { +void RenderNode::destroyHardwareResources(TreeObserver* observer) { if (mLayer) { - LayerRenderer::destroyLayer(mLayer); + destroyLayer(mLayer); mLayer = nullptr; } - if (mDisplayListData) { - for (size_t i = 0; i < mDisplayListData->children().size(); i++) { - mDisplayListData->children()[i]->mRenderNode->destroyHardwareResources(); + if (mDisplayList) { + for (auto&& child : mDisplayList->getChildren()) { + child->renderNode->destroyHardwareResources(observer); } - if (mNeedsDisplayListDataSync) { + if (mNeedsDisplayListSync) { // Next prepare tree we are going to push a new display list, so we can // drop our current one now - deleteDisplayListData(); + deleteDisplayList(observer); } } } -void RenderNode::decParentRefCount() { +void RenderNode::decParentRefCount(TreeObserver* observer) { LOG_ALWAYS_FATAL_IF(!mParentCount, "already 0!"); mParentCount--; if (!mParentCount) { + if (observer) { + observer->onMaybeRemovedFromTree(this); + } // If a child of ours is being attached to our parent then this will incorrectly // destroy its hardware resources. However, this situation is highly unlikely // and the failure is "just" that the layer is re-created, so this should // be safe enough - destroyHardwareResources(); + destroyHardwareResources(observer); } } @@ -431,7 +608,7 @@ void RenderNode::setViewProperties(OpenGLRenderer& renderer, T& handler) { layerBounds.left, layerBounds.top, layerBounds.right, layerBounds.bottom, (int) (properties().getAlpha() * 255), - SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kClipToLayer_SaveFlag); + SaveFlags::HasAlphaLayer | SaveFlags::ClipToLayer); handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds()); } @@ -520,46 +697,43 @@ void RenderNode::computeOrdering() { // TODO: create temporary DDLOp and call computeOrderingImpl on top DisplayList so that // transform properties are applied correctly to top level children - if (mDisplayListData == nullptr) return; - for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) { - DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - childOp->mRenderNode->computeOrderingImpl(childOp, - properties().getOutline().getPath(), &mProjectedNodes, &mat4::identity()); + if (mDisplayList == nullptr) return; + for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) { + renderNodeOp_t* childOp = mDisplayList->getChildren()[i]; + childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity()); } } void RenderNode::computeOrderingImpl( - DrawRenderNodeOp* opState, - const SkPath* outlineOfProjectionSurface, - Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface, + renderNodeOp_t* opState, + std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface, const mat4* transformFromProjectionSurface) { mProjectedNodes.clear(); - if (mDisplayListData == nullptr || mDisplayListData->isEmpty()) return; + if (mDisplayList == nullptr || mDisplayList->isEmpty()) return; // TODO: should avoid this calculation in most cases // TODO: just calculate single matrix, down to all leaf composited elements Matrix4 localTransformFromProjectionSurface(*transformFromProjectionSurface); - localTransformFromProjectionSurface.multiply(opState->mTransformFromParent); + localTransformFromProjectionSurface.multiply(opState->localMatrix); if (properties().getProjectBackwards()) { // composited projectee, flag for out of order draw, save matrix, and store in proj surface - opState->mSkipInOrderDraw = true; - opState->mTransformFromCompositingAncestor.load(localTransformFromProjectionSurface); - compositedChildrenOfProjectionSurface->add(opState); + opState->skipInOrderDraw = true; + opState->transformFromCompositingAncestor = localTransformFromProjectionSurface; + compositedChildrenOfProjectionSurface->push_back(opState); } else { // standard in order draw - opState->mSkipInOrderDraw = false; + opState->skipInOrderDraw = false; } - if (mDisplayListData->children().size() > 0) { - const bool isProjectionReceiver = mDisplayListData->projectionReceiveIndex >= 0; + if (mDisplayList->getChildren().size() > 0) { + const bool isProjectionReceiver = mDisplayList->projectionReceiveIndex >= 0; bool haveAppliedPropertiesToProjection = false; - for (unsigned int i = 0; i < mDisplayListData->children().size(); i++) { - DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - RenderNode* child = childOp->mRenderNode; + for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) { + renderNodeOp_t* childOp = mDisplayList->getChildren()[i]; + RenderNode* child = childOp->renderNode; - const SkPath* projectionOutline = nullptr; - Vector<DrawRenderNodeOp*>* projectionChildren = nullptr; + std::vector<renderNodeOp_t*>* projectionChildren = nullptr; const mat4* projectionTransform = nullptr; if (isProjectionReceiver && !child->properties().getProjectBackwards()) { // if receiving projections, collect projecting descendant @@ -567,7 +741,6 @@ void RenderNode::computeOrderingImpl( // Note that if a direct descendant is projecting backwards, we pass its // grandparent projection collection, since it shouldn't project onto its // parent, where it will already be drawing. - projectionOutline = properties().getOutline().getPath(); projectionChildren = &mProjectedNodes; projectionTransform = &mat4::identity(); } else { @@ -575,12 +748,10 @@ void RenderNode::computeOrderingImpl( applyViewPropertyTransforms(localTransformFromProjectionSurface); haveAppliedPropertiesToProjection = true; } - projectionOutline = outlineOfProjectionSurface; projectionChildren = compositedChildrenOfProjectionSurface; projectionTransform = &localTransformFromProjectionSurface; } - child->computeOrderingImpl(childOp, - projectionOutline, projectionChildren, projectionTransform); + child->computeOrderingImpl(childOp, projectionChildren, projectionTransform); } } } @@ -640,26 +811,28 @@ void RenderNode::replay(ReplayStateStruct& replayStruct, const int level) { issueOperations<ReplayOperationHandler>(replayStruct.mRenderer, handler); } -void RenderNode::buildZSortedChildList(const DisplayListData::Chunk& chunk, - Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) { +void RenderNode::buildZSortedChildList(const DisplayList::Chunk& chunk, + std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes) { +#if !HWUI_NEW_OPS if (chunk.beginChildIndex == chunk.endChildIndex) return; for (unsigned int i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) { - DrawRenderNodeOp* childOp = mDisplayListData->children()[i]; - RenderNode* child = childOp->mRenderNode; + DrawRenderNodeOp* childOp = mDisplayList->getChildren()[i]; + RenderNode* child = childOp->renderNode; float childZ = child->properties().getZ(); if (!MathUtils::isZero(childZ) && chunk.reorderChildren) { - zTranslatedNodes.add(ZDrawRenderNodeOpPair(childZ, childOp)); - childOp->mSkipInOrderDraw = true; + zTranslatedNodes.push_back(ZDrawRenderNodeOpPair(childZ, childOp)); + childOp->skipInOrderDraw = true; } else if (!child->properties().getProjectBackwards()) { // regular, in order drawing DisplayList - childOp->mSkipInOrderDraw = false; + childOp->skipInOrderDraw = false; } } // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order) std::stable_sort(zTranslatedNodes.begin(), zTranslatedNodes.end()); +#endif } template <class T> @@ -695,7 +868,7 @@ void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T& if (revealClipPath) { frameAllocatedPath = handler.allocPathForFrame(); - Op(*outlinePath, *revealClipPath, kIntersect_PathOp, frameAllocatedPath); + Op(*outlinePath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath); outlinePath = frameAllocatedPath; } @@ -711,7 +884,7 @@ void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T& clipBoundsPath.addRect(clipBounds.left, clipBounds.top, clipBounds.right, clipBounds.bottom); - Op(*outlinePath, clipBoundsPath, kIntersect_PathOp, frameAllocatedPath); + Op(*outlinePath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath); outlinePath = frameAllocatedPath; } @@ -724,20 +897,20 @@ void RenderNode::issueDrawShadowOperation(const Matrix4& transformFromParent, T& template <class T> void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, - const Matrix4& initialTransform, const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, + const Matrix4& initialTransform, const std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, OpenGLRenderer& renderer, T& handler) { const int size = zTranslatedNodes.size(); if (size == 0 - || (mode == kNegativeZChildren && zTranslatedNodes[0].key > 0.0f) - || (mode == kPositiveZChildren && zTranslatedNodes[size - 1].key < 0.0f)) { + || (mode == ChildrenSelectMode::NegativeZChildren && zTranslatedNodes[0].key > 0.0f) + || (mode == ChildrenSelectMode::PositiveZChildren && zTranslatedNodes[size - 1].key < 0.0f)) { // no 3d children to draw return; } // Apply the base transform of the parent of the 3d children. This isolates // 3d children of the current chunk from transformations made in previous chunks. - int rootRestoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag); - renderer.setMatrix(initialTransform); + int rootRestoreTo = renderer.save(SaveFlags::Matrix); + renderer.setGlobalMatrix(initialTransform); /** * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters @@ -748,7 +921,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, */ const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes); size_t drawIndex, shadowIndex, endIndex; - if (mode == kNegativeZChildren) { + if (mode == ChildrenSelectMode::NegativeZChildren) { drawIndex = 0; endIndex = nonNegativeIndex; shadowIndex = endIndex; // draw no shadows @@ -765,12 +938,12 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, while (shadowIndex < endIndex || drawIndex < endIndex) { if (shadowIndex < endIndex) { DrawRenderNodeOp* casterOp = zTranslatedNodes[shadowIndex].value; - RenderNode* caster = casterOp->mRenderNode; + RenderNode* caster = casterOp->renderNode; const float casterZ = zTranslatedNodes[shadowIndex].key; // attempt to render the shadow if the caster about to be drawn is its caster, // OR if its caster's Z value is similar to the previous potential caster if (shadowIndex == drawIndex || casterZ - lastCasterZ < SHADOW_DELTA) { - caster->issueDrawShadowOperation(casterOp->mTransformFromParent, handler); + caster->issueDrawShadowOperation(casterOp->localMatrix, handler); lastCasterZ = casterZ; // must do this even if current caster not casting a shadow shadowIndex++; @@ -780,14 +953,14 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, // only the actual child DL draw needs to be in save/restore, // since it modifies the renderer's matrix - int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag); + int restoreTo = renderer.save(SaveFlags::Matrix); DrawRenderNodeOp* childOp = zTranslatedNodes[drawIndex].value; - renderer.concatMatrix(childOp->mTransformFromParent); - childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone + renderer.concatMatrix(childOp->localMatrix); + childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds()); - childOp->mSkipInOrderDraw = true; + childOp->skipInOrderDraw = true; renderer.restoreToCount(restoreTo); drawIndex++; @@ -802,31 +975,36 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& int restoreTo = renderer.getSaveCount(); LinearAllocator& alloc = handler.allocator(); - handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag), + handler(new (alloc) SaveOp(SaveFlags::MatrixClip), PROPERTY_SAVECOUNT, properties().getClipToBounds()); // Transform renderer to match background we're projecting onto // (by offsetting canvas by translationX/Y of background rendernode, since only those are set) const DisplayListOp* op = - (mDisplayListData->displayListOps[mDisplayListData->projectionReceiveIndex]); +#if HWUI_NEW_OPS + nullptr; + LOG_ALWAYS_FATAL("unsupported"); +#else + (mDisplayList->getOps()[mDisplayList->projectionReceiveIndex]); +#endif const DrawRenderNodeOp* backgroundOp = reinterpret_cast<const DrawRenderNodeOp*>(op); - const RenderProperties& backgroundProps = backgroundOp->mRenderNode->properties(); + const RenderProperties& backgroundProps = backgroundOp->renderNode->properties(); renderer.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY()); - // If the projection reciever has an outline, we mask projected content to it + // If the projection receiver has an outline, we mask projected content to it // (which we know, apriori, are all tessellated paths) renderer.setProjectionPathMask(alloc, projectionReceiverOutline); // draw projected nodes for (size_t i = 0; i < mProjectedNodes.size(); i++) { - DrawRenderNodeOp* childOp = mProjectedNodes[i]; + renderNodeOp_t* childOp = mProjectedNodes[i]; // matrix save, concat, and restore can be done safely without allocating operations - int restoreTo = renderer.save(SkCanvas::kMatrix_SaveFlag); - renderer.concatMatrix(childOp->mTransformFromCompositingAncestor); - childOp->mSkipInOrderDraw = false; // this is horrible, I'm so sorry everyone + int restoreTo = renderer.save(SaveFlags::Matrix); + renderer.concatMatrix(childOp->transformFromCompositingAncestor); + childOp->skipInOrderDraw = false; // this is horrible, I'm so sorry everyone handler(childOp, renderer.getSaveCount() - 1, properties().getClipToBounds()); - childOp->mSkipInOrderDraw = true; + childOp->skipInOrderDraw = true; renderer.restoreToCount(restoreTo); } @@ -845,13 +1023,17 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& */ template <class T> void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { - if (mDisplayListData->isEmpty()) { + if (mDisplayList->isEmpty()) { DISPLAY_LIST_LOGD("%*sEmpty display list (%p, %s)", handler.level() * 2, "", this, getName()); return; } +#if HWUI_NEW_OPS + const bool drawLayer = false; +#else const bool drawLayer = (mLayer && (&renderer != mLayer->renderer.get())); +#endif // If we are updating the contents of mLayer, we don't want to apply any of // the RenderNode's properties to this issueOperations pass. Those will all // be applied when the layer is drawn, aka when this is true. @@ -879,16 +1061,19 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { LinearAllocator& alloc = handler.allocator(); int restoreTo = renderer.getSaveCount(); - handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag), + handler(new (alloc) SaveOp(SaveFlags::MatrixClip), PROPERTY_SAVECOUNT, properties().getClipToBounds()); DISPLAY_LIST_LOGD("%*sSave %d %d", (handler.level() + 1) * 2, "", - SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag, restoreTo); + SaveFlags::MatrixClip, restoreTo); if (useViewProperties) { setViewProperties<T>(renderer, handler); } +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("legacy op traversal not supported"); +#else bool quickRejected = properties().getClipToBounds() && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight()); if (!quickRejected) { @@ -896,39 +1081,39 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { renderer.setBaseTransform(initialTransform); if (drawLayer) { - handler(new (alloc) DrawLayerOp(mLayer, 0, 0), + handler(new (alloc) DrawLayerOp(mLayer), renderer.getSaveCount() - 1, properties().getClipToBounds()); } else { const int saveCountOffset = renderer.getSaveCount() - 1; - const int projectionReceiveIndex = mDisplayListData->projectionReceiveIndex; - for (size_t chunkIndex = 0; chunkIndex < mDisplayListData->getChunks().size(); chunkIndex++) { - const DisplayListData::Chunk& chunk = mDisplayListData->getChunks()[chunkIndex]; + const int projectionReceiveIndex = mDisplayList->projectionReceiveIndex; + for (size_t chunkIndex = 0; chunkIndex < mDisplayList->getChunks().size(); chunkIndex++) { + const DisplayList::Chunk& chunk = mDisplayList->getChunks()[chunkIndex]; - Vector<ZDrawRenderNodeOpPair> zTranslatedNodes; + std::vector<ZDrawRenderNodeOpPair> zTranslatedNodes; buildZSortedChildList(chunk, zTranslatedNodes); - issueOperationsOf3dChildren(kNegativeZChildren, + issueOperationsOf3dChildren(ChildrenSelectMode::NegativeZChildren, initialTransform, zTranslatedNodes, renderer, handler); - for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { - DisplayListOp *op = mDisplayListData->displayListOps[opIndex]; + DisplayListOp *op = mDisplayList->getOps()[opIndex]; #if DEBUG_DISPLAY_LIST op->output(handler.level() + 1); #endif handler(op, saveCountOffset, properties().getClipToBounds()); - if (CC_UNLIKELY(!mProjectedNodes.isEmpty() && projectionReceiveIndex >= 0 && + if (CC_UNLIKELY(!mProjectedNodes.empty() && projectionReceiveIndex >= 0 && opIndex == static_cast<size_t>(projectionReceiveIndex))) { issueOperationsOfProjectedChildren(renderer, handler); } } - issueOperationsOf3dChildren(kPositiveZChildren, + issueOperationsOf3dChildren(ChildrenSelectMode::PositiveZChildren, initialTransform, zTranslatedNodes, renderer, handler); } } } +#endif DISPLAY_LIST_LOGD("%*sRestoreToCount %d", (handler.level() + 1) * 2, "", restoreTo); handler(new (alloc) RestoreToCountOp(restoreTo), diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 025a4a416e4c..acdc3d835b4d 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -16,17 +16,12 @@ #ifndef RENDERNODE_H #define RENDERNODE_H -#ifndef LOG_TAG - #define LOG_TAG "OpenGLRenderer" -#endif - #include <SkCamera.h> #include <SkMatrix.h> #include <utils/LinearAllocator.h> #include <utils/RefBase.h> #include <utils/String8.h> -#include <utils/Vector.h> #include <cutils/compiler.h> @@ -34,10 +29,12 @@ #include "AnimatorManager.h" #include "Debug.h" -#include "Matrix.h" #include "DisplayList.h" +#include "Matrix.h" #include "RenderProperties.h" +#include <vector> + class SkBitmap; class SkPaint; class SkPath; @@ -46,33 +43,52 @@ class SkRegion; namespace android { namespace uirenderer { -class DisplayListOp; +class CanvasState; class DisplayListCanvas; +class DisplayListOp; class OpenGLRenderer; class Rect; -class Layer; class SkiaShader; +#if HWUI_NEW_OPS +class FrameBuilder; +class OffscreenBuffer; +struct RenderNodeOp; +typedef OffscreenBuffer layer_t; +typedef RenderNodeOp renderNodeOp_t; +#else +class Layer; +typedef Layer layer_t; +typedef DrawRenderNodeOp renderNodeOp_t; +#endif + class ClipRectOp; +class DrawRenderNodeOp; class SaveLayerOp; class SaveOp; class RestoreToCountOp; -class DrawRenderNodeOp; class TreeInfo; +class TreeObserver; + +namespace proto { +class RenderNode; +} /** * Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties. * * Recording of canvas commands is somewhat similar to SkPicture, except the canvas-recording - * functionality is split between DisplayListCanvas (which manages the recording), DisplayListData + * functionality is split between DisplayListCanvas (which manages the recording), DisplayList * (which holds the actual data), and DisplayList (which holds properties and performs playback onto * a renderer). * - * Note that DisplayListData is swapped out from beneath an individual DisplayList when a view's - * recorded stream of canvas operations is refreshed. The DisplayList (and its properties) stay + * Note that DisplayList is swapped out from beneath an individual RenderNode when a view's + * recorded stream of canvas operations is refreshed. The RenderNode (and its properties) stay * attached. */ class RenderNode : public VirtualLightRefBase { +friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties +friend class FrameBuilder; public: enum DirtyPropertyMask { GENERIC = 1 << 1, @@ -99,25 +115,29 @@ public: kReplayFlag_ClipChildren = 0x1 }; - static void outputLogBuffer(int fd); void debugDumpLayers(const char* prefix); - ANDROID_API void setStagingDisplayList(DisplayListData* newData); + ANDROID_API void setStagingDisplayList(DisplayList* newData, TreeObserver* observer); void computeOrdering(); void defer(DeferStateStruct& deferStruct, const int level); void replay(ReplayStateStruct& replayStruct, const int level); +#if HWUI_NEW_OPS + ANDROID_API void output(uint32_t level = 0, const char* label = "Root"); +#else ANDROID_API void output(uint32_t level = 1); +#endif ANDROID_API int getDebugSize(); + void copyTo(proto::RenderNode* node); bool isRenderable() const { - return mDisplayListData && !mDisplayListData->isEmpty(); + return mDisplayList && !mDisplayList->isEmpty(); } bool hasProjectionReceiver() const { - return mDisplayListData && mDisplayListData->projectionReceiveIndex >= 0; + return mDisplayList && mDisplayList->projectionReceiveIndex >= 0; } const char* getName() const { @@ -135,6 +155,14 @@ public: } } + VirtualLightRefBase* getUserContext() const { + return mUserContext.get(); + } + + void setUserContext(VirtualLightRefBase* context) { + mUserContext = context; + } + bool isPropertyFieldDirty(DirtyPropertyMask field) const { return mDirtyPropertyFields & field; } @@ -159,56 +187,97 @@ public: return mStagingProperties; } - int getWidth() { + int getWidth() const { return properties().getWidth(); } - int getHeight() { + int getHeight() const { return properties().getHeight(); } ANDROID_API virtual void prepareTree(TreeInfo& info); - void destroyHardwareResources(); + void destroyHardwareResources(TreeObserver* observer); // UI thread only! ANDROID_API void addAnimator(const sp<BaseRenderNodeAnimator>& animator); + void removeAnimator(const sp<BaseRenderNodeAnimator>& animator); + + // This can only happen during pushStaging() + void onAnimatorTargetChanged(BaseRenderNodeAnimator* animator) { + mAnimatorManager.onAnimatorTargetChanged(animator); + } AnimatorManager& animators() { return mAnimatorManager; } void applyViewPropertyTransforms(mat4& matrix, bool true3dTransform = false) const; + bool nothingToDraw() const { + const Outline& outline = properties().getOutline(); + return mDisplayList == nullptr + || properties().getAlpha() <= 0 + || (outline.getShouldClip() && outline.isEmpty()) + || properties().getScaleX() == 0 + || properties().getScaleY() == 0; + } + + const DisplayList* getDisplayList() const { + return mDisplayList; + } +#if HWUI_NEW_OPS + OffscreenBuffer* getLayer() const { return mLayer; } + OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh... +#endif + + class ANDROID_API PositionListener { + public: + virtual ~PositionListener() {} + virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) = 0; + }; + + // Note this is not thread safe, this needs to be called + // before the RenderNode is used for drawing. + // RenderNode takes ownership of the pointer + ANDROID_API void setPositionListener(PositionListener* listener) { + mPositionListener.reset(listener); + } + + // This is only modified in MODE_FULL, so it can be safely accessed + // on the UI thread. + ANDROID_API bool hasParents() { + return mParentCount; + } + private: typedef key_value_pair_t<float, DrawRenderNodeOp*> ZDrawRenderNodeOpPair; - static size_t findNonNegativeIndex(const Vector<ZDrawRenderNodeOpPair>& nodes) { + static size_t findNonNegativeIndex(const std::vector<ZDrawRenderNodeOpPair>& nodes) { for (size_t i = 0; i < nodes.size(); i++) { if (nodes[i].key >= 0.0f) return i; } return nodes.size(); } - enum ChildrenSelectMode { - kNegativeZChildren, - kPositiveZChildren + enum class ChildrenSelectMode { + NegativeZChildren, + PositiveZChildren }; - void computeOrderingImpl(DrawRenderNodeOp* opState, - const SkPath* outlineOfProjectionSurface, - Vector<DrawRenderNodeOp*>* compositedChildrenOfProjectionSurface, + void computeOrderingImpl(renderNodeOp_t* opState, + std::vector<renderNodeOp_t*>* compositedChildrenOfProjectionSurface, const mat4* transformFromProjectionSurface); template <class T> inline void setViewProperties(OpenGLRenderer& renderer, T& handler); - void buildZSortedChildList(const DisplayListData::Chunk& chunk, - Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes); + void buildZSortedChildList(const DisplayList::Chunk& chunk, + std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes); template<class T> inline void issueDrawShadowOperation(const Matrix4& transformFromParent, T& handler); template <class T> inline void issueOperationsOf3dChildren(ChildrenSelectMode mode, - const Matrix4& initialTransform, const Vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, + const Matrix4& initialTransform, const std::vector<ZDrawRenderNodeOpPair>& zTranslatedNodes, OpenGLRenderer& renderer, T& handler); template <class T> @@ -235,51 +304,60 @@ private: const char* mText; }; + + void syncProperties(); + void syncDisplayList(TreeObserver* observer); + void prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer); void pushStagingPropertiesChanges(TreeInfo& info); void pushStagingDisplayListChanges(TreeInfo& info); - void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayListData* subtree); + void prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayList* subtree); +#if !HWUI_NEW_OPS void applyLayerPropertiesToLayer(TreeInfo& info); +#endif void prepareLayer(TreeInfo& info, uint32_t dirtyMask); void pushLayerUpdate(TreeInfo& info); - void deleteDisplayListData(); + void deleteDisplayList(TreeObserver* observer); void damageSelf(TreeInfo& info); void incParentRefCount() { mParentCount++; } - void decParentRefCount(); + void decParentRefCount(TreeObserver* observer); String8 mName; + sp<VirtualLightRefBase> mUserContext; uint32_t mDirtyPropertyFields; RenderProperties mProperties; RenderProperties mStagingProperties; - bool mNeedsDisplayListDataSync; - // WARNING: Do not delete this directly, you must go through deleteDisplayListData()! - DisplayListData* mDisplayListData; - DisplayListData* mStagingDisplayListData; + bool mNeedsDisplayListSync; + // WARNING: Do not delete this directly, you must go through deleteDisplayList()! + DisplayList* mDisplayList; + DisplayList* mStagingDisplayList; friend class AnimatorManager; AnimatorManager mAnimatorManager; // Owned by RT. Lifecycle is managed by prepareTree(), with the exception // being in ~RenderNode() which may happen on any thread. - Layer* mLayer; + layer_t* mLayer = nullptr; /** * Draw time state - these properties are only set and used during rendering */ // for projection surfaces, contains a list of all children items - Vector<DrawRenderNodeOp*> mProjectedNodes; + std::vector<renderNodeOp_t*> mProjectedNodes; // How many references our parent(s) have to us. Typically this should alternate // between 2 and 1 (when a staging push happens we inc first then dec) // When this hits 0 we are no longer in the tree, so any hardware resources // (specifically Layers) should be released. // This is *NOT* thread-safe, and should therefore only be tracking - // mDisplayListData, not mStagingDisplayListData. + // mDisplayList, not mStagingDisplayList. uint32_t mParentCount; + + std::unique_ptr<PositionListener> mPositionListener; }; // class RenderNode } /* namespace uirenderer */ diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index 4f6ef4ef9e3d..5ebf5458da18 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -14,13 +14,10 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "RenderProperties.h" #include <utils/Trace.h> -#include <SkCanvas.h> #include <SkColorFilter.h> #include <SkMatrix.h> #include <SkPath.h> @@ -28,6 +25,7 @@ #include "Matrix.h" #include "OpenGLRenderer.h" +#include "hwui/Canvas.h" #include "utils/MathUtils.h" namespace android { @@ -54,11 +52,8 @@ bool LayerProperties::setColorFilter(SkColorFilter* filter) { bool LayerProperties::setFromPaint(const SkPaint* paint) { bool changed = false; - SkXfermode::Mode mode; - int alpha; - OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode); - changed |= setAlpha(static_cast<uint8_t>(alpha)); - changed |= setXferMode(mode); + changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint))); + changed |= setXferMode(PaintUtils::getXfermodeDirect(paint)); changed |= setColorFilter(paint ? paint->getColorFilter() : nullptr); return changed; } @@ -72,23 +67,6 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) { return *this; } -RenderProperties::PrimitiveFields::PrimitiveFields() - : mClippingFlags(CLIP_TO_BOUNDS) - , mProjectBackwards(false) - , mProjectionReceiver(false) - , mAlpha(1) - , mHasOverlappingRendering(true) - , mElevation(0) - , mTranslationX(0), mTranslationY(0), mTranslationZ(0) - , mRotation(0), mRotationX(0), mRotationY(0) - , mScaleX(1), mScaleY(1) - , mPivotX(0), mPivotY(0) - , mLeft(0), mTop(0), mRight(0), mBottom(0) - , mWidth(0), mHeight(0) - , mPivotExplicitlySet(false) - , mMatrixOrPivotDirty(false) { -} - RenderProperties::ComputedFields::ComputedFields() : mTransformMatrix(nullptr) { } @@ -124,22 +102,23 @@ RenderProperties& RenderProperties::operator=(const RenderProperties& other) { void RenderProperties::debugOutputProperties(const int level) const { if (mPrimitiveFields.mLeft != 0 || mPrimitiveFields.mTop != 0) { - ALOGD("%*sTranslate (left, top) %d, %d", level * 2, "", mPrimitiveFields.mLeft, mPrimitiveFields.mTop); + ALOGD("%*s(Translate (left, top) %d, %d)", level * 2, "", + mPrimitiveFields.mLeft, mPrimitiveFields.mTop); } if (mStaticMatrix) { - ALOGD("%*sConcatMatrix (static) %p: " SK_MATRIX_STRING, + ALOGD("%*s(ConcatMatrix (static) %p: " SK_MATRIX_STRING ")", level * 2, "", mStaticMatrix, SK_MATRIX_ARGS(mStaticMatrix)); } if (mAnimationMatrix) { - ALOGD("%*sConcatMatrix (animation) %p: " SK_MATRIX_STRING, + ALOGD("%*s(ConcatMatrix (animation) %p: " SK_MATRIX_STRING ")", level * 2, "", mAnimationMatrix, SK_MATRIX_ARGS(mAnimationMatrix)); } if (hasTransformMatrix()) { if (isTransformTranslateOnly()) { - ALOGD("%*sTranslate %.2f, %.2f, %.2f", + ALOGD("%*s(Translate %.2f, %.2f, %.2f)", level * 2, "", getTranslationX(), getTranslationY(), getZ()); } else { - ALOGD("%*sConcatMatrix %p: " SK_MATRIX_STRING, + ALOGD("%*s(ConcatMatrix %p: " SK_MATRIX_STRING ")", level * 2, "", mComputedFields.mTransformMatrix, SK_MATRIX_ARGS(mComputedFields.mTransformMatrix)); } } @@ -154,7 +133,7 @@ void RenderProperties::debugOutputProperties(const int level) const { if (CC_LIKELY(isLayer || !getHasOverlappingRendering())) { // simply scale rendering content's alpha - ALOGD("%*sScaleAlpha %.2f", level * 2, "", mPrimitiveFields.mAlpha); + ALOGD("%*s(ScaleAlpha %.2f)", level * 2, "", mPrimitiveFields.mAlpha); } else { // savelayeralpha to create an offscreen buffer to apply alpha Rect layerBounds(0, 0, getWidth(), getHeight()); @@ -162,21 +141,37 @@ void RenderProperties::debugOutputProperties(const int level) const { getClippingRectForFlags(clipFlags, &layerBounds); clipFlags = 0; // all clipping done by savelayer } - ALOGD("%*sSaveLayerAlpha %d, %d, %d, %d, %d, 0x%x", level * 2, "", + ALOGD("%*s(SaveLayerAlpha %d, %d, %d, %d, %d, 0x%x)", level * 2, "", (int)layerBounds.left, (int)layerBounds.top, (int)layerBounds.right, (int)layerBounds.bottom, (int)(mPrimitiveFields.mAlpha * 255), - SkCanvas::kHasAlphaLayer_SaveFlag | SkCanvas::kClipToLayer_SaveFlag); + SaveFlags::HasAlphaLayer | SaveFlags::ClipToLayer); } - - } + if (clipFlags) { Rect clipRect; getClippingRectForFlags(clipFlags, &clipRect); - ALOGD("%*sClipRect %d, %d, %d, %d", level * 2, "", + ALOGD("%*s(ClipRect %d, %d, %d, %d)", level * 2, "", (int)clipRect.left, (int)clipRect.top, (int)clipRect.right, (int)clipRect.bottom); } + + if (getRevealClip().willClip()) { + Rect bounds; + getRevealClip().getBounds(&bounds); + ALOGD("%*s(Clip to reveal clip with bounds %.2f %.2f %.2f %.2f)", level * 2, "", + RECT_ARGS(bounds)); + } + + auto& outline = mPrimitiveFields.mOutline; + if (outline.getShouldClip()) { + if (outline.isEmpty()) { + ALOGD("%*s(Clip to empty outline)", level * 2, ""); + } else if (outline.willClip()) { + ALOGD("%*s(Clip to outline with bounds %.2f %.2f %.2f %.2f)", level * 2, "", + RECT_ARGS(outline.getBounds())); + } + } } void RenderProperties::updateMatrix() { diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 11abd701b9d8..395279806adb 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -16,23 +16,24 @@ #ifndef RENDERNODEPROPERTIES_H #define RENDERNODEPROPERTIES_H -#include <algorithm> -#include <stddef.h> -#include <vector> -#include <cutils/compiler.h> -#include <androidfw/ResourceTypes.h> -#include <utils/Log.h> +#include "Caches.h" +#include "DeviceInfo.h" +#include "Rect.h" +#include "RevealClip.h" +#include "Outline.h" +#include "utils/MathUtils.h" #include <SkCamera.h> #include <SkMatrix.h> #include <SkRegion.h> #include <SkXfermode.h> -#include "Caches.h" -#include "Rect.h" -#include "RevealClip.h" -#include "Outline.h" -#include "utils/MathUtils.h" +#include <algorithm> +#include <stddef.h> +#include <vector> +#include <cutils/compiler.h> +#include <androidfw/ResourceTypes.h> +#include <utils/Log.h> class SkBitmap; class SkColorFilter; @@ -203,8 +204,8 @@ public: return RP_SET(mPrimitiveFields.mProjectBackwards, shouldProject); } - bool setProjectionReceiver(bool shouldRecieve) { - return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldRecieve); + bool setProjectionReceiver(bool shouldReceive) { + return RP_SET(mPrimitiveFields.mProjectionReceiver, shouldReceive); } bool isProjectionReceiver() const { @@ -417,7 +418,7 @@ public: return false; } - float getLeft() const { + int getLeft() const { return mPrimitiveFields.mLeft; } @@ -432,7 +433,7 @@ public: return false; } - float getTop() const { + int getTop() const { return mPrimitiveFields.mTop; } @@ -447,7 +448,7 @@ public: return false; } - float getRight() const { + int getRight() const { return mPrimitiveFields.mRight; } @@ -462,7 +463,7 @@ public: return false; } - float getBottom() const { + int getBottom() const { return mPrimitiveFields.mBottom; } @@ -541,11 +542,15 @@ public: return mPrimitiveFields.mClippingFlags & CLIP_TO_BOUNDS; } + const Rect& getClipBounds() const { + return mPrimitiveFields.mClipBounds; + } + void getClippingRectForFlags(uint32_t flags, Rect* outRect) const { if (flags & CLIP_TO_BOUNDS) { outRect->set(0, 0, getWidth(), getHeight()); if (flags & CLIP_TO_CLIP_BOUNDS) { - outRect->intersect(mPrimitiveFields.mClipBounds); + outRect->doIntersect(mPrimitiveFields.mClipBounds); } } else { outRect->set(mPrimitiveFields.mClipBounds); @@ -603,11 +608,15 @@ public: && getOutline().getAlpha() != 0.0f; } + bool fitsOnLayer() const { + const DeviceInfo* deviceInfo = DeviceInfo::get(); + return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() + && mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize(); + } + bool promotedToLayer() const { - const int maxTextureSize = Caches::getInstance().maxTextureSize; return mLayerProperties.mType == LayerType::None - && mPrimitiveFields.mWidth <= maxTextureSize - && mPrimitiveFields.mHeight <= maxTextureSize + && fitsOnLayer() && (mComputedFields.mNeedLayerForFunctors || (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 @@ -621,25 +630,23 @@ public: private: // Rendering properties struct PrimitiveFields { - PrimitiveFields(); - + int mLeft = 0, mTop = 0, mRight = 0, mBottom = 0; + int mWidth = 0, mHeight = 0; + int mClippingFlags = CLIP_TO_BOUNDS; + float mAlpha = 1; + float mTranslationX = 0, mTranslationY = 0, mTranslationZ = 0; + float mElevation = 0; + float mRotation = 0, mRotationX = 0, mRotationY = 0; + float mScaleX = 1, mScaleY = 1; + float mPivotX = 0, mPivotY = 0; + bool mHasOverlappingRendering = false; + bool mPivotExplicitlySet = false; + bool mMatrixOrPivotDirty = false; + bool mProjectBackwards = false; + bool mProjectionReceiver = false; + Rect mClipBounds; Outline mOutline; RevealClip mRevealClip; - int mClippingFlags; - bool mProjectBackwards; - bool mProjectionReceiver; - float mAlpha; - bool mHasOverlappingRendering; - float mElevation; - float mTranslationX, mTranslationY, mTranslationZ; - float mRotation, mRotationX, mRotationY; - float mScaleX, mScaleY; - float mPivotX, mPivotY; - int mLeft, mTop, mRight, mBottom; - int mWidth, mHeight; - bool mPivotExplicitlySet; - bool mMatrixOrPivotDirty; - Rect mClipBounds; } mPrimitiveFields; SkMatrix* mStaticMatrix; diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp index 75d81346d62a..b26e433cfa4c 100644 --- a/libs/hwui/ResourceCache.cpp +++ b/libs/hwui/ResourceCache.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "ResourceCache.h" #include "Caches.h" diff --git a/libs/hwui/RevealClip.h b/libs/hwui/RevealClip.h index 0084a8edccfc..63821dddd369 100644 --- a/libs/hwui/RevealClip.h +++ b/libs/hwui/RevealClip.h @@ -51,7 +51,10 @@ public: outBounds->set(mX - mRadius, mY - mRadius, mX + mRadius, mY + mRadius); } + float getRadius() const { return mRadius; } + float getX() const { return mX; } + float getY() const { return mY; } const SkPath* getPath() const { if (!mShouldClip) return nullptr; diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp index 6c8665b50c53..e94a70a9f80d 100644 --- a/libs/hwui/ShadowTessellator.cpp +++ b/libs/hwui/ShadowTessellator.cpp @@ -14,13 +14,9 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <math.h> #include <utils/Log.h> #include <utils/Trace.h> -#include <utils/Vector.h> #include <utils/MathUtils.h> #include "AmbientShadow.h" diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 644a4f305a2e..ce67554645d1 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -14,18 +14,28 @@ * limitations under the License. */ -#include "Canvas.h" +#include "CanvasProperty.h" +#include "Layer.h" +#include "RenderNode.h" +#include "hwui/Canvas.h" #include <SkCanvas.h> #include <SkClipStack.h> +#include <SkDrawable.h> #include <SkDevice.h> #include <SkDeque.h> #include <SkDrawFilter.h> #include <SkGraphics.h> +#include <SkImage.h> #include <SkShader.h> #include <SkTArray.h> +#include <SkTLazy.h> #include <SkTemplates.h> +#include "VectorDrawable.h" + +#include <memory> + namespace android { // Holds an SkCanvas reference plus additional native data. @@ -49,25 +59,41 @@ public: return mCanvas.get(); } + virtual void resetRecording(int width, int height) override { + LOG_ALWAYS_FATAL("SkiaCanvas cannot be reset as a recording canvas"); + } + + virtual uirenderer::DisplayList* finishRecording() override { + LOG_ALWAYS_FATAL("SkiaCanvas does not produce a DisplayList"); + return nullptr; + } + virtual void insertReorderBarrier(bool enableReorder) override { + LOG_ALWAYS_FATAL("SkiaCanvas does not support reordering barriers"); + } + virtual void setBitmap(const SkBitmap& bitmap) override; virtual bool isOpaque() override; virtual int width() override; virtual int height() override; + virtual void setHighContrastText(bool highContrastText) override { + mHighContrastText = highContrastText; + } + virtual bool isHighContrastText() override { return mHighContrastText; } + virtual int getSaveCount() const override; - virtual int save(SkCanvas::SaveFlags flags) override; + virtual int save(SaveFlags::Flags flags) override; virtual void restore() override; virtual void restoreToCount(int saveCount) override; virtual int saveLayer(float left, float top, float right, float bottom, - const SkPaint* paint, SkCanvas::SaveFlags flags) override; + const SkPaint* paint, SaveFlags::Flags flags) override; virtual int saveLayerAlpha(float left, float top, float right, float bottom, - int alpha, SkCanvas::SaveFlags flags) override; + int alpha, SaveFlags::Flags flags) override; virtual void getMatrix(SkMatrix* outMatrix) const override; virtual void setMatrix(const SkMatrix& matrix) override; - virtual void setLocalMatrix(const SkMatrix& matrix) override { this->setMatrix(matrix); } virtual void concat(const SkMatrix& matrix) override; virtual void rotate(float degrees) override; virtual void scale(float sx, float sy) override; @@ -95,6 +121,7 @@ public: virtual void drawLines(const float* points, int count, const SkPaint& paint) override; virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override; + virtual void drawRegion(const SkRegion& region, const SkPaint& paint) override; virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) override; virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) override; @@ -116,34 +143,51 @@ public: float dstRight, float dstBottom, const SkPaint* paint) override; virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, const float* vertices, const int* colors, const SkPaint* paint) override; + virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk, + float dstLeft, float dstTop, float dstRight, float dstBottom, + const SkPaint* paint) override; - virtual void drawText(const uint16_t* text, const float* positions, int count, + virtual bool drawTextAbsolutePos() const override { return true; } + virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override; + + virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left, + uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right, + uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx, + uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) override; + virtual void drawCircle(uirenderer::CanvasPropertyPrimitive* x, + uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius, + uirenderer::CanvasPropertyPaint* paint) override; + + virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) override; + virtual void drawRenderNode(uirenderer::RenderNode* renderNode) override; + virtual void callDrawGLFunction(Functor* functor, + uirenderer::GlFunctorLifecycleListener* listener) override; + +protected: + virtual void drawGlyphs(const uint16_t* text, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) override; - virtual void drawPosText(const uint16_t* text, const float* positions, int count, - int posCount, const SkPaint& paint) override; - virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, + virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) override; - virtual bool drawTextAbsolutePos() const override { return true; } - private: struct SaveRec { - int saveCount; - SkCanvas::SaveFlags saveFlags; + int saveCount; + SaveFlags::Flags saveFlags; }; - void recordPartialSave(SkCanvas::SaveFlags flags); + bool mHighContrastText = false; + + void recordPartialSave(SaveFlags::Flags flags); void saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount); void applyClips(const SkTArray<SkClipStack::Element>& clips); void drawPoints(const float* points, int count, const SkPaint& paint, SkCanvas::PointMode mode); - void drawTextDecorations(float x, float y, float length, const SkPaint& paint); SkAutoTUnref<SkCanvas> mCanvas; - SkAutoTDelete<SkDeque> mSaveStack; // lazily allocated, tracks partial saves. + std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves. }; Canvas* Canvas::create_canvas(const SkBitmap& bitmap) { @@ -182,15 +226,13 @@ private: void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { SkCanvas* newCanvas = new SkCanvas(bitmap); - SkASSERT(newCanvas); if (!bitmap.isNull()) { // Copy the canvas matrix & clip state. newCanvas->setMatrix(mCanvas->getTotalMatrix()); - if (NULL != mCanvas->getDevice() && NULL != newCanvas->getDevice()) { - ClipCopier copier(newCanvas); - mCanvas->replayClips(&copier); - } + + ClipCopier copier(newCanvas); + mCanvas->replayClips(&copier); } // unrefs the existing canvas @@ -205,15 +247,15 @@ void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { // ---------------------------------------------------------------------------- bool SkiaCanvas::isOpaque() { - return mCanvas->getDevice()->accessBitmap(false).isOpaque(); + return mCanvas->imageInfo().isOpaque(); } int SkiaCanvas::width() { - return mCanvas->getBaseLayerSize().width(); + return mCanvas->imageInfo().width(); } int SkiaCanvas::height() { - return mCanvas->getBaseLayerSize().height(); + return mCanvas->imageInfo().height(); } // ---------------------------------------------------------------------------- @@ -224,17 +266,21 @@ int SkiaCanvas::getSaveCount() const { return mCanvas->getSaveCount(); } -int SkiaCanvas::save(SkCanvas::SaveFlags flags) { +int SkiaCanvas::save(SaveFlags::Flags flags) { int count = mCanvas->save(); recordPartialSave(flags); return count; } +// The SkiaCanvas::restore operation layers on the capability to preserve +// either (or both) the matrix and/or clip state after a SkCanvas::restore +// operation. It does this by explicitly saving off the clip & matrix state +// when requested and playing it back after the SkCanvas::restore. void SkiaCanvas::restore() { const SaveRec* rec = (NULL == mSaveStack.get()) ? NULL : static_cast<SaveRec*>(mSaveStack->back()); - int currentSaveCount = mCanvas->getSaveCount() - 1; + int currentSaveCount = mCanvas->getSaveCount(); SkASSERT(NULL == rec || currentSaveCount >= rec->saveCount); if (NULL == rec || rec->saveCount != currentSaveCount) { @@ -243,8 +289,8 @@ void SkiaCanvas::restore() { return; } - bool preserveMatrix = !(rec->saveFlags & SkCanvas::kMatrix_SaveFlag); - bool preserveClip = !(rec->saveFlags & SkCanvas::kClip_SaveFlag); + bool preserveMatrix = !(rec->saveFlags & SaveFlags::Matrix); + bool preserveClip = !(rec->saveFlags & SaveFlags::Clip); SkMatrix savedMatrix; if (preserveMatrix) { @@ -252,8 +298,9 @@ void SkiaCanvas::restore() { } SkTArray<SkClipStack::Element> savedClips; + int topClipStackFrame = mCanvas->getClipStack()->getSaveCount(); if (preserveClip) { - saveClipsForFrame(savedClips, currentSaveCount); + saveClipsForFrame(savedClips, topClipStackFrame); } mCanvas->restore(); @@ -262,7 +309,11 @@ void SkiaCanvas::restore() { mCanvas->setMatrix(savedMatrix); } - if (preserveClip && !savedClips.empty()) { + if (preserveClip && !savedClips.empty() && + topClipStackFrame != mCanvas->getClipStack()->getSaveCount()) { + // Only reapply the saved clips if the top clip stack frame was actually + // popped by restore(). If it wasn't, it means it doesn't belong to the + // restored canvas frame (SkCanvas lazy save/restore kicked in). applyClips(savedClips); } @@ -275,58 +326,79 @@ void SkiaCanvas::restoreToCount(int restoreCount) { } } +static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) { + SkCanvas::SaveLayerFlags layerFlags = 0; + + if (!(flags & SaveFlags::HasAlphaLayer)) { + layerFlags |= SkCanvas::kIsOpaque_SaveLayerFlag; + } + + if (!(flags & SaveFlags::ClipToLayer)) { + layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag; + } + + return layerFlags; +} + int SkiaCanvas::saveLayer(float left, float top, float right, float bottom, - const SkPaint* paint, SkCanvas::SaveFlags flags) { - SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom); - int count = mCanvas->saveLayer(&bounds, paint, flags | SkCanvas::kMatrixClip_SaveFlag); + const SkPaint* paint, SaveFlags::Flags flags) { + const SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom); + const SkCanvas::SaveLayerRec rec(&bounds, paint, layerFlags(flags)); + + int count = mCanvas->saveLayer(rec); recordPartialSave(flags); return count; } int SkiaCanvas::saveLayerAlpha(float left, float top, float right, float bottom, - int alpha, SkCanvas::SaveFlags flags) { - SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom); - int count = mCanvas->saveLayerAlpha(&bounds, alpha, flags | SkCanvas::kMatrixClip_SaveFlag); - recordPartialSave(flags); - return count; + int alpha, SaveFlags::Flags flags) { + SkTLazy<SkPaint> alphaPaint; + if (static_cast<unsigned>(alpha) < 0xFF) { + alphaPaint.init()->setAlpha(alpha); + } + + return this->saveLayer(left, top, right, bottom, alphaPaint.getMaybeNull(), + flags); } // ---------------------------------------------------------------------------- // functions to emulate legacy SaveFlags (i.e. independent matrix/clip flags) // ---------------------------------------------------------------------------- -void SkiaCanvas::recordPartialSave(SkCanvas::SaveFlags flags) { +void SkiaCanvas::recordPartialSave(SaveFlags::Flags flags) { // A partial save is a save operation which doesn't capture the full canvas state. - // (either kMatrix_SaveFlags or kClip_SaveFlag is missing). + // (either SaveFlags::Matrix or SaveFlags::Clip is missing). // Mask-out non canvas state bits. - flags = static_cast<SkCanvas::SaveFlags>(flags & SkCanvas::kMatrixClip_SaveFlag); + flags &= SaveFlags::MatrixClip; - if (SkCanvas::kMatrixClip_SaveFlag == flags) { + if (flags == SaveFlags::MatrixClip) { // not a partial save. return; } if (NULL == mSaveStack.get()) { - mSaveStack.reset(SkNEW_ARGS(SkDeque, (sizeof(struct SaveRec), 8))); + mSaveStack.reset(new SkDeque(sizeof(struct SaveRec), 8)); } SaveRec* rec = static_cast<SaveRec*>(mSaveStack->push_back()); - // Store the save counter in the SkClipStack domain. - // (0-based, equal to the number of save ops on the stack). - rec->saveCount = mCanvas->getSaveCount() - 1; + rec->saveCount = mCanvas->getSaveCount(); rec->saveFlags = flags; } -void SkiaCanvas::saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, int frameSaveCount) { +void SkiaCanvas::saveClipsForFrame(SkTArray<SkClipStack::Element>& clips, + int saveCountToBackup) { + // Each SkClipStack::Element stores the index of the canvas save + // with which it is associated. Backup only those Elements that + // are associated with 'saveCountToBackup' SkClipStack::Iter clipIterator(*mCanvas->getClipStack(), SkClipStack::Iter::kTop_IterStart); - while (const SkClipStack::Element* elem = clipIterator.next()) { - if (elem->getSaveCount() < frameSaveCount) { - // done with the current frame. + while (const SkClipStack::Element* elem = clipIterator.prev()) { + if (elem->getSaveCount() < saveCountToBackup) { + // done with the target save count. break; } - SkASSERT(elem->getSaveCount() == frameSaveCount); + SkASSERT(elem->getSaveCount() == saveCountToBackup); clips.push_back(*elem); } } @@ -474,13 +546,12 @@ void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint SkCanvas::PointMode mode) { // convert the floats into SkPoints count >>= 1; // now it is the number of points - SkAutoSTMalloc<32, SkPoint> storage(count); - SkPoint* pts = storage.get(); + std::unique_ptr<SkPoint[]> pts(new SkPoint[count]); for (int i = 0; i < count; i++) { pts[i].set(points[0], points[1]); points += 2; } - mCanvas->drawPoints(mode, count, pts, paint); + mCanvas->drawPoints(mode, count, pts.get(), paint); } @@ -507,6 +578,14 @@ void SkiaCanvas::drawRect(float left, float top, float right, float bottom, } +void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { + SkRegion::Iterator it(region); + while (!it.done()) { + mCanvas->drawRect(SkRect::Make(it.rect()), paint); + it.next(); + } +} + void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) { SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); @@ -562,7 +641,7 @@ void SkiaCanvas::drawBitmap(const SkBitmap& bitmap, float srcLeft, float srcTop, float dstRight, float dstBottom, const SkPaint* paint) { SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - mCanvas->drawBitmapRectToRect(bitmap, &srcRect, dstRect, paint); + mCanvas->drawBitmapRect(bitmap, srcRect, dstRect, paint); } void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, @@ -584,7 +663,7 @@ void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshH #ifndef SK_SCALAR_IS_FLOAT SkDEBUGFAIL("SkScalar must be a float for these conversions to be valid"); #endif - SkAutoMalloc storage(storageSize); + std::unique_ptr<char[]> storage(new char[storageSize]); SkPoint* texs = (SkPoint*)storage.get(); uint16_t* indices = (uint16_t*)(texs + ptCount); @@ -662,43 +741,113 @@ void SkiaCanvas::drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshH indexCount, tmpPaint); } +void SkiaCanvas::drawNinePatch(const SkBitmap& bitmap, const Res_png_9patch& chunk, + float dstLeft, float dstTop, float dstRight, float dstBottom, const SkPaint* paint) { + SkRect bounds = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); + NinePatch::Draw(mCanvas, bounds, bitmap, chunk, paint, nullptr); +} + +void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) { + vectorDrawable->drawStaging(this); +} + // ---------------------------------------------------------------------------- // Canvas draw operations: Text // ---------------------------------------------------------------------------- -void SkiaCanvas::drawText(const uint16_t* text, const float* positions, int count, +void SkiaCanvas::drawGlyphs(const uint16_t* text, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) { - // Set align to left for drawing, as we don't want individual - // glyphs centered or right-aligned; the offset above takes - // care of all alignment. - SkPaint paintCopy(paint); - paintCopy.setTextAlign(SkPaint::kLeft_Align); + static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats"); + mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paint); + drawTextDecorations(x, y, totalAdvance, paint); +} - SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); - mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy); +void SkiaCanvas::drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path, + float hOffset, float vOffset, const SkPaint& paint) { + mCanvas->drawTextOnPathHV(glyphs, count << 1, path, hOffset, vOffset, paint); } -void SkiaCanvas::drawPosText(const uint16_t* text, const float* positions, int count, int posCount, - const SkPaint& paint) { - SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL; - int indx; - for (indx = 0; indx < posCount; indx++) { - posPtr[indx].fX = positions[indx << 1]; - posPtr[indx].fY = positions[(indx << 1) + 1]; - } +// ---------------------------------------------------------------------------- +// Canvas draw operations: Animations +// ---------------------------------------------------------------------------- - SkPaint paintCopy(paint); - paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding); - mCanvas->drawPosText(text, count, posPtr, paintCopy); +class AnimatedRoundRect : public SkDrawable { + public: + AnimatedRoundRect(uirenderer::CanvasPropertyPrimitive* left, + uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right, + uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx, + uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* p) : + mLeft(left), mTop(top), mRight(right), mBottom(bottom), mRx(rx), mRy(ry), mPaint(p) {} + + protected: + virtual SkRect onGetBounds() override { + return SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value); + } + virtual void onDraw(SkCanvas* canvas) override { + SkRect rect = SkRect::MakeLTRB(mLeft->value, mTop->value, mRight->value, mBottom->value); + canvas->drawRoundRect(rect, mRx->value, mRy->value, mPaint->value); + } + + private: + sp<uirenderer::CanvasPropertyPrimitive> mLeft; + sp<uirenderer::CanvasPropertyPrimitive> mTop; + sp<uirenderer::CanvasPropertyPrimitive> mRight; + sp<uirenderer::CanvasPropertyPrimitive> mBottom; + sp<uirenderer::CanvasPropertyPrimitive> mRx; + sp<uirenderer::CanvasPropertyPrimitive> mRy; + sp<uirenderer::CanvasPropertyPaint> mPaint; +}; - delete[] posPtr; +class AnimatedCircle : public SkDrawable { + public: + AnimatedCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y, + uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) : + mX(x), mY(y), mRadius(radius), mPaint(paint) {} + + protected: + virtual SkRect onGetBounds() override { + const float x = mX->value; + const float y = mY->value; + const float radius = mRadius->value; + return SkRect::MakeLTRB(x - radius, y - radius, x + radius, y + radius); + } + virtual void onDraw(SkCanvas* canvas) override { + canvas->drawCircle(mX->value, mY->value, mRadius->value, mPaint->value); + } + + private: + sp<uirenderer::CanvasPropertyPrimitive> mX; + sp<uirenderer::CanvasPropertyPrimitive> mY; + sp<uirenderer::CanvasPropertyPrimitive> mRadius; + sp<uirenderer::CanvasPropertyPaint> mPaint; +}; + +void SkiaCanvas::drawRoundRect(uirenderer::CanvasPropertyPrimitive* left, + uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right, + uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx, + uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) { + SkAutoTUnref<AnimatedRoundRect> drawable( + new AnimatedRoundRect(left, top, right, bottom, rx, ry, paint)); + mCanvas->drawDrawable(drawable.get()); } -void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, - float hOffset, float vOffset, const SkPaint& paint) { - mCanvas->drawTextOnPathHV(glyphs, count << 1, path, hOffset, vOffset, paint); +void SkiaCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x, uirenderer::CanvasPropertyPrimitive* y, + uirenderer::CanvasPropertyPrimitive* radius, uirenderer::CanvasPropertyPaint* paint) { + SkAutoTUnref<AnimatedCircle> drawable(new AnimatedCircle(x, y, radius, paint)); + mCanvas->drawDrawable(drawable.get()); } +// ---------------------------------------------------------------------------- +// Canvas draw operations: View System +// ---------------------------------------------------------------------------- + +void SkiaCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layer) { } + +void SkiaCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { } + +void SkiaCanvas::callDrawGLFunction(Functor* functor, + uirenderer::GlFunctorLifecycleListener* listener) { } + } // namespace android diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp index d96ca2afed00..9df32b28bf3b 100644 --- a/libs/hwui/SkiaCanvasProxy.cpp +++ b/libs/hwui/SkiaCanvasProxy.cpp @@ -18,6 +18,13 @@ #include <cutils/log.h> #include <SkPatchUtils.h> +#include <SkPaint.h> +#include <SkPath.h> +#include <SkPixelRef.h> +#include <SkRect.h> +#include <SkRRect.h> + +#include <memory> namespace android { namespace uirenderer { @@ -38,7 +45,7 @@ void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPo } // convert the SkPoints into floats - SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats"); const size_t floatCount = count << 1; const float* floatArray = &pts[0].fX; @@ -96,12 +103,31 @@ void SkiaCanvasProxy::onDrawPath(const SkPath& path, const SkPaint& paint) { void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, const SkPaint* paint) { - mCanvas->drawBitmap(bitmap, left, top, paint); + SkPixelRef* pxRef = bitmap.pixelRef(); + + // HWUI doesn't support extractSubset(), so convert any subsetted bitmap into + // a drawBitmapRect(); pass through an un-subsetted bitmap. + if (pxRef && bitmap.dimensions() != pxRef->info().dimensions()) { + SkBitmap fullBitmap; + fullBitmap.setInfo(pxRef->info()); + fullBitmap.setPixelRef(pxRef, 0, 0); + SkIPoint origin = bitmap.pixelRefOrigin(); + mCanvas->drawBitmap(fullBitmap, origin.fX, origin.fY, + origin.fX + bitmap.dimensions().width(), + origin.fY + bitmap.dimensions().height(), + left, top, + left + bitmap.dimensions().width(), + top + bitmap.dimensions().height(), + paint); + } else { + mCanvas->drawBitmap(bitmap, left, top, paint); + } } void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& bitmap, const SkRect* srcPtr, - const SkRect& dst, const SkPaint* paint, DrawBitmapRectFlags) { + const SkRect& dst, const SkPaint* paint, SrcRectConstraint) { SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(bitmap.width(), bitmap.height()); + // TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src? mCanvas->drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft, dst.fTop, dst.fRight, dst.fBottom, paint); } @@ -112,14 +138,6 @@ void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& ce SkDEBUGFAIL("SkiaCanvasProxy::onDrawBitmapNine is not yet supported"); } -void SkiaCanvasProxy::onDrawSprite(const SkBitmap& bitmap, int left, int top, - const SkPaint* paint) { - mCanvas->save(SkCanvas::kMatrixClip_SaveFlag); - mCanvas->setLocalMatrix(SkMatrix::I()); - mCanvas->drawBitmap(bitmap, left, top, paint); - mCanvas->restore(); -} - void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkPoint vertices[], const SkPoint texs[], const SkColor colors[], SkXfermode*, const uint16_t indices[], int indexCount, const SkPaint& paint) { @@ -127,7 +145,7 @@ void SkiaCanvasProxy::onDrawVertices(VertexMode mode, int vertexCount, const SkP return; } // convert the SkPoints into floats - SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); + static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats"); const int floatCount = vertexCount << 1; const float* vArray = &vertices[0].fX; const float* tArray = (texs) ? &texs[0].fX : NULL; @@ -141,18 +159,32 @@ SkSurface* SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProp } void SkiaCanvasProxy::willSave() { - mCanvas->save(SkCanvas::kMatrixClip_SaveFlag); + mCanvas->save(android::SaveFlags::MatrixClip); } -SkCanvas::SaveLayerStrategy SkiaCanvasProxy::willSaveLayer(const SkRect* rectPtr, - const SkPaint* paint, SaveFlags flags) { +static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) { + SaveFlags::Flags saveFlags = 0; + + if (!(layerFlags & SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag)) { + saveFlags |= SaveFlags::ClipToLayer; + } + + if (!(layerFlags & SkCanvas::kIsOpaque_SaveLayerFlag)) { + saveFlags |= SaveFlags::HasAlphaLayer; + } + + return saveFlags; +} + +SkCanvas::SaveLayerStrategy SkiaCanvasProxy::getSaveLayerStrategy(const SaveLayerRec& saveLayerRec) { SkRect rect; - if (rectPtr) { - rect = *rectPtr; - } else if(!mCanvas->getClipBounds(&rect)) { + if (saveLayerRec.fBounds) { + rect = *saveLayerRec.fBounds; + } else if (!mCanvas->getClipBounds(&rect)) { rect = SkRect::MakeEmpty(); } - mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint, flags); + mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, saveLayerRec.fPaint, + saveFlags(saveLayerRec.fSaveLayerFlags)); return SkCanvas::kNoLayer_SaveLayerStrategy; } @@ -165,9 +197,7 @@ void SkiaCanvasProxy::didConcat(const SkMatrix& matrix) { } void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) { - // SkCanvas setMatrix() is relative to the Canvas origin, but OpenGLRenderer's - // setMatrix() is relative to device origin; call setLocalMatrix() instead. - mCanvas->setLocalMatrix(matrix); + mCanvas->setMatrix(matrix); } void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, @@ -191,7 +221,8 @@ public: glyphIDs = (uint16_t*)text; count = byteLength >> 1; } else { - storage.reset(byteLength); // ensures space for one glyph per ID given UTF8 encoding. + // ensure space for one glyph per ID given UTF8 encoding. + storage.reset(new uint16_t[byteLength]); glyphIDs = storage.get(); count = paint.textToGlyphs(text, byteLength, storage.get()); paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); @@ -202,7 +233,7 @@ public: uint16_t* glyphIDs; int count; private: - SkAutoSTMalloc<32, uint16_t> storage; + std::unique_ptr<uint16_t[]> storage; }; void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, @@ -211,8 +242,8 @@ void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x GlyphIDConverter glyphs(text, byteLength, origPaint); // compute the glyph positions - SkAutoSTMalloc<32, SkPoint> pointStorage(glyphs.count); - SkAutoSTMalloc<32, SkScalar> glyphWidths(glyphs.count); + std::unique_ptr<SkPoint[]> pointStorage(new SkPoint[glyphs.count]); + std::unique_ptr<SkScalar[]> glyphWidths(new SkScalar[glyphs.count]); glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get()); // compute conservative bounds @@ -258,8 +289,8 @@ void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x } } - SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); - mCanvas->drawText(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint, + static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats"); + mCanvas->drawGlyphs(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0); } @@ -271,7 +302,7 @@ void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const S // convert to relative positions if necessary int x, y; const SkPoint* posArray; - SkAutoSTMalloc<32, SkPoint> pointStorage; + std::unique_ptr<SkPoint[]> pointStorage; if (mCanvas->drawTextAbsolutePos()) { x = 0; y = 0; @@ -279,41 +310,46 @@ void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const S } else { x = pos[0].fX; y = pos[0].fY; - posArray = pointStorage.reset(glyphs.count); + pointStorage.reset(new SkPoint[glyphs.count]); for (int i = 0; i < glyphs.count; i++) { - pointStorage[i].fX = pos[i].fX- x; - pointStorage[i].fY = pos[i].fY- y; + pointStorage[i].fX = pos[i].fX - x; + pointStorage[i].fY = pos[i].fY - y; } + posArray = pointStorage.get(); } - // compute conservative bounds - // NOTE: We could call the faster paint.getFontBounds for a less accurate, - // but even more conservative bounds if this is too slow. + // Compute conservative bounds. If the content has already been processed + // by Minikin then it had already computed these bounds. Unfortunately, + // there is no way to capture those bounds as part of the Skia drawPosText + // API so we need to do that computation again here. SkRect bounds; - glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds); - bounds.offset(x, y); + for (int i = 0; i < glyphs.count; i++) { + SkRect glyphBounds; + glyphs.paint.measureText(&glyphs.glyphIDs[i], sizeof(uint16_t), &glyphBounds); + glyphBounds.offset(pos[i].fX, pos[i].fY); + bounds.join(glyphBounds); + } - SK_COMPILE_ASSERT(sizeof(SkPoint) == sizeof(float)*2, SkPoint_is_no_longer_2_floats); - mCanvas->drawText(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y, + static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats"); + mCanvas->drawGlyphs(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0); } void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY, const SkPaint& paint) { const size_t pointCount = byteLength >> 1; - SkAutoSTMalloc<32, SkPoint> storage(pointCount); - SkPoint* pts = storage.get(); + std::unique_ptr<SkPoint[]> pts(new SkPoint[pointCount]); for (size_t i = 0; i < pointCount; i++) { pts[i].set(xpos[i], constY); } - this->onDrawPosText(text, byteLength, pts, paint); + this->onDrawPosText(text, byteLength, pts.get(), paint); } void SkiaCanvasProxy::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, const SkMatrix* matrix, const SkPaint& origPaint) { // convert to glyphIDs if necessary GlyphIDConverter glyphs(text, byteLength, origPaint); - mCanvas->drawTextOnPath(glyphs.glyphIDs, glyphs.count, path, 0, 0, glyphs.paint); + mCanvas->drawGlyphsOnPath(glyphs.glyphIDs, glyphs.count, path, 0, 0, glyphs.paint); } void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h index 0de965094e64..973c55fe2236 100644 --- a/libs/hwui/SkiaCanvasProxy.h +++ b/libs/hwui/SkiaCanvasProxy.h @@ -20,7 +20,7 @@ #include <cutils/compiler.h> #include <SkCanvas.h> -#include "Canvas.h" +#include "hwui/Canvas.h" namespace android { namespace uirenderer { @@ -47,7 +47,7 @@ protected: virtual SkSurface* onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; virtual void willSave() override; - virtual SaveLayerStrategy willSaveLayer(const SkRect*, const SkPaint*, SaveFlags) override; + virtual SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; virtual void willRestore() override; virtual void didConcat(const SkMatrix&) override; @@ -63,11 +63,9 @@ protected: virtual void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, const SkPaint*) override; virtual void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, - const SkPaint* paint, DrawBitmapRectFlags flags) override; + const SkPaint* paint, SrcRectConstraint) override; virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, const SkPaint*) override; - virtual void onDrawSprite(const SkBitmap&, int left, int top, - const SkPaint*) override; virtual void onDrawVertices(VertexMode, int vertexCount, const SkPoint vertices[], const SkPoint texs[], const SkColor colors[], SkXfermode*, const uint16_t indices[], int indexCount, diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 81d8516168dd..6f4a6839be4e 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "SkiaShader.h" #include "Caches.h" @@ -34,12 +32,19 @@ namespace uirenderer { // Support /////////////////////////////////////////////////////////////////////////////// -static const GLenum gTileModes[] = { +static constexpr GLenum gTileModes[] = { GL_CLAMP_TO_EDGE, // == SkShader::kClamp_TileMode GL_REPEAT, // == SkShader::kRepeat_Mode GL_MIRRORED_REPEAT // == SkShader::kMirror_TileMode }; +static_assert(gTileModes[SkShader::kClamp_TileMode] == GL_CLAMP_TO_EDGE, + "SkShader TileModes have changed"); +static_assert(gTileModes[SkShader::kRepeat_TileMode] == GL_REPEAT, + "SkShader TileModes have changed"); +static_assert(gTileModes[SkShader::kMirror_TileMode] == GL_MIRRORED_REPEAT, + "SkShader TileModes have changed"); + /** * This function does not work for n == 0. */ @@ -52,7 +57,7 @@ static inline void bindUniformColor(int slot, FloatColor color) { } static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) { - caches->textureState().bindTexture(texture->id); + caches->textureState().bindTexture(texture->id()); texture->setWrapST(wrapS, wrapT); } @@ -199,7 +204,7 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model SkiaShaderData::BitmapShaderData* outData) { SkBitmap bitmap; SkShader::TileMode xy[2]; - if (shader.asABitmap(&bitmap, nullptr, xy) != SkShader::kDefault_BitmapType) { + if (!shader.isABitmap(&bitmap, nullptr, xy)) { return false; } @@ -214,8 +219,8 @@ bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& model outData->bitmapSampler = (*textureUnit)++; - const float width = outData->bitmapTexture->width; - const float height = outData->bitmapTexture->height; + const float width = outData->bitmapTexture->width(); + const float height = outData->bitmapTexture->height(); description->hasBitmap = true; if (!caches.extensions().hasNPot() @@ -267,7 +272,7 @@ SkiaShaderType getComposeSubType(const SkShader& shader) { } // The shader is not a gradient. Check for a bitmap shader. - if (shader.asABitmap(nullptr, nullptr, nullptr) == SkShader::kDefault_BitmapType) { + if (shader.isABitmap()) { return kBitmap_SkiaShaderType; } return kNone_SkiaShaderType; diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index beb2e1d0481c..2c9c9d90f686 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -14,11 +14,9 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "Snapshot.h" -#include <SkCanvas.h> +#include "hwui/Canvas.h" namespace android { namespace uirenderer { @@ -46,7 +44,7 @@ Snapshot::Snapshot() * Copies the specified snapshot/ The specified snapshot is stored as * the previous snapshot. */ -Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) +Snapshot::Snapshot(Snapshot* s, int saveFlags) : flags(0) , previous(s) , layer(s->layer) @@ -59,14 +57,14 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) , mClipArea(nullptr) , mViewportData(s->mViewportData) , mRelativeLightCenter(s->mRelativeLightCenter) { - if (saveFlags & SkCanvas::kMatrix_SaveFlag) { - mTransformRoot.load(*s->transform); + if (saveFlags & SaveFlags::Matrix) { + mTransformRoot = *s->transform; transform = &mTransformRoot; } else { transform = s->transform; } - if (saveFlags & SkCanvas::kClip_SaveFlag) { + if (saveFlags & SaveFlags::Clip) { mClipAreaRoot = s->getClipArea(); mClipArea = &mClipAreaRoot; } else { @@ -85,24 +83,24 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) // Clipping /////////////////////////////////////////////////////////////////////////////// -bool Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) { +void Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) { flags |= Snapshot::kFlagClipSet; - return mClipArea->clipRegion(region, op); + mClipArea->clipRegion(region, op); } -bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) { +void Snapshot::clip(const Rect& localClip, SkRegion::Op op) { flags |= Snapshot::kFlagClipSet; - return mClipArea->clipRectWithTransform(left, top, right, bottom, transform, op); + mClipArea->clipRectWithTransform(localClip, transform, op); } -bool Snapshot::clipPath(const SkPath& path, SkRegion::Op op) { +void Snapshot::clipPath(const SkPath& path, SkRegion::Op op) { flags |= Snapshot::kFlagClipSet; - return mClipArea->clipPathWithTransform(path, transform, op); + mClipArea->clipPathWithTransform(path, transform, op); } void Snapshot::setClip(float left, float top, float right, float bottom) { - mClipArea->setClip(left, top, right, bottom); flags |= Snapshot::kFlagClipSet; + mClipArea->setClip(left, top, right, bottom); } bool Snapshot::hasPerspectiveTransform() const { @@ -132,6 +130,9 @@ void Snapshot::resetClip(float left, float top, float right, float bottom) { /////////////////////////////////////////////////////////////////////////////// void Snapshot::resetTransform(float x, float y, float z) { +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("not supported - light center managed differently"); +#else // before resetting, map current light pos with inverse of current transform Vector3 center = mRelativeLightCenter; mat4 inverse; @@ -141,16 +142,20 @@ void Snapshot::resetTransform(float x, float y, float z) { transform = &mTransformRoot; transform->loadTranslate(x, y, z); +#endif } void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const { +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("not supported - not needed by new ops"); +#else // build (reverse ordered) list of the stack of snapshots, terminated with a NULL Vector<const Snapshot*> snapshotList; snapshotList.push(nullptr); const Snapshot* current = this; do { snapshotList.push(current); - current = current->previous.get(); + current = current->previous; } while (current); // traverse the list, adding in each transform that contributes to the total transform @@ -169,6 +174,7 @@ void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const { outTransform->multiply(*(current->transform)); } } +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -192,8 +198,7 @@ void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& boun state->highPriority = highPriority; // store the inverse drawing matrix - Matrix4 roundRectDrawingMatrix; - roundRectDrawingMatrix.load(getOrthoMatrix()); + Matrix4 roundRectDrawingMatrix = getOrthoMatrix(); roundRectDrawingMatrix.multiply(*transform); state->matrix.loadInverse(roundRectDrawingMatrix); @@ -222,15 +227,46 @@ void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& boun } void Snapshot::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) { +#if HWUI_NEW_OPS + // TODO: remove allocator param for HWUI_NEW_OPS + projectionPathMask = path; +#else if (path) { ProjectionPathMask* mask = new (allocator) ProjectionPathMask; mask->projectionMask = path; buildScreenSpaceTransform(&(mask->projectionMaskTransform)); - projectionPathMask = mask; } else { projectionPathMask = nullptr; } +#endif +} + +static Snapshot* getClipRoot(Snapshot* target) { + while (target->previous && target->previous->previous) { + target = target->previous; + } + return target; +} + +const ClipBase* Snapshot::serializeIntersectedClip(LinearAllocator& allocator, + const ClipBase* recordedClip, const Matrix4& recordedClipTransform) { + auto target = this; + if (CC_UNLIKELY(recordedClip && recordedClip->intersectWithRoot)) { + // Clip must be intersected with root, instead of current clip. + target = getClipRoot(this); + } + + return target->mClipArea->serializeIntersectedClip(allocator, + recordedClip, recordedClipTransform); +} + +void Snapshot::applyClip(const ClipBase* recordedClip, const Matrix4& transform) { + if (CC_UNLIKELY(recordedClip && recordedClip->intersectWithRoot)) { + // current clip is being replaced, but must intersect with clip root + *mClipArea = *(getClipRoot(this)->mClipArea); + } + mClipArea->applyClip(recordedClip, transform); } /////////////////////////////////////////////////////////////////////////////// @@ -243,7 +279,7 @@ bool Snapshot::isIgnored() const { void Snapshot::dump() const { ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d", - this, flags, previous.get(), getViewportHeight(), isIgnored(), !mClipArea->isSimple()); + this, flags, previous, getViewportHeight(), isIgnored(), !mClipArea->isSimple()); const Rect& clipRect(mClipArea->getClipRect()); ALOGD(" ClipRect %.1f %.1f %.1f %.1f, clip simple %d", clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, mClipArea->isSimple()); diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index af6ad727da85..d8f926ef3925 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -44,9 +44,9 @@ namespace uirenderer { */ class RoundRectClipState { public: - /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/ + static void* operator new(size_t size) = delete; static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); + return allocator.alloc<RoundRectClipState>(size); } bool areaRequiresRoundRectClip(const Rect& rect) const { @@ -63,11 +63,12 @@ public: float radius; }; +// TODO: remove for HWUI_NEW_OPS class ProjectionPathMask { public: - /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/ + static void* operator new(size_t size) = delete; static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc(size); + return allocator.alloc<ProjectionPathMask>(size); } const SkPath* projectionMask; @@ -83,11 +84,11 @@ public: * Each snapshot has a link to a previous snapshot, indicating the previous * state of the renderer. */ -class Snapshot: public LightRefBase<Snapshot> { +class Snapshot { public: Snapshot(); - Snapshot(const sp<Snapshot>& s, int saveFlags); + Snapshot(Snapshot* s, int saveFlags); /** * Various flags set on ::flags. @@ -116,7 +117,7 @@ public: * Indicates that this snapshot or an ancestor snapshot is * an FBO layer. */ - kFlagFboTarget = 0x8, + kFlagFboTarget = 0x8, // TODO: remove for HWUI_NEW_OPS }; /** @@ -124,26 +125,25 @@ public: * the specified operation. The specified rectangle is transformed * by this snapshot's trasnformation. */ - bool clip(float left, float top, float right, float bottom, - SkRegion::Op op = SkRegion::kIntersect_Op); + void clip(const Rect& localClip, SkRegion::Op op); /** * Modifies the current clip with the new clip rectangle and * the specified operation. The specified rectangle is considered * already transformed. */ - bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op); + void clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op); /** * Modifies the current clip with the specified region and operation. * The specified region is considered already transformed. */ - bool clipRegionTransformed(const SkRegion& region, SkRegion::Op op); + void clipRegionTransformed(const SkRegion& region, SkRegion::Op op); /** * Modifies the current clip with the specified path and operation. */ - bool clipPath(const SkPath& path, SkRegion::Op op); + void clipPath(const SkPath& path, SkRegion::Op op); /** * Sets the current clip. @@ -159,16 +159,20 @@ public: /** * Returns the current clip in render target coordinates. */ - const Rect& getRenderTargetClip() { return mClipArea->getClipRect(); } + const Rect& getRenderTargetClip() const { return mClipArea->getClipRect(); } /* * Accessor functions so that the clip area can stay private */ bool clipIsEmpty() const { return mClipArea->isEmpty(); } - const Rect& getClipRect() const { return mClipArea->getClipRect(); } const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); } bool clipIsSimple() const { return mClipArea->isSimple(); } const ClipArea& getClipArea() const { return *mClipArea; } + ClipArea& mutateClipArea() { return *mClipArea; } + + WARN_UNUSED_RESULT const ClipBase* serializeIntersectedClip(LinearAllocator& allocator, + const ClipBase* recordedClip, const Matrix4& recordedClipTransform); + void applyClip(const ClipBase* clip, const Matrix4& transform); /** * Resets the clip to the specified rect. @@ -220,6 +224,7 @@ public: * Fills outTransform with the current, total transform to screen space, * across layer boundaries. */ + // TODO: remove for HWUI_NEW_OPS void buildScreenSpaceTransform(Matrix4* outTransform) const; /** @@ -230,7 +235,7 @@ public: /** * Previous snapshot. */ - sp<Snapshot> previous; + Snapshot* previous; /** * A pointer to the currently active layer. @@ -295,9 +300,13 @@ public: const RoundRectClipState* roundRectClipState; /** - * Current projection masking path - used exclusively to mask tessellated circles. + * Current projection masking path - used exclusively to mask projected, tessellated circles. */ +#if HWUI_NEW_OPS + const SkPath* projectionPathMask; +#else const ProjectionPathMask* projectionPathMask; +#endif void dump() const; diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp index 7a2b9af2ba57..759e39b2e4e2 100644 --- a/libs/hwui/SpotShadow.cpp +++ b/libs/hwui/SpotShadow.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - // The highest z value can't be higher than (CASTER_Z_CAP_RATIO * light.z) #define CASTER_Z_CAP_RATIO 0.95f @@ -48,17 +46,18 @@ #define TRANSFORMED_PENUMBRA_ALPHA 1.0f #define TRANSFORMED_UMBRA_ALPHA 0.0f -#include <algorithm> -#include <math.h> -#include <stdlib.h> -#include <utils/Log.h> +#include "SpotShadow.h" #include "ShadowTessellator.h" -#include "SpotShadow.h" #include "Vertex.h" #include "VertexBuffer.h" #include "utils/MathUtils.h" +#include <algorithm> +#include <math.h> +#include <stdlib.h> +#include <utils/Log.h> + // TODO: After we settle down the new algorithm, we can remove the old one and // its utility functions. // Right now, we still need to keep it for comparison purpose and future expansion. @@ -545,7 +544,7 @@ void SpotShadow::createSpotShadow(bool isCasterOpaque, const Vector3& lightCente } float ratioVI = outlineData[i].radius / distOutline; - minRaitoVI = MathUtils::min(minRaitoVI, ratioVI); + minRaitoVI = std::min(minRaitoVI, ratioVI); if (ratioVI >= (1 - FAKE_UMBRA_SIZE_RATIO)) { ratioVI = (1 - FAKE_UMBRA_SIZE_RATIO); } @@ -742,7 +741,7 @@ inline void genNewPenumbraAndPairWithUmbra(const Vector2* penumbra, int penumbra // vertex's location. int newPenumbraNumber = indexDelta - 1; - float accumulatedDeltaLength[newPenumbraNumber]; + float accumulatedDeltaLength[indexDelta]; float totalDeltaLength = 0; // To save time, cache the previous umbra vertex info outside the loop diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp index 17cb3a7fd6fd..cfdc0848c8b0 100644 --- a/libs/hwui/TessellationCache.cpp +++ b/libs/hwui/TessellationCache.cpp @@ -35,13 +35,14 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// TessellationCache::Description::Description() - : type(kNone) + : type(Type::None) , scaleX(1.0f) , scaleY(1.0f) , aa(false) , cap(SkPaint::kDefault_Cap) , style(SkPaint::kFill_Style) , strokeWidth(1.0f) { + // Shape bits should be set to zeroes, because they are used for hash calculation. memset(&shape, 0, sizeof(Shape)); } @@ -52,11 +53,30 @@ TessellationCache::Description::Description(Type type, const Matrix4& transform, , style(paint.getStyle()) , strokeWidth(paint.getStrokeWidth()) { PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY); + // Shape bits should be set to zeroes, because they are used for hash calculation. memset(&shape, 0, sizeof(Shape)); } +bool TessellationCache::Description::operator==(const TessellationCache::Description& rhs) const { + if (type != rhs.type) return false; + if (scaleX != rhs.scaleX) return false; + if (scaleY != rhs.scaleY) return false; + if (aa != rhs.aa) return false; + if (cap != rhs.cap) return false; + if (style != rhs.style) return false; + if (strokeWidth != rhs.strokeWidth) return false; + if (type == Type::None) return true; + const Shape::RoundRect& lRect = shape.roundRect; + const Shape::RoundRect& rRect = rhs.shape.roundRect; + + if (lRect.width != rRect.width) return false; + if (lRect.height != rRect.height) return false; + if (lRect.rx != rRect.rx) return false; + return lRect.ry == rRect.ry; +} + hash_t TessellationCache::Description::hash() const { - uint32_t hash = JenkinsHashMix(0, type); + uint32_t hash = JenkinsHashMix(0, static_cast<int>(type)); hash = JenkinsHashMix(hash, aa); hash = JenkinsHashMix(hash, cap); hash = JenkinsHashMix(hash, style); @@ -77,17 +97,23 @@ void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPain TessellationCache::ShadowDescription::ShadowDescription() : nodeKey(nullptr) { - memset(&matrixData, 0, 16 * sizeof(float)); + memset(&matrixData, 0, sizeof(matrixData)); } -TessellationCache::ShadowDescription::ShadowDescription(const void* nodeKey, const Matrix4* drawTransform) +TessellationCache::ShadowDescription::ShadowDescription(const SkPath* nodeKey, const Matrix4* drawTransform) : nodeKey(nodeKey) { - memcpy(&matrixData, drawTransform->data, 16 * sizeof(float)); + memcpy(&matrixData, drawTransform->data, sizeof(matrixData)); +} + +bool TessellationCache::ShadowDescription::operator==( + const TessellationCache::ShadowDescription& rhs) const { + return nodeKey == rhs.nodeKey + && memcmp(&matrixData, &rhs.matrixData, sizeof(matrixData)) == 0; } hash_t TessellationCache::ShadowDescription::hash() const { uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*) &nodeKey, sizeof(const void*)); - hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, 16 * sizeof(float)); + hash = JenkinsHashMixBytes(hash, (uint8_t*) &matrixData, sizeof(matrixData)); return JenkinsHashWhiten(hash); } @@ -160,45 +186,6 @@ private: // Shadow tessellation task processing /////////////////////////////////////////////////////////////////////////////// -class ShadowTask : public Task<TessellationCache::vertexBuffer_pair_t*> { -public: - ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque, - const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ, - const Vector3& lightCenter, float lightRadius) - : drawTransform(*drawTransform) - , localClip(localClip) - , opaque(opaque) - , casterPerimeter(*casterPerimeter) - , transformXY(*transformXY) - , transformZ(*transformZ) - , lightCenter(lightCenter) - , lightRadius(lightRadius) { - } - - ~ShadowTask() { - TessellationCache::vertexBuffer_pair_t* bufferPair = getResult(); - delete bufferPair->getFirst(); - delete bufferPair->getSecond(); - delete bufferPair; - } - - /* Note - we deep copy all task parameters, because *even though* pointers into Allocator - * controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame, - * certain Allocators are destroyed before trim() is called to flush incomplete tasks. - * - * These deep copies could be avoided, long term, by cancelling or flushing outstanding tasks - * before tearning down single-frame LinearAllocators. - */ - const Matrix4 drawTransform; - const Rect localClip; - bool opaque; - const SkPath casterPerimeter; - const Matrix4 transformXY; - const Matrix4 transformZ; - const Vector3 lightCenter; - const float lightRadius; -}; - static void mapPointFakeZ(Vector3& point, const mat4* transformXY, const mat4* transformZ) { // map z coordinate with true 3d matrix point.z = transformZ->mapZ(point); @@ -217,7 +204,7 @@ static void reverseVertexArray(Vertex* polygon, int len) { } } -static void tessellateShadows( +void tessellateShadows( const Matrix4* drawTransform, const Rect* localClip, bool isCasterOpaque, const SkPath* casterPerimeter, const Matrix4* casterTransformXY, const Matrix4* casterTransformZ, @@ -225,13 +212,13 @@ static void tessellateShadows( VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer) { // tessellate caster outline into a 2d polygon - Vector<Vertex> casterVertices2d; + std::vector<Vertex> casterVertices2d; const float casterRefinementThreshold = 2.0f; PathTessellator::approximatePathOutlineVertices(*casterPerimeter, casterRefinementThreshold, casterVertices2d); // Shadow requires CCW for now. TODO: remove potential double-reverse - reverseVertexArray(casterVertices2d.editArray(), casterVertices2d.size()); + reverseVertexArray(&casterVertices2d.front(), casterVertices2d.size()); if (casterVertices2d.size() == 0) return; @@ -250,7 +237,7 @@ static void tessellateShadows( // map the centroid of the caster into 3d Vector2 centroid = ShadowTessellator::centroid2d( - reinterpret_cast<const Vector2*>(casterVertices2d.array()), + reinterpret_cast<const Vector2*>(&casterVertices2d.front()), casterVertexCount); Vector3 centroid3d = {centroid.x, centroid.y, 0}; mapPointFakeZ(centroid3d, casterTransformXY, casterTransformZ); @@ -281,23 +268,21 @@ static void tessellateShadows( spotBuffer); } -class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t*> { +class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t> { public: ShadowProcessor(Caches& caches) - : TaskProcessor<TessellationCache::vertexBuffer_pair_t*>(&caches.tasks) {} + : TaskProcessor<TessellationCache::vertexBuffer_pair_t>(&caches.tasks) {} ~ShadowProcessor() {} - virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t*> >& task) override { - ShadowTask* t = static_cast<ShadowTask*>(task.get()); + virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t> >& task) override { + TessellationCache::ShadowTask* t = static_cast<TessellationCache::ShadowTask*>(task.get()); ATRACE_NAME("shadow tessellation"); - VertexBuffer* ambientBuffer = new VertexBuffer; - VertexBuffer* spotBuffer = new VertexBuffer; tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter, &t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius, - *ambientBuffer, *spotBuffer); + t->ambientBuffer, t->spotBuffer); - t->setResult(new TessellationCache::vertexBuffer_pair_t(ambientBuffer, spotBuffer)); + t->setResult(TessellationCache::vertexBuffer_pair_t(&t->ambientBuffer, &t->spotBuffer)); } }; @@ -306,18 +291,9 @@ public: /////////////////////////////////////////////////////////////////////////////// TessellationCache::TessellationCache() - : mSize(0) - , mMaxSize(MB(DEFAULT_VERTEX_CACHE_SIZE)) + : mMaxSize(Properties::tessellationCacheSize) , mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity) , mShadowCache(LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_VERTEX_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting %s cache size to %sMB", name, property); - setMaxSize(MB(atof(property))); - } else { - INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_VERTEX_CACHE_SIZE); - } - mCache.setOnEntryRemovedListener(&mBufferRemovedListener); mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener); mDebugEnabled = Properties::debugLevel & kDebugCaches; @@ -344,13 +320,6 @@ uint32_t TessellationCache::getMaxSize() { return mMaxSize; } -void TessellationCache::setMaxSize(uint32_t maxSize) { - mMaxSize = maxSize; - while (mSize > mMaxSize) { - mCache.removeOldest(); - } -} - /////////////////////////////////////////////////////////////////////////////// // Caching /////////////////////////////////////////////////////////////////////////////// @@ -412,7 +381,23 @@ void TessellationCache::getShadowBuffers(const Matrix4* drawTransform, const Rec task = static_cast<ShadowTask*>(mShadowCache.get(key)); } LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached"); - outBuffers = *(task->getResult()); + outBuffers = task->getResult(); +} + +sp<TessellationCache::ShadowTask> TessellationCache::getShadowTask( + const Matrix4* drawTransform, const Rect& localClip, + bool opaque, const SkPath* casterPerimeter, + const Matrix4* transformXY, const Matrix4* transformZ, + const Vector3& lightCenter, float lightRadius) { + ShadowDescription key(casterPerimeter, drawTransform); + ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key)); + if (!task) { + precacheShadows(drawTransform, localClip, opaque, casterPerimeter, + transformXY, transformZ, lightCenter, lightRadius); + task = static_cast<ShadowTask*>(mShadowCache.get(key)); + } + LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached"); + return task; } /////////////////////////////////////////////////////////////////////////////// @@ -469,7 +454,7 @@ static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& d TessellationCache::Buffer* TessellationCache::getRoundRectBuffer( const Matrix4& transform, const SkPaint& paint, float width, float height, float rx, float ry) { - Description entry(Description::kRoundRect, transform, paint); + Description entry(Description::Type::RoundRect, transform, paint); entry.shape.roundRect.width = width; entry.shape.roundRect.height = height; entry.shape.roundRect.rx = rx; diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h index 3efeaf64d486..6141b4ef63d7 100644 --- a/libs/hwui/TessellationCache.h +++ b/libs/hwui/TessellationCache.h @@ -17,18 +17,24 @@ #ifndef ANDROID_HWUI_TESSELLATION_CACHE_H #define ANDROID_HWUI_TESSELLATION_CACHE_H -#include <utils/LruCache.h> -#include <utils/Mutex.h> -#include <utils/Vector.h> - #include "Debug.h" +#include "Matrix.h" +#include "Rect.h" +#include "Vector.h" +#include "VertexBuffer.h" +#include "thread/TaskProcessor.h" #include "utils/Macros.h" #include "utils/Pair.h" +#include <SkPaint.h> +#include <SkPath.h> + +#include <utils/LruCache.h> +#include <utils/Mutex.h> +#include <utils/StrongPointer.h> + class SkBitmap; class SkCanvas; -class SkPaint; -class SkPath; struct SkRect; namespace android { @@ -46,10 +52,10 @@ public: typedef Pair<VertexBuffer*, VertexBuffer*> vertexBuffer_pair_t; struct Description { - DESCRIPTION_TYPE(Description); - enum Type { - kNone, - kRoundRect, + HASHABLE_TYPE(Description); + enum class Type { + None, + RoundRect, }; Type type; @@ -70,18 +76,50 @@ public: Description(); Description(Type type, const Matrix4& transform, const SkPaint& paint); - hash_t hash() const; void setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const; }; struct ShadowDescription { - DESCRIPTION_TYPE(ShadowDescription); - const void* nodeKey; + HASHABLE_TYPE(ShadowDescription); + const SkPath* nodeKey; float matrixData[16]; ShadowDescription(); - ShadowDescription(const void* nodeKey, const Matrix4* drawTransform); - hash_t hash() const; + ShadowDescription(const SkPath* nodeKey, const Matrix4* drawTransform); + }; + + class ShadowTask : public Task<vertexBuffer_pair_t> { + public: + ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque, + const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ, + const Vector3& lightCenter, float lightRadius) + : drawTransform(*drawTransform) + , localClip(localClip) + , opaque(opaque) + , casterPerimeter(*casterPerimeter) + , transformXY(*transformXY) + , transformZ(*transformZ) + , lightCenter(lightCenter) + , lightRadius(lightRadius) { + } + + /* Note - we deep copy all task parameters, because *even though* pointers into Allocator + * controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame, + * certain Allocators are destroyed before trim() is called to flush incomplete tasks. + * + * These deep copies could be avoided, long term, by canceling or flushing outstanding + * tasks before tearing down single-frame LinearAllocators. + */ + const Matrix4 drawTransform; + const Rect localClip; + bool opaque; + const SkPath casterPerimeter; + const Matrix4 transformXY; + const Matrix4 transformZ; + const Vector3 lightCenter; + const float lightRadius; + VertexBuffer ambientBuffer; + VertexBuffer spotBuffer; }; TessellationCache(); @@ -91,11 +129,6 @@ public: * Clears the cache. This causes all TessellationBuffers to be deleted. */ void clear(); - - /** - * Sets the maximum size of the cache in bytes. - */ - void setMaxSize(uint32_t maxSize); /** * Returns the maximum size of the cache in bytes. */ @@ -128,17 +161,22 @@ public: const VertexBuffer* getRoundRect(const Matrix4& transform, const SkPaint& paint, float width, float height, float rx, float ry); + // TODO: delete these when switching to HWUI_NEW_OPS void precacheShadows(const Matrix4* drawTransform, const Rect& localClip, bool opaque, const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ, const Vector3& lightCenter, float lightRadius); - void getShadowBuffers(const Matrix4* drawTransform, const Rect& localClip, bool opaque, const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ, const Vector3& lightCenter, float lightRadius, vertexBuffer_pair_t& outBuffers); + sp<ShadowTask> getShadowTask(const Matrix4* drawTransform, const Rect& localClip, + bool opaque, const SkPath* casterPerimeter, + const Matrix4* transformXY, const Matrix4* transformZ, + const Vector3& lightCenter, float lightRadius); + private: class Buffer; class TessellationTask; @@ -153,8 +191,7 @@ private: Buffer* getOrCreateBuffer(const Description& entry, Tessellator tessellator); - uint32_t mSize; - uint32_t mMaxSize; + const uint32_t mMaxSize; bool mDebugEnabled; @@ -173,12 +210,12 @@ private: /////////////////////////////////////////////////////////////////////////////// // Shadow tessellation caching /////////////////////////////////////////////////////////////////////////////// - sp<TaskProcessor<vertexBuffer_pair_t*> > mShadowProcessor; + sp<TaskProcessor<vertexBuffer_pair_t> > mShadowProcessor; // holds a pointer, and implicit strong ref to each shadow task of the frame - LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*> mShadowCache; - class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t*>*> { - void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t*>*& bufferPairTask) override { + LruCache<ShadowDescription, Task<vertexBuffer_pair_t>*> mShadowCache; + class BufferPairRemovedListener : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t>*> { + void operator()(ShadowDescription& description, Task<vertexBuffer_pair_t>*& bufferPairTask) override { bufferPairTask->decStrong(nullptr); } }; @@ -186,6 +223,13 @@ private: }; // class TessellationCache +void tessellateShadows( + const Matrix4* drawTransform, const Rect* localClip, + bool isCasterOpaque, const SkPath* casterPerimeter, + const Matrix4* casterTransformXY, const Matrix4* casterTransformZ, + const Vector3& lightCenter, float lightRadius, + VertexBuffer& ambientBuffer, VertexBuffer& spotBuffer); + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp index 8b1d4cb2196c..e1f0b2a20172 100644 --- a/libs/hwui/TextDropShadowCache.cpp +++ b/libs/hwui/TextDropShadowCache.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <utils/JenkinsHash.h> #include "Caches.h" @@ -32,20 +30,19 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// hash_t ShadowText::hash() const { - uint32_t charCount = len / sizeof(char16_t); - uint32_t hash = JenkinsHashMix(0, len); + uint32_t hash = JenkinsHashMix(0, glyphCount); hash = JenkinsHashMix(hash, android::hash_type(radius)); hash = JenkinsHashMix(hash, android::hash_type(textSize)); hash = JenkinsHashMix(hash, android::hash_type(typeface)); hash = JenkinsHashMix(hash, flags); hash = JenkinsHashMix(hash, android::hash_type(italicStyle)); hash = JenkinsHashMix(hash, android::hash_type(scaleX)); - if (text) { + if (glyphs) { hash = JenkinsHashMixShorts( - hash, reinterpret_cast<const uint16_t*>(text), charCount); + hash, reinterpret_cast<const uint16_t*>(glyphs), glyphCount); } if (positions) { - for (uint32_t i = 0; i < charCount * 2; i++) { + for (uint32_t i = 0; i < glyphCount * 2; i++) { hash = JenkinsHashMix(hash, android::hash_type(positions[i])); } } @@ -53,7 +50,7 @@ hash_t ShadowText::hash() const { } int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { - int deltaInt = int(lhs.len) - int(rhs.len); + int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount); if (deltaInt != 0) return deltaInt; deltaInt = lhs.flags - rhs.flags; @@ -74,11 +71,11 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { if (lhs.scaleX < rhs.scaleX) return -1; if (lhs.scaleX > rhs.scaleX) return +1; - if (lhs.text != rhs.text) { - if (!lhs.text) return -1; - if (!rhs.text) return +1; + if (lhs.glyphs != rhs.glyphs) { + if (!lhs.glyphs) return -1; + if (!rhs.glyphs) return +1; - deltaInt = memcmp(lhs.text, rhs.text, lhs.len); + deltaInt = memcmp(lhs.glyphs, rhs.glyphs, lhs.glyphCount * sizeof(glyph_t)); if (deltaInt != 0) return deltaInt; } @@ -86,7 +83,7 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { if (!lhs.positions) return -1; if (!rhs.positions) return +1; - return memcmp(lhs.positions, rhs.positions, lhs.len << 2); + return memcmp(lhs.positions, rhs.positions, lhs.glyphCount * sizeof(float) * 2); } return 0; @@ -96,36 +93,21 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { // Constructors/destructor /////////////////////////////////////////////////////////////////////////////// -TextDropShadowCache::TextDropShadowCache(): - mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity), - mSize(0), mMaxSize(MB(DEFAULT_DROP_SHADOW_CACHE_SIZE)) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_DROP_SHADOW_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting drop shadow cache size to %sMB", property); - setMaxSize(MB(atof(property))); - } else { - INIT_LOGD(" Using default drop shadow cache size of %.2fMB", - DEFAULT_DROP_SHADOW_CACHE_SIZE); - } - - init(); -} +TextDropShadowCache::TextDropShadowCache() + : TextDropShadowCache(Properties::textDropShadowCacheSize) {} -TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize): - mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity), - mSize(0), mMaxSize(maxByteSize) { - init(); +TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize) + : mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity) + , mSize(0) + , mMaxSize(maxByteSize) { + mCache.setOnEntryRemovedListener(this); + mDebugEnabled = Properties::debugLevel & kDebugMoreCaches; } TextDropShadowCache::~TextDropShadowCache() { mCache.clear(); } -void TextDropShadowCache::init() { - mCache.setOnEntryRemovedListener(this); - mDebugEnabled = Properties::debugLevel & kDebugMoreCaches; -} - /////////////////////////////////////////////////////////////////////////////// // Size management /////////////////////////////////////////////////////////////////////////////// @@ -138,20 +120,13 @@ uint32_t TextDropShadowCache::getMaxSize() { return mMaxSize; } -void TextDropShadowCache::setMaxSize(uint32_t maxSize) { - mMaxSize = maxSize; - while (mSize > mMaxSize) { - mCache.removeOldest(); - } -} - /////////////////////////////////////////////////////////////////////////////// // Callbacks /////////////////////////////////////////////////////////////////////////////// void TextDropShadowCache::operator()(ShadowText&, ShadowTexture*& texture) { if (texture) { - mSize -= texture->bitmapSize; + mSize -= texture->objectSize(); if (mDebugEnabled) { ALOGD("Shadow texture deleted, size = %d", texture->bitmapSize); @@ -170,16 +145,16 @@ void TextDropShadowCache::clear() { mCache.clear(); } -ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, uint32_t len, - int numGlyphs, float radius, const float* positions) { - ShadowText entry(paint, radius, len, text, positions); +ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, + float radius, const float* positions) { + ShadowText entry(paint, radius, numGlyphs, glyphs, positions); ShadowTexture* texture = mCache.get(entry); if (!texture) { SkPaint paintCopy(*paint); paintCopy.setTextAlign(SkPaint::kLeft_Align); - FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, text, 0, - len, numGlyphs, radius, positions); + FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs, + radius, positions); if (!shadow.image) { return nullptr; @@ -190,30 +165,23 @@ ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, texture = new ShadowTexture(caches); texture->left = shadow.penX; texture->top = shadow.penY; - texture->width = shadow.width; - texture->height = shadow.height; texture->generation = 0; texture->blend = true; const uint32_t size = shadow.width * shadow.height; - texture->bitmapSize = size; // Don't even try to cache a bitmap that's bigger than the cache if (size < mMaxSize) { while (mSize + size > mMaxSize) { - mCache.removeOldest(); + LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(), + "Failed to remove oldest from cache. mSize = %" + PRIu32 ", mCache.size() = %zu", mSize, mCache.size()); } } - glGenTextures(1, &texture->id); - - caches.textureState().bindTexture(texture->id); // Textures are Alpha8 - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0, + texture->upload(GL_ALPHA, shadow.width, shadow.height, GL_ALPHA, GL_UNSIGNED_BYTE, shadow.image); - texture->setFilter(GL_LINEAR); texture->setWrap(GL_CLAMP_TO_EDGE); @@ -224,7 +192,7 @@ ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, entry.copyTextLocally(); - mSize += size; + mSize += texture->objectSize(); mCache.put(entry, texture); } else { texture->cleanup = true; diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h index caf089f6d2a5..d536c40756ff 100644 --- a/libs/hwui/TextDropShadowCache.h +++ b/libs/hwui/TextDropShadowCache.h @@ -34,27 +34,22 @@ class Caches; class FontRenderer; struct ShadowText { - ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(nullptr), - flags(0), italicStyle(0.0f), scaleX(0), text(nullptr), positions(nullptr) { + ShadowText(): glyphCount(0), radius(0.0f), textSize(0.0f), typeface(nullptr), + flags(0), italicStyle(0.0f), scaleX(0), glyphs(nullptr), positions(nullptr) { } // len is the number of bytes in text - ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText, - const float* positions): - len(len), radius(radius), positions(positions) { - // TODO: Propagate this through the API, we should not cast here - text = (const char16_t*) srcText; - - textSize = paint->getTextSize(); - typeface = paint->getTypeface(); - - flags = 0; - if (paint->isFakeBoldText()) { - flags |= Font::kFakeBold; - } - - italicStyle = paint->getTextSkewX(); - scaleX = paint->getTextScaleX(); + ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const glyph_t* srcGlyphs, + const float* positions) + : glyphCount(glyphCount) + , radius(radius) + , textSize(paint->getTextSize()) + , typeface(paint->getTypeface()) + , flags(paint->isFakeBoldText() ? Font::kFakeBold : 0) + , italicStyle(paint->getTextSkewX()) + , scaleX(paint->getTextScaleX()) + , glyphs(srcGlyphs) + , positions(positions) { } ~ShadowText() { @@ -73,24 +68,23 @@ struct ShadowText { } void copyTextLocally() { - uint32_t charCount = len / sizeof(char16_t); - str.setTo((const char16_t*) text, charCount); - text = str.string(); + str.setTo(reinterpret_cast<const char16_t*>(glyphs), glyphCount); + glyphs = reinterpret_cast<const glyph_t*>(str.string()); if (positions != nullptr) { positionsCopy.clear(); - positionsCopy.appendArray(positions, charCount * 2); + positionsCopy.appendArray(positions, glyphCount * 2); positions = positionsCopy.array(); } } - uint32_t len; + uint32_t glyphCount; float radius; float textSize; SkTypeface* typeface; uint32_t flags; float italicStyle; float scaleX; - const char16_t* text; + const glyph_t* glyphs; const float* positions; // Not directly used to compute the cache key @@ -136,7 +130,7 @@ public: */ void operator()(ShadowText& text, ShadowTexture*& texture) override; - ShadowTexture* get(const SkPaint* paint, const char* text, uint32_t len, + ShadowTexture* get(const SkPaint* paint, const glyph_t* text, int numGlyphs, float radius, const float* positions); /** @@ -149,10 +143,6 @@ public: } /** - * Sets the maximum size of the cache in bytes. - */ - void setMaxSize(uint32_t maxSize); - /** * Returns the maximum size of the cache in bytes. */ uint32_t getMaxSize(); @@ -162,13 +152,11 @@ public: uint32_t getSize(); private: - void init(); - LruCache<ShadowText, ShadowTexture*> mCache; uint32_t mSize; - uint32_t mMaxSize; - FontRenderer* mRenderer; + const uint32_t mMaxSize; + FontRenderer* mRenderer = nullptr; bool mDebugEnabled; }; // class TextDropShadowCache diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 593e91818093..4f49a3518be0 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -14,27 +14,45 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" +#include "Caches.h" +#include "Texture.h" +#include "utils/GLUtils.h" +#include "utils/TraceUtils.h" #include <utils/Log.h> -#include "Caches.h" -#include "Texture.h" +#include <SkCanvas.h> namespace android { namespace uirenderer { +static int bytesPerPixel(GLint glFormat) { + switch (glFormat) { + // The wrapped-texture case, usually means a SurfaceTexture + case 0: + return 0; + case GL_ALPHA: + return 1; + case GL_RGB: + return 3; + case GL_RGBA: + return 4; + case GL_RGBA16F: + return 16; + default: + LOG_ALWAYS_FATAL("UNKNOWN FORMAT %d", glFormat); + } +} + void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force, GLenum renderTarget) { - if (mFirstWrap || force || wrapS != mWrapS || wrapT != mWrapT) { - mFirstWrap = false; - + if (force || wrapS != mWrapS || wrapT != mWrapT) { mWrapS = wrapS; mWrapT = wrapT; if (bindTexture) { - mCaches.textureState().bindTexture(renderTarget, id); + mCaches.textureState().bindTexture(renderTarget, mId); } glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); @@ -45,14 +63,12 @@ void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force, GLenum renderTarget) { - if (mFirstFilter || force || min != mMinFilter || mag != mMagFilter) { - mFirstFilter = false; - + if (force || min != mMinFilter || mag != mMagFilter) { mMinFilter = min; mMagFilter = mag; if (bindTexture) { - mCaches.textureState().bindTexture(renderTarget, id); + mCaches.textureState().bindTexture(renderTarget, mId); } if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; @@ -62,8 +78,197 @@ void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool for } } -void Texture::deleteTexture() const { - mCaches.textureState().deleteTexture(id); +void Texture::deleteTexture() { + mCaches.textureState().deleteTexture(mId); + mId = 0; +} + +bool Texture::updateSize(uint32_t width, uint32_t height, GLint format) { + if (mWidth == width && mHeight == height && mFormat == format) { + return false; + } + mWidth = width; + mHeight = height; + mFormat = format; + notifySizeChanged(mWidth * mHeight * bytesPerPixel(mFormat)); + return true; +} + +void Texture::resetCachedParams() { + mWrapS = GL_REPEAT; + mWrapT = GL_REPEAT; + mMinFilter = GL_NEAREST_MIPMAP_LINEAR; + mMagFilter = GL_LINEAR; +} + +void Texture::upload(GLint internalformat, uint32_t width, uint32_t height, + GLenum format, GLenum type, const void* pixels) { + GL_CHECKPOINT(MODERATE); + bool needsAlloc = updateSize(width, height, internalformat); + if (!mId) { + glGenTextures(1, &mId); + needsAlloc = true; + resetCachedParams(); + } + mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId); + if (needsAlloc) { + glTexImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, + format, type, pixels); + } else if (pixels) { + glTexSubImage2D(GL_TEXTURE_2D, 0, mFormat, mWidth, mHeight, 0, + format, type, pixels); + } + GL_CHECKPOINT(MODERATE); +} + +static void uploadToTexture(bool resize, GLenum format, GLenum type, GLsizei stride, GLsizei bpp, + GLsizei width, GLsizei height, const GLvoid * data) { + + const bool useStride = stride != width + && Caches::getInstance().extensions().hasUnpackRowLength(); + if ((stride == width) || useStride) { + if (useStride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); + } + + if (resize) { + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); + } + + if (useStride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } + } else { + // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer + // if the stride doesn't match the width + + GLvoid * temp = (GLvoid *) malloc(width * height * bpp); + if (!temp) return; + + uint8_t * pDst = (uint8_t *)temp; + uint8_t * pSrc = (uint8_t *)data; + for (GLsizei i = 0; i < height; i++) { + memcpy(pDst, pSrc, width * bpp); + pDst += width * bpp; + pSrc += stride * bpp; + } + + if (resize) { + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp); + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp); + } + + free(temp); + } +} + +static void uploadSkBitmapToTexture(const SkBitmap& bitmap, + bool resize, GLenum format, GLenum type) { + uploadToTexture(resize, format, type, bitmap.rowBytesAsPixels(), bitmap.bytesPerPixel(), + bitmap.width(), bitmap.height(), bitmap.getPixels()); +} + +static void colorTypeToGlFormatAndType(SkColorType colorType, + GLint* outFormat, GLint* outType) { + switch (colorType) { + case kAlpha_8_SkColorType: + *outFormat = GL_ALPHA; + *outType = GL_UNSIGNED_BYTE; + break; + case kRGB_565_SkColorType: + *outFormat = GL_RGB; + *outType = GL_UNSIGNED_SHORT_5_6_5; + break; + // ARGB_4444 and Index_8 are both upconverted to RGBA_8888 + case kARGB_4444_SkColorType: + case kIndex_8_SkColorType: + case kN32_SkColorType: + *outFormat = GL_RGBA; + *outType = GL_UNSIGNED_BYTE; + break; + case kGray_8_SkColorType: + *outFormat = GL_LUMINANCE; + *outType = GL_UNSIGNED_BYTE; + break; + default: + LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType); + break; + } +} + +void Texture::upload(const SkBitmap& bitmap) { + SkAutoLockPixels alp(bitmap); + + if (!bitmap.readyToDraw()) { + ALOGE("Cannot generate texture from bitmap"); + return; + } + + ATRACE_FORMAT("Upload %ux%u Texture", bitmap.width(), bitmap.height()); + + // We could also enable mipmapping if both bitmap dimensions are powers + // of 2 but we'd have to deal with size changes. Let's keep this simple + const bool canMipMap = mCaches.extensions().hasNPot(); + + // If the texture had mipmap enabled but not anymore, + // force a glTexImage2D to discard the mipmap levels + bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap(); + bool setDefaultParams = false; + + if (!mId) { + glGenTextures(1, &mId); + needsAlloc = true; + setDefaultParams = true; + } + + GLint format, type; + colorTypeToGlFormatAndType(bitmap.colorType(), &format, &type); + + if (updateSize(bitmap.width(), bitmap.height(), format)) { + needsAlloc = true; + } + + blend = !bitmap.isOpaque(); + mCaches.textureState().bindTexture(mId); + + if (CC_UNLIKELY(bitmap.colorType() == kARGB_4444_SkColorType + || bitmap.colorType() == kIndex_8_SkColorType)) { + SkBitmap rgbaBitmap; + rgbaBitmap.allocPixels(SkImageInfo::MakeN32(mWidth, mHeight, + bitmap.alphaType())); + rgbaBitmap.eraseColor(0); + + SkCanvas canvas(rgbaBitmap); + canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); + + uploadSkBitmapToTexture(rgbaBitmap, needsAlloc, format, type); + } else { + uploadSkBitmapToTexture(bitmap, needsAlloc, format, type); + } + + if (canMipMap) { + mipMap = bitmap.hasHardwareMipMap(); + if (mipMap) { + glGenerateMipmap(GL_TEXTURE_2D); + } + } + + if (setDefaultParams) { + setFilter(GL_NEAREST); + setWrap(GL_CLAMP_TO_EDGE); + } +} + +void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint format) { + mId = id; + mWidth = width; + mHeight = height; + mFormat = format; + // We're wrapping an existing texture, so don't double count this memory + notifySizeChanged(0); } }; // namespace uirenderer diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h index 4bcd96dd32f7..9749f734fd1f 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -17,20 +17,27 @@ #ifndef ANDROID_HWUI_TEXTURE_H #define ANDROID_HWUI_TEXTURE_H +#include "GpuMemoryTracker.h" + #include <GLES2/gl2.h> +#include <SkBitmap.h> namespace android { namespace uirenderer { class Caches; class UvMapper; +class Layer; /** * Represents an OpenGL texture. */ -class Texture { +class Texture : public GpuMemoryTracker { public: - Texture(Caches& caches) : mCaches(caches) { } + Texture(Caches& caches) + : GpuMemoryTracker(GpuObjectType::Texture) + , mCaches(caches) + { } virtual ~Texture() { } @@ -53,28 +60,63 @@ public: /** * Convenience method to call glDeleteTextures() on this texture's id. */ - void deleteTexture() const; + void deleteTexture(); /** - * Name of the texture. + * Sets the width, height, and format of the texture along with allocating + * the texture ID. Does nothing if the width, height, and format are already + * the requested values. + * + * The image data is undefined after calling this. */ - GLuint id = 0; + void resize(uint32_t width, uint32_t height, GLint format) { + upload(format, width, height, format, GL_UNSIGNED_BYTE, nullptr); + } + /** - * Generation of the backing bitmap, + * Updates this Texture with the contents of the provided SkBitmap, + * also setting the appropriate width, height, and format. It is not necessary + * to call resize() prior to this. + * + * Note this does not set the generation from the SkBitmap. */ - uint32_t generation = 0; + void upload(const SkBitmap& source); + /** - * Indicates whether the texture requires blending. + * Basically glTexImage2D/glTexSubImage2D. */ - bool blend = false; + void upload(GLint internalformat, uint32_t width, uint32_t height, + GLenum format, GLenum type, const void* pixels); + + /** + * Wraps an existing texture. + */ + void wrap(GLuint id, uint32_t width, uint32_t height, GLint format); + + GLuint id() const { + return mId; + } + + uint32_t width() const { + return mWidth; + } + + uint32_t height() const { + return mHeight; + } + + GLint format() const { + return mFormat; + } + /** - * Width of the backing bitmap. + * Generation of the backing bitmap, */ - uint32_t width = 0; + uint32_t generation = 0; /** - * Height of the backing bitmap. + * Indicates whether the texture requires blending. */ - uint32_t height = 0; + bool blend = false; /** * Indicates whether this texture should be cleaned up after use. */ @@ -100,36 +142,45 @@ public: void* isInUse = nullptr; private: - /** - * Last wrap modes set on this texture. + // TODO: Temporarily grant private access to Layer, remove once + // Layer can be de-tangled from being a dual-purpose render target + // and external texture wrapper + friend class Layer; + + // Returns true if the size changed, false if it was the same + bool updateSize(uint32_t width, uint32_t height, GLint format); + void resetCachedParams(); + + GLuint mId = 0; + uint32_t mWidth = 0; + uint32_t mHeight = 0; + GLint mFormat = 0; + + /* See GLES spec section 3.8.14 + * "In the initial state, the value assigned to TEXTURE_MIN_FILTER is + * NEAREST_MIPMAP_LINEAR and the value for TEXTURE_MAG_FILTER is LINEAR. + * s, t, and r wrap modes are all set to REPEAT." */ - GLenum mWrapS = GL_CLAMP_TO_EDGE; - GLenum mWrapT = GL_CLAMP_TO_EDGE; - - /** - * Last filters set on this texture. - */ - GLenum mMinFilter = GL_NEAREST; - GLenum mMagFilter = GL_NEAREST; - - bool mFirstFilter = true; - bool mFirstWrap = true; + GLenum mWrapS = GL_REPEAT; + GLenum mWrapT = GL_REPEAT; + GLenum mMinFilter = GL_NEAREST_MIPMAP_LINEAR; + GLenum mMagFilter = GL_LINEAR; Caches& mCaches; }; // struct Texture class AutoTexture { public: - AutoTexture(const Texture* texture): mTexture(texture) { } + AutoTexture(Texture* texture) + : texture(texture) {} ~AutoTexture() { - if (mTexture && mTexture->cleanup) { - mTexture->deleteTexture(); - delete mTexture; + if (texture && texture->cleanup) { + texture->deleteTexture(); + delete texture; } } -private: - const Texture* mTexture; + Texture* const texture; }; // class AutoTexture }; // namespace uirenderer diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp index fda009108aba..ade8600ab78b 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -14,14 +14,8 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <GLES2/gl2.h> -#include <SkCanvas.h> -#include <SkPixelRef.h> - #include <utils/Mutex.h> #include "AssetAtlas.h" @@ -41,26 +35,9 @@ namespace uirenderer { TextureCache::TextureCache() : mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity) , mSize(0) - , mMaxSize(MB(DEFAULT_TEXTURE_CACHE_SIZE)) - , mFlushRate(DEFAULT_TEXTURE_CACHE_FLUSH_RATE) + , mMaxSize(Properties::textureCacheSize) + , mFlushRate(Properties::textureCacheFlushRate) , mAssetAtlas(nullptr) { - char property[PROPERTY_VALUE_MAX]; - if (property_get(PROPERTY_TEXTURE_CACHE_SIZE, property, nullptr) > 0) { - INIT_LOGD(" Setting texture cache size to %sMB", property); - setMaxSize(MB(atof(property))); - } else { - INIT_LOGD(" Using default texture cache size of %.2fMB", DEFAULT_TEXTURE_CACHE_SIZE); - } - - if (property_get(PROPERTY_TEXTURE_CACHE_FLUSH_RATE, property, nullptr) > 0) { - float flushRate = atof(property); - INIT_LOGD(" Setting texture cache flush rate to %.2f%%", flushRate * 100.0f); - setFlushRate(flushRate); - } else { - INIT_LOGD(" Using default texture cache flush rate of %.2f%%", - DEFAULT_TEXTURE_CACHE_FLUSH_RATE * 100.0f); - } - mCache.setOnEntryRemovedListener(this); glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); @@ -85,17 +62,6 @@ uint32_t TextureCache::getMaxSize() { return mMaxSize; } -void TextureCache::setMaxSize(uint32_t maxSize) { - mMaxSize = maxSize; - while (mSize > mMaxSize) { - mCache.removeOldest(); - } -} - -void TextureCache::setFlushRate(float flushRate) { - mFlushRate = std::max(0.0f, std::min(1.0f, flushRate)); -} - /////////////////////////////////////////////////////////////////////////////// // Callbacks /////////////////////////////////////////////////////////////////////////////// @@ -144,7 +110,7 @@ bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) { // in the cache (and is thus added to the cache) Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType) { if (CC_LIKELY(mAssetAtlas != nullptr) && atlasUsageType == AtlasUsageType::Use) { - AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap); + AssetAtlas::Entry* entry = mAssetAtlas->getEntry(bitmap->pixelRef()); if (CC_UNLIKELY(entry)) { return entry->texture; } @@ -172,7 +138,8 @@ Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType a if (canCache) { texture = new Texture(Caches::getInstance()); texture->bitmapSize = size; - generateTexture(bitmap, texture, false); + texture->generation = bitmap->getGenerationID(); + texture->upload(*bitmap); mSize += size; TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d", @@ -185,7 +152,8 @@ Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap, AtlasUsageType a } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) { // Texture was in the cache but is dirty, re-upload // TODO: Re-adjust the cache size if the bitmap's dimensions have changed - generateTexture(bitmap, texture, true); + texture->upload(*bitmap); + texture->generation = bitmap->getGenerationID(); } return texture; @@ -210,7 +178,8 @@ Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType const uint32_t size = bitmap->rowBytes() * bitmap->height(); texture = new Texture(Caches::getInstance()); texture->bitmapSize = size; - generateTexture(bitmap, texture, false); + texture->upload(*bitmap); + texture->generation = bitmap->getGenerationID(); texture->cleanup = true; } @@ -219,14 +188,14 @@ Texture* TextureCache::get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType void TextureCache::releaseTexture(uint32_t pixelRefStableID) { Mutex::Autolock _l(mLock); - mGarbage.push(pixelRefStableID); + mGarbage.push_back(pixelRefStableID); } void TextureCache::clearGarbage() { Mutex::Autolock _l(mLock); size_t count = mGarbage.size(); for (size_t i = 0; i < count; i++) { - uint32_t pixelRefId = mGarbage.itemAt(i); + uint32_t pixelRefId = mGarbage[i]; mCache.remove(pixelRefId); } mGarbage.clear(); @@ -252,133 +221,5 @@ void TextureCache::flush() { } } -void TextureCache::generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate) { - SkAutoLockPixels alp(*bitmap); - - if (!bitmap->readyToDraw()) { - ALOGE("Cannot generate texture from bitmap"); - return; - } - - ATRACE_FORMAT("Upload %ux%u Texture", bitmap->width(), bitmap->height()); - - // We could also enable mipmapping if both bitmap dimensions are powers - // of 2 but we'd have to deal with size changes. Let's keep this simple - const bool canMipMap = Caches::getInstance().extensions().hasNPot(); - - // If the texture had mipmap enabled but not anymore, - // force a glTexImage2D to discard the mipmap levels - const bool resize = !regenerate || bitmap->width() != int(texture->width) || - bitmap->height() != int(texture->height) || - (regenerate && canMipMap && texture->mipMap && !bitmap->hasHardwareMipMap()); - - if (!regenerate) { - glGenTextures(1, &texture->id); - } - - texture->generation = bitmap->getGenerationID(); - texture->width = bitmap->width(); - texture->height = bitmap->height(); - - Caches::getInstance().textureState().bindTexture(texture->id); - - switch (bitmap->colorType()) { - case kAlpha_8_SkColorType: - uploadToTexture(resize, GL_ALPHA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(), - texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels()); - texture->blend = true; - break; - case kRGB_565_SkColorType: - uploadToTexture(resize, GL_RGB, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(), - texture->width, texture->height, GL_UNSIGNED_SHORT_5_6_5, bitmap->getPixels()); - texture->blend = false; - break; - case kN32_SkColorType: - uploadToTexture(resize, GL_RGBA, bitmap->rowBytesAsPixels(), bitmap->bytesPerPixel(), - texture->width, texture->height, GL_UNSIGNED_BYTE, bitmap->getPixels()); - // Do this after calling getPixels() to make sure Skia's deferred - // decoding happened - texture->blend = !bitmap->isOpaque(); - break; - case kARGB_4444_SkColorType: - case kIndex_8_SkColorType: - uploadLoFiTexture(resize, bitmap, texture->width, texture->height); - texture->blend = !bitmap->isOpaque(); - break; - default: - ALOGW("Unsupported bitmap colorType: %d", bitmap->colorType()); - break; - } - - if (canMipMap) { - texture->mipMap = bitmap->hasHardwareMipMap(); - if (texture->mipMap) { - glGenerateMipmap(GL_TEXTURE_2D); - } - } - - if (!regenerate) { - texture->setFilter(GL_NEAREST); - texture->setWrap(GL_CLAMP_TO_EDGE); - } -} - -void TextureCache::uploadLoFiTexture(bool resize, const SkBitmap* bitmap, - uint32_t width, uint32_t height) { - SkBitmap rgbaBitmap; - rgbaBitmap.allocPixels(SkImageInfo::MakeN32(width, height, bitmap->alphaType())); - rgbaBitmap.eraseColor(0); - - SkCanvas canvas(rgbaBitmap); - canvas.drawBitmap(*bitmap, 0.0f, 0.0f, nullptr); - - uploadToTexture(resize, GL_RGBA, rgbaBitmap.rowBytesAsPixels(), rgbaBitmap.bytesPerPixel(), - width, height, GL_UNSIGNED_BYTE, rgbaBitmap.getPixels()); -} - -void TextureCache::uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp, - GLsizei width, GLsizei height, GLenum type, const GLvoid * data) { - glPixelStorei(GL_UNPACK_ALIGNMENT, bpp); - const bool useStride = stride != width - && Caches::getInstance().extensions().hasUnpackRowLength(); - if ((stride == width) || useStride) { - if (useStride) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); - } - - if (resize) { - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, data); - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, data); - } - - if (useStride) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - } - } else { - // With OpenGL ES 2.0 we need to copy the bitmap in a temporary buffer - // if the stride doesn't match the width - - GLvoid * temp = (GLvoid *) malloc(width * height * bpp); - if (!temp) return; - - uint8_t * pDst = (uint8_t *)temp; - uint8_t * pSrc = (uint8_t *)data; - for (GLsizei i = 0; i < height; i++) { - memcpy(pDst, pSrc, width * bpp); - pDst += width * bpp; - pSrc += stride * bpp; - } - - if (resize) { - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, type, temp); - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp); - } - - free(temp); - } -} - }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h index 7a7ee5aeb554..a4317cee73fd 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -21,10 +21,12 @@ #include <utils/LruCache.h> #include <utils/Mutex.h> -#include <utils/Vector.h> #include "Debug.h" +#include <vector> +#include <unordered_map> + namespace android { namespace uirenderer { @@ -107,10 +109,6 @@ public: void clear(); /** - * Sets the maximum size of the cache in bytes. - */ - void setMaxSize(uint32_t maxSize); - /** * Returns the maximum size of the cache in bytes. */ uint32_t getMaxSize(); @@ -124,11 +122,6 @@ public: * is defined by the flush rate. */ void flush(); - /** - * Indicates the percentage of the cache to retain when a - * memory trim is requested (see Caches::flush). - */ - void setFlushRate(float flushRate); void setAssetAtlas(AssetAtlas* assetAtlas); @@ -143,29 +136,17 @@ private: Texture* get(const SkBitmap* bitmap, AtlasUsageType atlasUsageType); Texture* getCachedTexture(const SkBitmap* bitmap, AtlasUsageType atlasUsageType); - /** - * Generates the texture from a bitmap into the specified texture structure. - * - * @param regenerate If true, the bitmap data is reuploaded into the texture, but - * no new texture is generated. - */ - void generateTexture(const SkBitmap* bitmap, Texture* texture, bool regenerate = false); - - void uploadLoFiTexture(bool resize, const SkBitmap* bitmap, uint32_t width, uint32_t height); - void uploadToTexture(bool resize, GLenum format, GLsizei stride, GLsizei bpp, - GLsizei width, GLsizei height, GLenum type, const GLvoid * data); - LruCache<uint32_t, Texture*> mCache; uint32_t mSize; - uint32_t mMaxSize; + const uint32_t mMaxSize; GLint mMaxTextureSize; - float mFlushRate; + const float mFlushRate; bool mDebugEnabled; - Vector<uint32_t> mGarbage; + std::vector<uint32_t> mGarbage; mutable Mutex mLock; AssetAtlas* mAssetAtlas; diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index ed853f72539d..ac2bdccf5255 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -16,11 +16,11 @@ #ifndef TREEINFO_H #define TREEINFO_H -#include <string> +#include "utils/Macros.h" #include <utils/Timers.h> -#include "utils/Macros.h" +#include <string> namespace android { namespace uirenderer { @@ -30,7 +30,9 @@ class CanvasContext; } class DamageAccumulator; +class LayerUpdateQueue; class OpenGLRenderer; +class RenderNode; class RenderState; class ErrorHandler { @@ -40,6 +42,17 @@ protected: ~ErrorHandler() {} }; +class TreeObserver { +public: + // Called when a RenderNode's parent count hits 0. + // Due to the unordered nature of tree pushes, once prepareTree + // is finished it is possible that the node was "resurrected" and has + // a non-zero parent count. + virtual void onMaybeRemovedFromTree(RenderNode* node) {} +protected: + ~TreeObserver() {} +}; + // This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN class TreeInfo { PREVENT_COPY_AND_ASSIGN(TreeInfo); @@ -55,70 +68,59 @@ public: MODE_RT_ONLY, }; - explicit TreeInfo(TraversalMode mode, RenderState& renderState) - : mode(mode) - , prepareTextures(mode == MODE_FULL) - , runAnimations(true) - , damageAccumulator(nullptr) - , renderState(renderState) - , renderer(nullptr) - , errorHandler(nullptr) - , canvasContext(nullptr) - {} - - explicit TreeInfo(TraversalMode mode, const TreeInfo& clone) - : mode(mode) - , prepareTextures(mode == MODE_FULL) - , runAnimations(clone.runAnimations) - , damageAccumulator(clone.damageAccumulator) - , renderState(clone.renderState) - , renderer(clone.renderer) - , errorHandler(clone.errorHandler) - , canvasContext(clone.canvasContext) + TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext) + : mode(mode) + , prepareTextures(mode == MODE_FULL) + , canvasContext(canvasContext) {} - const TraversalMode mode; + TraversalMode mode; // TODO: Remove this? Currently this is used to signal to stop preparing // textures if we run out of cache space. bool prepareTextures; + renderthread::CanvasContext& canvasContext; // TODO: buildLayer uses this to suppress running any animations, but this // should probably be refactored somehow. The reason this is done is // because buildLayer is not setup for injecting the animationHook, as well // as this being otherwise wasted work as all the animators will be // re-evaluated when the frame is actually drawn - bool runAnimations; + bool runAnimations = true; // Must not be null during actual usage - DamageAccumulator* damageAccumulator; - RenderState& renderState; + DamageAccumulator* damageAccumulator = nullptr; + +#if HWUI_NEW_OPS + LayerUpdateQueue* layerUpdateQueue = nullptr; +#else // The renderer that will be drawing the next frame. Use this to push any // layer updates or similar. May be NULL. - OpenGLRenderer* renderer; - ErrorHandler* errorHandler; - // May be NULL (TODO: can it really?) - renderthread::CanvasContext* canvasContext; + OpenGLRenderer* renderer = nullptr; +#endif + ErrorHandler* errorHandler = nullptr; + + // Optional, may be nullptr. Used to allow things to observe interesting + // tree state changes + TreeObserver* observer = nullptr; + + int32_t windowInsetLeft = 0; + int32_t windowInsetTop = 0; + bool updateWindowPositions = false; struct Out { - Out() - : hasFunctors(false) - , hasAnimations(false) - , requiresUiRedraw(false) - , canDrawThisFrame(true) - {} - bool hasFunctors; + bool hasFunctors = false; // This is only updated if evaluateAnimations is true - bool hasAnimations; + bool hasAnimations = false; // This is set to true if there is an animation that RenderThread cannot // animate itself, such as if hasFunctors is true // This is only set if hasAnimations is true - bool requiresUiRedraw; + bool requiresUiRedraw = false; // This is set to true if draw() can be called this frame // false means that we must delay until the next vsync pulse as frame // production is outrunning consumption // NOTE that if this is false CanvasContext will set either requiresUiRedraw // *OR* will post itself for the next vsync automatically, use this // only to avoid calling draw() - bool canDrawThisFrame; + bool canDrawThisFrame = true; } out; // TODO: Damage calculations diff --git a/libs/hwui/Vector.h b/libs/hwui/Vector.h index 7c3f2fd379e4..6367dbd7b660 100644 --- a/libs/hwui/Vector.h +++ b/libs/hwui/Vector.h @@ -135,8 +135,8 @@ public: } - void dump() { - ALOGD("Vector3[%.2f, %.2f, %.2f]", x, y, z); + void dump(const char* label = "Vector3") const { + ALOGD("%s[%.2f, %.2f, %.2f]", label, x, y, z); } }; diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp new file mode 100644 index 000000000000..ac17ed2eb735 --- /dev/null +++ b/libs/hwui/VectorDrawable.cpp @@ -0,0 +1,588 @@ +/* + * Copyright (C) 2015 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 "VectorDrawable.h" + +#include "PathParser.h" +#include "SkColorFilter.h" +#include "SkImageInfo.h" +#include "SkShader.h" +#include <utils/Log.h> +#include "utils/Macros.h" +#include "utils/VectorDrawableUtils.h" + +#include <math.h> +#include <string.h> + +namespace android { +namespace uirenderer { +namespace VectorDrawable { + +const int Tree::MAX_CACHED_BITMAP_SIZE = 2048; + +void Path::draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, float scaleX, float scaleY, + bool useStagingData) { + float matrixScale = getMatrixScale(groupStackedMatrix); + if (matrixScale == 0) { + // When either x or y is scaled to 0, we don't need to draw anything. + return; + } + + SkMatrix pathMatrix(groupStackedMatrix); + pathMatrix.postScale(scaleX, scaleY); + + //TODO: try apply the path matrix to the canvas instead of creating a new path. + SkPath renderPath; + renderPath.reset(); + + if (useStagingData) { + SkPath tmpPath; + getStagingPath(&tmpPath); + renderPath.addPath(tmpPath, pathMatrix); + } else { + renderPath.addPath(getUpdatedPath(), pathMatrix); + } + + float minScale = fmin(scaleX, scaleY); + float strokeScale = minScale * matrixScale; + drawPath(outCanvas, renderPath, strokeScale, pathMatrix, useStagingData); +} + +void Path::dump() { + ALOGD("Path: %s has %zu points", mName.c_str(), mProperties.getData().points.size()); +} + +float Path::getMatrixScale(const SkMatrix& groupStackedMatrix) { + // Given unit vectors A = (0, 1) and B = (1, 0). + // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'. + // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)), + // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|); + // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0. + // + // For non-skew case, which is most of the cases, matrix scale is computing exactly the + // scale on x and y axis, and take the minimal of these two. + // For skew case, an unit square will mapped to a parallelogram. And this function will + // return the minimal height of the 2 bases. + SkVector skVectors[2]; + skVectors[0].set(0, 1); + skVectors[1].set(1, 0); + groupStackedMatrix.mapVectors(skVectors, 2); + float scaleX = hypotf(skVectors[0].fX, skVectors[0].fY); + float scaleY = hypotf(skVectors[1].fX, skVectors[1].fY); + float crossProduct = skVectors[0].cross(skVectors[1]); + float maxScale = fmax(scaleX, scaleY); + + float matrixScale = 0; + if (maxScale > 0) { + matrixScale = fabs(crossProduct) / maxScale; + } + return matrixScale; +} + +// Called from UI thread during the initial setup/theme change. +Path::Path(const char* pathStr, size_t strLength) { + PathParser::ParseResult result; + Data data; + PathParser::getPathDataFromAsciiString(&data, &result, pathStr, strLength); + mStagingProperties.setData(data); +} + +Path::Path(const Path& path) : Node(path) { + mStagingProperties.syncProperties(path.mStagingProperties); +} + +const SkPath& Path::getUpdatedPath() { + if (mSkPathDirty) { + mSkPath.reset(); + VectorDrawableUtils::verbsToPath(&mSkPath, mProperties.getData()); + mSkPathDirty = false; + } + return mSkPath; +} + +void Path::getStagingPath(SkPath* outPath) { + outPath->reset(); + VectorDrawableUtils::verbsToPath(outPath, mStagingProperties.getData()); +} + +void Path::syncProperties() { + if (mStagingPropertiesDirty) { + mProperties.syncProperties(mStagingProperties); + } else { + mStagingProperties.syncProperties(mProperties); + } + mStagingPropertiesDirty = false; +} + +FullPath::FullPath(const FullPath& path) : Path(path) { + mStagingProperties.syncProperties(path.mStagingProperties); +} + +static void applyTrim(SkPath* outPath, const SkPath& inPath, float trimPathStart, float trimPathEnd, + float trimPathOffset) { + if (trimPathStart == 0.0f && trimPathEnd == 1.0f) { + *outPath = inPath; + return; + } + outPath->reset(); + if (trimPathStart == trimPathEnd) { + // Trimmed path should be empty. + return; + } + SkPathMeasure measure(inPath, false); + float len = SkScalarToFloat(measure.getLength()); + float start = len * fmod((trimPathStart + trimPathOffset), 1.0f); + float end = len * fmod((trimPathEnd + trimPathOffset), 1.0f); + + if (start > end) { + measure.getSegment(start, len, outPath, true); + if (end > 0) { + measure.getSegment(0, end, outPath, true); + } + } else { + measure.getSegment(start, end, outPath, true); + } +} + +const SkPath& FullPath::getUpdatedPath() { + if (!mSkPathDirty && !mProperties.mTrimDirty) { + return mTrimmedSkPath; + } + Path::getUpdatedPath(); + if (mProperties.getTrimPathStart() != 0.0f || mProperties.getTrimPathEnd() != 1.0f) { + mProperties.mTrimDirty = false; + applyTrim(&mTrimmedSkPath, mSkPath, mProperties.getTrimPathStart(), + mProperties.getTrimPathEnd(), mProperties.getTrimPathOffset()); + return mTrimmedSkPath; + } else { + return mSkPath; + } +} + +void FullPath::getStagingPath(SkPath* outPath) { + Path::getStagingPath(outPath); + SkPath inPath = *outPath; + applyTrim(outPath, inPath, mStagingProperties.getTrimPathStart(), + mStagingProperties.getTrimPathEnd(), mStagingProperties.getTrimPathOffset()); +} + +void FullPath::dump() { + Path::dump(); + ALOGD("stroke width, color, alpha: %f, %d, %f, fill color, alpha: %d, %f", + mProperties.getStrokeWidth(), mProperties.getStrokeColor(), mProperties.getStrokeAlpha(), + mProperties.getFillColor(), mProperties.getFillAlpha()); +} + + +inline SkColor applyAlpha(SkColor color, float alpha) { + int alphaBytes = SkColorGetA(color); + return SkColorSetA(color, alphaBytes * alpha); +} + +void FullPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, float strokeScale, + const SkMatrix& matrix, bool useStagingData){ + const FullPathProperties& properties = useStagingData ? mStagingProperties : mProperties; + + // Draw path's fill, if fill color or gradient is valid + bool needsFill = false; + SkPaint paint; + if (properties.getFillGradient() != nullptr) { + paint.setColor(applyAlpha(SK_ColorBLACK, properties.getFillAlpha())); + SkShader* newShader = properties.getFillGradient()->newWithLocalMatrix(matrix); + paint.setShader(newShader); + needsFill = true; + } else if (properties.getFillColor() != SK_ColorTRANSPARENT) { + paint.setColor(applyAlpha(properties.getFillColor(), properties.getFillAlpha())); + needsFill = true; + } + + if (needsFill) { + paint.setStyle(SkPaint::Style::kFill_Style); + paint.setAntiAlias(true); + SkPath::FillType ft = static_cast<SkPath::FillType>(properties.getFillType()); + renderPath.setFillType(ft); + outCanvas->drawPath(renderPath, paint); + } + + // Draw path's stroke, if stroke color or Gradient is valid + bool needsStroke = false; + if (properties.getStrokeGradient() != nullptr) { + paint.setColor(applyAlpha(SK_ColorBLACK, properties.getStrokeAlpha())); + SkShader* newShader = properties.getStrokeGradient()->newWithLocalMatrix(matrix); + paint.setShader(newShader); + needsStroke = true; + } else if (properties.getStrokeColor() != SK_ColorTRANSPARENT) { + paint.setColor(applyAlpha(properties.getStrokeColor(), properties.getStrokeAlpha())); + needsStroke = true; + } + if (needsStroke) { + paint.setStyle(SkPaint::Style::kStroke_Style); + paint.setAntiAlias(true); + paint.setStrokeJoin(SkPaint::Join(properties.getStrokeLineJoin())); + paint.setStrokeCap(SkPaint::Cap(properties.getStrokeLineCap())); + paint.setStrokeMiter(properties.getStrokeMiterLimit()); + paint.setStrokeWidth(properties.getStrokeWidth() * strokeScale); + outCanvas->drawPath(renderPath, paint); + } +} + +void FullPath::syncProperties() { + Path::syncProperties(); + + if (mStagingPropertiesDirty) { + mProperties.syncProperties(mStagingProperties); + } else { + // Update staging property with property values from animation. + mStagingProperties.syncProperties(mProperties); + } + mStagingPropertiesDirty = false; +} + +REQUIRE_COMPATIBLE_LAYOUT(FullPath::FullPathProperties::PrimitiveFields); + +static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t"); +static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t"); + +bool FullPath::FullPathProperties::copyProperties(int8_t* outProperties, int length) const { + int propertyDataSize = sizeof(FullPathProperties::PrimitiveFields); + if (length != propertyDataSize) { + LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", + propertyDataSize, length); + return false; + } + + PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties); + *out = mPrimitiveFields; + return true; +} + +void FullPath::FullPathProperties::setColorPropertyValue(int propertyId, int32_t value) { + Property currentProperty = static_cast<Property>(propertyId); + if (currentProperty == Property::strokeColor) { + setStrokeColor(value); + } else if (currentProperty == Property::fillColor) { + setFillColor(value); + } else { + LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property" + " with id: %d", propertyId); + } +} + +void FullPath::FullPathProperties::setPropertyValue(int propertyId, float value) { + Property property = static_cast<Property>(propertyId); + switch (property) { + case Property::strokeWidth: + setStrokeWidth(value); + break; + case Property::strokeAlpha: + setStrokeAlpha(value); + break; + case Property::fillAlpha: + setFillAlpha(value); + break; + case Property::trimPathStart: + setTrimPathStart(value); + break; + case Property::trimPathEnd: + setTrimPathEnd(value); + break; + case Property::trimPathOffset: + setTrimPathOffset(value); + break; + default: + LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId); + break; + } +} + +void ClipPath::drawPath(SkCanvas* outCanvas, SkPath& renderPath, + float strokeScale, const SkMatrix& matrix, bool useStagingData){ + outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op); +} + +Group::Group(const Group& group) : Node(group) { + mStagingProperties.syncProperties(group.mStagingProperties); +} + +void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX, + float scaleY, bool useStagingData) { + // TODO: Try apply the matrix to the canvas instead of passing it down the tree + + // Calculate current group's matrix by preConcat the parent's and + // and the current one on the top of the stack. + // Basically the Mfinal = Mviewport * M0 * M1 * M2; + // Mi the local matrix at level i of the group tree. + SkMatrix stackedMatrix; + const GroupProperties& prop = useStagingData ? mStagingProperties : mProperties; + getLocalMatrix(&stackedMatrix, prop); + stackedMatrix.postConcat(currentMatrix); + + // Save the current clip information, which is local to this group. + outCanvas->save(); + // Draw the group tree in the same order as the XML file. + for (auto& child : mChildren) { + child->draw(outCanvas, stackedMatrix, scaleX, scaleY, useStagingData); + } + // Restore the previous clip information. + outCanvas->restore(); +} + +void Group::dump() { + ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size()); + ALOGD("Group translateX, Y : %f, %f, scaleX, Y: %f, %f", mProperties.getTranslateX(), + mProperties.getTranslateY(), mProperties.getScaleX(), mProperties.getScaleY()); + for (size_t i = 0; i < mChildren.size(); i++) { + mChildren[i]->dump(); + } +} + +void Group::syncProperties() { + // Copy over the dirty staging properties + if (mStagingPropertiesDirty) { + mProperties.syncProperties(mStagingProperties); + } else { + mStagingProperties.syncProperties(mProperties); + } + mStagingPropertiesDirty = false; + for (auto& child : mChildren) { + child->syncProperties(); + } +} + +void Group::getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties) { + outMatrix->reset(); + // TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of + // translating to pivot for rotating and scaling, then translating back. + outMatrix->postTranslate(-properties.getPivotX(), -properties.getPivotY()); + outMatrix->postScale(properties.getScaleX(), properties.getScaleY()); + outMatrix->postRotate(properties.getRotation(), 0, 0); + outMatrix->postTranslate(properties.getTranslateX() + properties.getPivotX(), + properties.getTranslateY() + properties.getPivotY()); +} + +void Group::addChild(Node* child) { + mChildren.emplace_back(child); + if (mPropertyChangedListener != nullptr) { + child->setPropertyChangedListener(mPropertyChangedListener); + } +} + +bool Group::GroupProperties::copyProperties(float* outProperties, int length) const { + int propertyCount = static_cast<int>(Property::count); + if (length != propertyCount) { + LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", + propertyCount, length); + return false; + } + + PrimitiveFields* out = reinterpret_cast<PrimitiveFields*>(outProperties); + *out = mPrimitiveFields; + return true; +} + +// TODO: Consider animating the properties as float pointers +// Called on render thread +float Group::GroupProperties::getPropertyValue(int propertyId) const { + Property currentProperty = static_cast<Property>(propertyId); + switch (currentProperty) { + case Property::rotate: + return getRotation(); + case Property::pivotX: + return getPivotX(); + case Property::pivotY: + return getPivotY(); + case Property::scaleX: + return getScaleX(); + case Property::scaleY: + return getScaleY(); + case Property::translateX: + return getTranslateX(); + case Property::translateY: + return getTranslateY(); + default: + LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); + return 0; + } +} + +// Called on render thread +void Group::GroupProperties::setPropertyValue(int propertyId, float value) { + Property currentProperty = static_cast<Property>(propertyId); + switch (currentProperty) { + case Property::rotate: + setRotation(value); + break; + case Property::pivotX: + setPivotX(value); + break; + case Property::pivotY: + setPivotY(value); + break; + case Property::scaleX: + setScaleX(value); + break; + case Property::scaleY: + setScaleY(value); + break; + case Property::translateX: + setTranslateX(value); + break; + case Property::translateY: + setTranslateY(value); + break; + default: + LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId); + } +} + +bool Group::isValidProperty(int propertyId) { + return GroupProperties::isValidProperty(propertyId); +} + +bool Group::GroupProperties::isValidProperty(int propertyId) { + return propertyId >= 0 && propertyId < static_cast<int>(Property::count); +} + +void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter, + const SkRect& bounds, bool needsMirroring, bool canReuseCache) { + // The imageView can scale the canvas in different ways, in order to + // avoid blurry scaling, we have to draw into a bitmap with exact pixel + // size first. This bitmap size is determined by the bounds and the + // canvas scale. + SkMatrix canvasMatrix; + outCanvas->getMatrix(&canvasMatrix); + float canvasScaleX = 1.0f; + float canvasScaleY = 1.0f; + if (canvasMatrix.getSkewX() == 0 && canvasMatrix.getSkewY() == 0) { + // Only use the scale value when there's no skew or rotation in the canvas matrix. + // TODO: Add a cts test for drawing VD on a canvas with negative scaling factors. + canvasScaleX = fabs(canvasMatrix.getScaleX()); + canvasScaleY = fabs(canvasMatrix.getScaleY()); + } + int scaledWidth = (int) (bounds.width() * canvasScaleX); + int scaledHeight = (int) (bounds.height() * canvasScaleY); + scaledWidth = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledWidth); + scaledHeight = std::min(Tree::MAX_CACHED_BITMAP_SIZE, scaledHeight); + + if (scaledWidth <= 0 || scaledHeight <= 0) { + return; + } + + mStagingProperties.setScaledSize(scaledWidth, scaledHeight); + int saveCount = outCanvas->save(SaveFlags::MatrixClip); + outCanvas->translate(bounds.fLeft, bounds.fTop); + + // Handle RTL mirroring. + if (needsMirroring) { + outCanvas->translate(bounds.width(), 0); + outCanvas->scale(-1.0f, 1.0f); + } + mStagingProperties.setColorFilter(colorFilter); + + // At this point, canvas has been translated to the right position. + // And we use this bound for the destination rect for the drawBitmap, so + // we offset to (0, 0); + SkRect tmpBounds = bounds; + tmpBounds.offsetTo(0, 0); + mStagingProperties.setBounds(tmpBounds); + outCanvas->drawVectorDrawable(this); + outCanvas->restoreToCount(saveCount); +} + +void Tree::drawStaging(Canvas* outCanvas) { + bool redrawNeeded = allocateBitmapIfNeeded(&mStagingCache.bitmap, + mStagingProperties.getScaledWidth(), mStagingProperties.getScaledHeight()); + // draw bitmap cache + if (redrawNeeded || mStagingCache.dirty) { + updateBitmapCache(&mStagingCache.bitmap, true); + mStagingCache.dirty = false; + } + + SkPaint tmpPaint; + SkPaint* paint = updatePaint(&tmpPaint, &mStagingProperties); + outCanvas->drawBitmap(mStagingCache.bitmap, 0, 0, + mStagingCache.bitmap.width(), mStagingCache.bitmap.height(), + mStagingProperties.getBounds().left(), mStagingProperties.getBounds().top(), + mStagingProperties.getBounds().right(), mStagingProperties.getBounds().bottom(), paint); +} + +SkPaint* Tree::getPaint() { + return updatePaint(&mPaint, &mProperties); +} + +// Update the given paint with alpha and color filter. Return nullptr if no color filter is +// specified and root alpha is 1. Otherwise, return updated paint. +SkPaint* Tree::updatePaint(SkPaint* outPaint, TreeProperties* prop) { + if (prop->getRootAlpha() == 1.0f && prop->getColorFilter() == nullptr) { + return nullptr; + } else { + outPaint->setColorFilter(mStagingProperties.getColorFilter()); + outPaint->setFilterQuality(kLow_SkFilterQuality); + outPaint->setAlpha(prop->getRootAlpha() * 255); + return outPaint; + } +} + +const SkBitmap& Tree::getBitmapUpdateIfDirty() { + bool redrawNeeded = allocateBitmapIfNeeded(&mCache.bitmap, mProperties.getScaledWidth(), + mProperties.getScaledHeight()); + if (redrawNeeded || mCache.dirty) { + updateBitmapCache(&mCache.bitmap, false); + mCache.dirty = false; + } + return mCache.bitmap; +} + +void Tree::updateBitmapCache(SkBitmap* outCache, bool useStagingData) { + outCache->eraseColor(SK_ColorTRANSPARENT); + SkCanvas outCanvas(*outCache); + float viewportWidth = useStagingData ? + mStagingProperties.getViewportWidth() : mProperties.getViewportWidth(); + float viewportHeight = useStagingData ? + mStagingProperties.getViewportHeight() : mProperties.getViewportHeight(); + float scaleX = outCache->width() / viewportWidth; + float scaleY = outCache->height() / viewportHeight; + mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY, useStagingData); +} + +bool Tree::allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height) { + if (!canReuseBitmap(*outCache, width, height)) { + SkImageInfo info = SkImageInfo::Make(width, height, + kN32_SkColorType, kPremul_SkAlphaType); + outCache->setInfo(info); + // TODO: Count the bitmap cache against app's java heap + outCache->allocPixels(info); + return true; + } + return false; +} + +bool Tree::canReuseBitmap(const SkBitmap& bitmap, int width, int height) { + return width == bitmap.width() && height == bitmap.height(); +} + +void Tree::onPropertyChanged(TreeProperties* prop) { + if (prop == &mStagingProperties) { + mStagingCache.dirty = true; + } else { + mCache.dirty = true; + } +} + +}; // namespace VectorDrawable + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h new file mode 100644 index 000000000000..1c6f48e7276b --- /dev/null +++ b/libs/hwui/VectorDrawable.h @@ -0,0 +1,720 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_VPATH_H +#define ANDROID_HWUI_VPATH_H + +#include "hwui/Canvas.h" +#include "DisplayList.h" + +#include <SkBitmap.h> +#include <SkColor.h> +#include <SkColorFilter.h> +#include <SkCanvas.h> +#include <SkMatrix.h> +#include <SkPaint.h> +#include <SkPath.h> +#include <SkPathMeasure.h> +#include <SkRect.h> +#include <SkShader.h> + +#include <cutils/compiler.h> +#include <stddef.h> +#include <vector> +#include <string> + +namespace android { +namespace uirenderer { + +namespace VectorDrawable { +#define VD_SET_PRIMITIVE_FIELD_WITH_FLAG(field, value, flag) (VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(field, (value)) ? ((flag) = true, true) : false) +#define VD_SET_PROP(field, value) ((value) != (field) ? ((field) = (value), true) : false) +#define VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(field, value) ({ bool retVal = VD_SET_PROP((mPrimitiveFields.field), (value));\ + onPropertyChanged(); retVal;}) +#define UPDATE_SKPROP(field, value) ({bool retVal = ((field) != (value)); if ((field) != (value)) SkRefCnt_SafeAssign((field), (value)); retVal;}) + +/* A VectorDrawable is composed of a tree of nodes. + * Each node can be a group node, or a path. + * A group node can have groups or paths as children, but a path node has + * no children. + * One example can be: + * Root Group + * / | \ + * Group Path Group + * / \ | + * Path Path Path + * + * VectorDrawables are drawn into bitmap caches first, then the caches are drawn to the given + * canvas with root alpha applied. Two caches are maintained for VD, one in UI thread, the other in + * Render Thread. A generation id is used to keep track of changes in the vector drawable tree. + * Each cache has their own generation id to track whether they are up to date with the latest + * change in the tree. + * + * Any property change to the vector drawable coming from UI thread (such as bulk setters to update + * all the properties, and viewport change, etc.) are only modifying the staging properties. The + * staging properties will then be marked dirty and will be pushed over to render thread properties + * at sync point. If staging properties are not dirty at sync point, we sync backwards by updating + * staging properties with render thread properties to reflect the latest animation value. + * + */ + +class PropertyChangedListener { +public: + PropertyChangedListener(bool* dirty, bool* stagingDirty) + : mDirty(dirty), mStagingDirty(stagingDirty) {} + void onPropertyChanged() { + *mDirty = true; + } + void onStagingPropertyChanged() { + *mStagingDirty = true; + } +private: + bool* mDirty; + bool* mStagingDirty; +}; + +class ANDROID_API Node { +public: + class Properties { + public: + Properties(Node* node) : mNode(node) {} + inline void onPropertyChanged() { + mNode->onPropertyChanged(this); + } + private: + Node* mNode; + }; + Node(const Node& node) { + mName = node.mName; + } + Node() {} + virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, + float scaleX, float scaleY, bool useStagingData) = 0; + virtual void dump() = 0; + void setName(const char* name) { + mName = name; + } + virtual void setPropertyChangedListener(PropertyChangedListener* listener) { + mPropertyChangedListener = listener; + } + virtual void onPropertyChanged(Properties* properties) = 0; + virtual ~Node(){} + virtual void syncProperties() = 0; +protected: + std::string mName; + PropertyChangedListener* mPropertyChangedListener = nullptr; +}; + +class ANDROID_API Path : public Node { +public: + struct ANDROID_API Data { + std::vector<char> verbs; + std::vector<size_t> verbSizes; + std::vector<float> points; + bool operator==(const Data& data) const { + return verbs == data.verbs && verbSizes == data.verbSizes + && points == data.points; + } + }; + + class PathProperties : public Properties { + public: + PathProperties(Node* node) : Properties(node) {} + void syncProperties(const PathProperties& prop) { + mData = prop.mData; + onPropertyChanged(); + } + void setData(const Data& data) { + // Updates the path data. Note that we don't generate a new Skia path right away + // because there are cases where the animation is changing the path data, but the view + // that hosts the VD has gone off screen, in which case we won't even draw. So we + // postpone the Skia path generation to the draw time. + if (data == mData) { + return; + } + mData = data; + onPropertyChanged(); + + } + const Data& getData() const { + return mData; + } + private: + Data mData; + }; + + Path(const Path& path); + Path(const char* path, size_t strLength); + Path() {} + + void dump() override; + void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, + float scaleX, float scaleY, bool useStagingData) override; + static float getMatrixScale(const SkMatrix& groupStackedMatrix); + virtual void syncProperties() override; + virtual void onPropertyChanged(Properties* prop) override { + if (prop == &mStagingProperties) { + mStagingPropertiesDirty = true; + if (mPropertyChangedListener) { + mPropertyChangedListener->onStagingPropertyChanged(); + } + } else if (prop == &mProperties){ + mSkPathDirty = true; + if (mPropertyChangedListener) { + mPropertyChangedListener->onPropertyChanged(); + } + } + } + PathProperties* mutateStagingProperties() { return &mStagingProperties; } + const PathProperties* stagingProperties() { return &mStagingProperties; } + + // This should only be called from animations on RT + PathProperties* mutateProperties() { return &mProperties; } + +protected: + virtual const SkPath& getUpdatedPath(); + virtual void getStagingPath(SkPath* outPath); + virtual void drawPath(SkCanvas *outCanvas, SkPath& renderPath, + float strokeScale, const SkMatrix& matrix, bool useStagingData) = 0; + + // Internal data, render thread only. + bool mSkPathDirty = true; + SkPath mSkPath; + +private: + PathProperties mProperties = PathProperties(this); + PathProperties mStagingProperties = PathProperties(this); + bool mStagingPropertiesDirty = true; +}; + +class ANDROID_API FullPath: public Path { +public: + class FullPathProperties : public Properties { + public: + struct PrimitiveFields { + float strokeWidth = 0; + SkColor strokeColor = SK_ColorTRANSPARENT; + float strokeAlpha = 1; + SkColor fillColor = SK_ColorTRANSPARENT; + float fillAlpha = 1; + float trimPathStart = 0; + float trimPathEnd = 1; + float trimPathOffset = 0; + int32_t strokeLineCap = SkPaint::Cap::kButt_Cap; + int32_t strokeLineJoin = SkPaint::Join::kMiter_Join; + float strokeMiterLimit = 4; + int fillType = 0; /* non-zero or kWinding_FillType in Skia */ + }; + FullPathProperties(Node* mNode) : Properties(mNode), mTrimDirty(false) {} + ~FullPathProperties() { + SkSafeUnref(fillGradient); + SkSafeUnref(strokeGradient); + } + void syncProperties(const FullPathProperties& prop) { + mPrimitiveFields = prop.mPrimitiveFields; + mTrimDirty = true; + UPDATE_SKPROP(fillGradient, prop.fillGradient); + UPDATE_SKPROP(strokeGradient, prop.strokeGradient); + onPropertyChanged(); + } + void setFillGradient(SkShader* gradient) { + if(UPDATE_SKPROP(fillGradient, gradient)) { + onPropertyChanged(); + } + } + void setStrokeGradient(SkShader* gradient) { + if(UPDATE_SKPROP(strokeGradient, gradient)) { + onPropertyChanged(); + } + } + SkShader* getFillGradient() const { + return fillGradient; + } + SkShader* getStrokeGradient() const { + return strokeGradient; + } + float getStrokeWidth() const{ + return mPrimitiveFields.strokeWidth; + } + void setStrokeWidth(float strokeWidth) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth); + } + SkColor getStrokeColor() const{ + return mPrimitiveFields.strokeColor; + } + void setStrokeColor(SkColor strokeColor) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeColor, strokeColor); + } + float getStrokeAlpha() const{ + return mPrimitiveFields.strokeAlpha; + } + void setStrokeAlpha(float strokeAlpha) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeAlpha, strokeAlpha); + } + SkColor getFillColor() const { + return mPrimitiveFields.fillColor; + } + void setFillColor(SkColor fillColor) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(fillColor, fillColor); + } + float getFillAlpha() const{ + return mPrimitiveFields.fillAlpha; + } + void setFillAlpha(float fillAlpha) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(fillAlpha, fillAlpha); + } + float getTrimPathStart() const{ + return mPrimitiveFields.trimPathStart; + } + void setTrimPathStart(float trimPathStart) { + VD_SET_PRIMITIVE_FIELD_WITH_FLAG(trimPathStart, trimPathStart, mTrimDirty); + } + float getTrimPathEnd() const{ + return mPrimitiveFields.trimPathEnd; + } + void setTrimPathEnd(float trimPathEnd) { + VD_SET_PRIMITIVE_FIELD_WITH_FLAG(trimPathEnd, trimPathEnd, mTrimDirty); + } + float getTrimPathOffset() const{ + return mPrimitiveFields.trimPathOffset; + } + void setTrimPathOffset(float trimPathOffset) { + VD_SET_PRIMITIVE_FIELD_WITH_FLAG(trimPathOffset, trimPathOffset, mTrimDirty); + } + + float getStrokeMiterLimit() const { + return mPrimitiveFields.strokeMiterLimit; + } + float getStrokeLineCap() const { + return mPrimitiveFields.strokeLineCap; + } + float getStrokeLineJoin() const { + return mPrimitiveFields.strokeLineJoin; + } + float getFillType() const { + return mPrimitiveFields.fillType; + } + bool copyProperties(int8_t* outProperties, int length) const; + void updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha, + SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, + float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin, + int fillType) { + mPrimitiveFields.strokeWidth = strokeWidth; + mPrimitiveFields.strokeColor = strokeColor; + mPrimitiveFields.strokeAlpha = strokeAlpha; + mPrimitiveFields.fillColor = fillColor; + mPrimitiveFields.fillAlpha = fillAlpha; + mPrimitiveFields.trimPathStart = trimPathStart; + mPrimitiveFields.trimPathEnd = trimPathEnd; + mPrimitiveFields.trimPathOffset = trimPathOffset; + mPrimitiveFields.strokeMiterLimit = strokeMiterLimit; + mPrimitiveFields.strokeLineCap = strokeLineCap; + mPrimitiveFields.strokeLineJoin = strokeLineJoin; + mPrimitiveFields.fillType = fillType; + mTrimDirty = true; + onPropertyChanged(); + } + // Set property values during animation + void setColorPropertyValue(int propertyId, int32_t value); + void setPropertyValue(int propertyId, float value); + bool mTrimDirty; + private: + enum class Property { + strokeWidth = 0, + strokeColor, + strokeAlpha, + fillColor, + fillAlpha, + trimPathStart, + trimPathEnd, + trimPathOffset, + strokeLineCap, + strokeLineJoin, + strokeMiterLimit, + fillType, + count, + }; + PrimitiveFields mPrimitiveFields; + SkShader* fillGradient = nullptr; + SkShader* strokeGradient = nullptr; + }; + + // Called from UI thread + FullPath(const FullPath& path); // for cloning + FullPath(const char* path, size_t strLength) : Path(path, strLength) {} + FullPath() : Path() {} + void dump() override; + FullPathProperties* mutateStagingProperties() { return &mStagingProperties; } + const FullPathProperties* stagingProperties() { return &mStagingProperties; } + + // This should only be called from animations on RT + FullPathProperties* mutateProperties() { return &mProperties; } + + virtual void syncProperties() override; + virtual void onPropertyChanged(Properties* properties) override { + Path::onPropertyChanged(properties); + if (properties == &mStagingProperties) { + mStagingPropertiesDirty = true; + if (mPropertyChangedListener) { + mPropertyChangedListener->onStagingPropertyChanged(); + } + } else if (properties == &mProperties) { + if (mPropertyChangedListener) { + mPropertyChangedListener->onPropertyChanged(); + } + } + } + +protected: + const SkPath& getUpdatedPath() override; + void getStagingPath(SkPath* outPath) override; + void drawPath(SkCanvas* outCanvas, SkPath& renderPath, + float strokeScale, const SkMatrix& matrix, bool useStagingData) override; +private: + + FullPathProperties mProperties = FullPathProperties(this); + FullPathProperties mStagingProperties = FullPathProperties(this); + bool mStagingPropertiesDirty = true; + + // Intermediate data for drawing, render thread only + SkPath mTrimmedSkPath; + +}; + +class ANDROID_API ClipPath: public Path { +public: + ClipPath(const ClipPath& path) : Path(path) {} + ClipPath(const char* path, size_t strLength) : Path(path, strLength) {} + ClipPath() : Path() {} + +protected: + void drawPath(SkCanvas* outCanvas, SkPath& renderPath, + float strokeScale, const SkMatrix& matrix, bool useStagingData) override; +}; + +class ANDROID_API Group: public Node { +public: + class GroupProperties : public Properties { + public: + GroupProperties(Node* mNode) : Properties(mNode) {} + struct PrimitiveFields { + float rotate = 0; + float pivotX = 0; + float pivotY = 0; + float scaleX = 1; + float scaleY = 1; + float translateX = 0; + float translateY = 0; + } mPrimitiveFields; + void syncProperties(const GroupProperties& prop) { + mPrimitiveFields = prop.mPrimitiveFields; + onPropertyChanged(); + } + float getRotation() const { + return mPrimitiveFields.rotate; + } + void setRotation(float rotation) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(rotate, rotation); + } + float getPivotX() const { + return mPrimitiveFields.pivotX; + } + void setPivotX(float pivotX) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(pivotX, pivotX); + } + float getPivotY() const { + return mPrimitiveFields.pivotY; + } + void setPivotY(float pivotY) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(pivotY, pivotY); + } + float getScaleX() const { + return mPrimitiveFields.scaleX; + } + void setScaleX(float scaleX) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(scaleX, scaleX); + } + float getScaleY() const { + return mPrimitiveFields.scaleY; + } + void setScaleY(float scaleY) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(scaleY, scaleY); + } + float getTranslateX() const { + return mPrimitiveFields.translateX; + } + void setTranslateX(float translateX) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(translateX, translateX); + } + float getTranslateY() const { + return mPrimitiveFields.translateY; + } + void setTranslateY(float translateY) { + VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(translateY, translateY); + } + void updateProperties(float rotate, float pivotX, float pivotY, + float scaleX, float scaleY, float translateX, float translateY) { + mPrimitiveFields.rotate = rotate; + mPrimitiveFields.pivotX = pivotX; + mPrimitiveFields.pivotY = pivotY; + mPrimitiveFields.scaleX = scaleX; + mPrimitiveFields.scaleY = scaleY; + mPrimitiveFields.translateX = translateX; + mPrimitiveFields.translateY = translateY; + onPropertyChanged(); + } + void setPropertyValue(int propertyId, float value); + float getPropertyValue(int propertyId) const; + bool copyProperties(float* outProperties, int length) const; + static bool isValidProperty(int propertyId); + private: + enum class Property { + rotate = 0, + pivotX, + pivotY, + scaleX, + scaleY, + translateX, + translateY, + // Count of the properties, must be at the end. + count, + }; + }; + + Group(const Group& group); + Group() {} + void addChild(Node* child); + virtual void setPropertyChangedListener(PropertyChangedListener* listener) override { + Node::setPropertyChangedListener(listener); + for (auto& child : mChildren) { + child->setPropertyChangedListener(listener); + } + } + virtual void syncProperties() override; + GroupProperties* mutateStagingProperties() { return &mStagingProperties; } + const GroupProperties* stagingProperties() { return &mStagingProperties; } + + // This should only be called from animations on RT + GroupProperties* mutateProperties() { return &mProperties; } + + // Methods below could be called from either UI thread or Render Thread. + virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, + float scaleX, float scaleY, bool useStagingData) override; + void getLocalMatrix(SkMatrix* outMatrix, const GroupProperties& properties); + void dump() override; + static bool isValidProperty(int propertyId); + + virtual void onPropertyChanged(Properties* properties) override { + if (properties == &mStagingProperties) { + mStagingPropertiesDirty = true; + if (mPropertyChangedListener) { + mPropertyChangedListener->onStagingPropertyChanged(); + } + } else { + if (mPropertyChangedListener) { + mPropertyChangedListener->onPropertyChanged(); + } + } + } + +private: + GroupProperties mProperties = GroupProperties(this); + GroupProperties mStagingProperties = GroupProperties(this); + bool mStagingPropertiesDirty = true; + std::vector< std::unique_ptr<Node> > mChildren; +}; + +class ANDROID_API Tree : public VirtualLightRefBase { +public: + Tree(Group* rootNode) : mRootNode(rootNode) { + mRootNode->setPropertyChangedListener(&mPropertyChangedListener); + } + void draw(Canvas* outCanvas, SkColorFilter* colorFilter, + const SkRect& bounds, bool needsMirroring, bool canReuseCache); + void drawStaging(Canvas* canvas); + + const SkBitmap& getBitmapUpdateIfDirty(); + void setAllowCaching(bool allowCaching) { + mAllowCaching = allowCaching; + } + SkPaint* getPaint(); + void syncProperties() { + if (mStagingProperties.mNonAnimatablePropertiesDirty) { + mProperties.syncNonAnimatableProperties(mStagingProperties); + mStagingProperties.mNonAnimatablePropertiesDirty = false; + } + + if (mStagingProperties.mAnimatablePropertiesDirty) { + mProperties.syncAnimatableProperties(mStagingProperties); + } else { + mStagingProperties.syncAnimatableProperties(mProperties); + } + mStagingProperties.mAnimatablePropertiesDirty = false; + mRootNode->syncProperties(); + } + + class TreeProperties { + public: + TreeProperties(Tree* tree) : mTree(tree) {} + // Properties that can only be modified by UI thread, therefore sync should + // only go from UI to RT + struct NonAnimatableProperties { + float viewportWidth = 0; + float viewportHeight = 0; + SkRect bounds; + int scaledWidth = 0; + int scaledHeight = 0; + SkColorFilter* colorFilter = nullptr; + ~NonAnimatableProperties() { + SkSafeUnref(colorFilter); + } + } mNonAnimatableProperties; + bool mNonAnimatablePropertiesDirty = true; + + float mRootAlpha = 1.0f; + bool mAnimatablePropertiesDirty = true; + + void syncNonAnimatableProperties(const TreeProperties& prop) { + // Copy over the data that can only be changed in UI thread + if (mNonAnimatableProperties.colorFilter != prop.mNonAnimatableProperties.colorFilter) { + SkRefCnt_SafeAssign(mNonAnimatableProperties.colorFilter, + prop.mNonAnimatableProperties.colorFilter); + } + mNonAnimatableProperties = prop.mNonAnimatableProperties; + } + + void setViewportSize(float width, float height) { + if (mNonAnimatableProperties.viewportWidth != width + || mNonAnimatableProperties.viewportHeight != height) { + mNonAnimatablePropertiesDirty = true; + mNonAnimatableProperties.viewportWidth = width; + mNonAnimatableProperties.viewportHeight = height; + mTree->onPropertyChanged(this); + } + } + void setBounds(const SkRect& bounds) { + if (mNonAnimatableProperties.bounds != bounds) { + mNonAnimatableProperties.bounds = bounds; + mNonAnimatablePropertiesDirty = true; + mTree->onPropertyChanged(this); + } + } + + void setScaledSize(int width, int height) { + if (mNonAnimatableProperties.scaledWidth != width + || mNonAnimatableProperties.scaledHeight != height) { + mNonAnimatableProperties.scaledWidth = width; + mNonAnimatableProperties.scaledHeight = height; + mNonAnimatablePropertiesDirty = true; + mTree->onPropertyChanged(this); + } + } + void setColorFilter(SkColorFilter* filter) { + if (UPDATE_SKPROP(mNonAnimatableProperties.colorFilter, filter)) { + mNonAnimatablePropertiesDirty = true; + mTree->onPropertyChanged(this); + } + } + SkColorFilter* getColorFilter() const{ + return mNonAnimatableProperties.colorFilter; + } + + float getViewportWidth() const { + return mNonAnimatableProperties.viewportWidth; + } + float getViewportHeight() const { + return mNonAnimatableProperties.viewportHeight; + } + float getScaledWidth() const { + return mNonAnimatableProperties.scaledWidth; + } + float getScaledHeight() const { + return mNonAnimatableProperties.scaledHeight; + } + void syncAnimatableProperties(const TreeProperties& prop) { + mRootAlpha = prop.mRootAlpha; + } + bool setRootAlpha(float rootAlpha) { + if (rootAlpha != mRootAlpha) { + mAnimatablePropertiesDirty = true; + mRootAlpha = rootAlpha; + mTree->onPropertyChanged(this); + return true; + } + return false; + } + float getRootAlpha() const { return mRootAlpha;} + const SkRect& getBounds() const { + return mNonAnimatableProperties.bounds; + } + Tree* mTree; + }; + void onPropertyChanged(TreeProperties* prop); + TreeProperties* mutateStagingProperties() { return &mStagingProperties; } + const TreeProperties* stagingProperties() { return &mStagingProperties; } + PushStagingFunctor* getFunctor() { return &mFunctor;} + + // This should only be called from animations on RT + TreeProperties* mutateProperties() { return &mProperties; } + +private: + class VectorDrawableFunctor : public PushStagingFunctor { + public: + VectorDrawableFunctor(Tree* tree) : mTree(tree) {} + virtual void operator ()() { + mTree->syncProperties(); + } + private: + Tree* mTree; + }; + + SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop); + bool allocateBitmapIfNeeded(SkBitmap* outCache, int width, int height); + bool canReuseBitmap(const SkBitmap&, int width, int height); + void updateBitmapCache(SkBitmap* outCache, bool useStagingData); + // Cap the bitmap size, such that it won't hurt the performance too much + // and it won't crash due to a very large scale. + // The drawable will look blurry above this size. + const static int MAX_CACHED_BITMAP_SIZE; + + bool mAllowCaching = true; + std::unique_ptr<Group> mRootNode; + + TreeProperties mProperties = TreeProperties(this); + TreeProperties mStagingProperties = TreeProperties(this); + + VectorDrawableFunctor mFunctor = VectorDrawableFunctor(this); + + SkPaint mPaint; + struct Cache { + SkBitmap bitmap; + bool dirty = true; + }; + + Cache mStagingCache; + Cache mCache; + + PropertyChangedListener mPropertyChangedListener + = PropertyChangedListener(&mCache.dirty, &mStagingCache.dirty); +}; + +} // namespace VectorDrawable + +typedef VectorDrawable::Path::Data PathData; +} // namespace uirenderer +} // namespace android + +#endif // ANDROID_HWUI_VPATH_H diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h index 11d0c4bef84f..c1bf980658b2 100644 --- a/libs/hwui/Vertex.h +++ b/libs/hwui/Vertex.h @@ -37,7 +37,6 @@ struct Vertex { */ static float GeometryFudgeFactor() { return 0.0656f; } - float x, y; static inline void set(Vertex* vertex, float x, float y) { diff --git a/libs/hwui/VertexBuffer.h b/libs/hwui/VertexBuffer.h index 9be4d8487505..bdb5b7b381bf 100644 --- a/libs/hwui/VertexBuffer.h +++ b/libs/hwui/VertexBuffer.h @@ -17,7 +17,7 @@ #ifndef ANDROID_HWUI_VERTEX_BUFFER_H #define ANDROID_HWUI_VERTEX_BUFFER_H -#include "utils/MathUtils.h" +#include <algorithm> namespace android { namespace uirenderer { @@ -118,7 +118,7 @@ public: TYPE* end = current + vertexCount; mBounds.set(current->x, current->y, current->x, current->y); for (; current < end; current++) { - mBounds.expandToCoverVertex(current->x, current->y); + mBounds.expandToCover(current->x, current->y); } } @@ -129,10 +129,10 @@ public: unsigned int getSize() const { return mByteCount; } unsigned int getIndexCount() const { return mIndexCount; } void updateIndexCount(unsigned int newCount) { - mIndexCount = MathUtils::min(newCount, mAllocatedIndexCount); + mIndexCount = std::min(newCount, mAllocatedIndexCount); } void updateVertexCount(unsigned int newCount) { - mVertexCount = MathUtils::min(newCount, mAllocatedVertexCount); + mVertexCount = std::min(newCount, mAllocatedVertexCount); } MeshFeatureFlags getMeshFeatureFlags() const { return mMeshFeatureFlags; } void setMeshFeatureFlags(int flags) { diff --git a/libs/hwui/tests/nullegl.cpp b/libs/hwui/debug/nullegl.cpp index b6cc2f247627..b6cc2f247627 100644 --- a/libs/hwui/tests/nullegl.cpp +++ b/libs/hwui/debug/nullegl.cpp diff --git a/libs/hwui/tests/nullgles.cpp b/libs/hwui/debug/nullgles.cpp index 8ca7598a91a0..8689f9814f7b 100644 --- a/libs/hwui/tests/nullgles.cpp +++ b/libs/hwui/debug/nullgles.cpp @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "unwrap_gles.h" + #include <GLES3/gl3.h> #include <GLES2/gl2ext.h> @@ -131,6 +133,15 @@ void glGetIntegerv(GLenum pname, GLint *data) { } } +GLenum glCheckFramebufferStatus(GLenum target) { + switch (target) { + case GL_FRAMEBUFFER: + return GL_FRAMEBUFFER_COMPLETE; + default: + return 0; // error case + } +} + const char* getString(GLenum name) { switch (name) { case GL_VENDOR: @@ -261,8 +272,6 @@ void glInsertEventMarkerEXT(GLsizei length, const GLchar *marker) {} void glPushGroupMarkerEXT(GLsizei length, const GLchar *marker) {} void glPopGroupMarkerEXT(void) {} void glDiscardFramebufferEXT(GLenum target, GLsizei numAttachments, const GLenum *attachments) {} -void glStartTilingQCOM(GLuint x, GLuint y, GLuint width, GLuint height, GLbitfield preserveMask) {} -void glEndTilingQCOM(GLbitfield preserveMask) {} void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) {} // GLES3 diff --git a/libs/hwui/debug/unwrap_gles.h b/libs/hwui/debug/unwrap_gles.h new file mode 100644 index 000000000000..7716a735a63b --- /dev/null +++ b/libs/hwui/debug/unwrap_gles.h @@ -0,0 +1,918 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifdef HWUI_GLES_WRAP_ENABLED +#undef HWUI_GLES_WRAP_ENABLED + +#undef glActiveShaderProgram +#undef glActiveShaderProgramEXT +#undef glActiveTexture +#undef glAlphaFunc +#undef glAlphaFuncQCOM +#undef glAlphaFuncx +#undef glAlphaFuncxOES +#undef glApplyFramebufferAttachmentCMAAINTEL +#undef glAttachShader +#undef glBeginConditionalRenderNV +#undef glBeginPerfMonitorAMD +#undef glBeginPerfQueryINTEL +#undef glBeginQuery +#undef glBeginQueryEXT +#undef glBeginTransformFeedback +#undef glBindAttribLocation +#undef glBindBuffer +#undef glBindBufferBase +#undef glBindBufferRange +#undef glBindFragDataLocationEXT +#undef glBindFragDataLocationIndexedEXT +#undef glBindFramebuffer +#undef glBindFramebufferOES +#undef glBindImageTexture +#undef glBindProgramPipeline +#undef glBindProgramPipelineEXT +#undef glBindRenderbuffer +#undef glBindRenderbufferOES +#undef glBindSampler +#undef glBindTexture +#undef glBindTransformFeedback +#undef glBindVertexArray +#undef glBindVertexArrayOES +#undef glBindVertexBuffer +#undef glBlendBarrier +#undef glBlendBarrierKHR +#undef glBlendBarrierNV +#undef glBlendColor +#undef glBlendEquation +#undef glBlendEquationOES +#undef glBlendEquationSeparate +#undef glBlendEquationSeparateOES +#undef glBlendEquationSeparatei +#undef glBlendEquationSeparateiEXT +#undef glBlendEquationSeparateiOES +#undef glBlendEquationi +#undef glBlendEquationiEXT +#undef glBlendEquationiOES +#undef glBlendFunc +#undef glBlendFuncSeparate +#undef glBlendFuncSeparateOES +#undef glBlendFuncSeparatei +#undef glBlendFuncSeparateiEXT +#undef glBlendFuncSeparateiOES +#undef glBlendFunci +#undef glBlendFunciEXT +#undef glBlendFunciOES +#undef glBlendParameteriNV +#undef glBlitFramebuffer +#undef glBlitFramebufferANGLE +#undef glBlitFramebufferNV +#undef glBufferData +#undef glBufferStorageEXT +#undef glBufferSubData +#undef glCheckFramebufferStatus +#undef glCheckFramebufferStatusOES +#undef glClear +#undef glClearBufferfi +#undef glClearBufferfv +#undef glClearBufferiv +#undef glClearBufferuiv +#undef glClearColor +#undef glClearColorx +#undef glClearColorxOES +#undef glClearDepthf +#undef glClearDepthfOES +#undef glClearDepthx +#undef glClearDepthxOES +#undef glClearStencil +#undef glClientActiveTexture +#undef glClientWaitSync +#undef glClientWaitSyncAPPLE +#undef glClipPlanef +#undef glClipPlanefIMG +#undef glClipPlanefOES +#undef glClipPlanex +#undef glClipPlanexIMG +#undef glClipPlanexOES +#undef glColor4f +#undef glColor4ub +#undef glColor4x +#undef glColor4xOES +#undef glColorMask +#undef glColorMaski +#undef glColorMaskiEXT +#undef glColorMaskiOES +#undef glColorPointer +#undef glCompileShader +#undef glCompressedTexImage2D +#undef glCompressedTexImage3D +#undef glCompressedTexImage3DOES +#undef glCompressedTexSubImage2D +#undef glCompressedTexSubImage3D +#undef glCompressedTexSubImage3DOES +#undef glCopyBufferSubData +#undef glCopyBufferSubDataNV +#undef glCopyImageSubData +#undef glCopyImageSubDataEXT +#undef glCopyImageSubDataOES +#undef glCopyPathNV +#undef glCopyTexImage2D +#undef glCopyTexSubImage2D +#undef glCopyTexSubImage3D +#undef glCopyTexSubImage3DOES +#undef glCopyTextureLevelsAPPLE +#undef glCoverFillPathInstancedNV +#undef glCoverFillPathNV +#undef glCoverStrokePathInstancedNV +#undef glCoverStrokePathNV +#undef glCoverageMaskNV +#undef glCoverageModulationNV +#undef glCoverageModulationTableNV +#undef glCoverageOperationNV +#undef glCreatePerfQueryINTEL +#undef glCreateProgram +#undef glCreateShader +#undef glCreateShaderProgramv +#undef glCreateShaderProgramvEXT +#undef glCullFace +#undef glCurrentPaletteMatrixOES +#undef glDebugMessageCallback +#undef glDebugMessageCallbackKHR +#undef glDebugMessageControl +#undef glDebugMessageControlKHR +#undef glDebugMessageInsert +#undef glDebugMessageInsertKHR +#undef glDeleteBuffers +#undef glDeleteFencesNV +#undef glDeleteFramebuffers +#undef glDeleteFramebuffersOES +#undef glDeletePathsNV +#undef glDeletePerfMonitorsAMD +#undef glDeletePerfQueryINTEL +#undef glDeleteProgram +#undef glDeleteProgramPipelines +#undef glDeleteProgramPipelinesEXT +#undef glDeleteQueries +#undef glDeleteQueriesEXT +#undef glDeleteRenderbuffers +#undef glDeleteRenderbuffersOES +#undef glDeleteSamplers +#undef glDeleteShader +#undef glDeleteSync +#undef glDeleteSyncAPPLE +#undef glDeleteTextures +#undef glDeleteTransformFeedbacks +#undef glDeleteVertexArrays +#undef glDeleteVertexArraysOES +#undef glDepthFunc +#undef glDepthMask +#undef glDepthRangeArrayfvNV +#undef glDepthRangeIndexedfNV +#undef glDepthRangef +#undef glDepthRangefOES +#undef glDepthRangex +#undef glDepthRangexOES +#undef glDetachShader +#undef glDisable +#undef glDisableClientState +#undef glDisableDriverControlQCOM +#undef glDisableVertexAttribArray +#undef glDisablei +#undef glDisableiEXT +#undef glDisableiNV +#undef glDisableiOES +#undef glDiscardFramebufferEXT +#undef glDispatchCompute +#undef glDispatchComputeIndirect +#undef glDrawArrays +#undef glDrawArraysIndirect +#undef glDrawArraysInstanced +#undef glDrawArraysInstancedANGLE +#undef glDrawArraysInstancedBaseInstanceEXT +#undef glDrawArraysInstancedEXT +#undef glDrawArraysInstancedNV +#undef glDrawBuffers +#undef glDrawBuffersEXT +#undef glDrawBuffersIndexedEXT +#undef glDrawBuffersNV +#undef glDrawElements +#undef glDrawElementsBaseVertex +#undef glDrawElementsBaseVertexEXT +#undef glDrawElementsBaseVertexOES +#undef glDrawElementsIndirect +#undef glDrawElementsInstanced +#undef glDrawElementsInstancedANGLE +#undef glDrawElementsInstancedBaseInstanceEXT +#undef glDrawElementsInstancedBaseVertex +#undef glDrawElementsInstancedBaseVertexBaseInstanceEXT +#undef glDrawElementsInstancedBaseVertexEXT +#undef glDrawElementsInstancedBaseVertexOES +#undef glDrawElementsInstancedEXT +#undef glDrawElementsInstancedNV +#undef glDrawRangeElements +#undef glDrawRangeElementsBaseVertex +#undef glDrawRangeElementsBaseVertexEXT +#undef glDrawRangeElementsBaseVertexOES +#undef glDrawTexfOES +#undef glDrawTexfvOES +#undef glDrawTexiOES +#undef glDrawTexivOES +#undef glDrawTexsOES +#undef glDrawTexsvOES +#undef glDrawTexxOES +#undef glDrawTexxvOES +#undef glEGLImageTargetRenderbufferStorageOES +#undef glEGLImageTargetTexture2DOES +#undef glEnable +#undef glEnableClientState +#undef glEnableDriverControlQCOM +#undef glEnableVertexAttribArray +#undef glEnablei +#undef glEnableiEXT +#undef glEnableiNV +#undef glEnableiOES +#undef glEndConditionalRenderNV +#undef glEndPerfMonitorAMD +#undef glEndPerfQueryINTEL +#undef glEndQuery +#undef glEndQueryEXT +#undef glEndTilingQCOM +#undef glEndTransformFeedback +#undef glExtGetBufferPointervQCOM +#undef glExtGetBuffersQCOM +#undef glExtGetFramebuffersQCOM +#undef glExtGetProgramBinarySourceQCOM +#undef glExtGetProgramsQCOM +#undef glExtGetRenderbuffersQCOM +#undef glExtGetShadersQCOM +#undef glExtGetTexLevelParameterivQCOM +#undef glExtGetTexSubImageQCOM +#undef glExtGetTexturesQCOM +#undef glExtIsProgramBinaryQCOM +#undef glExtTexObjectStateOverrideiQCOM +#undef glFenceSync +#undef glFenceSyncAPPLE +#undef glFinish +#undef glFinishFenceNV +#undef glFlush +#undef glFlushMappedBufferRange +#undef glFlushMappedBufferRangeEXT +#undef glFogf +#undef glFogfv +#undef glFogx +#undef glFogxOES +#undef glFogxv +#undef glFogxvOES +#undef glFragmentCoverageColorNV +#undef glFramebufferParameteri +#undef glFramebufferRenderbuffer +#undef glFramebufferRenderbufferOES +#undef glFramebufferSampleLocationsfvNV +#undef glFramebufferTexture +#undef glFramebufferTexture2D +#undef glFramebufferTexture2DMultisampleEXT +#undef glFramebufferTexture2DMultisampleIMG +#undef glFramebufferTexture2DOES +#undef glFramebufferTexture3DOES +#undef glFramebufferTextureEXT +#undef glFramebufferTextureLayer +#undef glFramebufferTextureMultisampleMultiviewOVR +#undef glFramebufferTextureMultiviewOVR +#undef glFramebufferTextureOES +#undef glFrontFace +#undef glFrustumf +#undef glFrustumfOES +#undef glFrustumx +#undef glFrustumxOES +#undef glGenBuffers +#undef glGenFencesNV +#undef glGenFramebuffers +#undef glGenFramebuffersOES +#undef glGenPathsNV +#undef glGenPerfMonitorsAMD +#undef glGenProgramPipelines +#undef glGenProgramPipelinesEXT +#undef glGenQueries +#undef glGenQueriesEXT +#undef glGenRenderbuffers +#undef glGenRenderbuffersOES +#undef glGenSamplers +#undef glGenTextures +#undef glGenTransformFeedbacks +#undef glGenVertexArrays +#undef glGenVertexArraysOES +#undef glGenerateMipmap +#undef glGenerateMipmapOES +#undef glGetActiveAttrib +#undef glGetActiveUniform +#undef glGetActiveUniformBlockName +#undef glGetActiveUniformBlockiv +#undef glGetActiveUniformsiv +#undef glGetAttachedShaders +#undef glGetAttribLocation +#undef glGetBooleani_v +#undef glGetBooleanv +#undef glGetBufferParameteri64v +#undef glGetBufferParameteriv +#undef glGetBufferPointerv +#undef glGetBufferPointervOES +#undef glGetClipPlanef +#undef glGetClipPlanefOES +#undef glGetClipPlanex +#undef glGetClipPlanexOES +#undef glGetCoverageModulationTableNV +#undef glGetDebugMessageLog +#undef glGetDebugMessageLogKHR +#undef glGetDriverControlStringQCOM +#undef glGetDriverControlsQCOM +#undef glGetError +#undef glGetFenceivNV +#undef glGetFirstPerfQueryIdINTEL +#undef glGetFixedv +#undef glGetFixedvOES +#undef glGetFloati_vNV +#undef glGetFloatv +#undef glGetFragDataIndexEXT +#undef glGetFragDataLocation +#undef glGetFramebufferAttachmentParameteriv +#undef glGetFramebufferAttachmentParameterivOES +#undef glGetFramebufferParameteriv +#undef glGetGraphicsResetStatus +#undef glGetGraphicsResetStatusEXT +#undef glGetGraphicsResetStatusKHR +#undef glGetImageHandleNV +#undef glGetInteger64i_v +#undef glGetInteger64v +#undef glGetInteger64vAPPLE +#undef glGetIntegeri_v +#undef glGetIntegeri_vEXT +#undef glGetIntegerv +#undef glGetInternalformatSampleivNV +#undef glGetInternalformativ +#undef glGetLightfv +#undef glGetLightxv +#undef glGetLightxvOES +#undef glGetMaterialfv +#undef glGetMaterialxv +#undef glGetMaterialxvOES +#undef glGetMultisamplefv +#undef glGetNextPerfQueryIdINTEL +#undef glGetObjectLabel +#undef glGetObjectLabelEXT +#undef glGetObjectLabelKHR +#undef glGetObjectPtrLabel +#undef glGetObjectPtrLabelKHR +#undef glGetPathCommandsNV +#undef glGetPathCoordsNV +#undef glGetPathDashArrayNV +#undef glGetPathLengthNV +#undef glGetPathMetricRangeNV +#undef glGetPathMetricsNV +#undef glGetPathParameterfvNV +#undef glGetPathParameterivNV +#undef glGetPathSpacingNV +#undef glGetPerfCounterInfoINTEL +#undef glGetPerfMonitorCounterDataAMD +#undef glGetPerfMonitorCounterInfoAMD +#undef glGetPerfMonitorCounterStringAMD +#undef glGetPerfMonitorCountersAMD +#undef glGetPerfMonitorGroupStringAMD +#undef glGetPerfMonitorGroupsAMD +#undef glGetPerfQueryDataINTEL +#undef glGetPerfQueryIdByNameINTEL +#undef glGetPerfQueryInfoINTEL +#undef glGetPointerv +#undef glGetPointervKHR +#undef glGetProgramBinary +#undef glGetProgramBinaryOES +#undef glGetProgramInfoLog +#undef glGetProgramInterfaceiv +#undef glGetProgramPipelineInfoLog +#undef glGetProgramPipelineInfoLogEXT +#undef glGetProgramPipelineiv +#undef glGetProgramPipelineivEXT +#undef glGetProgramResourceIndex +#undef glGetProgramResourceLocation +#undef glGetProgramResourceLocationIndexEXT +#undef glGetProgramResourceName +#undef glGetProgramResourcefvNV +#undef glGetProgramResourceiv +#undef glGetProgramiv +#undef glGetQueryObjecti64vEXT +#undef glGetQueryObjectivEXT +#undef glGetQueryObjectui64vEXT +#undef glGetQueryObjectuiv +#undef glGetQueryObjectuivEXT +#undef glGetQueryiv +#undef glGetQueryivEXT +#undef glGetRenderbufferParameteriv +#undef glGetRenderbufferParameterivOES +#undef glGetSamplerParameterIiv +#undef glGetSamplerParameterIivEXT +#undef glGetSamplerParameterIivOES +#undef glGetSamplerParameterIuiv +#undef glGetSamplerParameterIuivEXT +#undef glGetSamplerParameterIuivOES +#undef glGetSamplerParameterfv +#undef glGetSamplerParameteriv +#undef glGetShaderInfoLog +#undef glGetShaderPrecisionFormat +#undef glGetShaderSource +#undef glGetShaderiv +#undef glGetString +#undef glGetStringi +#undef glGetSynciv +#undef glGetSyncivAPPLE +#undef glGetTexEnvfv +#undef glGetTexEnviv +#undef glGetTexEnvxv +#undef glGetTexEnvxvOES +#undef glGetTexGenfvOES +#undef glGetTexGenivOES +#undef glGetTexGenxvOES +#undef glGetTexLevelParameterfv +#undef glGetTexLevelParameteriv +#undef glGetTexParameterIiv +#undef glGetTexParameterIivEXT +#undef glGetTexParameterIivOES +#undef glGetTexParameterIuiv +#undef glGetTexParameterIuivEXT +#undef glGetTexParameterIuivOES +#undef glGetTexParameterfv +#undef glGetTexParameteriv +#undef glGetTexParameterxv +#undef glGetTexParameterxvOES +#undef glGetTextureHandleNV +#undef glGetTextureSamplerHandleNV +#undef glGetTransformFeedbackVarying +#undef glGetTranslatedShaderSourceANGLE +#undef glGetUniformBlockIndex +#undef glGetUniformIndices +#undef glGetUniformLocation +#undef glGetUniformfv +#undef glGetUniformiv +#undef glGetUniformuiv +#undef glGetVertexAttribIiv +#undef glGetVertexAttribIuiv +#undef glGetVertexAttribPointerv +#undef glGetVertexAttribfv +#undef glGetVertexAttribiv +#undef glGetnUniformfv +#undef glGetnUniformfvEXT +#undef glGetnUniformfvKHR +#undef glGetnUniformiv +#undef glGetnUniformivEXT +#undef glGetnUniformivKHR +#undef glGetnUniformuiv +#undef glGetnUniformuivKHR +#undef glHint +#undef glInsertEventMarkerEXT +#undef glInterpolatePathsNV +#undef glInvalidateFramebuffer +#undef glInvalidateSubFramebuffer +#undef glIsBuffer +#undef glIsEnabled +#undef glIsEnabledi +#undef glIsEnablediEXT +#undef glIsEnablediNV +#undef glIsEnablediOES +#undef glIsFenceNV +#undef glIsFramebuffer +#undef glIsFramebufferOES +#undef glIsImageHandleResidentNV +#undef glIsPathNV +#undef glIsPointInFillPathNV +#undef glIsPointInStrokePathNV +#undef glIsProgram +#undef glIsProgramPipeline +#undef glIsProgramPipelineEXT +#undef glIsQuery +#undef glIsQueryEXT +#undef glIsRenderbuffer +#undef glIsRenderbufferOES +#undef glIsSampler +#undef glIsShader +#undef glIsSync +#undef glIsSyncAPPLE +#undef glIsTexture +#undef glIsTextureHandleResidentNV +#undef glIsTransformFeedback +#undef glIsVertexArray +#undef glIsVertexArrayOES +#undef glLabelObjectEXT +#undef glLightModelf +#undef glLightModelfv +#undef glLightModelx +#undef glLightModelxOES +#undef glLightModelxv +#undef glLightModelxvOES +#undef glLightf +#undef glLightfv +#undef glLightx +#undef glLightxOES +#undef glLightxv +#undef glLightxvOES +#undef glLineWidth +#undef glLineWidthx +#undef glLineWidthxOES +#undef glLinkProgram +#undef glLoadIdentity +#undef glLoadMatrixf +#undef glLoadMatrixx +#undef glLoadMatrixxOES +#undef glLoadPaletteFromModelViewMatrixOES +#undef glLogicOp +#undef glMakeImageHandleNonResidentNV +#undef glMakeImageHandleResidentNV +#undef glMakeTextureHandleNonResidentNV +#undef glMakeTextureHandleResidentNV +#undef glMapBufferOES +#undef glMapBufferRange +#undef glMapBufferRangeEXT +#undef glMaterialf +#undef glMaterialfv +#undef glMaterialx +#undef glMaterialxOES +#undef glMaterialxv +#undef glMaterialxvOES +#undef glMatrixIndexPointerOES +#undef glMatrixLoad3x2fNV +#undef glMatrixLoad3x3fNV +#undef glMatrixLoadTranspose3x3fNV +#undef glMatrixMode +#undef glMatrixMult3x2fNV +#undef glMatrixMult3x3fNV +#undef glMatrixMultTranspose3x3fNV +#undef glMemoryBarrier +#undef glMemoryBarrierByRegion +#undef glMinSampleShading +#undef glMinSampleShadingOES +#undef glMultMatrixf +#undef glMultMatrixx +#undef glMultMatrixxOES +#undef glMultiDrawArraysEXT +#undef glMultiDrawArraysIndirectEXT +#undef glMultiDrawElementsBaseVertexEXT +#undef glMultiDrawElementsBaseVertexOES +#undef glMultiDrawElementsEXT +#undef glMultiDrawElementsIndirectEXT +#undef glMultiTexCoord4f +#undef glMultiTexCoord4x +#undef glMultiTexCoord4xOES +#undef glNamedFramebufferSampleLocationsfvNV +#undef glNormal3f +#undef glNormal3x +#undef glNormal3xOES +#undef glNormalPointer +#undef glObjectLabel +#undef glObjectLabelKHR +#undef glObjectPtrLabel +#undef glObjectPtrLabelKHR +#undef glOrthof +#undef glOrthofOES +#undef glOrthox +#undef glOrthoxOES +#undef glPatchParameteri +#undef glPatchParameteriEXT +#undef glPatchParameteriOES +#undef glPathCommandsNV +#undef glPathCoordsNV +#undef glPathCoverDepthFuncNV +#undef glPathDashArrayNV +#undef glPathGlyphIndexArrayNV +#undef glPathGlyphIndexRangeNV +#undef glPathGlyphRangeNV +#undef glPathGlyphsNV +#undef glPathMemoryGlyphIndexArrayNV +#undef glPathParameterfNV +#undef glPathParameterfvNV +#undef glPathParameteriNV +#undef glPathParameterivNV +#undef glPathStencilDepthOffsetNV +#undef glPathStencilFuncNV +#undef glPathStringNV +#undef glPathSubCommandsNV +#undef glPathSubCoordsNV +#undef glPauseTransformFeedback +#undef glPixelStorei +#undef glPointAlongPathNV +#undef glPointParameterf +#undef glPointParameterfv +#undef glPointParameterx +#undef glPointParameterxOES +#undef glPointParameterxv +#undef glPointParameterxvOES +#undef glPointSize +#undef glPointSizePointerOES +#undef glPointSizex +#undef glPointSizexOES +#undef glPolygonModeNV +#undef glPolygonOffset +#undef glPolygonOffsetx +#undef glPolygonOffsetxOES +#undef glPopDebugGroup +#undef glPopDebugGroupKHR +#undef glPopGroupMarkerEXT +#undef glPopMatrix +#undef glPrimitiveBoundingBox +#undef glPrimitiveBoundingBoxEXT +#undef glPrimitiveBoundingBoxOES +#undef glProgramBinary +#undef glProgramBinaryOES +#undef glProgramParameteri +#undef glProgramParameteriEXT +#undef glProgramPathFragmentInputGenNV +#undef glProgramUniform1f +#undef glProgramUniform1fEXT +#undef glProgramUniform1fv +#undef glProgramUniform1fvEXT +#undef glProgramUniform1i +#undef glProgramUniform1iEXT +#undef glProgramUniform1iv +#undef glProgramUniform1ivEXT +#undef glProgramUniform1ui +#undef glProgramUniform1uiEXT +#undef glProgramUniform1uiv +#undef glProgramUniform1uivEXT +#undef glProgramUniform2f +#undef glProgramUniform2fEXT +#undef glProgramUniform2fv +#undef glProgramUniform2fvEXT +#undef glProgramUniform2i +#undef glProgramUniform2iEXT +#undef glProgramUniform2iv +#undef glProgramUniform2ivEXT +#undef glProgramUniform2ui +#undef glProgramUniform2uiEXT +#undef glProgramUniform2uiv +#undef glProgramUniform2uivEXT +#undef glProgramUniform3f +#undef glProgramUniform3fEXT +#undef glProgramUniform3fv +#undef glProgramUniform3fvEXT +#undef glProgramUniform3i +#undef glProgramUniform3iEXT +#undef glProgramUniform3iv +#undef glProgramUniform3ivEXT +#undef glProgramUniform3ui +#undef glProgramUniform3uiEXT +#undef glProgramUniform3uiv +#undef glProgramUniform3uivEXT +#undef glProgramUniform4f +#undef glProgramUniform4fEXT +#undef glProgramUniform4fv +#undef glProgramUniform4fvEXT +#undef glProgramUniform4i +#undef glProgramUniform4iEXT +#undef glProgramUniform4iv +#undef glProgramUniform4ivEXT +#undef glProgramUniform4ui +#undef glProgramUniform4uiEXT +#undef glProgramUniform4uiv +#undef glProgramUniform4uivEXT +#undef glProgramUniformHandleui64NV +#undef glProgramUniformHandleui64vNV +#undef glProgramUniformMatrix2fv +#undef glProgramUniformMatrix2fvEXT +#undef glProgramUniformMatrix2x3fv +#undef glProgramUniformMatrix2x3fvEXT +#undef glProgramUniformMatrix2x4fv +#undef glProgramUniformMatrix2x4fvEXT +#undef glProgramUniformMatrix3fv +#undef glProgramUniformMatrix3fvEXT +#undef glProgramUniformMatrix3x2fv +#undef glProgramUniformMatrix3x2fvEXT +#undef glProgramUniformMatrix3x4fv +#undef glProgramUniformMatrix3x4fvEXT +#undef glProgramUniformMatrix4fv +#undef glProgramUniformMatrix4fvEXT +#undef glProgramUniformMatrix4x2fv +#undef glProgramUniformMatrix4x2fvEXT +#undef glProgramUniformMatrix4x3fv +#undef glProgramUniformMatrix4x3fvEXT +#undef glPushDebugGroup +#undef glPushDebugGroupKHR +#undef glPushGroupMarkerEXT +#undef glPushMatrix +#undef glQueryCounterEXT +#undef glQueryMatrixxOES +#undef glRasterSamplesEXT +#undef glReadBuffer +#undef glReadBufferIndexedEXT +#undef glReadBufferNV +#undef glReadPixels +#undef glReadnPixels +#undef glReadnPixelsEXT +#undef glReadnPixelsKHR +#undef glReleaseShaderCompiler +#undef glRenderbufferStorage +#undef glRenderbufferStorageMultisample +#undef glRenderbufferStorageMultisampleANGLE +#undef glRenderbufferStorageMultisampleAPPLE +#undef glRenderbufferStorageMultisampleEXT +#undef glRenderbufferStorageMultisampleIMG +#undef glRenderbufferStorageMultisampleNV +#undef glRenderbufferStorageOES +#undef glResolveDepthValuesNV +#undef glResolveMultisampleFramebufferAPPLE +#undef glResumeTransformFeedback +#undef glRotatef +#undef glRotatex +#undef glRotatexOES +#undef glSampleCoverage +#undef glSampleCoveragex +#undef glSampleCoveragexOES +#undef glSampleMaski +#undef glSamplerParameterIiv +#undef glSamplerParameterIivEXT +#undef glSamplerParameterIivOES +#undef glSamplerParameterIuiv +#undef glSamplerParameterIuivEXT +#undef glSamplerParameterIuivOES +#undef glSamplerParameterf +#undef glSamplerParameterfv +#undef glSamplerParameteri +#undef glSamplerParameteriv +#undef glScalef +#undef glScalex +#undef glScalexOES +#undef glScissor +#undef glScissorArrayvNV +#undef glScissorIndexedNV +#undef glScissorIndexedvNV +#undef glSelectPerfMonitorCountersAMD +#undef glSetFenceNV +#undef glShadeModel +#undef glShaderBinary +#undef glShaderSource +#undef glStartTilingQCOM +#undef glStencilFillPathInstancedNV +#undef glStencilFillPathNV +#undef glStencilFunc +#undef glStencilFuncSeparate +#undef glStencilMask +#undef glStencilMaskSeparate +#undef glStencilOp +#undef glStencilOpSeparate +#undef glStencilStrokePathInstancedNV +#undef glStencilStrokePathNV +#undef glStencilThenCoverFillPathInstancedNV +#undef glStencilThenCoverFillPathNV +#undef glStencilThenCoverStrokePathInstancedNV +#undef glStencilThenCoverStrokePathNV +#undef glSubpixelPrecisionBiasNV +#undef glTestFenceNV +#undef glTexBuffer +#undef glTexBufferEXT +#undef glTexBufferOES +#undef glTexBufferRange +#undef glTexBufferRangeEXT +#undef glTexBufferRangeOES +#undef glTexCoordPointer +#undef glTexEnvf +#undef glTexEnvfv +#undef glTexEnvi +#undef glTexEnviv +#undef glTexEnvx +#undef glTexEnvxOES +#undef glTexEnvxv +#undef glTexEnvxvOES +#undef glTexGenfOES +#undef glTexGenfvOES +#undef glTexGeniOES +#undef glTexGenivOES +#undef glTexGenxOES +#undef glTexGenxvOES +#undef glTexImage2D +#undef glTexImage3D +#undef glTexImage3DOES +#undef glTexPageCommitmentEXT +#undef glTexParameterIiv +#undef glTexParameterIivEXT +#undef glTexParameterIivOES +#undef glTexParameterIuiv +#undef glTexParameterIuivEXT +#undef glTexParameterIuivOES +#undef glTexParameterf +#undef glTexParameterfv +#undef glTexParameteri +#undef glTexParameteriv +#undef glTexParameterx +#undef glTexParameterxOES +#undef glTexParameterxv +#undef glTexParameterxvOES +#undef glTexStorage1DEXT +#undef glTexStorage2D +#undef glTexStorage2DEXT +#undef glTexStorage2DMultisample +#undef glTexStorage3D +#undef glTexStorage3DEXT +#undef glTexStorage3DMultisample +#undef glTexStorage3DMultisampleOES +#undef glTexSubImage2D +#undef glTexSubImage3D +#undef glTexSubImage3DOES +#undef glTextureStorage1DEXT +#undef glTextureStorage2DEXT +#undef glTextureStorage3DEXT +#undef glTextureViewEXT +#undef glTextureViewOES +#undef glTransformFeedbackVaryings +#undef glTransformPathNV +#undef glTranslatef +#undef glTranslatex +#undef glTranslatexOES +#undef glUniform1f +#undef glUniform1fv +#undef glUniform1i +#undef glUniform1iv +#undef glUniform1ui +#undef glUniform1uiv +#undef glUniform2f +#undef glUniform2fv +#undef glUniform2i +#undef glUniform2iv +#undef glUniform2ui +#undef glUniform2uiv +#undef glUniform3f +#undef glUniform3fv +#undef glUniform3i +#undef glUniform3iv +#undef glUniform3ui +#undef glUniform3uiv +#undef glUniform4f +#undef glUniform4fv +#undef glUniform4i +#undef glUniform4iv +#undef glUniform4ui +#undef glUniform4uiv +#undef glUniformBlockBinding +#undef glUniformHandleui64NV +#undef glUniformHandleui64vNV +#undef glUniformMatrix2fv +#undef glUniformMatrix2x3fv +#undef glUniformMatrix2x3fvNV +#undef glUniformMatrix2x4fv +#undef glUniformMatrix2x4fvNV +#undef glUniformMatrix3fv +#undef glUniformMatrix3x2fv +#undef glUniformMatrix3x2fvNV +#undef glUniformMatrix3x4fv +#undef glUniformMatrix3x4fvNV +#undef glUniformMatrix4fv +#undef glUniformMatrix4x2fv +#undef glUniformMatrix4x2fvNV +#undef glUniformMatrix4x3fv +#undef glUniformMatrix4x3fvNV +#undef glUnmapBuffer +#undef glUnmapBufferOES +#undef glUseProgram +#undef glUseProgramStages +#undef glUseProgramStagesEXT +#undef glValidateProgram +#undef glValidateProgramPipeline +#undef glValidateProgramPipelineEXT +#undef glVertexAttrib1f +#undef glVertexAttrib1fv +#undef glVertexAttrib2f +#undef glVertexAttrib2fv +#undef glVertexAttrib3f +#undef glVertexAttrib3fv +#undef glVertexAttrib4f +#undef glVertexAttrib4fv +#undef glVertexAttribBinding +#undef glVertexAttribDivisor +#undef glVertexAttribDivisorANGLE +#undef glVertexAttribDivisorEXT +#undef glVertexAttribDivisorNV +#undef glVertexAttribFormat +#undef glVertexAttribI4i +#undef glVertexAttribI4iv +#undef glVertexAttribI4ui +#undef glVertexAttribI4uiv +#undef glVertexAttribIFormat +#undef glVertexAttribIPointer +#undef glVertexAttribPointer +#undef glVertexBindingDivisor +#undef glVertexPointer +#undef glViewport +#undef glViewportArrayvNV +#undef glViewportIndexedfNV +#undef glViewportIndexedfvNV +#undef glWaitSync +#undef glWaitSyncAPPLE +#undef glWeightPathsNV +#undef glWeightPointerOES + +#endif // HWUI_GLES_WRAP_ENABLED diff --git a/libs/hwui/debug/wrap_gles.cpp b/libs/hwui/debug/wrap_gles.cpp new file mode 100644 index 000000000000..c4f2e3537fe8 --- /dev/null +++ b/libs/hwui/debug/wrap_gles.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2016 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 "unwrap_gles.h" + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES/gl.h> +#include <GLES/glext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES3/gl3.h> +#include <GLES3/gl31.h> +#include <GLES3/gl32.h> + +#include <cutils/log.h> + +void assertNoGlErrors(const char* apicall) { + GLenum status = GL_NO_ERROR; + GLenum lastError = GL_NO_ERROR; + const char* lastErrorName = nullptr; + while ((status = glGetError()) != GL_NO_ERROR) { + lastError = status; + switch (status) { + case GL_INVALID_ENUM: + ALOGE("GL error: GL_INVALID_ENUM"); + lastErrorName = "GL_INVALID_ENUM"; + break; + case GL_INVALID_VALUE: + ALOGE("GL error: GL_INVALID_VALUE"); + lastErrorName = "GL_INVALID_VALUE"; + break; + case GL_INVALID_OPERATION: + ALOGE("GL error: GL_INVALID_OPERATION"); + lastErrorName = "GL_INVALID_OPERATION"; + break; + case GL_OUT_OF_MEMORY: + ALOGE("GL error: Out of memory!"); + lastErrorName = "GL_OUT_OF_MEMORY"; + break; + default: + ALOGE("GL error: 0x%x", status); + lastErrorName = "UNKNOWN"; + } + } + LOG_ALWAYS_FATAL_IF(lastError != GL_NO_ERROR, + "%s error! %s (0x%x)", apicall, lastErrorName, lastError); +} + +#define API_ENTRY(x) wrap_##x +#define CALL_GL_API(x, ...) x(__VA_ARGS__); assertNoGlErrors(#x) +#define CALL_GL_API_RETURN(x, ...) auto ret = x(__VA_ARGS__);\ + assertNoGlErrors(#x);\ + return ret + +extern "C" { +#include <gl2_api.in> +#include <gl2ext_api.in> + +// libGLESv2 handles these specially, so they are not in gl2_api.in + +void API_ENTRY(glGetBooleanv)(GLenum pname, GLboolean *data) { + CALL_GL_API(glGetBooleanv, pname, data); +} +void API_ENTRY(glGetFloatv)(GLenum pname, GLfloat *data) { + CALL_GL_API(glGetFloatv, pname, data); +} +void API_ENTRY(glGetIntegerv)(GLenum pname, GLint *data) { + CALL_GL_API(glGetIntegerv, pname, data); +} +const GLubyte * API_ENTRY(glGetString)(GLenum name) { + CALL_GL_API_RETURN(glGetString, name); +} +const GLubyte * API_ENTRY(glGetStringi)(GLenum name, GLuint index) { + CALL_GL_API_RETURN(glGetStringi, name, index); +} +void API_ENTRY(glGetInteger64v)(GLenum pname, GLint64 *data) { + CALL_GL_API(glGetInteger64v, pname, data); +} +} diff --git a/libs/hwui/debug/wrap_gles.h b/libs/hwui/debug/wrap_gles.h new file mode 100644 index 000000000000..4a3537442e73 --- /dev/null +++ b/libs/hwui/debug/wrap_gles.h @@ -0,0 +1,918 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef HWUI_GLES_WRAP_ENABLED +#define HWUI_GLES_WRAP_ENABLED + +#define glActiveShaderProgram wrap_glActiveShaderProgram +#define glActiveShaderProgramEXT wrap_glActiveShaderProgramEXT +#define glActiveTexture wrap_glActiveTexture +#define glAlphaFunc wrap_glAlphaFunc +#define glAlphaFuncQCOM wrap_glAlphaFuncQCOM +#define glAlphaFuncx wrap_glAlphaFuncx +#define glAlphaFuncxOES wrap_glAlphaFuncxOES +#define glApplyFramebufferAttachmentCMAAINTEL wrap_glApplyFramebufferAttachmentCMAAINTEL +#define glAttachShader wrap_glAttachShader +#define glBeginConditionalRenderNV wrap_glBeginConditionalRenderNV +#define glBeginPerfMonitorAMD wrap_glBeginPerfMonitorAMD +#define glBeginPerfQueryINTEL wrap_glBeginPerfQueryINTEL +#define glBeginQuery wrap_glBeginQuery +#define glBeginQueryEXT wrap_glBeginQueryEXT +#define glBeginTransformFeedback wrap_glBeginTransformFeedback +#define glBindAttribLocation wrap_glBindAttribLocation +#define glBindBuffer wrap_glBindBuffer +#define glBindBufferBase wrap_glBindBufferBase +#define glBindBufferRange wrap_glBindBufferRange +#define glBindFragDataLocationEXT wrap_glBindFragDataLocationEXT +#define glBindFragDataLocationIndexedEXT wrap_glBindFragDataLocationIndexedEXT +#define glBindFramebuffer wrap_glBindFramebuffer +#define glBindFramebufferOES wrap_glBindFramebufferOES +#define glBindImageTexture wrap_glBindImageTexture +#define glBindProgramPipeline wrap_glBindProgramPipeline +#define glBindProgramPipelineEXT wrap_glBindProgramPipelineEXT +#define glBindRenderbuffer wrap_glBindRenderbuffer +#define glBindRenderbufferOES wrap_glBindRenderbufferOES +#define glBindSampler wrap_glBindSampler +#define glBindTexture wrap_glBindTexture +#define glBindTransformFeedback wrap_glBindTransformFeedback +#define glBindVertexArray wrap_glBindVertexArray +#define glBindVertexArrayOES wrap_glBindVertexArrayOES +#define glBindVertexBuffer wrap_glBindVertexBuffer +#define glBlendBarrier wrap_glBlendBarrier +#define glBlendBarrierKHR wrap_glBlendBarrierKHR +#define glBlendBarrierNV wrap_glBlendBarrierNV +#define glBlendColor wrap_glBlendColor +#define glBlendEquation wrap_glBlendEquation +#define glBlendEquationOES wrap_glBlendEquationOES +#define glBlendEquationSeparate wrap_glBlendEquationSeparate +#define glBlendEquationSeparateOES wrap_glBlendEquationSeparateOES +#define glBlendEquationSeparatei wrap_glBlendEquationSeparatei +#define glBlendEquationSeparateiEXT wrap_glBlendEquationSeparateiEXT +#define glBlendEquationSeparateiOES wrap_glBlendEquationSeparateiOES +#define glBlendEquationi wrap_glBlendEquationi +#define glBlendEquationiEXT wrap_glBlendEquationiEXT +#define glBlendEquationiOES wrap_glBlendEquationiOES +#define glBlendFunc wrap_glBlendFunc +#define glBlendFuncSeparate wrap_glBlendFuncSeparate +#define glBlendFuncSeparateOES wrap_glBlendFuncSeparateOES +#define glBlendFuncSeparatei wrap_glBlendFuncSeparatei +#define glBlendFuncSeparateiEXT wrap_glBlendFuncSeparateiEXT +#define glBlendFuncSeparateiOES wrap_glBlendFuncSeparateiOES +#define glBlendFunci wrap_glBlendFunci +#define glBlendFunciEXT wrap_glBlendFunciEXT +#define glBlendFunciOES wrap_glBlendFunciOES +#define glBlendParameteriNV wrap_glBlendParameteriNV +#define glBlitFramebuffer wrap_glBlitFramebuffer +#define glBlitFramebufferANGLE wrap_glBlitFramebufferANGLE +#define glBlitFramebufferNV wrap_glBlitFramebufferNV +#define glBufferData wrap_glBufferData +#define glBufferStorageEXT wrap_glBufferStorageEXT +#define glBufferSubData wrap_glBufferSubData +#define glCheckFramebufferStatus wrap_glCheckFramebufferStatus +#define glCheckFramebufferStatusOES wrap_glCheckFramebufferStatusOES +#define glClear wrap_glClear +#define glClearBufferfi wrap_glClearBufferfi +#define glClearBufferfv wrap_glClearBufferfv +#define glClearBufferiv wrap_glClearBufferiv +#define glClearBufferuiv wrap_glClearBufferuiv +#define glClearColor wrap_glClearColor +#define glClearColorx wrap_glClearColorx +#define glClearColorxOES wrap_glClearColorxOES +#define glClearDepthf wrap_glClearDepthf +#define glClearDepthfOES wrap_glClearDepthfOES +#define glClearDepthx wrap_glClearDepthx +#define glClearDepthxOES wrap_glClearDepthxOES +#define glClearStencil wrap_glClearStencil +#define glClientActiveTexture wrap_glClientActiveTexture +#define glClientWaitSync wrap_glClientWaitSync +#define glClientWaitSyncAPPLE wrap_glClientWaitSyncAPPLE +#define glClipPlanef wrap_glClipPlanef +#define glClipPlanefIMG wrap_glClipPlanefIMG +#define glClipPlanefOES wrap_glClipPlanefOES +#define glClipPlanex wrap_glClipPlanex +#define glClipPlanexIMG wrap_glClipPlanexIMG +#define glClipPlanexOES wrap_glClipPlanexOES +#define glColor4f wrap_glColor4f +#define glColor4ub wrap_glColor4ub +#define glColor4x wrap_glColor4x +#define glColor4xOES wrap_glColor4xOES +#define glColorMask wrap_glColorMask +#define glColorMaski wrap_glColorMaski +#define glColorMaskiEXT wrap_glColorMaskiEXT +#define glColorMaskiOES wrap_glColorMaskiOES +#define glColorPointer wrap_glColorPointer +#define glCompileShader wrap_glCompileShader +#define glCompressedTexImage2D wrap_glCompressedTexImage2D +#define glCompressedTexImage3D wrap_glCompressedTexImage3D +#define glCompressedTexImage3DOES wrap_glCompressedTexImage3DOES +#define glCompressedTexSubImage2D wrap_glCompressedTexSubImage2D +#define glCompressedTexSubImage3D wrap_glCompressedTexSubImage3D +#define glCompressedTexSubImage3DOES wrap_glCompressedTexSubImage3DOES +#define glCopyBufferSubData wrap_glCopyBufferSubData +#define glCopyBufferSubDataNV wrap_glCopyBufferSubDataNV +#define glCopyImageSubData wrap_glCopyImageSubData +#define glCopyImageSubDataEXT wrap_glCopyImageSubDataEXT +#define glCopyImageSubDataOES wrap_glCopyImageSubDataOES +#define glCopyPathNV wrap_glCopyPathNV +#define glCopyTexImage2D wrap_glCopyTexImage2D +#define glCopyTexSubImage2D wrap_glCopyTexSubImage2D +#define glCopyTexSubImage3D wrap_glCopyTexSubImage3D +#define glCopyTexSubImage3DOES wrap_glCopyTexSubImage3DOES +#define glCopyTextureLevelsAPPLE wrap_glCopyTextureLevelsAPPLE +#define glCoverFillPathInstancedNV wrap_glCoverFillPathInstancedNV +#define glCoverFillPathNV wrap_glCoverFillPathNV +#define glCoverStrokePathInstancedNV wrap_glCoverStrokePathInstancedNV +#define glCoverStrokePathNV wrap_glCoverStrokePathNV +#define glCoverageMaskNV wrap_glCoverageMaskNV +#define glCoverageModulationNV wrap_glCoverageModulationNV +#define glCoverageModulationTableNV wrap_glCoverageModulationTableNV +#define glCoverageOperationNV wrap_glCoverageOperationNV +#define glCreatePerfQueryINTEL wrap_glCreatePerfQueryINTEL +#define glCreateProgram wrap_glCreateProgram +#define glCreateShader wrap_glCreateShader +#define glCreateShaderProgramv wrap_glCreateShaderProgramv +#define glCreateShaderProgramvEXT wrap_glCreateShaderProgramvEXT +#define glCullFace wrap_glCullFace +#define glCurrentPaletteMatrixOES wrap_glCurrentPaletteMatrixOES +#define glDebugMessageCallback wrap_glDebugMessageCallback +#define glDebugMessageCallbackKHR wrap_glDebugMessageCallbackKHR +#define glDebugMessageControl wrap_glDebugMessageControl +#define glDebugMessageControlKHR wrap_glDebugMessageControlKHR +#define glDebugMessageInsert wrap_glDebugMessageInsert +#define glDebugMessageInsertKHR wrap_glDebugMessageInsertKHR +#define glDeleteBuffers wrap_glDeleteBuffers +#define glDeleteFencesNV wrap_glDeleteFencesNV +#define glDeleteFramebuffers wrap_glDeleteFramebuffers +#define glDeleteFramebuffersOES wrap_glDeleteFramebuffersOES +#define glDeletePathsNV wrap_glDeletePathsNV +#define glDeletePerfMonitorsAMD wrap_glDeletePerfMonitorsAMD +#define glDeletePerfQueryINTEL wrap_glDeletePerfQueryINTEL +#define glDeleteProgram wrap_glDeleteProgram +#define glDeleteProgramPipelines wrap_glDeleteProgramPipelines +#define glDeleteProgramPipelinesEXT wrap_glDeleteProgramPipelinesEXT +#define glDeleteQueries wrap_glDeleteQueries +#define glDeleteQueriesEXT wrap_glDeleteQueriesEXT +#define glDeleteRenderbuffers wrap_glDeleteRenderbuffers +#define glDeleteRenderbuffersOES wrap_glDeleteRenderbuffersOES +#define glDeleteSamplers wrap_glDeleteSamplers +#define glDeleteShader wrap_glDeleteShader +#define glDeleteSync wrap_glDeleteSync +#define glDeleteSyncAPPLE wrap_glDeleteSyncAPPLE +#define glDeleteTextures wrap_glDeleteTextures +#define glDeleteTransformFeedbacks wrap_glDeleteTransformFeedbacks +#define glDeleteVertexArrays wrap_glDeleteVertexArrays +#define glDeleteVertexArraysOES wrap_glDeleteVertexArraysOES +#define glDepthFunc wrap_glDepthFunc +#define glDepthMask wrap_glDepthMask +#define glDepthRangeArrayfvNV wrap_glDepthRangeArrayfvNV +#define glDepthRangeIndexedfNV wrap_glDepthRangeIndexedfNV +#define glDepthRangef wrap_glDepthRangef +#define glDepthRangefOES wrap_glDepthRangefOES +#define glDepthRangex wrap_glDepthRangex +#define glDepthRangexOES wrap_glDepthRangexOES +#define glDetachShader wrap_glDetachShader +#define glDisable wrap_glDisable +#define glDisableClientState wrap_glDisableClientState +#define glDisableDriverControlQCOM wrap_glDisableDriverControlQCOM +#define glDisableVertexAttribArray wrap_glDisableVertexAttribArray +#define glDisablei wrap_glDisablei +#define glDisableiEXT wrap_glDisableiEXT +#define glDisableiNV wrap_glDisableiNV +#define glDisableiOES wrap_glDisableiOES +#define glDiscardFramebufferEXT wrap_glDiscardFramebufferEXT +#define glDispatchCompute wrap_glDispatchCompute +#define glDispatchComputeIndirect wrap_glDispatchComputeIndirect +#define glDrawArrays wrap_glDrawArrays +#define glDrawArraysIndirect wrap_glDrawArraysIndirect +#define glDrawArraysInstanced wrap_glDrawArraysInstanced +#define glDrawArraysInstancedANGLE wrap_glDrawArraysInstancedANGLE +#define glDrawArraysInstancedBaseInstanceEXT wrap_glDrawArraysInstancedBaseInstanceEXT +#define glDrawArraysInstancedEXT wrap_glDrawArraysInstancedEXT +#define glDrawArraysInstancedNV wrap_glDrawArraysInstancedNV +#define glDrawBuffers wrap_glDrawBuffers +#define glDrawBuffersEXT wrap_glDrawBuffersEXT +#define glDrawBuffersIndexedEXT wrap_glDrawBuffersIndexedEXT +#define glDrawBuffersNV wrap_glDrawBuffersNV +#define glDrawElements wrap_glDrawElements +#define glDrawElementsBaseVertex wrap_glDrawElementsBaseVertex +#define glDrawElementsBaseVertexEXT wrap_glDrawElementsBaseVertexEXT +#define glDrawElementsBaseVertexOES wrap_glDrawElementsBaseVertexOES +#define glDrawElementsIndirect wrap_glDrawElementsIndirect +#define glDrawElementsInstanced wrap_glDrawElementsInstanced +#define glDrawElementsInstancedANGLE wrap_glDrawElementsInstancedANGLE +#define glDrawElementsInstancedBaseInstanceEXT wrap_glDrawElementsInstancedBaseInstanceEXT +#define glDrawElementsInstancedBaseVertex wrap_glDrawElementsInstancedBaseVertex +#define glDrawElementsInstancedBaseVertexBaseInstanceEXT wrap_glDrawElementsInstancedBaseVertexBaseInstanceEXT +#define glDrawElementsInstancedBaseVertexEXT wrap_glDrawElementsInstancedBaseVertexEXT +#define glDrawElementsInstancedBaseVertexOES wrap_glDrawElementsInstancedBaseVertexOES +#define glDrawElementsInstancedEXT wrap_glDrawElementsInstancedEXT +#define glDrawElementsInstancedNV wrap_glDrawElementsInstancedNV +#define glDrawRangeElements wrap_glDrawRangeElements +#define glDrawRangeElementsBaseVertex wrap_glDrawRangeElementsBaseVertex +#define glDrawRangeElementsBaseVertexEXT wrap_glDrawRangeElementsBaseVertexEXT +#define glDrawRangeElementsBaseVertexOES wrap_glDrawRangeElementsBaseVertexOES +#define glDrawTexfOES wrap_glDrawTexfOES +#define glDrawTexfvOES wrap_glDrawTexfvOES +#define glDrawTexiOES wrap_glDrawTexiOES +#define glDrawTexivOES wrap_glDrawTexivOES +#define glDrawTexsOES wrap_glDrawTexsOES +#define glDrawTexsvOES wrap_glDrawTexsvOES +#define glDrawTexxOES wrap_glDrawTexxOES +#define glDrawTexxvOES wrap_glDrawTexxvOES +#define glEGLImageTargetRenderbufferStorageOES wrap_glEGLImageTargetRenderbufferStorageOES +#define glEGLImageTargetTexture2DOES wrap_glEGLImageTargetTexture2DOES +#define glEnable wrap_glEnable +#define glEnableClientState wrap_glEnableClientState +#define glEnableDriverControlQCOM wrap_glEnableDriverControlQCOM +#define glEnableVertexAttribArray wrap_glEnableVertexAttribArray +#define glEnablei wrap_glEnablei +#define glEnableiEXT wrap_glEnableiEXT +#define glEnableiNV wrap_glEnableiNV +#define glEnableiOES wrap_glEnableiOES +#define glEndConditionalRenderNV wrap_glEndConditionalRenderNV +#define glEndPerfMonitorAMD wrap_glEndPerfMonitorAMD +#define glEndPerfQueryINTEL wrap_glEndPerfQueryINTEL +#define glEndQuery wrap_glEndQuery +#define glEndQueryEXT wrap_glEndQueryEXT +#define glEndTilingQCOM wrap_glEndTilingQCOM +#define glEndTransformFeedback wrap_glEndTransformFeedback +#define glExtGetBufferPointervQCOM wrap_glExtGetBufferPointervQCOM +#define glExtGetBuffersQCOM wrap_glExtGetBuffersQCOM +#define glExtGetFramebuffersQCOM wrap_glExtGetFramebuffersQCOM +#define glExtGetProgramBinarySourceQCOM wrap_glExtGetProgramBinarySourceQCOM +#define glExtGetProgramsQCOM wrap_glExtGetProgramsQCOM +#define glExtGetRenderbuffersQCOM wrap_glExtGetRenderbuffersQCOM +#define glExtGetShadersQCOM wrap_glExtGetShadersQCOM +#define glExtGetTexLevelParameterivQCOM wrap_glExtGetTexLevelParameterivQCOM +#define glExtGetTexSubImageQCOM wrap_glExtGetTexSubImageQCOM +#define glExtGetTexturesQCOM wrap_glExtGetTexturesQCOM +#define glExtIsProgramBinaryQCOM wrap_glExtIsProgramBinaryQCOM +#define glExtTexObjectStateOverrideiQCOM wrap_glExtTexObjectStateOverrideiQCOM +#define glFenceSync wrap_glFenceSync +#define glFenceSyncAPPLE wrap_glFenceSyncAPPLE +#define glFinish wrap_glFinish +#define glFinishFenceNV wrap_glFinishFenceNV +#define glFlush wrap_glFlush +#define glFlushMappedBufferRange wrap_glFlushMappedBufferRange +#define glFlushMappedBufferRangeEXT wrap_glFlushMappedBufferRangeEXT +#define glFogf wrap_glFogf +#define glFogfv wrap_glFogfv +#define glFogx wrap_glFogx +#define glFogxOES wrap_glFogxOES +#define glFogxv wrap_glFogxv +#define glFogxvOES wrap_glFogxvOES +#define glFragmentCoverageColorNV wrap_glFragmentCoverageColorNV +#define glFramebufferParameteri wrap_glFramebufferParameteri +#define glFramebufferRenderbuffer wrap_glFramebufferRenderbuffer +#define glFramebufferRenderbufferOES wrap_glFramebufferRenderbufferOES +#define glFramebufferSampleLocationsfvNV wrap_glFramebufferSampleLocationsfvNV +#define glFramebufferTexture wrap_glFramebufferTexture +#define glFramebufferTexture2D wrap_glFramebufferTexture2D +#define glFramebufferTexture2DMultisampleEXT wrap_glFramebufferTexture2DMultisampleEXT +#define glFramebufferTexture2DMultisampleIMG wrap_glFramebufferTexture2DMultisampleIMG +#define glFramebufferTexture2DOES wrap_glFramebufferTexture2DOES +#define glFramebufferTexture3DOES wrap_glFramebufferTexture3DOES +#define glFramebufferTextureEXT wrap_glFramebufferTextureEXT +#define glFramebufferTextureLayer wrap_glFramebufferTextureLayer +#define glFramebufferTextureMultisampleMultiviewOVR wrap_glFramebufferTextureMultisampleMultiviewOVR +#define glFramebufferTextureMultiviewOVR wrap_glFramebufferTextureMultiviewOVR +#define glFramebufferTextureOES wrap_glFramebufferTextureOES +#define glFrontFace wrap_glFrontFace +#define glFrustumf wrap_glFrustumf +#define glFrustumfOES wrap_glFrustumfOES +#define glFrustumx wrap_glFrustumx +#define glFrustumxOES wrap_glFrustumxOES +#define glGenBuffers wrap_glGenBuffers +#define glGenFencesNV wrap_glGenFencesNV +#define glGenFramebuffers wrap_glGenFramebuffers +#define glGenFramebuffersOES wrap_glGenFramebuffersOES +#define glGenPathsNV wrap_glGenPathsNV +#define glGenPerfMonitorsAMD wrap_glGenPerfMonitorsAMD +#define glGenProgramPipelines wrap_glGenProgramPipelines +#define glGenProgramPipelinesEXT wrap_glGenProgramPipelinesEXT +#define glGenQueries wrap_glGenQueries +#define glGenQueriesEXT wrap_glGenQueriesEXT +#define glGenRenderbuffers wrap_glGenRenderbuffers +#define glGenRenderbuffersOES wrap_glGenRenderbuffersOES +#define glGenSamplers wrap_glGenSamplers +#define glGenTextures wrap_glGenTextures +#define glGenTransformFeedbacks wrap_glGenTransformFeedbacks +#define glGenVertexArrays wrap_glGenVertexArrays +#define glGenVertexArraysOES wrap_glGenVertexArraysOES +#define glGenerateMipmap wrap_glGenerateMipmap +#define glGenerateMipmapOES wrap_glGenerateMipmapOES +#define glGetActiveAttrib wrap_glGetActiveAttrib +#define glGetActiveUniform wrap_glGetActiveUniform +#define glGetActiveUniformBlockName wrap_glGetActiveUniformBlockName +#define glGetActiveUniformBlockiv wrap_glGetActiveUniformBlockiv +#define glGetActiveUniformsiv wrap_glGetActiveUniformsiv +#define glGetAttachedShaders wrap_glGetAttachedShaders +#define glGetAttribLocation wrap_glGetAttribLocation +#define glGetBooleani_v wrap_glGetBooleani_v +#define glGetBooleanv wrap_glGetBooleanv +#define glGetBufferParameteri64v wrap_glGetBufferParameteri64v +#define glGetBufferParameteriv wrap_glGetBufferParameteriv +#define glGetBufferPointerv wrap_glGetBufferPointerv +#define glGetBufferPointervOES wrap_glGetBufferPointervOES +#define glGetClipPlanef wrap_glGetClipPlanef +#define glGetClipPlanefOES wrap_glGetClipPlanefOES +#define glGetClipPlanex wrap_glGetClipPlanex +#define glGetClipPlanexOES wrap_glGetClipPlanexOES +#define glGetCoverageModulationTableNV wrap_glGetCoverageModulationTableNV +#define glGetDebugMessageLog wrap_glGetDebugMessageLog +#define glGetDebugMessageLogKHR wrap_glGetDebugMessageLogKHR +#define glGetDriverControlStringQCOM wrap_glGetDriverControlStringQCOM +#define glGetDriverControlsQCOM wrap_glGetDriverControlsQCOM +#define glGetError wrap_glGetError +#define glGetFenceivNV wrap_glGetFenceivNV +#define glGetFirstPerfQueryIdINTEL wrap_glGetFirstPerfQueryIdINTEL +#define glGetFixedv wrap_glGetFixedv +#define glGetFixedvOES wrap_glGetFixedvOES +#define glGetFloati_vNV wrap_glGetFloati_vNV +#define glGetFloatv wrap_glGetFloatv +#define glGetFragDataIndexEXT wrap_glGetFragDataIndexEXT +#define glGetFragDataLocation wrap_glGetFragDataLocation +#define glGetFramebufferAttachmentParameteriv wrap_glGetFramebufferAttachmentParameteriv +#define glGetFramebufferAttachmentParameterivOES wrap_glGetFramebufferAttachmentParameterivOES +#define glGetFramebufferParameteriv wrap_glGetFramebufferParameteriv +#define glGetGraphicsResetStatus wrap_glGetGraphicsResetStatus +#define glGetGraphicsResetStatusEXT wrap_glGetGraphicsResetStatusEXT +#define glGetGraphicsResetStatusKHR wrap_glGetGraphicsResetStatusKHR +#define glGetImageHandleNV wrap_glGetImageHandleNV +#define glGetInteger64i_v wrap_glGetInteger64i_v +#define glGetInteger64v wrap_glGetInteger64v +#define glGetInteger64vAPPLE wrap_glGetInteger64vAPPLE +#define glGetIntegeri_v wrap_glGetIntegeri_v +#define glGetIntegeri_vEXT wrap_glGetIntegeri_vEXT +#define glGetIntegerv wrap_glGetIntegerv +#define glGetInternalformatSampleivNV wrap_glGetInternalformatSampleivNV +#define glGetInternalformativ wrap_glGetInternalformativ +#define glGetLightfv wrap_glGetLightfv +#define glGetLightxv wrap_glGetLightxv +#define glGetLightxvOES wrap_glGetLightxvOES +#define glGetMaterialfv wrap_glGetMaterialfv +#define glGetMaterialxv wrap_glGetMaterialxv +#define glGetMaterialxvOES wrap_glGetMaterialxvOES +#define glGetMultisamplefv wrap_glGetMultisamplefv +#define glGetNextPerfQueryIdINTEL wrap_glGetNextPerfQueryIdINTEL +#define glGetObjectLabel wrap_glGetObjectLabel +#define glGetObjectLabelEXT wrap_glGetObjectLabelEXT +#define glGetObjectLabelKHR wrap_glGetObjectLabelKHR +#define glGetObjectPtrLabel wrap_glGetObjectPtrLabel +#define glGetObjectPtrLabelKHR wrap_glGetObjectPtrLabelKHR +#define glGetPathCommandsNV wrap_glGetPathCommandsNV +#define glGetPathCoordsNV wrap_glGetPathCoordsNV +#define glGetPathDashArrayNV wrap_glGetPathDashArrayNV +#define glGetPathLengthNV wrap_glGetPathLengthNV +#define glGetPathMetricRangeNV wrap_glGetPathMetricRangeNV +#define glGetPathMetricsNV wrap_glGetPathMetricsNV +#define glGetPathParameterfvNV wrap_glGetPathParameterfvNV +#define glGetPathParameterivNV wrap_glGetPathParameterivNV +#define glGetPathSpacingNV wrap_glGetPathSpacingNV +#define glGetPerfCounterInfoINTEL wrap_glGetPerfCounterInfoINTEL +#define glGetPerfMonitorCounterDataAMD wrap_glGetPerfMonitorCounterDataAMD +#define glGetPerfMonitorCounterInfoAMD wrap_glGetPerfMonitorCounterInfoAMD +#define glGetPerfMonitorCounterStringAMD wrap_glGetPerfMonitorCounterStringAMD +#define glGetPerfMonitorCountersAMD wrap_glGetPerfMonitorCountersAMD +#define glGetPerfMonitorGroupStringAMD wrap_glGetPerfMonitorGroupStringAMD +#define glGetPerfMonitorGroupsAMD wrap_glGetPerfMonitorGroupsAMD +#define glGetPerfQueryDataINTEL wrap_glGetPerfQueryDataINTEL +#define glGetPerfQueryIdByNameINTEL wrap_glGetPerfQueryIdByNameINTEL +#define glGetPerfQueryInfoINTEL wrap_glGetPerfQueryInfoINTEL +#define glGetPointerv wrap_glGetPointerv +#define glGetPointervKHR wrap_glGetPointervKHR +#define glGetProgramBinary wrap_glGetProgramBinary +#define glGetProgramBinaryOES wrap_glGetProgramBinaryOES +#define glGetProgramInfoLog wrap_glGetProgramInfoLog +#define glGetProgramInterfaceiv wrap_glGetProgramInterfaceiv +#define glGetProgramPipelineInfoLog wrap_glGetProgramPipelineInfoLog +#define glGetProgramPipelineInfoLogEXT wrap_glGetProgramPipelineInfoLogEXT +#define glGetProgramPipelineiv wrap_glGetProgramPipelineiv +#define glGetProgramPipelineivEXT wrap_glGetProgramPipelineivEXT +#define glGetProgramResourceIndex wrap_glGetProgramResourceIndex +#define glGetProgramResourceLocation wrap_glGetProgramResourceLocation +#define glGetProgramResourceLocationIndexEXT wrap_glGetProgramResourceLocationIndexEXT +#define glGetProgramResourceName wrap_glGetProgramResourceName +#define glGetProgramResourcefvNV wrap_glGetProgramResourcefvNV +#define glGetProgramResourceiv wrap_glGetProgramResourceiv +#define glGetProgramiv wrap_glGetProgramiv +#define glGetQueryObjecti64vEXT wrap_glGetQueryObjecti64vEXT +#define glGetQueryObjectivEXT wrap_glGetQueryObjectivEXT +#define glGetQueryObjectui64vEXT wrap_glGetQueryObjectui64vEXT +#define glGetQueryObjectuiv wrap_glGetQueryObjectuiv +#define glGetQueryObjectuivEXT wrap_glGetQueryObjectuivEXT +#define glGetQueryiv wrap_glGetQueryiv +#define glGetQueryivEXT wrap_glGetQueryivEXT +#define glGetRenderbufferParameteriv wrap_glGetRenderbufferParameteriv +#define glGetRenderbufferParameterivOES wrap_glGetRenderbufferParameterivOES +#define glGetSamplerParameterIiv wrap_glGetSamplerParameterIiv +#define glGetSamplerParameterIivEXT wrap_glGetSamplerParameterIivEXT +#define glGetSamplerParameterIivOES wrap_glGetSamplerParameterIivOES +#define glGetSamplerParameterIuiv wrap_glGetSamplerParameterIuiv +#define glGetSamplerParameterIuivEXT wrap_glGetSamplerParameterIuivEXT +#define glGetSamplerParameterIuivOES wrap_glGetSamplerParameterIuivOES +#define glGetSamplerParameterfv wrap_glGetSamplerParameterfv +#define glGetSamplerParameteriv wrap_glGetSamplerParameteriv +#define glGetShaderInfoLog wrap_glGetShaderInfoLog +#define glGetShaderPrecisionFormat wrap_glGetShaderPrecisionFormat +#define glGetShaderSource wrap_glGetShaderSource +#define glGetShaderiv wrap_glGetShaderiv +#define glGetString wrap_glGetString +#define glGetStringi wrap_glGetStringi +#define glGetSynciv wrap_glGetSynciv +#define glGetSyncivAPPLE wrap_glGetSyncivAPPLE +#define glGetTexEnvfv wrap_glGetTexEnvfv +#define glGetTexEnviv wrap_glGetTexEnviv +#define glGetTexEnvxv wrap_glGetTexEnvxv +#define glGetTexEnvxvOES wrap_glGetTexEnvxvOES +#define glGetTexGenfvOES wrap_glGetTexGenfvOES +#define glGetTexGenivOES wrap_glGetTexGenivOES +#define glGetTexGenxvOES wrap_glGetTexGenxvOES +#define glGetTexLevelParameterfv wrap_glGetTexLevelParameterfv +#define glGetTexLevelParameteriv wrap_glGetTexLevelParameteriv +#define glGetTexParameterIiv wrap_glGetTexParameterIiv +#define glGetTexParameterIivEXT wrap_glGetTexParameterIivEXT +#define glGetTexParameterIivOES wrap_glGetTexParameterIivOES +#define glGetTexParameterIuiv wrap_glGetTexParameterIuiv +#define glGetTexParameterIuivEXT wrap_glGetTexParameterIuivEXT +#define glGetTexParameterIuivOES wrap_glGetTexParameterIuivOES +#define glGetTexParameterfv wrap_glGetTexParameterfv +#define glGetTexParameteriv wrap_glGetTexParameteriv +#define glGetTexParameterxv wrap_glGetTexParameterxv +#define glGetTexParameterxvOES wrap_glGetTexParameterxvOES +#define glGetTextureHandleNV wrap_glGetTextureHandleNV +#define glGetTextureSamplerHandleNV wrap_glGetTextureSamplerHandleNV +#define glGetTransformFeedbackVarying wrap_glGetTransformFeedbackVarying +#define glGetTranslatedShaderSourceANGLE wrap_glGetTranslatedShaderSourceANGLE +#define glGetUniformBlockIndex wrap_glGetUniformBlockIndex +#define glGetUniformIndices wrap_glGetUniformIndices +#define glGetUniformLocation wrap_glGetUniformLocation +#define glGetUniformfv wrap_glGetUniformfv +#define glGetUniformiv wrap_glGetUniformiv +#define glGetUniformuiv wrap_glGetUniformuiv +#define glGetVertexAttribIiv wrap_glGetVertexAttribIiv +#define glGetVertexAttribIuiv wrap_glGetVertexAttribIuiv +#define glGetVertexAttribPointerv wrap_glGetVertexAttribPointerv +#define glGetVertexAttribfv wrap_glGetVertexAttribfv +#define glGetVertexAttribiv wrap_glGetVertexAttribiv +#define glGetnUniformfv wrap_glGetnUniformfv +#define glGetnUniformfvEXT wrap_glGetnUniformfvEXT +#define glGetnUniformfvKHR wrap_glGetnUniformfvKHR +#define glGetnUniformiv wrap_glGetnUniformiv +#define glGetnUniformivEXT wrap_glGetnUniformivEXT +#define glGetnUniformivKHR wrap_glGetnUniformivKHR +#define glGetnUniformuiv wrap_glGetnUniformuiv +#define glGetnUniformuivKHR wrap_glGetnUniformuivKHR +#define glHint wrap_glHint +#define glInsertEventMarkerEXT wrap_glInsertEventMarkerEXT +#define glInterpolatePathsNV wrap_glInterpolatePathsNV +#define glInvalidateFramebuffer wrap_glInvalidateFramebuffer +#define glInvalidateSubFramebuffer wrap_glInvalidateSubFramebuffer +#define glIsBuffer wrap_glIsBuffer +#define glIsEnabled wrap_glIsEnabled +#define glIsEnabledi wrap_glIsEnabledi +#define glIsEnablediEXT wrap_glIsEnablediEXT +#define glIsEnablediNV wrap_glIsEnablediNV +#define glIsEnablediOES wrap_glIsEnablediOES +#define glIsFenceNV wrap_glIsFenceNV +#define glIsFramebuffer wrap_glIsFramebuffer +#define glIsFramebufferOES wrap_glIsFramebufferOES +#define glIsImageHandleResidentNV wrap_glIsImageHandleResidentNV +#define glIsPathNV wrap_glIsPathNV +#define glIsPointInFillPathNV wrap_glIsPointInFillPathNV +#define glIsPointInStrokePathNV wrap_glIsPointInStrokePathNV +#define glIsProgram wrap_glIsProgram +#define glIsProgramPipeline wrap_glIsProgramPipeline +#define glIsProgramPipelineEXT wrap_glIsProgramPipelineEXT +#define glIsQuery wrap_glIsQuery +#define glIsQueryEXT wrap_glIsQueryEXT +#define glIsRenderbuffer wrap_glIsRenderbuffer +#define glIsRenderbufferOES wrap_glIsRenderbufferOES +#define glIsSampler wrap_glIsSampler +#define glIsShader wrap_glIsShader +#define glIsSync wrap_glIsSync +#define glIsSyncAPPLE wrap_glIsSyncAPPLE +#define glIsTexture wrap_glIsTexture +#define glIsTextureHandleResidentNV wrap_glIsTextureHandleResidentNV +#define glIsTransformFeedback wrap_glIsTransformFeedback +#define glIsVertexArray wrap_glIsVertexArray +#define glIsVertexArrayOES wrap_glIsVertexArrayOES +#define glLabelObjectEXT wrap_glLabelObjectEXT +#define glLightModelf wrap_glLightModelf +#define glLightModelfv wrap_glLightModelfv +#define glLightModelx wrap_glLightModelx +#define glLightModelxOES wrap_glLightModelxOES +#define glLightModelxv wrap_glLightModelxv +#define glLightModelxvOES wrap_glLightModelxvOES +#define glLightf wrap_glLightf +#define glLightfv wrap_glLightfv +#define glLightx wrap_glLightx +#define glLightxOES wrap_glLightxOES +#define glLightxv wrap_glLightxv +#define glLightxvOES wrap_glLightxvOES +#define glLineWidth wrap_glLineWidth +#define glLineWidthx wrap_glLineWidthx +#define glLineWidthxOES wrap_glLineWidthxOES +#define glLinkProgram wrap_glLinkProgram +#define glLoadIdentity wrap_glLoadIdentity +#define glLoadMatrixf wrap_glLoadMatrixf +#define glLoadMatrixx wrap_glLoadMatrixx +#define glLoadMatrixxOES wrap_glLoadMatrixxOES +#define glLoadPaletteFromModelViewMatrixOES wrap_glLoadPaletteFromModelViewMatrixOES +#define glLogicOp wrap_glLogicOp +#define glMakeImageHandleNonResidentNV wrap_glMakeImageHandleNonResidentNV +#define glMakeImageHandleResidentNV wrap_glMakeImageHandleResidentNV +#define glMakeTextureHandleNonResidentNV wrap_glMakeTextureHandleNonResidentNV +#define glMakeTextureHandleResidentNV wrap_glMakeTextureHandleResidentNV +#define glMapBufferOES wrap_glMapBufferOES +#define glMapBufferRange wrap_glMapBufferRange +#define glMapBufferRangeEXT wrap_glMapBufferRangeEXT +#define glMaterialf wrap_glMaterialf +#define glMaterialfv wrap_glMaterialfv +#define glMaterialx wrap_glMaterialx +#define glMaterialxOES wrap_glMaterialxOES +#define glMaterialxv wrap_glMaterialxv +#define glMaterialxvOES wrap_glMaterialxvOES +#define glMatrixIndexPointerOES wrap_glMatrixIndexPointerOES +#define glMatrixLoad3x2fNV wrap_glMatrixLoad3x2fNV +#define glMatrixLoad3x3fNV wrap_glMatrixLoad3x3fNV +#define glMatrixLoadTranspose3x3fNV wrap_glMatrixLoadTranspose3x3fNV +#define glMatrixMode wrap_glMatrixMode +#define glMatrixMult3x2fNV wrap_glMatrixMult3x2fNV +#define glMatrixMult3x3fNV wrap_glMatrixMult3x3fNV +#define glMatrixMultTranspose3x3fNV wrap_glMatrixMultTranspose3x3fNV +#define glMemoryBarrier wrap_glMemoryBarrier +#define glMemoryBarrierByRegion wrap_glMemoryBarrierByRegion +#define glMinSampleShading wrap_glMinSampleShading +#define glMinSampleShadingOES wrap_glMinSampleShadingOES +#define glMultMatrixf wrap_glMultMatrixf +#define glMultMatrixx wrap_glMultMatrixx +#define glMultMatrixxOES wrap_glMultMatrixxOES +#define glMultiDrawArraysEXT wrap_glMultiDrawArraysEXT +#define glMultiDrawArraysIndirectEXT wrap_glMultiDrawArraysIndirectEXT +#define glMultiDrawElementsBaseVertexEXT wrap_glMultiDrawElementsBaseVertexEXT +#define glMultiDrawElementsBaseVertexOES wrap_glMultiDrawElementsBaseVertexOES +#define glMultiDrawElementsEXT wrap_glMultiDrawElementsEXT +#define glMultiDrawElementsIndirectEXT wrap_glMultiDrawElementsIndirectEXT +#define glMultiTexCoord4f wrap_glMultiTexCoord4f +#define glMultiTexCoord4x wrap_glMultiTexCoord4x +#define glMultiTexCoord4xOES wrap_glMultiTexCoord4xOES +#define glNamedFramebufferSampleLocationsfvNV wrap_glNamedFramebufferSampleLocationsfvNV +#define glNormal3f wrap_glNormal3f +#define glNormal3x wrap_glNormal3x +#define glNormal3xOES wrap_glNormal3xOES +#define glNormalPointer wrap_glNormalPointer +#define glObjectLabel wrap_glObjectLabel +#define glObjectLabelKHR wrap_glObjectLabelKHR +#define glObjectPtrLabel wrap_glObjectPtrLabel +#define glObjectPtrLabelKHR wrap_glObjectPtrLabelKHR +#define glOrthof wrap_glOrthof +#define glOrthofOES wrap_glOrthofOES +#define glOrthox wrap_glOrthox +#define glOrthoxOES wrap_glOrthoxOES +#define glPatchParameteri wrap_glPatchParameteri +#define glPatchParameteriEXT wrap_glPatchParameteriEXT +#define glPatchParameteriOES wrap_glPatchParameteriOES +#define glPathCommandsNV wrap_glPathCommandsNV +#define glPathCoordsNV wrap_glPathCoordsNV +#define glPathCoverDepthFuncNV wrap_glPathCoverDepthFuncNV +#define glPathDashArrayNV wrap_glPathDashArrayNV +#define glPathGlyphIndexArrayNV wrap_glPathGlyphIndexArrayNV +#define glPathGlyphIndexRangeNV wrap_glPathGlyphIndexRangeNV +#define glPathGlyphRangeNV wrap_glPathGlyphRangeNV +#define glPathGlyphsNV wrap_glPathGlyphsNV +#define glPathMemoryGlyphIndexArrayNV wrap_glPathMemoryGlyphIndexArrayNV +#define glPathParameterfNV wrap_glPathParameterfNV +#define glPathParameterfvNV wrap_glPathParameterfvNV +#define glPathParameteriNV wrap_glPathParameteriNV +#define glPathParameterivNV wrap_glPathParameterivNV +#define glPathStencilDepthOffsetNV wrap_glPathStencilDepthOffsetNV +#define glPathStencilFuncNV wrap_glPathStencilFuncNV +#define glPathStringNV wrap_glPathStringNV +#define glPathSubCommandsNV wrap_glPathSubCommandsNV +#define glPathSubCoordsNV wrap_glPathSubCoordsNV +#define glPauseTransformFeedback wrap_glPauseTransformFeedback +#define glPixelStorei wrap_glPixelStorei +#define glPointAlongPathNV wrap_glPointAlongPathNV +#define glPointParameterf wrap_glPointParameterf +#define glPointParameterfv wrap_glPointParameterfv +#define glPointParameterx wrap_glPointParameterx +#define glPointParameterxOES wrap_glPointParameterxOES +#define glPointParameterxv wrap_glPointParameterxv +#define glPointParameterxvOES wrap_glPointParameterxvOES +#define glPointSize wrap_glPointSize +#define glPointSizePointerOES wrap_glPointSizePointerOES +#define glPointSizex wrap_glPointSizex +#define glPointSizexOES wrap_glPointSizexOES +#define glPolygonModeNV wrap_glPolygonModeNV +#define glPolygonOffset wrap_glPolygonOffset +#define glPolygonOffsetx wrap_glPolygonOffsetx +#define glPolygonOffsetxOES wrap_glPolygonOffsetxOES +#define glPopDebugGroup wrap_glPopDebugGroup +#define glPopDebugGroupKHR wrap_glPopDebugGroupKHR +#define glPopGroupMarkerEXT wrap_glPopGroupMarkerEXT +#define glPopMatrix wrap_glPopMatrix +#define glPrimitiveBoundingBox wrap_glPrimitiveBoundingBox +#define glPrimitiveBoundingBoxEXT wrap_glPrimitiveBoundingBoxEXT +#define glPrimitiveBoundingBoxOES wrap_glPrimitiveBoundingBoxOES +#define glProgramBinary wrap_glProgramBinary +#define glProgramBinaryOES wrap_glProgramBinaryOES +#define glProgramParameteri wrap_glProgramParameteri +#define glProgramParameteriEXT wrap_glProgramParameteriEXT +#define glProgramPathFragmentInputGenNV wrap_glProgramPathFragmentInputGenNV +#define glProgramUniform1f wrap_glProgramUniform1f +#define glProgramUniform1fEXT wrap_glProgramUniform1fEXT +#define glProgramUniform1fv wrap_glProgramUniform1fv +#define glProgramUniform1fvEXT wrap_glProgramUniform1fvEXT +#define glProgramUniform1i wrap_glProgramUniform1i +#define glProgramUniform1iEXT wrap_glProgramUniform1iEXT +#define glProgramUniform1iv wrap_glProgramUniform1iv +#define glProgramUniform1ivEXT wrap_glProgramUniform1ivEXT +#define glProgramUniform1ui wrap_glProgramUniform1ui +#define glProgramUniform1uiEXT wrap_glProgramUniform1uiEXT +#define glProgramUniform1uiv wrap_glProgramUniform1uiv +#define glProgramUniform1uivEXT wrap_glProgramUniform1uivEXT +#define glProgramUniform2f wrap_glProgramUniform2f +#define glProgramUniform2fEXT wrap_glProgramUniform2fEXT +#define glProgramUniform2fv wrap_glProgramUniform2fv +#define glProgramUniform2fvEXT wrap_glProgramUniform2fvEXT +#define glProgramUniform2i wrap_glProgramUniform2i +#define glProgramUniform2iEXT wrap_glProgramUniform2iEXT +#define glProgramUniform2iv wrap_glProgramUniform2iv +#define glProgramUniform2ivEXT wrap_glProgramUniform2ivEXT +#define glProgramUniform2ui wrap_glProgramUniform2ui +#define glProgramUniform2uiEXT wrap_glProgramUniform2uiEXT +#define glProgramUniform2uiv wrap_glProgramUniform2uiv +#define glProgramUniform2uivEXT wrap_glProgramUniform2uivEXT +#define glProgramUniform3f wrap_glProgramUniform3f +#define glProgramUniform3fEXT wrap_glProgramUniform3fEXT +#define glProgramUniform3fv wrap_glProgramUniform3fv +#define glProgramUniform3fvEXT wrap_glProgramUniform3fvEXT +#define glProgramUniform3i wrap_glProgramUniform3i +#define glProgramUniform3iEXT wrap_glProgramUniform3iEXT +#define glProgramUniform3iv wrap_glProgramUniform3iv +#define glProgramUniform3ivEXT wrap_glProgramUniform3ivEXT +#define glProgramUniform3ui wrap_glProgramUniform3ui +#define glProgramUniform3uiEXT wrap_glProgramUniform3uiEXT +#define glProgramUniform3uiv wrap_glProgramUniform3uiv +#define glProgramUniform3uivEXT wrap_glProgramUniform3uivEXT +#define glProgramUniform4f wrap_glProgramUniform4f +#define glProgramUniform4fEXT wrap_glProgramUniform4fEXT +#define glProgramUniform4fv wrap_glProgramUniform4fv +#define glProgramUniform4fvEXT wrap_glProgramUniform4fvEXT +#define glProgramUniform4i wrap_glProgramUniform4i +#define glProgramUniform4iEXT wrap_glProgramUniform4iEXT +#define glProgramUniform4iv wrap_glProgramUniform4iv +#define glProgramUniform4ivEXT wrap_glProgramUniform4ivEXT +#define glProgramUniform4ui wrap_glProgramUniform4ui +#define glProgramUniform4uiEXT wrap_glProgramUniform4uiEXT +#define glProgramUniform4uiv wrap_glProgramUniform4uiv +#define glProgramUniform4uivEXT wrap_glProgramUniform4uivEXT +#define glProgramUniformHandleui64NV wrap_glProgramUniformHandleui64NV +#define glProgramUniformHandleui64vNV wrap_glProgramUniformHandleui64vNV +#define glProgramUniformMatrix2fv wrap_glProgramUniformMatrix2fv +#define glProgramUniformMatrix2fvEXT wrap_glProgramUniformMatrix2fvEXT +#define glProgramUniformMatrix2x3fv wrap_glProgramUniformMatrix2x3fv +#define glProgramUniformMatrix2x3fvEXT wrap_glProgramUniformMatrix2x3fvEXT +#define glProgramUniformMatrix2x4fv wrap_glProgramUniformMatrix2x4fv +#define glProgramUniformMatrix2x4fvEXT wrap_glProgramUniformMatrix2x4fvEXT +#define glProgramUniformMatrix3fv wrap_glProgramUniformMatrix3fv +#define glProgramUniformMatrix3fvEXT wrap_glProgramUniformMatrix3fvEXT +#define glProgramUniformMatrix3x2fv wrap_glProgramUniformMatrix3x2fv +#define glProgramUniformMatrix3x2fvEXT wrap_glProgramUniformMatrix3x2fvEXT +#define glProgramUniformMatrix3x4fv wrap_glProgramUniformMatrix3x4fv +#define glProgramUniformMatrix3x4fvEXT wrap_glProgramUniformMatrix3x4fvEXT +#define glProgramUniformMatrix4fv wrap_glProgramUniformMatrix4fv +#define glProgramUniformMatrix4fvEXT wrap_glProgramUniformMatrix4fvEXT +#define glProgramUniformMatrix4x2fv wrap_glProgramUniformMatrix4x2fv +#define glProgramUniformMatrix4x2fvEXT wrap_glProgramUniformMatrix4x2fvEXT +#define glProgramUniformMatrix4x3fv wrap_glProgramUniformMatrix4x3fv +#define glProgramUniformMatrix4x3fvEXT wrap_glProgramUniformMatrix4x3fvEXT +#define glPushDebugGroup wrap_glPushDebugGroup +#define glPushDebugGroupKHR wrap_glPushDebugGroupKHR +#define glPushGroupMarkerEXT wrap_glPushGroupMarkerEXT +#define glPushMatrix wrap_glPushMatrix +#define glQueryCounterEXT wrap_glQueryCounterEXT +#define glQueryMatrixxOES wrap_glQueryMatrixxOES +#define glRasterSamplesEXT wrap_glRasterSamplesEXT +#define glReadBuffer wrap_glReadBuffer +#define glReadBufferIndexedEXT wrap_glReadBufferIndexedEXT +#define glReadBufferNV wrap_glReadBufferNV +#define glReadPixels wrap_glReadPixels +#define glReadnPixels wrap_glReadnPixels +#define glReadnPixelsEXT wrap_glReadnPixelsEXT +#define glReadnPixelsKHR wrap_glReadnPixelsKHR +#define glReleaseShaderCompiler wrap_glReleaseShaderCompiler +#define glRenderbufferStorage wrap_glRenderbufferStorage +#define glRenderbufferStorageMultisample wrap_glRenderbufferStorageMultisample +#define glRenderbufferStorageMultisampleANGLE wrap_glRenderbufferStorageMultisampleANGLE +#define glRenderbufferStorageMultisampleAPPLE wrap_glRenderbufferStorageMultisampleAPPLE +#define glRenderbufferStorageMultisampleEXT wrap_glRenderbufferStorageMultisampleEXT +#define glRenderbufferStorageMultisampleIMG wrap_glRenderbufferStorageMultisampleIMG +#define glRenderbufferStorageMultisampleNV wrap_glRenderbufferStorageMultisampleNV +#define glRenderbufferStorageOES wrap_glRenderbufferStorageOES +#define glResolveDepthValuesNV wrap_glResolveDepthValuesNV +#define glResolveMultisampleFramebufferAPPLE wrap_glResolveMultisampleFramebufferAPPLE +#define glResumeTransformFeedback wrap_glResumeTransformFeedback +#define glRotatef wrap_glRotatef +#define glRotatex wrap_glRotatex +#define glRotatexOES wrap_glRotatexOES +#define glSampleCoverage wrap_glSampleCoverage +#define glSampleCoveragex wrap_glSampleCoveragex +#define glSampleCoveragexOES wrap_glSampleCoveragexOES +#define glSampleMaski wrap_glSampleMaski +#define glSamplerParameterIiv wrap_glSamplerParameterIiv +#define glSamplerParameterIivEXT wrap_glSamplerParameterIivEXT +#define glSamplerParameterIivOES wrap_glSamplerParameterIivOES +#define glSamplerParameterIuiv wrap_glSamplerParameterIuiv +#define glSamplerParameterIuivEXT wrap_glSamplerParameterIuivEXT +#define glSamplerParameterIuivOES wrap_glSamplerParameterIuivOES +#define glSamplerParameterf wrap_glSamplerParameterf +#define glSamplerParameterfv wrap_glSamplerParameterfv +#define glSamplerParameteri wrap_glSamplerParameteri +#define glSamplerParameteriv wrap_glSamplerParameteriv +#define glScalef wrap_glScalef +#define glScalex wrap_glScalex +#define glScalexOES wrap_glScalexOES +#define glScissor wrap_glScissor +#define glScissorArrayvNV wrap_glScissorArrayvNV +#define glScissorIndexedNV wrap_glScissorIndexedNV +#define glScissorIndexedvNV wrap_glScissorIndexedvNV +#define glSelectPerfMonitorCountersAMD wrap_glSelectPerfMonitorCountersAMD +#define glSetFenceNV wrap_glSetFenceNV +#define glShadeModel wrap_glShadeModel +#define glShaderBinary wrap_glShaderBinary +#define glShaderSource wrap_glShaderSource +#define glStartTilingQCOM wrap_glStartTilingQCOM +#define glStencilFillPathInstancedNV wrap_glStencilFillPathInstancedNV +#define glStencilFillPathNV wrap_glStencilFillPathNV +#define glStencilFunc wrap_glStencilFunc +#define glStencilFuncSeparate wrap_glStencilFuncSeparate +#define glStencilMask wrap_glStencilMask +#define glStencilMaskSeparate wrap_glStencilMaskSeparate +#define glStencilOp wrap_glStencilOp +#define glStencilOpSeparate wrap_glStencilOpSeparate +#define glStencilStrokePathInstancedNV wrap_glStencilStrokePathInstancedNV +#define glStencilStrokePathNV wrap_glStencilStrokePathNV +#define glStencilThenCoverFillPathInstancedNV wrap_glStencilThenCoverFillPathInstancedNV +#define glStencilThenCoverFillPathNV wrap_glStencilThenCoverFillPathNV +#define glStencilThenCoverStrokePathInstancedNV wrap_glStencilThenCoverStrokePathInstancedNV +#define glStencilThenCoverStrokePathNV wrap_glStencilThenCoverStrokePathNV +#define glSubpixelPrecisionBiasNV wrap_glSubpixelPrecisionBiasNV +#define glTestFenceNV wrap_glTestFenceNV +#define glTexBuffer wrap_glTexBuffer +#define glTexBufferEXT wrap_glTexBufferEXT +#define glTexBufferOES wrap_glTexBufferOES +#define glTexBufferRange wrap_glTexBufferRange +#define glTexBufferRangeEXT wrap_glTexBufferRangeEXT +#define glTexBufferRangeOES wrap_glTexBufferRangeOES +#define glTexCoordPointer wrap_glTexCoordPointer +#define glTexEnvf wrap_glTexEnvf +#define glTexEnvfv wrap_glTexEnvfv +#define glTexEnvi wrap_glTexEnvi +#define glTexEnviv wrap_glTexEnviv +#define glTexEnvx wrap_glTexEnvx +#define glTexEnvxOES wrap_glTexEnvxOES +#define glTexEnvxv wrap_glTexEnvxv +#define glTexEnvxvOES wrap_glTexEnvxvOES +#define glTexGenfOES wrap_glTexGenfOES +#define glTexGenfvOES wrap_glTexGenfvOES +#define glTexGeniOES wrap_glTexGeniOES +#define glTexGenivOES wrap_glTexGenivOES +#define glTexGenxOES wrap_glTexGenxOES +#define glTexGenxvOES wrap_glTexGenxvOES +#define glTexImage2D wrap_glTexImage2D +#define glTexImage3D wrap_glTexImage3D +#define glTexImage3DOES wrap_glTexImage3DOES +#define glTexPageCommitmentEXT wrap_glTexPageCommitmentEXT +#define glTexParameterIiv wrap_glTexParameterIiv +#define glTexParameterIivEXT wrap_glTexParameterIivEXT +#define glTexParameterIivOES wrap_glTexParameterIivOES +#define glTexParameterIuiv wrap_glTexParameterIuiv +#define glTexParameterIuivEXT wrap_glTexParameterIuivEXT +#define glTexParameterIuivOES wrap_glTexParameterIuivOES +#define glTexParameterf wrap_glTexParameterf +#define glTexParameterfv wrap_glTexParameterfv +#define glTexParameteri wrap_glTexParameteri +#define glTexParameteriv wrap_glTexParameteriv +#define glTexParameterx wrap_glTexParameterx +#define glTexParameterxOES wrap_glTexParameterxOES +#define glTexParameterxv wrap_glTexParameterxv +#define glTexParameterxvOES wrap_glTexParameterxvOES +#define glTexStorage1DEXT wrap_glTexStorage1DEXT +#define glTexStorage2D wrap_glTexStorage2D +#define glTexStorage2DEXT wrap_glTexStorage2DEXT +#define glTexStorage2DMultisample wrap_glTexStorage2DMultisample +#define glTexStorage3D wrap_glTexStorage3D +#define glTexStorage3DEXT wrap_glTexStorage3DEXT +#define glTexStorage3DMultisample wrap_glTexStorage3DMultisample +#define glTexStorage3DMultisampleOES wrap_glTexStorage3DMultisampleOES +#define glTexSubImage2D wrap_glTexSubImage2D +#define glTexSubImage3D wrap_glTexSubImage3D +#define glTexSubImage3DOES wrap_glTexSubImage3DOES +#define glTextureStorage1DEXT wrap_glTextureStorage1DEXT +#define glTextureStorage2DEXT wrap_glTextureStorage2DEXT +#define glTextureStorage3DEXT wrap_glTextureStorage3DEXT +#define glTextureViewEXT wrap_glTextureViewEXT +#define glTextureViewOES wrap_glTextureViewOES +#define glTransformFeedbackVaryings wrap_glTransformFeedbackVaryings +#define glTransformPathNV wrap_glTransformPathNV +#define glTranslatef wrap_glTranslatef +#define glTranslatex wrap_glTranslatex +#define glTranslatexOES wrap_glTranslatexOES +#define glUniform1f wrap_glUniform1f +#define glUniform1fv wrap_glUniform1fv +#define glUniform1i wrap_glUniform1i +#define glUniform1iv wrap_glUniform1iv +#define glUniform1ui wrap_glUniform1ui +#define glUniform1uiv wrap_glUniform1uiv +#define glUniform2f wrap_glUniform2f +#define glUniform2fv wrap_glUniform2fv +#define glUniform2i wrap_glUniform2i +#define glUniform2iv wrap_glUniform2iv +#define glUniform2ui wrap_glUniform2ui +#define glUniform2uiv wrap_glUniform2uiv +#define glUniform3f wrap_glUniform3f +#define glUniform3fv wrap_glUniform3fv +#define glUniform3i wrap_glUniform3i +#define glUniform3iv wrap_glUniform3iv +#define glUniform3ui wrap_glUniform3ui +#define glUniform3uiv wrap_glUniform3uiv +#define glUniform4f wrap_glUniform4f +#define glUniform4fv wrap_glUniform4fv +#define glUniform4i wrap_glUniform4i +#define glUniform4iv wrap_glUniform4iv +#define glUniform4ui wrap_glUniform4ui +#define glUniform4uiv wrap_glUniform4uiv +#define glUniformBlockBinding wrap_glUniformBlockBinding +#define glUniformHandleui64NV wrap_glUniformHandleui64NV +#define glUniformHandleui64vNV wrap_glUniformHandleui64vNV +#define glUniformMatrix2fv wrap_glUniformMatrix2fv +#define glUniformMatrix2x3fv wrap_glUniformMatrix2x3fv +#define glUniformMatrix2x3fvNV wrap_glUniformMatrix2x3fvNV +#define glUniformMatrix2x4fv wrap_glUniformMatrix2x4fv +#define glUniformMatrix2x4fvNV wrap_glUniformMatrix2x4fvNV +#define glUniformMatrix3fv wrap_glUniformMatrix3fv +#define glUniformMatrix3x2fv wrap_glUniformMatrix3x2fv +#define glUniformMatrix3x2fvNV wrap_glUniformMatrix3x2fvNV +#define glUniformMatrix3x4fv wrap_glUniformMatrix3x4fv +#define glUniformMatrix3x4fvNV wrap_glUniformMatrix3x4fvNV +#define glUniformMatrix4fv wrap_glUniformMatrix4fv +#define glUniformMatrix4x2fv wrap_glUniformMatrix4x2fv +#define glUniformMatrix4x2fvNV wrap_glUniformMatrix4x2fvNV +#define glUniformMatrix4x3fv wrap_glUniformMatrix4x3fv +#define glUniformMatrix4x3fvNV wrap_glUniformMatrix4x3fvNV +#define glUnmapBuffer wrap_glUnmapBuffer +#define glUnmapBufferOES wrap_glUnmapBufferOES +#define glUseProgram wrap_glUseProgram +#define glUseProgramStages wrap_glUseProgramStages +#define glUseProgramStagesEXT wrap_glUseProgramStagesEXT +#define glValidateProgram wrap_glValidateProgram +#define glValidateProgramPipeline wrap_glValidateProgramPipeline +#define glValidateProgramPipelineEXT wrap_glValidateProgramPipelineEXT +#define glVertexAttrib1f wrap_glVertexAttrib1f +#define glVertexAttrib1fv wrap_glVertexAttrib1fv +#define glVertexAttrib2f wrap_glVertexAttrib2f +#define glVertexAttrib2fv wrap_glVertexAttrib2fv +#define glVertexAttrib3f wrap_glVertexAttrib3f +#define glVertexAttrib3fv wrap_glVertexAttrib3fv +#define glVertexAttrib4f wrap_glVertexAttrib4f +#define glVertexAttrib4fv wrap_glVertexAttrib4fv +#define glVertexAttribBinding wrap_glVertexAttribBinding +#define glVertexAttribDivisor wrap_glVertexAttribDivisor +#define glVertexAttribDivisorANGLE wrap_glVertexAttribDivisorANGLE +#define glVertexAttribDivisorEXT wrap_glVertexAttribDivisorEXT +#define glVertexAttribDivisorNV wrap_glVertexAttribDivisorNV +#define glVertexAttribFormat wrap_glVertexAttribFormat +#define glVertexAttribI4i wrap_glVertexAttribI4i +#define glVertexAttribI4iv wrap_glVertexAttribI4iv +#define glVertexAttribI4ui wrap_glVertexAttribI4ui +#define glVertexAttribI4uiv wrap_glVertexAttribI4uiv +#define glVertexAttribIFormat wrap_glVertexAttribIFormat +#define glVertexAttribIPointer wrap_glVertexAttribIPointer +#define glVertexAttribPointer wrap_glVertexAttribPointer +#define glVertexBindingDivisor wrap_glVertexBindingDivisor +#define glVertexPointer wrap_glVertexPointer +#define glViewport wrap_glViewport +#define glViewportArrayvNV wrap_glViewportArrayvNV +#define glViewportIndexedfNV wrap_glViewportIndexedfNV +#define glViewportIndexedfvNV wrap_glViewportIndexedfvNV +#define glWaitSync wrap_glWaitSync +#define glWaitSyncAPPLE wrap_glWaitSyncAPPLE +#define glWeightPathsNV wrap_glWeightPathsNV +#define glWeightPointerOES wrap_glWeightPointerOES + +#endif // HWUI_GLES_WRAP_ENABLED diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp index d2685daa1711..8ba4761c1b2e 100644 --- a/libs/hwui/font/CacheTexture.cpp +++ b/libs/hwui/font/CacheTexture.cpp @@ -111,11 +111,11 @@ CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) CacheTexture::CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount) : mTexture(Caches::getInstance()) + , mWidth(width) + , mHeight(height) , mFormat(format) , mMaxQuadCount(maxQuadCount) , mCaches(Caches::getInstance()) { - mTexture.width = width; - mTexture.height = height; mTexture.blend = true; mCacheBlocks = new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, @@ -160,10 +160,7 @@ void CacheTexture::releasePixelBuffer() { delete mPixelBuffer; mPixelBuffer = nullptr; } - if (mTexture.id) { - mCaches.textureState().deleteTexture(mTexture.id); - mTexture.id = 0; - } + mTexture.deleteTexture(); mDirty = false; mCurrentQuad = 0; } @@ -183,22 +180,9 @@ void CacheTexture::allocatePixelBuffer() { mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight()); } - if (!mTexture.id) { - glGenTextures(1, &mTexture.id); - - mCaches.textureState().bindTexture(mTexture.id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - // Initialize texture dimensions - glTexImage2D(GL_TEXTURE_2D, 0, mFormat, getWidth(), getHeight(), 0, - mFormat, GL_UNSIGNED_BYTE, nullptr); - - const GLenum filtering = getLinearFiltering() ? GL_LINEAR : GL_NEAREST; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filtering); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filtering); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } + mTexture.resize(mWidth, mHeight, mFormat); + mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST); + mTexture.setWrap(GL_CLAMP_TO_EDGE); } bool CacheTexture::upload() { diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h index 6dabc768ce6b..4dfb41dafcc7 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.h @@ -23,7 +23,7 @@ #include "Vertex.h" #include <GLES3/gl3.h> -#include <SkScalerContext.h> +#include <SkGlyph.h> #include <utils/Log.h> @@ -92,11 +92,11 @@ public: bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY); inline uint16_t getWidth() const { - return mTexture.width; + return mWidth; } inline uint16_t getHeight() const { - return mTexture.height; + return mHeight; } inline GLenum getFormat() const { @@ -122,7 +122,7 @@ public: GLuint getTextureId() { allocatePixelBuffer(); - return mTexture.id; + return mTexture.id(); } inline bool isDirty() const { @@ -183,6 +183,7 @@ private: PixelBuffer* mPixelBuffer = nullptr; Texture mTexture; + uint32_t mWidth, mHeight; GLenum mFormat; bool mLinearFiltering = false; bool mDirty = false; diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index 5de64a4b1654..8e04c8715f62 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -14,15 +14,12 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <cutils/compiler.h> #include <utils/JenkinsHash.h> #include <utils/Trace.h> -#include <SkDeviceProperties.h> +#include <SkSurfaceProps.h> #include <SkGlyph.h> #include <SkGlyphCache.h> #include <SkUtils.h> @@ -64,8 +61,6 @@ Font::FontDescription::FontDescription(const SkPaint* paint, const SkMatrix& ras } Font::~Font() { - mState->removeFont(this); - for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { delete mCachedGlyphs.valueAt(i); } @@ -284,8 +279,8 @@ CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bo if (cachedGlyph) { // Is the glyph still in texture cache? if (!cachedGlyph->mIsValid) { - SkDeviceProperties deviceProperties(kUnknown_SkPixelGeometry, 1.0f); - SkAutoGlyphCache autoCache(*paint, &deviceProperties, &mDescription.mLookupTransform); + SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry); + SkAutoGlyphCacheNoGamma autoCache(*paint, &surfaceProps, &mDescription.mLookupTransform); const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), textUnit); updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), cachedGlyph, precaching); } @@ -296,20 +291,18 @@ CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bo return cachedGlyph; } -void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len, +void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y, const float* positions) { - render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, nullptr, + render(paint, glyphs, numGlyphs, x, y, FRAMEBUFFER, nullptr, 0, 0, nullptr, positions); } -void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len, - int numGlyphs, const SkPath* path, float hOffset, float vOffset) { - if (numGlyphs == 0 || text == nullptr || len == 0) { +void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, + const SkPath* path, float hOffset, float vOffset) { + if (numGlyphs == 0 || glyphs == nullptr) { return; } - text += start; - int glyphsCount = 0; SkFixed prevRsbDelta = 0; @@ -322,7 +315,7 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32 float pathLength = SkScalarToFloat(measure.getLength()); if (paint->getTextAlign() != SkPaint::kLeft_Align) { - float textWidth = SkScalarToFloat(paint->measureText(text, len)); + float textWidth = SkScalarToFloat(paint->measureText(glyphs, numGlyphs * 2)); float pathOffset = pathLength; if (paint->getTextAlign() == SkPaint::kCenter_Align) { textWidth *= 0.5f; @@ -332,7 +325,7 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32 } while (glyphsCount < numGlyphs && penX < pathLength) { - glyph_t glyph = GET_GLYPH(text); + glyph_t glyph = *(glyphs++); if (IS_END_OF_STRING(glyph)) { break; @@ -352,26 +345,24 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32 } } -void Font::measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, +void Font::measure(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, Rect *bounds, const float* positions) { if (bounds == nullptr) { ALOGE("No return rectangle provided to measure text"); return; } bounds->set(1e6, -1e6, -1e6, 1e6); - render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions); + render(paint, glyphs, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions); } -void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) { - ATRACE_NAME("Precache Glyphs"); - - if (numGlyphs == 0 || text == nullptr) { +void Font::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs) { + if (numGlyphs == 0 || glyphs == nullptr) { return; } int glyphsCount = 0; while (glyphsCount < numGlyphs) { - glyph_t glyph = GET_GLYPH(text); + glyph_t glyph = *(glyphs++); // Reached the end of the string if (IS_END_OF_STRING(glyph)) { @@ -383,10 +374,10 @@ void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) { } } -void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, +void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) { - if (numGlyphs == 0 || text == nullptr || len == 0) { + if (numGlyphs == 0 || glyphs == nullptr) { return; } @@ -400,11 +391,10 @@ void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32 }; RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform]; - text += start; int glyphsCount = 0; while (glyphsCount < numGlyphs) { - glyph_t glyph = GET_GLYPH(text); + glyph_t glyph = *(glyphs++); // Reached the end of the string if (IS_END_OF_STRING(glyph)) { @@ -475,8 +465,8 @@ CachedGlyphInfo* Font::cacheGlyph(const SkPaint* paint, glyph_t glyph, bool prec CachedGlyphInfo* newGlyph = new CachedGlyphInfo(); mCachedGlyphs.add(glyph, newGlyph); - SkDeviceProperties deviceProperties(kUnknown_SkPixelGeometry, 1.0f); - SkAutoGlyphCache autoCache(*paint, &deviceProperties, &mDescription.mLookupTransform); + SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry); + SkAutoGlyphCacheNoGamma autoCache(*paint, &surfaceProps, &mDescription.mLookupTransform); const SkGlyph& skiaGlyph = GET_METRICS(autoCache.getCache(), glyph); newGlyph->mIsValid = false; newGlyph->mGlyphIndex = skiaGlyph.fID; diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h index 3119d734bc2b..288f73361bbc 100644 --- a/libs/hwui/font/Font.h +++ b/libs/hwui/font/Font.h @@ -23,7 +23,6 @@ #include <SkScalar.h> #include <SkGlyphCache.h> -#include <SkScalerContext.h> #include <SkPaint.h> #include <SkPathMeasure.h> @@ -82,10 +81,10 @@ public: ~Font(); - void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, + void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y, const float* positions); - void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, + void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkPath* path, float hOffset, float vOffset); const Font::FontDescription& getDescription() const { @@ -111,13 +110,13 @@ private: MEASURE, }; - void precache(const SkPaint* paint, const char* text, int numGlyphs); + void precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs); - void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len, + void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions); - void measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, + void measure(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, Rect *bounds, const float* positions); void invalidateTextureCache(CacheTexture* cacheTexture = nullptr); diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h index 4e5debe33c4a..aa77d98c9343 100644 --- a/libs/hwui/font/FontUtil.h +++ b/libs/hwui/font/FontUtil.h @@ -40,26 +40,9 @@ #define CACHE_BLOCK_ROUNDING_SIZE 4 -#if RENDER_TEXT_AS_GLYPHS - typedef uint16_t glyph_t; - #define TO_GLYPH(g) g - #define GET_METRICS(cache, glyph) cache->getGlyphIDMetrics(glyph) - #define GET_GLYPH(text) nextGlyph((const uint16_t**) &text) - #define IS_END_OF_STRING(glyph) false - - static inline glyph_t nextGlyph(const uint16_t** srcPtr) { - const uint16_t* src = *srcPtr; - glyph_t g = *src++; - *srcPtr = src; - return g; - } -#else - typedef SkUnichar glyph_t; - #define TO_GLYPH(g) ((SkUnichar) g) - #define GET_METRICS(cache, glyph) cache->getUnicharMetrics(glyph) - #define GET_GLYPH(text) SkUTF16_NextUnichar((const uint16_t**) &text) - #define IS_END_OF_STRING(glyph) glyph < 0 -#endif +typedef uint16_t glyph_t; +#define GET_METRICS(cache, glyph) cache->getGlyphIDMetrics(glyph) +#define IS_END_OF_STRING(glyph) false #define AUTO_KERN(prev, next) (((next) - (prev) + 32) >> 6 << 16) diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp new file mode 100644 index 000000000000..7bfa15a26d56 --- /dev/null +++ b/libs/hwui/hwui/Canvas.cpp @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2015 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 "Canvas.h" + +#include "DisplayListCanvas.h" +#include "RecordingCanvas.h" +#include "MinikinUtils.h" +#include "Paint.h" +#include "Typeface.h" + +#include <SkDrawFilter.h> + +namespace android { + +Canvas* Canvas::create_recording_canvas(int width, int height) { +#if HWUI_NEW_OPS + return new uirenderer::RecordingCanvas(width, height); +#else + return new uirenderer::DisplayListCanvas(width, height); +#endif +} + +void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { + uint32_t flags; + SkDrawFilter* drawFilter = getDrawFilter(); + if (drawFilter) { + SkPaint paintCopy(paint); + drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type); + flags = paintCopy.getFlags(); + } else { + flags = paint.getFlags(); + } + if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { + // Same values used by Skia + const float kStdStrikeThru_Offset = (-6.0f / 21.0f); + const float kStdUnderline_Offset = (1.0f / 9.0f); + const float kStdUnderline_Thickness = (1.0f / 18.0f); + + SkScalar left = x; + SkScalar right = x + length; + float textSize = paint.getTextSize(); + float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); + if (flags & SkPaint::kUnderlineText_Flag) { + SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth; + SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth; + drawRect(left, top, right, bottom, paint); + } + if (flags & SkPaint::kStrikeThruText_Flag) { + SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth; + SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth; + drawRect(left, top, right, bottom, paint); + } + } +} + +static void simplifyPaint(int color, SkPaint* paint) { + paint->setColor(color); + paint->setShader(nullptr); + paint->setColorFilter(nullptr); + paint->setLooper(nullptr); + paint->setStrokeWidth(4 + 0.04 * paint->getTextSize()); + paint->setStrokeJoin(SkPaint::kRound_Join); + paint->setLooper(nullptr); +} + +class DrawTextFunctor { +public: + DrawTextFunctor(const Layout& layout, Canvas* canvas, uint16_t* glyphs, float* pos, + const SkPaint& paint, float x, float y, MinikinRect& bounds, float totalAdvance) + : layout(layout) + , canvas(canvas) + , glyphs(glyphs) + , pos(pos) + , paint(paint) + , x(x) + , y(y) + , bounds(bounds) + , totalAdvance(totalAdvance) { + } + + void operator()(size_t start, size_t end) { + if (canvas->drawTextAbsolutePos()) { + for (size_t i = start; i < end; i++) { + glyphs[i] = layout.getGlyphId(i); + pos[2 * i] = x + layout.getX(i); + pos[2 * i + 1] = y + layout.getY(i); + } + } else { + for (size_t i = start; i < end; i++) { + glyphs[i] = layout.getGlyphId(i); + pos[2 * i] = layout.getX(i); + pos[2 * i + 1] = layout.getY(i); + } + } + + size_t glyphCount = end - start; + + if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) { + // high contrast draw path + int color = paint.getColor(); + int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color); + bool darken = channelSum < (128 * 3); + + // outline + SkPaint outlinePaint(paint); + simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint); + outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style); + canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, outlinePaint, x, y, + bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance); + + // inner + SkPaint innerPaint(paint); + simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint); + innerPaint.setStyle(SkPaint::kFill_Style); + canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, innerPaint, x, y, + bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance); + } else { + // standard draw path + canvas->drawGlyphs(glyphs + start, pos + (2 * start), glyphCount, paint, x, y, + bounds.mLeft, bounds.mTop, bounds.mRight, bounds.mBottom, totalAdvance); + } + } +private: + const Layout& layout; + Canvas* canvas; + uint16_t* glyphs; + float* pos; + const SkPaint& paint; + float x; + float y; + MinikinRect& bounds; + float totalAdvance; +}; + +void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount, + float x, float y, int bidiFlags, const Paint& origPaint, Typeface* typeface) { + // minikin may modify the original paint + Paint paint(origPaint); + + Layout layout; + MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount); + + size_t nGlyphs = layout.nGlyphs(); + std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]); + std::unique_ptr<float[]> pos(new float[nGlyphs * 2]); + + x += MinikinUtils::xOffsetForTextAlign(&paint, layout); + + MinikinRect bounds; + layout.getBounds(&bounds); + if (!drawTextAbsolutePos()) { + bounds.offset(x, y); + } + + // Set align to left for drawing, as we don't want individual + // glyphs centered or right-aligned; the offset above takes + // care of all alignment. + paint.setTextAlign(Paint::kLeft_Align); + + DrawTextFunctor f(layout, this, glyphs.get(), pos.get(), + paint, x, y, bounds, layout.getAdvance()); + MinikinUtils::forFontRun(layout, &paint, f); +} + +class DrawTextOnPathFunctor { +public: + DrawTextOnPathFunctor(const Layout& layout, Canvas* canvas, float hOffset, + float vOffset, const Paint& paint, const SkPath& path) + : layout(layout) + , canvas(canvas) + , hOffset(hOffset) + , vOffset(vOffset) + , paint(paint) + , path(path) { + } + + void operator()(size_t start, size_t end) { + uint16_t glyphs[1]; + for (size_t i = start; i < end; i++) { + glyphs[0] = layout.getGlyphId(i); + float x = hOffset + layout.getX(i); + float y = vOffset + layout.getY(i); + canvas->drawGlyphsOnPath(glyphs, 1, path, x, y, paint); + } + } +private: + const Layout& layout; + Canvas* canvas; + float hOffset; + float vOffset; + const Paint& paint; + const SkPath& path; +}; + +void Canvas::drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path, + float hOffset, float vOffset, const Paint& paint, Typeface* typeface) { + Paint paintCopy(paint); + Layout layout; + MinikinUtils::doLayout(&layout, &paintCopy, bidiFlags, typeface, text, 0, count, count); + hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path); + + // Set align to left for drawing, as we don't want individual + // glyphs centered or right-aligned; the offset above takes + // care of all alignment. + paintCopy.setTextAlign(Paint::kLeft_Align); + + DrawTextOnPathFunctor f(layout, this, hOffset, vOffset, paintCopy, path); + MinikinUtils::forFontRun(layout, &paintCopy, f); +} + +} // namespace android diff --git a/libs/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 160d9a8a87dd..55af33e80256 100644 --- a/libs/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -18,6 +18,10 @@ #define ANDROID_GRAPHICS_CANVAS_H #include <cutils/compiler.h> +#include <utils/Functor.h> + +#include "GlFunctorLifecycleListener.h" +#include "utils/NinePatch.h" #include <SkBitmap.h> #include <SkCanvas.h> @@ -25,12 +29,49 @@ namespace android { +namespace uirenderer { + class CanvasPropertyPaint; + class CanvasPropertyPrimitive; + class DeferredLayerUpdater; + class DisplayList; + class RenderNode; +} + +namespace SaveFlags { + +// These must match the corresponding Canvas API constants. +enum { + Matrix = 0x01, + Clip = 0x02, + HasAlphaLayer = 0x04, + ClipToLayer = 0x10, + + // Helper constant + MatrixClip = Matrix | Clip, +}; +typedef uint32_t Flags; + +} // namespace SaveFlags + +namespace uirenderer { +class SkiaCanvasProxy; +namespace VectorDrawable { +class Tree; +}; +}; +typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; + +class Paint; +struct Typeface; + class ANDROID_API Canvas { public: virtual ~Canvas() {}; static Canvas* create_canvas(const SkBitmap& bitmap); + static Canvas* create_recording_canvas(int width, int height); + /** * Create a new Canvas object which delegates to an SkCanvas. * @@ -56,6 +97,7 @@ public: */ virtual SkCanvas* asSkCanvas() = 0; + virtual void setBitmap(const SkBitmap& bitmap) = 0; virtual bool isOpaque() = 0; @@ -63,27 +105,48 @@ public: virtual int height() = 0; // ---------------------------------------------------------------------------- +// View System operations (not exposed in public Canvas API) +// ---------------------------------------------------------------------------- + + virtual void resetRecording(int width, int height) = 0; + virtual uirenderer::DisplayList* finishRecording() = 0; + virtual void insertReorderBarrier(bool enableReorder) = 0; + + virtual void setHighContrastText(bool highContrastText) = 0; + virtual bool isHighContrastText() = 0; + + virtual void drawRoundRect(uirenderer::CanvasPropertyPrimitive* left, + uirenderer::CanvasPropertyPrimitive* top, uirenderer::CanvasPropertyPrimitive* right, + uirenderer::CanvasPropertyPrimitive* bottom, uirenderer::CanvasPropertyPrimitive* rx, + uirenderer::CanvasPropertyPrimitive* ry, uirenderer::CanvasPropertyPaint* paint) = 0; + virtual void drawCircle(uirenderer::CanvasPropertyPrimitive* x, + uirenderer::CanvasPropertyPrimitive* y, uirenderer::CanvasPropertyPrimitive* radius, + uirenderer::CanvasPropertyPaint* paint) = 0; + + virtual void drawLayer(uirenderer::DeferredLayerUpdater* layerHandle) = 0; + virtual void drawRenderNode(uirenderer::RenderNode* renderNode) = 0; + virtual void callDrawGLFunction(Functor* functor, + uirenderer::GlFunctorLifecycleListener* listener) = 0; + +// ---------------------------------------------------------------------------- // Canvas state operations // ---------------------------------------------------------------------------- + // Save (layer) virtual int getSaveCount() const = 0; - virtual int save(SkCanvas::SaveFlags flags) = 0; + virtual int save(SaveFlags::Flags flags) = 0; virtual void restore() = 0; virtual void restoreToCount(int saveCount) = 0; virtual int saveLayer(float left, float top, float right, float bottom, - const SkPaint* paint, SkCanvas::SaveFlags flags) = 0; + const SkPaint* paint, SaveFlags::Flags flags) = 0; virtual int saveLayerAlpha(float left, float top, float right, float bottom, - int alpha, SkCanvas::SaveFlags flags) = 0; + int alpha, SaveFlags::Flags flags) = 0; // Matrix virtual void getMatrix(SkMatrix* outMatrix) const = 0; virtual void setMatrix(const SkMatrix& matrix) = 0; - /// Like setMatrix(), but to be translated into local / view-relative coordinates - /// rather than executed in global / device coordinates at rendering time. - virtual void setLocalMatrix(const SkMatrix& matrix) = 0; - virtual void concat(const SkMatrix& matrix) = 0; virtual void rotate(float degrees) = 0; virtual void scale(float sx, float sy) = 0; @@ -112,12 +175,13 @@ public: // Geometry virtual void drawPoint(float x, float y, const SkPaint& paint) = 0; - virtual void drawPoints(const float* points, int count, const SkPaint& paint) = 0; + virtual void drawPoints(const float* points, int floatCount, const SkPaint& paint) = 0; virtual void drawLine(float startX, float startY, float stopX, float stopY, const SkPaint& paint) = 0; - virtual void drawLines(const float* points, int count, const SkPaint& paint) = 0; + virtual void drawLines(const float* points, int floatCount, const SkPaint& paint) = 0; virtual void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) = 0; + virtual void drawRegion(const SkRegion& region, const SkPaint& paint) = 0; virtual void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) = 0; virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) = 0; @@ -140,32 +204,52 @@ public: float dstRight, float dstBottom, const SkPaint* paint) = 0; virtual void drawBitmapMesh(const SkBitmap& bitmap, int meshWidth, int meshHeight, const float* vertices, const int* colors, const SkPaint* paint) = 0; + virtual void drawNinePatch(const SkBitmap& bitmap, const android::Res_png_9patch& chunk, + float dstLeft, float dstTop, float dstRight, float dstBottom, + const SkPaint* paint) = 0; + + /** + * Specifies if the positions passed to ::drawText are absolute or relative + * to the (x,y) value provided. + * + * If true the (x,y) values are ignored. Otherwise, those (x,y) values need + * to be added to each glyph's position to get its absolute position. + */ + virtual bool drawTextAbsolutePos() const = 0; + + /** + * Draws a VectorDrawable onto the canvas. + */ + virtual void drawVectorDrawable(VectorDrawableRoot* tree); + + /** + * Converts utf16 text to glyphs, calculating position and boundary, + * and delegating the final draw to virtual drawGlyphs method. + */ + void drawText(const uint16_t* text, int start, int count, int contextCount, + float x, float y, int bidiFlags, const Paint& origPaint, Typeface* typeface); + + void drawTextOnPath(const uint16_t* text, int count, int bidiFlags, const SkPath& path, + float hOffset, float vOffset, const Paint& paint, Typeface* typeface); + +protected: + void drawTextDecorations(float x, float y, float length, const SkPaint& paint); - // Text /** * drawText: count is of glyphs - * totalAdvance is ignored in software renderering, used by hardware renderer for - * text decorations (underlines, strikethroughs). + * totalAdvance: used to define width of text decorations (underlines, strikethroughs). */ - virtual void drawText(const uint16_t* glyphs, const float* positions, int count, + virtual void drawGlyphs(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) = 0; - /** drawPosText: count is of UTF16 characters, posCount is floats (2 * glyphs) */ - virtual void drawPosText(const uint16_t* text, const float* positions, int count, - int posCount, const SkPaint& paint) = 0; /** drawTextOnPath: count is of glyphs */ - virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, + virtual void drawGlyphsOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) = 0; - /** - * Specifies if the positions passed to ::drawText are absolute or relative - * to the (x,y) value provided. - * - * If true the (x,y) values are ignored. Otherwise, those (x,y) values need - * to be added to each glyph's position to get its absolute position. - */ - virtual bool drawTextAbsolutePos() const = 0; + friend class DrawTextFunctor; + friend class DrawTextOnPathFunctor; + friend class uirenderer::SkiaCanvasProxy; }; }; // namespace android diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp new file mode 100644 index 000000000000..a455f576e38d --- /dev/null +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013 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 "MinikinSkia.h" + +#include <SkPaint.h> +#include <SkTypeface.h> +#include <cutils/log.h> + +namespace android { + +MinikinFontSkia::MinikinFontSkia(SkTypeface* typeface, const void* fontData, size_t fontSize, + int ttcIndex) : + MinikinFont(typeface->uniqueID()), mTypeface(typeface), mFontData(fontData), + mFontSize(fontSize), mTtcIndex(ttcIndex) { +} + +MinikinFontSkia::~MinikinFontSkia() { + SkSafeUnref(mTypeface); +} + +static void MinikinFontSkia_SetSkiaPaint(const MinikinFont* font, SkPaint* skPaint, const MinikinPaint& paint) { + skPaint->setTextEncoding(SkPaint::kGlyphID_TextEncoding); + skPaint->setTextSize(paint.size); + skPaint->setTextScaleX(paint.scaleX); + skPaint->setTextSkewX(paint.skewX); + MinikinFontSkia::unpackPaintFlags(skPaint, paint.paintFlags); + // Apply font fakery on top of user-supplied flags. + MinikinFontSkia::populateSkPaint(skPaint, font, paint.fakery); +} + +float MinikinFontSkia::GetHorizontalAdvance(uint32_t glyph_id, + const MinikinPaint &paint) const { + SkPaint skPaint; + uint16_t glyph16 = glyph_id; + SkScalar skWidth; + MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint); + skPaint.getTextWidths(&glyph16, sizeof(glyph16), &skWidth, NULL); +#ifdef VERBOSE + ALOGD("width for typeface %d glyph %d = %f", mTypeface->uniqueID(), glyph_id, skWidth); +#endif + return skWidth; +} + +void MinikinFontSkia::GetBounds(MinikinRect* bounds, uint32_t glyph_id, + const MinikinPaint& paint) const { + SkPaint skPaint; + uint16_t glyph16 = glyph_id; + SkRect skBounds; + MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint); + skPaint.getTextWidths(&glyph16, sizeof(glyph16), NULL, &skBounds); + bounds->mLeft = skBounds.fLeft; + bounds->mTop = skBounds.fTop; + bounds->mRight = skBounds.fRight; + bounds->mBottom = skBounds.fBottom; +} + +const void* MinikinFontSkia::GetTable(uint32_t tag, size_t* size, MinikinDestroyFunc* destroy) { + // we don't have a buffer to the font data, copy to own buffer + const size_t tableSize = mTypeface->getTableSize(tag); + *size = tableSize; + if (tableSize == 0) { + return nullptr; + } + void* buf = malloc(tableSize); + if (buf == nullptr) { + return nullptr; + } + mTypeface->getTableData(tag, 0, tableSize, buf); + *destroy = free; + return buf; +} + +SkTypeface *MinikinFontSkia::GetSkTypeface() const { + return mTypeface; +} + +const void* MinikinFontSkia::GetFontData() const { + return mFontData; +} + +size_t MinikinFontSkia::GetFontSize() const { + return mFontSize; +} + +int MinikinFontSkia::GetFontIndex() const { + return mTtcIndex; +} + +uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) { + uint32_t flags = paint->getFlags(); + SkPaint::Hinting hinting = paint->getHinting(); + // select only flags that might affect text layout + flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag | + SkPaint::kSubpixelText_Flag | SkPaint::kDevKernText_Flag | + SkPaint::kEmbeddedBitmapText_Flag | SkPaint::kAutoHinting_Flag | + SkPaint::kVerticalText_Flag); + flags |= (hinting << 16); + return flags; +} + +void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) { + paint->setFlags(paintFlags & SkPaint::kAllFlags); + paint->setHinting(static_cast<SkPaint::Hinting>(paintFlags >> 16)); +} + +void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font, FontFakery fakery) { + paint->setTypeface(reinterpret_cast<const MinikinFontSkia*>(font)->GetSkTypeface()); + paint->setFakeBoldText(paint->isFakeBoldText() || fakery.isFakeBold()); + if (fakery.isFakeItalic()) { + paint->setTextSkewX(paint->getTextSkewX() - 0.25f); + } +} + +} diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h new file mode 100644 index 000000000000..a7c9fb0b09d4 --- /dev/null +++ b/libs/hwui/hwui/MinikinSkia.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_GRAPHICS_MINIKIN_SKIA_H_ +#define _ANDROID_GRAPHICS_MINIKIN_SKIA_H_ + +#include <cutils/compiler.h> +#include <minikin/MinikinFont.h> + +class SkPaint; +class SkTypeface; + +namespace android { + +class ANDROID_API MinikinFontSkia : public MinikinFont { +public: + // Note: this takes ownership of the reference (will unref on dtor) + explicit MinikinFontSkia(SkTypeface *typeface, const void* fontData, size_t fontSize, + int ttcIndex); + + ~MinikinFontSkia(); + + float GetHorizontalAdvance(uint32_t glyph_id, + const MinikinPaint &paint) const; + + void GetBounds(MinikinRect* bounds, uint32_t glyph_id, + const MinikinPaint &paint) const; + + const void* GetTable(uint32_t tag, size_t* size, MinikinDestroyFunc* destroy); + + SkTypeface* GetSkTypeface() const; + + // Access to underlying raw font bytes + const void* GetFontData() const; + size_t GetFontSize() const; + int GetFontIndex() const; + + static uint32_t packPaintFlags(const SkPaint* paint); + static void unpackPaintFlags(SkPaint* paint, uint32_t paintFlags); + + // set typeface and fake bold/italic parameters + static void populateSkPaint(SkPaint* paint, const MinikinFont* font, FontFakery fakery); +private: + SkTypeface* mTypeface; + + // A raw pointer to the font data - it should be owned by some other object with + // lifetime at least as long as this object. + const void* mFontData; + size_t mFontSize; + int mTtcIndex; +}; + +} // namespace android + +#endif // _ANDROID_GRAPHICS_MINIKIN_SKIA_H_ diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp new file mode 100644 index 000000000000..67b775d98356 --- /dev/null +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 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 "MinikinUtils.h" + +#include "Paint.h" +#include "SkPathMeasure.h" +#include "Typeface.h" + +#include <cutils/log.h> +#include <string> + +namespace android { + +FontStyle MinikinUtils::prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont, + const Paint* paint, Typeface* typeface) { + const Typeface* resolvedFace = Typeface::resolveDefault(typeface); + *pFont = resolvedFace->fFontCollection; + FontStyle resolved = resolvedFace->fStyle; + + /* Prepare minikin FontStyle */ + FontVariant minikinVariant = (paint->getFontVariant() == VARIANT_ELEGANT) ? VARIANT_ELEGANT + : VARIANT_COMPACT; + const uint32_t langListId = paint->getMinikinLangListId(); + FontStyle minikinStyle(langListId, minikinVariant, resolved.getWeight(), resolved.getItalic()); + + /* Prepare minikin Paint */ + // Note: it would be nice to handle fractional size values (it would improve smooth zoom + // behavior), but historically size has been treated as an int. + // TODO: explore whether to enable fractional sizes, possibly when linear text flag is set. + minikinPaint->size = (int)paint->getTextSize(); + minikinPaint->scaleX = paint->getTextScaleX(); + minikinPaint->skewX = paint->getTextSkewX(); + minikinPaint->letterSpacing = paint->getLetterSpacing(); + minikinPaint->paintFlags = MinikinFontSkia::packPaintFlags(paint); + minikinPaint->fontFeatureSettings = paint->getFontFeatureSettings(); + minikinPaint->hyphenEdit = HyphenEdit(paint->getHyphenEdit()); + return minikinStyle; +} + +void MinikinUtils::doLayout(Layout* layout, const Paint* paint, int bidiFlags, + Typeface* typeface, const uint16_t* buf, size_t start, size_t count, + size_t bufSize) { + FontCollection *font; + MinikinPaint minikinPaint; + FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, &font, paint, typeface); + layout->setFontCollection(font); + layout->doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint); +} + +float MinikinUtils::measureText(const Paint* paint, int bidiFlags, Typeface* typeface, + const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances) { + FontCollection *font; + MinikinPaint minikinPaint; + FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, &font, paint, typeface); + return Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint, + font, advances); +} + +bool MinikinUtils::hasVariationSelector(Typeface* typeface, uint32_t codepoint, uint32_t vs) { + const Typeface* resolvedFace = Typeface::resolveDefault(typeface); + return resolvedFace->fFontCollection->hasVariationSelector(codepoint, vs); +} + +float MinikinUtils::xOffsetForTextAlign(Paint* paint, const Layout& layout) { + switch (paint->getTextAlign()) { + case Paint::kCenter_Align: + return layout.getAdvance() * -0.5f; + break; + case Paint::kRight_Align: + return -layout.getAdvance(); + break; + default: + break; + } + return 0; +} + +float MinikinUtils::hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path) { + float align = 0; + switch (paint->getTextAlign()) { + case Paint::kCenter_Align: + align = -0.5f; + break; + case Paint::kRight_Align: + align = -1; + break; + default: + return 0; + } + SkPathMeasure measure(path, false); + return align * (layout.getAdvance() - measure.getLength()); +} + +} diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h new file mode 100644 index 000000000000..cfaa961ac1fc --- /dev/null +++ b/libs/hwui/hwui/MinikinUtils.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 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. + */ + +/** + * Utilities for making Minikin work, especially from existing objects like + * Paint and so on. + **/ + + // TODO: does this really need to be separate from MinikinSkia? + +#ifndef _ANDROID_GRAPHICS_MINIKIN_UTILS_H_ +#define _ANDROID_GRAPHICS_MINIKIN_UTILS_H_ + +#include <cutils/compiler.h> +#include <minikin/Layout.h> +#include "Paint.h" +#include "MinikinSkia.h" +#include "Typeface.h" + +namespace android { + +class MinikinUtils { +public: + ANDROID_API static FontStyle prepareMinikinPaint(MinikinPaint* minikinPaint, FontCollection** pFont, + const Paint* paint, Typeface* typeface); + + ANDROID_API static void doLayout(Layout* layout, const Paint* paint, int bidiFlags, + Typeface* typeface, const uint16_t* buf, size_t start, size_t count, + size_t bufSize); + + ANDROID_API static float measureText(const Paint* paint, int bidiFlags, Typeface* typeface, + const uint16_t* buf, size_t start, size_t count, size_t bufSize, float *advances); + + ANDROID_API static bool hasVariationSelector(Typeface* typeface, uint32_t codepoint, uint32_t vs); + + ANDROID_API static float xOffsetForTextAlign(Paint* paint, const Layout& layout); + + ANDROID_API static float hOffsetForTextAlign(Paint* paint, const Layout& layout, const SkPath& path); + // f is a functor of type void f(size_t start, size_t end); + template <typename F> + ANDROID_API static void forFontRun(const Layout& layout, Paint* paint, F& f) { + float saveSkewX = paint->getTextSkewX(); + bool savefakeBold = paint->isFakeBoldText(); + MinikinFont* curFont = NULL; + size_t start = 0; + size_t nGlyphs = layout.nGlyphs(); + for (size_t i = 0; i < nGlyphs; i++) { + MinikinFont* nextFont = layout.getFont(i); + if (i > 0 && nextFont != curFont) { + MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + f(start, i); + paint->setTextSkewX(saveSkewX); + paint->setFakeBoldText(savefakeBold); + start = i; + } + curFont = nextFont; + } + if (nGlyphs > start) { + MinikinFontSkia::populateSkPaint(paint, curFont, layout.getFakery(start)); + f(start, nGlyphs); + paint->setTextSkewX(saveSkewX); + paint->setFakeBoldText(savefakeBold); + } + } +}; + +} // namespace android + +#endif // _ANDROID_GRAPHICS_MINIKIN_UTILS_H_ diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h new file mode 100644 index 000000000000..9599c30c639b --- /dev/null +++ b/libs/hwui/hwui/Paint.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_GRAPHICS_PAINT_H_ +#define ANDROID_GRAPHICS_PAINT_H_ + +#include <cutils/compiler.h> + +#include <SkPaint.h> +#include <string> + +#include <minikin/FontFamily.h> + +namespace android { + +class ANDROID_API Paint : public SkPaint { +public: + Paint(); + Paint(const Paint& paint); + Paint(const SkPaint& paint); + ~Paint(); + + Paint& operator=(const Paint& other); + + friend bool operator==(const Paint& a, const Paint& b); + friend bool operator!=(const Paint& a, const Paint& b) { + return !(a == b); + } + + void setLetterSpacing(float letterSpacing) { + mLetterSpacing = letterSpacing; + } + + float getLetterSpacing() const { + return mLetterSpacing; + } + + void setFontFeatureSettings(const std::string& fontFeatureSettings) { + mFontFeatureSettings = fontFeatureSettings; + } + + std::string getFontFeatureSettings() const { + return mFontFeatureSettings; + } + + void setMinikinLangListId(uint32_t minikinLangListId) { + mMinikinLangListId = minikinLangListId; + } + + uint32_t getMinikinLangListId() const { + return mMinikinLangListId; + } + + void setFontVariant(FontVariant variant) { + mFontVariant = variant; + } + + FontVariant getFontVariant() const { + return mFontVariant; + } + + void setHyphenEdit(uint32_t hyphen) { + mHyphenEdit = hyphen; + } + + uint32_t getHyphenEdit() const { + return mHyphenEdit; + } + +private: + float mLetterSpacing = 0; + std::string mFontFeatureSettings; + uint32_t mMinikinLangListId; + FontVariant mFontVariant; + uint32_t mHyphenEdit = 0; +}; + +} // namespace android + +#endif // ANDROID_GRAPHICS_PAINT_H_ diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp new file mode 100644 index 000000000000..b27672ce16a0 --- /dev/null +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2013 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 "Paint.h" + +namespace android { + +Paint::Paint() : + SkPaint(), mLetterSpacing(0), mFontFeatureSettings(), mMinikinLangListId(0), + mFontVariant(VARIANT_DEFAULT) { +} + +Paint::Paint(const Paint& paint) : SkPaint(paint), + mLetterSpacing(paint.mLetterSpacing), mFontFeatureSettings(paint.mFontFeatureSettings), + mMinikinLangListId(paint.mMinikinLangListId), mFontVariant(paint.mFontVariant), + mHyphenEdit(paint.mHyphenEdit) { +} + +Paint::Paint(const SkPaint& paint) : SkPaint(paint), + mLetterSpacing(0), mFontFeatureSettings(), mMinikinLangListId(0), + mFontVariant(VARIANT_DEFAULT) { +} + +Paint::~Paint() { +} + +Paint& Paint::operator=(const Paint& other) { + SkPaint::operator=(other); + mLetterSpacing = other.mLetterSpacing; + mFontFeatureSettings = other.mFontFeatureSettings; + mMinikinLangListId = other.mMinikinLangListId; + mFontVariant = other.mFontVariant; + mHyphenEdit = other.mHyphenEdit; + return *this; +} + +bool operator==(const Paint& a, const Paint& b) { + return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) + && a.mLetterSpacing == b.mLetterSpacing + && a.mFontFeatureSettings == b.mFontFeatureSettings + && a.mMinikinLangListId == b.mMinikinLangListId + && a.mFontVariant == b.mFontVariant + && a.mHyphenEdit == b.mHyphenEdit; +} + +} diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp new file mode 100644 index 000000000000..c583988554c7 --- /dev/null +++ b/libs/hwui/hwui/Typeface.cpp @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2013 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. + */ + +/** + * This is the implementation of the Typeface object. Historically, it has + * just been SkTypeface, but we are migrating to Minikin. For the time + * being, that choice is hidden under the USE_MINIKIN compile-time flag. + */ + +#include "Typeface.h" + +#include "MinikinSkia.h" +#include "SkTypeface.h" +#include "SkPaint.h" + +#include <minikin/FontCollection.h> +#include <minikin/FontFamily.h> +#include <minikin/Layout.h> +#include <utils/Log.h> + +namespace android { + +// Resolve the 1..9 weight based on base weight and bold flag +static void resolveStyle(Typeface* typeface) { + int weight = typeface->fBaseWeight / 100; + if (typeface->fSkiaStyle & SkTypeface::kBold) { + weight += 3; + } + if (weight > 9) { + weight = 9; + } + bool italic = (typeface->fSkiaStyle & SkTypeface::kItalic) != 0; + typeface->fStyle = FontStyle(weight, italic); +} + +Typeface* gDefaultTypeface = NULL; +pthread_once_t gDefaultTypefaceOnce = PTHREAD_ONCE_INIT; + +// This installs a default typeface (from a hardcoded path) that allows +// layouts to work (not crash on null pointer) before the default +// typeface is set. +// TODO: investigate why layouts are being created before Typeface.java +// class initialization. +static FontCollection *makeFontCollection() { + std::vector<FontFamily *>typefaces; + const char *fns[] = { + "/system/fonts/Roboto-Regular.ttf", + }; + + FontFamily *family = new FontFamily(); + for (size_t i = 0; i < sizeof(fns)/sizeof(fns[0]); i++) { + const char *fn = fns[i]; + ALOGD("makeFontCollection adding %s", fn); + SkTypeface *skFace = SkTypeface::CreateFromFile(fn); + if (skFace != NULL) { + // TODO: might be a nice optimization to get access to the underlying font + // data, but would require us opening the file ourselves and passing that + // to the appropriate Create method of SkTypeface. + MinikinFont *font = new MinikinFontSkia(skFace, NULL, 0, 0); + family->addFont(font); + font->Unref(); + } else { + ALOGE("failed to create font %s", fn); + } + } + typefaces.push_back(family); + + FontCollection *result = new FontCollection(typefaces); + family->Unref(); + return result; +} + +static void getDefaultTypefaceOnce() { + Layout::init(); + if (gDefaultTypeface == NULL) { + // We expect the client to set a default typeface, but provide a + // default so we can make progress before that happens. + gDefaultTypeface = new Typeface; + gDefaultTypeface->fFontCollection = makeFontCollection(); + gDefaultTypeface->fSkiaStyle = SkTypeface::kNormal; + gDefaultTypeface->fBaseWeight = 400; + resolveStyle(gDefaultTypeface); + } +} + +Typeface* Typeface::resolveDefault(Typeface* src) { + if (src == NULL) { + pthread_once(&gDefaultTypefaceOnce, getDefaultTypefaceOnce); + return gDefaultTypeface; + } else { + return src; + } +} + +Typeface* Typeface::createFromTypeface(Typeface* src, SkTypeface::Style style) { + Typeface* resolvedFace = Typeface::resolveDefault(src); + Typeface* result = new Typeface; + if (result != 0) { + result->fFontCollection = resolvedFace->fFontCollection; + result->fFontCollection->Ref(); + result->fSkiaStyle = style; + result->fBaseWeight = resolvedFace->fBaseWeight; + resolveStyle(result); + } + return result; +} + +Typeface* Typeface::createWeightAlias(Typeface* src, int weight) { + Typeface* resolvedFace = Typeface::resolveDefault(src); + Typeface* result = new Typeface; + if (result != 0) { + result->fFontCollection = resolvedFace->fFontCollection; + result->fFontCollection->Ref(); + result->fSkiaStyle = resolvedFace->fSkiaStyle; + result->fBaseWeight = weight; + resolveStyle(result); + } + return result; +} + +Typeface* Typeface::createFromFamilies(const std::vector<FontFamily*>& families) { + Typeface* result = new Typeface; + result->fFontCollection = new FontCollection(families); + if (families.empty()) { + ALOGW("createFromFamilies creating empty collection"); + result->fSkiaStyle = SkTypeface::kNormal; + } else { + const FontStyle defaultStyle; + FontFamily* firstFamily = reinterpret_cast<FontFamily*>(families[0]); + MinikinFont* mf = firstFamily->getClosestMatch(defaultStyle).font; + if (mf != NULL) { + SkTypeface* skTypeface = reinterpret_cast<MinikinFontSkia*>(mf)->GetSkTypeface(); + // TODO: probably better to query more precise style from family, will be important + // when we open up API to access 100..900 weights + result->fSkiaStyle = skTypeface->style(); + } else { + result->fSkiaStyle = SkTypeface::kNormal; + } + } + result->fBaseWeight = 400; + resolveStyle(result); + return result; +} + +void Typeface::unref() { + fFontCollection->Unref(); + delete this; +} + +void Typeface::setDefault(Typeface* face) { + gDefaultTypeface = face; +} + +} diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h new file mode 100644 index 000000000000..8862e5a5a911 --- /dev/null +++ b/libs/hwui/hwui/Typeface.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_ +#define _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_ + +#include "SkTypeface.h" + +#include <cutils/compiler.h> +#include <minikin/FontCollection.h> +#include <vector> + +namespace android { + +struct ANDROID_API Typeface { + FontCollection *fFontCollection; + + // style used for constructing and querying Typeface objects + SkTypeface::Style fSkiaStyle; + // base weight in CSS-style units, 100..900 + int fBaseWeight; + + // resolved style actually used for rendering + FontStyle fStyle; + + void unref(); + + static Typeface* resolveDefault(Typeface* src); + + static Typeface* createFromTypeface(Typeface* src, SkTypeface::Style style); + + static Typeface* createWeightAlias(Typeface* src, int baseweight); + + static Typeface* createFromFamilies(const std::vector<FontFamily*>& families); + + static void setDefault(Typeface* face); +}; + +} + +#endif // _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_ diff --git a/libs/hwui/hwui_static_deps.mk b/libs/hwui/hwui_static_deps.mk new file mode 100644 index 000000000000..299095217a8d --- /dev/null +++ b/libs/hwui/hwui_static_deps.mk @@ -0,0 +1,31 @@ +############################################################################### +# +# +# This file contains the shared and static dependencies needed by any target +# that attempts to statically link HWUI (i.e. libhwui_static build target). This +# file should be included by any target that lists libhwui_static as a +# dependency. +# +# This is a workaround for the fact that the build system does not add these +# transitive dependencies when it attempts to link libhwui_static into another +# library. +# +############################################################################### + +LOCAL_SHARED_LIBRARIES += \ + liblog \ + libcutils \ + libutils \ + libEGL \ + libGLESv2 \ + libskia \ + libui \ + libgui \ + libprotobuf-cpp-lite \ + libharfbuzz_ng \ + libft2 \ + libminikin + +ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) + LOCAL_SHARED_LIBRARIES += libRS libRScpp +endif diff --git a/libs/hwui/protos/ProtoHelpers.h b/libs/hwui/protos/ProtoHelpers.h new file mode 100644 index 000000000000..832e31200eb6 --- /dev/null +++ b/libs/hwui/protos/ProtoHelpers.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef PROTOHELPERS_H +#define PROTOHELPERS_H + +#include "Rect.h" +#include "protos/hwui.pb.h" + +namespace android { +namespace uirenderer { + +void set(proto::RectF* dest, const Rect& src) { + dest->set_left(src.left); + dest->set_top(src.top); + dest->set_right(src.right); + dest->set_bottom(src.bottom); +} + +void set(std::string* dest, const SkPath& src) { + size_t size = src.writeToMemory(nullptr); + dest->resize(size); + src.writeToMemory(&*dest->begin()); +} + +} // namespace uirenderer +} // namespace android + +#endif // PROTOHELPERS_H diff --git a/libs/hwui/protos/hwui.proto b/libs/hwui/protos/hwui.proto new file mode 100644 index 000000000000..dcff80a24974 --- /dev/null +++ b/libs/hwui/protos/hwui.proto @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 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. + */ + +syntax = "proto2"; + +package android.uirenderer.proto; + +option optimize_for = LITE_RUNTIME; + +message RenderNode { + required uint64 id = 1; + required string name = 2; + required RenderProperties properties = 3; + optional DisplayList display_list = 4; + repeated RenderNode children = 5; +}; + +message RenderProperties { + required int32 left = 1; + required int32 right = 2; + required int32 top = 3; + required int32 bottom = 4; + required int32 clip_flags = 5; + required float alpha = 6; + required float translation_x = 7; + required float translation_y = 8; + required float translation_z = 9; + required float elevation = 10; + required float rotation = 11; + required float rotation_x = 12; + required float rotation_y = 13; + required float scale_x = 14; + required float scale_y = 15; + required float pivot_x = 16; + required float pivot_y = 17; + required bool has_overlapping_rendering = 18; + required bool pivot_explicitly_set = 19; + required bool project_backwards = 20; + required bool projection_receiver = 21; + required RectF clip_bounds = 22; + optional Outline outline = 23; + optional RevealClip reveal_clip = 24; +}; + +message RectF { + required float left = 1; + required float right = 2; + required float top = 3; + required float bottom = 4; +} + +message Outline { + required bool should_clip = 1; + enum Type { + None = 0; + Empty = 1; + ConvexPath = 2; + RoundRect = 3; + } + required Type type = 2; + required RectF bounds = 3; + required float radius = 4; + required float alpha = 5; + optional bytes path = 6; +} + +message RevealClip { + required float x = 1; + required float y = 2; + required float radius = 3; +} + +message DisplayList { + optional int32 projection_receive_index = 1; + repeated DrawOp draw_ops = 2; +} + +message DrawOp { + oneof drawop { + DrawOp_RenderNode render_node = 1; + } +} + +message DrawOp_RenderNode { + optional RenderNode node = 1; +} diff --git a/libs/hwui/renderstate/Blend.cpp b/libs/hwui/renderstate/Blend.cpp index 29927ed8667b..93f787d31745 100644 --- a/libs/hwui/renderstate/Blend.cpp +++ b/libs/hwui/renderstate/Blend.cpp @@ -30,6 +30,26 @@ struct Blender { GLenum dst; }; +// assumptions made by lookup tables in either this file or ProgramCache +static_assert(0 == SkXfermode::kClear_Mode, "SkXfermode enums have changed"); +static_assert(1 == SkXfermode::kSrc_Mode, "SkXfermode enums have changed"); +static_assert(2 == SkXfermode::kDst_Mode, "SkXfermode enums have changed"); +static_assert(3 == SkXfermode::kSrcOver_Mode, "SkXfermode enums have changed"); +static_assert(4 == SkXfermode::kDstOver_Mode, "SkXfermode enums have changed"); +static_assert(5 == SkXfermode::kSrcIn_Mode, "SkXfermode enums have changed"); +static_assert(6 == SkXfermode::kDstIn_Mode, "SkXfermode enums have changed"); +static_assert(7 == SkXfermode::kSrcOut_Mode, "SkXfermode enums have changed"); +static_assert(8 == SkXfermode::kDstOut_Mode, "SkXfermode enums have changed"); +static_assert(9 == SkXfermode::kSrcATop_Mode, "SkXfermode enums have changed"); +static_assert(10 == SkXfermode::kDstATop_Mode, "SkXfermode enums have changed"); +static_assert(11 == SkXfermode::kXor_Mode, "SkXfermode enums have changed"); +static_assert(12 == SkXfermode::kPlus_Mode, "SkXfermode enums have changed"); +static_assert(13 == SkXfermode::kModulate_Mode, "SkXfermode enums have changed"); +static_assert(14 == SkXfermode::kScreen_Mode, "SkXfermode enums have changed"); +static_assert(15 == SkXfermode::kOverlay_Mode, "SkXfermode enums have changed"); +static_assert(16 == SkXfermode::kDarken_Mode, "SkXfermode enums have changed"); +static_assert(17 == SkXfermode::kLighten_Mode, "SkXfermode enums have changed"); + // In this array, the index of each Blender equals the value of the first // entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode] const Blender kBlends[] = { @@ -78,20 +98,6 @@ Blend::Blend() // gl blending off by default } -void Blend::enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage) { - GLenum srcMode; - GLenum dstMode; - getFactors(mode, modeUsage, &srcMode, &dstMode); - setFactors(srcMode, dstMode); -} - -void Blend::disable() { - if (mEnabled) { - glDisable(GL_BLEND); - mEnabled = false; - } -} - void Blend::invalidate() { syncEnabled(); mSrcMode = mDstMode = GL_ZERO; @@ -112,8 +118,13 @@ void Blend::getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage, GLenum* o void Blend::setFactors(GLenum srcMode, GLenum dstMode) { if (srcMode == GL_ZERO && dstMode == GL_ZERO) { - disable(); + // disable blending + if (mEnabled) { + glDisable(GL_BLEND); + mEnabled = false; + } } else { + // enable blending if (!mEnabled) { glEnable(GL_BLEND); mEnabled = true; diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h index dcc681d4aa50..df9e5a8af879 100644 --- a/libs/hwui/renderstate/Blend.h +++ b/libs/hwui/renderstate/Blend.h @@ -34,9 +34,6 @@ public: NoSwap, Swap, }; - - void enable(SkXfermode::Mode mode, ModeOrderSwap modeUsage); - void disable(); void syncEnabled(); static void getFactors(SkXfermode::Mode mode, ModeOrderSwap modeUsage, diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp index 0521f6573e39..b575c696586e 100644 --- a/libs/hwui/renderstate/MeshState.cpp +++ b/libs/hwui/renderstate/MeshState.cpp @@ -34,7 +34,6 @@ MeshState::MeshState() glGenBuffers(1, &mUnitQuadBuffer); glBindBuffer(GL_ARRAY_BUFFER, mUnitQuadBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(kUnitQuadVertices), kUnitQuadVertices, GL_STATIC_DRAW); - mCurrentBuffer = mUnitQuadBuffer; uint16_t regionIndices[kMaxNumberOfQuads * 6]; @@ -78,42 +77,58 @@ void MeshState::dump() { // Buffer Objects /////////////////////////////////////////////////////////////////////////////// -bool MeshState::bindMeshBuffer() { - return bindMeshBuffer(mUnitQuadBuffer); +void MeshState::bindMeshBuffer(GLuint buffer) { + if (mCurrentBuffer != buffer) { + glBindBuffer(GL_ARRAY_BUFFER, buffer); + mCurrentBuffer = buffer; + + // buffer has changed, so invalidate cached vertex pos/texcoord pointers + resetVertexPointers(); + } } -bool MeshState::bindMeshBuffer(GLuint buffer) { - if (!buffer) buffer = mUnitQuadBuffer; - return bindMeshBufferInternal(buffer); +void MeshState::unbindMeshBuffer() { + return bindMeshBuffer(0); } -bool MeshState::unbindMeshBuffer() { - return bindMeshBufferInternal(0); +void MeshState::genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, + const void* data, GLenum usage) { + if (!*buffer) { + glGenBuffers(1, buffer); + } + bindMeshBuffer(*buffer); + glBufferData(GL_ARRAY_BUFFER, size, data, usage); } -bool MeshState::bindMeshBufferInternal(GLuint buffer) { - if (mCurrentBuffer != buffer) { - glBindBuffer(GL_ARRAY_BUFFER, buffer); - mCurrentBuffer = buffer; - return true; +void MeshState::deleteMeshBuffer(GLuint buffer) { + if (buffer == mCurrentBuffer) { + // GL defines that deleting the currently bound VBO rebinds to 0 (no VBO). + // Reflect this in our cached value. + mCurrentBuffer = 0; } - return false; + glDeleteBuffers(1, &buffer); } /////////////////////////////////////////////////////////////////////////////// // Vertices /////////////////////////////////////////////////////////////////////////////// -void MeshState::bindPositionVertexPointer(bool force, const GLvoid* vertices, GLsizei stride) { - if (force || vertices != mCurrentPositionPointer || stride != mCurrentPositionStride) { +void MeshState::bindPositionVertexPointer(const GLvoid* vertices, GLsizei stride) { + // update pos coords if !current vbo, since vertices may point into mutable memory (e.g. stack) + if (mCurrentBuffer == 0 + || vertices != mCurrentPositionPointer + || stride != mCurrentPositionStride) { glVertexAttribPointer(Program::kBindingPosition, 2, GL_FLOAT, GL_FALSE, stride, vertices); mCurrentPositionPointer = vertices; mCurrentPositionStride = stride; } } -void MeshState::bindTexCoordsVertexPointer(bool force, const GLvoid* vertices, GLsizei stride) { - if (force || vertices != mCurrentTexCoordsPointer || stride != mCurrentTexCoordsStride) { +void MeshState::bindTexCoordsVertexPointer(const GLvoid* vertices, GLsizei stride) { + // update tex coords if !current vbo, since vertices may point into mutable memory (e.g. stack) + if (mCurrentBuffer == 0 + || vertices != mCurrentTexCoordsPointer + || stride != mCurrentTexCoordsStride) { glVertexAttribPointer(Program::kBindingTexCoords, 2, GL_FLOAT, GL_FALSE, stride, vertices); mCurrentTexCoordsPointer = vertices; mCurrentTexCoordsStride = stride; @@ -125,10 +140,6 @@ void MeshState::resetVertexPointers() { mCurrentTexCoordsPointer = this; } -void MeshState::resetTexCoordsVertexPointer() { - mCurrentTexCoordsPointer = this; -} - void MeshState::enableTexCoordsVertexArray() { if (!mTexCoordsArrayEnabled) { glEnableVertexAttribArray(Program::kBindingTexCoords); @@ -148,26 +159,18 @@ void MeshState::disableTexCoordsVertexArray() { // Indices /////////////////////////////////////////////////////////////////////////////// -bool MeshState::bindIndicesBufferInternal(const GLuint buffer) { +void MeshState::bindIndicesBuffer(const GLuint buffer) { if (mCurrentIndicesBuffer != buffer) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); mCurrentIndicesBuffer = buffer; - return true; } - return false; -} - -bool MeshState::bindQuadIndicesBuffer() { - return bindIndicesBufferInternal(mQuadListIndices); } -bool MeshState::unbindIndicesBuffer() { +void MeshState::unbindIndicesBuffer() { if (mCurrentIndicesBuffer) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); mCurrentIndicesBuffer = 0; - return true; } - return false; } } /* namespace uirenderer */ diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h index e80f4d0d6c41..dd684686f584 100644 --- a/libs/hwui/renderstate/MeshState.h +++ b/libs/hwui/renderstate/MeshState.h @@ -60,20 +60,19 @@ public: /////////////////////////////////////////////////////////////////////////////// // Buffer objects /////////////////////////////////////////////////////////////////////////////// - /** - * Binds the VBO used to render simple textured quads. - */ - bool bindMeshBuffer(); /** * Binds the specified VBO if needed. If buffer == 0, binds default simple textured quad. */ - bool bindMeshBuffer(GLuint buffer); + void bindMeshBuffer(GLuint buffer); /** - * Unbinds the VBO used to render simple textured quads. + * Unbinds the current VBO if active. */ - bool unbindMeshBuffer(); + void unbindMeshBuffer(); + + void genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, GLenum usage); + void deleteMeshBuffer(GLuint); /////////////////////////////////////////////////////////////////////////////// // Vertices @@ -82,21 +81,20 @@ public: * Binds an attrib to the specified float vertex pointer. * Assumes a stride of gTextureVertexStride and a size of 2. */ - void bindPositionVertexPointer(bool force, const GLvoid* vertices, + void bindPositionVertexPointer(const GLvoid* vertices, GLsizei stride = kTextureVertexStride); /** * Binds an attrib to the specified float vertex pointer. * Assumes a stride of gTextureVertexStride and a size of 2. */ - void bindTexCoordsVertexPointer(bool force, const GLvoid* vertices, + void bindTexCoordsVertexPointer(const GLvoid* vertices, GLsizei stride = kTextureVertexStride); /** * Resets the vertex pointers. */ void resetVertexPointers(); - void resetTexCoordsVertexPointer(); void enableTexCoordsVertexArray(); void disableTexCoordsVertexArray(); @@ -104,12 +102,8 @@ public: /////////////////////////////////////////////////////////////////////////////// // Indices /////////////////////////////////////////////////////////////////////////////// - /** - * Binds a global indices buffer that can draw up to - * gMaxNumberOfQuads quads. - */ - bool bindQuadIndicesBuffer(); - bool unbindIndicesBuffer(); + void bindIndicesBuffer(const GLuint buffer); + void unbindIndicesBuffer(); /////////////////////////////////////////////////////////////////////////////// // Getters - for use in Glop building @@ -118,8 +112,6 @@ public: GLuint getQuadListIBO() { return mQuadListIndices; } private: MeshState(); - bool bindMeshBufferInternal(const GLuint buffer); - bool bindIndicesBufferInternal(const GLuint buffer); GLuint mUnitQuadBuffer; diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp new file mode 100644 index 000000000000..73b6c02a3fdc --- /dev/null +++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2015 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 "OffscreenBufferPool.h" + +#include "Caches.h" +#include "Properties.h" +#include "renderstate/RenderState.h" +#include "utils/FatVector.h" + +#include <utils/Log.h> + +#include <GLES2/gl2.h> + +namespace android { +namespace uirenderer { + +//////////////////////////////////////////////////////////////////////////////// +// OffscreenBuffer +//////////////////////////////////////////////////////////////////////////////// + +OffscreenBuffer::OffscreenBuffer(RenderState& renderState, Caches& caches, + uint32_t viewportWidth, uint32_t viewportHeight) + : GpuMemoryTracker(GpuObjectType::OffscreenBuffer) + , renderState(renderState) + , viewportWidth(viewportWidth) + , viewportHeight(viewportHeight) + , texture(caches) { + uint32_t width = computeIdealDimension(viewportWidth); + uint32_t height = computeIdealDimension(viewportHeight); + caches.textureState().activateTexture(0); + texture.resize(width, height, GL_RGBA); + texture.blend = true; + texture.setWrap(GL_CLAMP_TO_EDGE); + // not setting filter on texture, since it's set when drawing, based on transform +} + +Rect OffscreenBuffer::getTextureCoordinates() { + const float texX = 1.0f / static_cast<float>(texture.width()); + const float texY = 1.0f / static_cast<float>(texture.height()); + return Rect(0, viewportHeight * texY, viewportWidth * texX, 0); +} + +void OffscreenBuffer::dirty(Rect dirtyArea) { + dirtyArea.doIntersect(0, 0, viewportWidth, viewportHeight); + if (!dirtyArea.isEmpty()) { + region.orSelf(android::Rect(dirtyArea.left, dirtyArea.top, + dirtyArea.right, dirtyArea.bottom)); + } +} + +void OffscreenBuffer::updateMeshFromRegion() { + // avoid T-junctions as they cause artifacts in between the resultant + // geometry when complex transforms occur. + // TODO: generate the safeRegion only if necessary based on drawing transform + Region safeRegion = Region::createTJunctionFreeRegion(region); + + size_t count; + const android::Rect* rects = safeRegion.getArray(&count); + + const float texX = 1.0f / float(texture.width()); + const float texY = 1.0f / float(texture.height()); + + FatVector<TextureVertex, 64> meshVector(count * 4); // uses heap if more than 64 vertices needed + TextureVertex* mesh = &meshVector[0]; + for (size_t i = 0; i < count; i++) { + const android::Rect* r = &rects[i]; + + const float u1 = r->left * texX; + const float v1 = (viewportHeight - r->top) * texY; + const float u2 = r->right * texX; + const float v2 = (viewportHeight - r->bottom) * texY; + + TextureVertex::set(mesh++, r->left, r->top, u1, v1); + TextureVertex::set(mesh++, r->right, r->top, u2, v1); + TextureVertex::set(mesh++, r->left, r->bottom, u1, v2); + TextureVertex::set(mesh++, r->right, r->bottom, u2, v2); + } + elementCount = count * 6; + renderState.meshState().genOrUpdateMeshBuffer(&vbo, + sizeof(TextureVertex) * count * 4, + &meshVector[0], + GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer +} + +uint32_t OffscreenBuffer::computeIdealDimension(uint32_t dimension) { + return uint32_t(ceilf(dimension / float(LAYER_SIZE)) * LAYER_SIZE); +} + +OffscreenBuffer::~OffscreenBuffer() { + texture.deleteTexture(); + renderState.meshState().deleteMeshBuffer(vbo); + elementCount = 0; + vbo = 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// OffscreenBufferPool +/////////////////////////////////////////////////////////////////////////////// + +OffscreenBufferPool::OffscreenBufferPool() + : mMaxSize(Properties::layerPoolSize) { +} + +OffscreenBufferPool::~OffscreenBufferPool() { + clear(); // TODO: unique_ptr? +} + +int OffscreenBufferPool::Entry::compare(const Entry& lhs, const Entry& rhs) { + int deltaInt = int(lhs.width) - int(rhs.width); + if (deltaInt != 0) return deltaInt; + + return int(lhs.height) - int(rhs.height); +} + +void OffscreenBufferPool::clear() { + for (auto& entry : mPool) { + delete entry.layer; + } + mPool.clear(); + mSize = 0; +} + +OffscreenBuffer* OffscreenBufferPool::get(RenderState& renderState, + const uint32_t width, const uint32_t height) { + OffscreenBuffer* layer = nullptr; + + Entry entry(width, height); + auto iter = mPool.find(entry); + + if (iter != mPool.end()) { + entry = *iter; + mPool.erase(iter); + + layer = entry.layer; + layer->viewportWidth = width; + layer->viewportHeight = height; + mSize -= layer->getSizeInBytes(); + } else { + layer = new OffscreenBuffer(renderState, Caches::getInstance(), width, height); + } + + return layer; +} + +OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer, + const uint32_t width, const uint32_t height) { + RenderState& renderState = layer->renderState; + if (layer->texture.width() == OffscreenBuffer::computeIdealDimension(width) + && layer->texture.height() == OffscreenBuffer::computeIdealDimension(height)) { + // resize in place + layer->viewportWidth = width; + layer->viewportHeight = height; + + // entire area will be repainted (and may be smaller) so clear usage region + layer->region.clear(); + return layer; + } + putOrDelete(layer); + return get(renderState, width, height); +} + +void OffscreenBufferPool::dump() { + for (auto entry : mPool) { + ALOGD(" Layer size %dx%d", entry.width, entry.height); + } +} + +void OffscreenBufferPool::putOrDelete(OffscreenBuffer* layer) { + const uint32_t size = layer->getSizeInBytes(); + // Don't even try to cache a layer that's bigger than the cache + if (size < mMaxSize) { + // TODO: Use an LRU + while (mSize + size > mMaxSize) { + OffscreenBuffer* victim = mPool.begin()->layer; + mSize -= victim->getSizeInBytes(); + delete victim; + mPool.erase(mPool.begin()); + } + + // clear region, since it's no longer valid + layer->region.clear(); + + Entry entry(layer); + + mPool.insert(entry); + mSize += size; + } else { + delete layer; + } +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/renderstate/OffscreenBufferPool.h b/libs/hwui/renderstate/OffscreenBufferPool.h new file mode 100644 index 000000000000..089f131668a0 --- /dev/null +++ b/libs/hwui/renderstate/OffscreenBufferPool.h @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H +#define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H + +#include <GpuMemoryTracker.h> +#include "Caches.h" +#include "Texture.h" +#include "utils/Macros.h" +#include <ui/Region.h> + +#include <set> + +namespace android { +namespace uirenderer { + +class RenderState; + +/** + * Lightweight alternative to Layer. Owns the persistent state of an offscreen render target, and + * encompasses enough information to draw it back on screen (minus paint properties, which are held + * by LayerOp). + * + * Has two distinct sizes - viewportWidth/viewportHeight describe content area, + * texture.width/.height are actual allocated texture size. Texture will tend to be larger than the + * viewport bounds, since textures are always allocated with width / height as a multiple of 64, for + * the purpose of improving reuse. + */ +class OffscreenBuffer : GpuMemoryTracker { +public: + OffscreenBuffer(RenderState& renderState, Caches& caches, + uint32_t viewportWidth, uint32_t viewportHeight); + ~OffscreenBuffer(); + + Rect getTextureCoordinates(); + + void dirty(Rect dirtyArea); + + // must be called prior to rendering, to construct/update vertex buffer + void updateMeshFromRegion(); + + // Set by RenderNode for HW layers, TODO for clipped saveLayers + void setWindowTransform(const Matrix4& transform) { + inverseTransformInWindow.loadInverse(transform); + } + + static uint32_t computeIdealDimension(uint32_t dimension); + + uint32_t getSizeInBytes() { return texture.objectSize(); } + + RenderState& renderState; + + uint32_t viewportWidth; + uint32_t viewportHeight; + Texture texture; + + // Portion of layer that has been drawn to. Used to minimize drawing area when + // drawing back to screen / parent FBO. + Region region; + + Matrix4 inverseTransformInWindow; + + // vbo / size of mesh + GLsizei elementCount = 0; + GLuint vbo = 0; +}; + +/** + * Pool of OffscreenBuffers allocated, but not currently in use. + */ +class OffscreenBufferPool { +public: + OffscreenBufferPool(); + ~OffscreenBufferPool(); + + WARN_UNUSED_RESULT OffscreenBuffer* get(RenderState& renderState, + const uint32_t width, const uint32_t height); + + WARN_UNUSED_RESULT OffscreenBuffer* resize(OffscreenBuffer* layer, + const uint32_t width, const uint32_t height); + + void putOrDelete(OffscreenBuffer* layer); + + /** + * Clears the pool. This causes all layers to be deleted. + */ + void clear(); + + /** + * Returns the maximum size of the pool in bytes. + */ + uint32_t getMaxSize() { return mMaxSize; } + + /** + * Returns the current size of the pool in bytes. + */ + uint32_t getSize() { return mSize; } + + size_t getCount() { return mPool.size(); } + + /** + * Prints out the content of the pool. + */ + void dump(); +private: + struct Entry { + Entry() {} + + Entry(const uint32_t layerWidth, const uint32_t layerHeight) + : width(OffscreenBuffer::computeIdealDimension(layerWidth)) + , height(OffscreenBuffer::computeIdealDimension(layerHeight)) {} + + Entry(OffscreenBuffer* layer) + : layer(layer) + , width(layer->texture.width()) + , height(layer->texture.height()) { + } + + static int compare(const Entry& lhs, const Entry& rhs); + + bool operator==(const Entry& other) const { + return compare(*this, other) == 0; + } + + bool operator!=(const Entry& other) const { + return compare(*this, other) != 0; + } + + bool operator<(const Entry& other) const { + return Entry::compare(*this, other) < 0; + } + + OffscreenBuffer* layer = nullptr; + uint32_t width = 0; + uint32_t height = 0; + }; // struct Entry + + std::multiset<Entry> mPool; + + uint32_t mSize = 0; + uint32_t mMaxSize; +}; // class OffscreenBufferCache + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index 1e39bfa4b583..ea4391b87006 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include <GpuMemoryTracker.h> #include "renderstate/RenderState.h" #include "renderthread/CanvasContext.h" #include "renderthread/EglManager.h" #include "utils/GLUtils.h" +#include <algorithm> namespace android { namespace uirenderer { @@ -38,6 +40,8 @@ RenderState::~RenderState() { void RenderState::onGLContextCreated() { LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil, "State object lifecycle not managed correctly"); + GpuMemoryTracker::onGLContextCreated(); + mBlend = new Blend(); mMeshState = new MeshState(); mScissor = new Scissor(); @@ -88,6 +92,8 @@ void RenderState::onGLContextDestroyed() { } */ + mLayerPool.clear(); + // TODO: reset all cached state in state objects std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext); mAssetAtlas.terminate(); @@ -102,6 +108,21 @@ void RenderState::onGLContextDestroyed() { mScissor = nullptr; delete mStencil; mStencil = nullptr; + + GpuMemoryTracker::onGLContextDestroyed(); +} + +void RenderState::flush(Caches::FlushMode mode) { + switch (mode) { + case Caches::FlushMode::Full: + // fall through + case Caches::FlushMode::Moderate: + // fall through + case Caches::FlushMode::Layers: + mLayerPool.clear(); + break; + } + mCaches->flush(mode); } void RenderState::setViewport(GLsizei width, GLsizei height) { @@ -123,6 +144,21 @@ void RenderState::bindFramebuffer(GLuint fbo) { } } +GLuint RenderState::createFramebuffer() { + GLuint ret; + glGenFramebuffers(1, &ret); + return ret; +} + +void RenderState::deleteFramebuffer(GLuint fbo) { + if (mFramebuffer == fbo) { + // GL defines that deleting the currently bound FBO rebinds FBO 0. + // Reflect this in our cached value. + mFramebuffer = 0; + } + glDeleteFramebuffers(1, &fbo); +} + void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) { if (mode == DrawGlInfo::kModeProcessNoContext) { // If there's no context we don't need to interrupt as there's @@ -173,17 +209,6 @@ void RenderState::debugOverdraw(bool enable, bool clear) { } } -void RenderState::requireGLContext() { - assertOnGLThread(); - LOG_ALWAYS_FATAL_IF(!mRenderThread.eglManager().hasEglContext(), - "No GL context!"); -} - -void RenderState::assertOnGLThread() { - pthread_t curr = pthread_self(); - LOG_ALWAYS_FATAL_IF(!pthread_equal(mThreadId, curr), "Wrong thread!"); -} - class DecStrongTask : public renderthread::RenderTask { public: DecStrongTask(VirtualLightRefBase* object) : mObject(object) {} @@ -199,19 +224,25 @@ private: }; void RenderState::postDecStrong(VirtualLightRefBase* object) { - mRenderThread.queue(new DecStrongTask(object)); + if (pthread_equal(mThreadId, pthread_self())) { + object->decStrong(nullptr); + } else { + mRenderThread.queue(new DecStrongTask(object)); + } } /////////////////////////////////////////////////////////////////////////////// // Render /////////////////////////////////////////////////////////////////////////////// -void RenderState::render(const Glop& glop) { +void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix) { const Glop::Mesh& mesh = glop.mesh; const Glop::Mesh::Vertices& vertices = mesh.vertices; const Glop::Mesh::Indices& indices = mesh.indices; const Glop::Fill& fill = glop.fill; + GL_CHECKPOINT(MODERATE); + // --------------------------------------------- // ---------- Program + uniform setup ---------- // --------------------------------------------- @@ -221,17 +252,17 @@ void RenderState::render(const Glop& glop) { fill.program->setColor(fill.color); } - fill.program->set(glop.transform.ortho, + fill.program->set(orthoMatrix, glop.transform.modelView, glop.transform.meshTransform(), glop.transform.transformFlags & TransformFlags::OffsetByFudgeFactor); // Color filter uniforms - if (fill.filterMode == ProgramDescription::kColorBlend) { + if (fill.filterMode == ProgramDescription::ColorFilterMode::Blend) { const FloatColor& color = fill.filter.color; glUniform4f(mCaches->program().getUniform("colorBlend"), color.r, color.g, color.b, color.a); - } else if (fill.filterMode == ProgramDescription::kColorMatrix) { + } else if (fill.filterMode == ProgramDescription::ColorFilterMode::Matrix) { glUniformMatrix4fv(mCaches->program().getUniform("colorMatrix"), 1, GL_FALSE, fill.filter.matrix.matrix); glUniform4fv(mCaches->program().getUniform("colorMatrixVector"), 1, @@ -255,32 +286,33 @@ void RenderState::render(const Glop& glop) { roundedOutRadius); } + GL_CHECKPOINT(MODERATE); + // -------------------------------- // ---------- Mesh setup ---------- // -------------------------------- // vertices - const bool force = meshState().bindMeshBufferInternal(vertices.bufferObject) - || (vertices.position != nullptr); - meshState().bindPositionVertexPointer(force, vertices.position, vertices.stride); + meshState().bindMeshBuffer(vertices.bufferObject); + meshState().bindPositionVertexPointer(vertices.position, vertices.stride); // indices - meshState().bindIndicesBufferInternal(indices.bufferObject); + meshState().bindIndicesBuffer(indices.bufferObject); if (vertices.attribFlags & VertexAttribFlags::TextureCoord) { const Glop::Fill::TextureData& texture = fill.texture; // texture always takes slot 0, shader samplers increment from there mCaches->textureState().activateTexture(0); + mCaches->textureState().bindTexture(texture.target, texture.texture->id()); if (texture.clamp != GL_INVALID_ENUM) { - texture.texture->setWrap(texture.clamp, true, false, texture.target); + texture.texture->setWrap(texture.clamp, false, false, texture.target); } if (texture.filter != GL_INVALID_ENUM) { - texture.texture->setFilter(texture.filter, true, false, texture.target); + texture.texture->setFilter(texture.filter, false, false, texture.target); } - mCaches->textureState().bindTexture(texture.target, texture.texture->id); meshState().enableTexCoordsVertexArray(); - meshState().bindTexCoordsVertexPointer(force, vertices.texCoord, vertices.stride); + meshState().bindTexCoordsVertexPointer(vertices.texCoord, vertices.stride); if (texture.textureTransform) { glUniformMatrix4fv(fill.program->getUniform("mainTextureTransform"), 1, @@ -306,11 +338,18 @@ void RenderState::render(const Glop& glop) { // Shader uniforms SkiaShader::apply(*mCaches, fill.skiaShaderData); + GL_CHECKPOINT(MODERATE); + Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ? + fill.skiaShaderData.bitmapData.bitmapTexture : nullptr; + const AutoTexture autoCleanup(texture); + // ------------------------------------ // ---------- GL state setup ---------- // ------------------------------------ blend().setFactors(glop.blend.src, glop.blend.dst); + GL_CHECKPOINT(MODERATE); + // ------------------------------------ // ---------- Actual drawing ---------- // ------------------------------------ @@ -320,12 +359,10 @@ void RenderState::render(const Glop& glop) { GLsizei elementsCount = mesh.elementCount; const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position); while (elementsCount > 0) { - GLsizei drawCount = MathUtils::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6); - - // rebind pointers without forcing, since initial bind handled above - meshState().bindPositionVertexPointer(false, vertexData, vertices.stride); + GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6); + meshState().bindPositionVertexPointer(vertexData, vertices.stride); if (vertices.attribFlags & VertexAttribFlags::TextureCoord) { - meshState().bindTexCoordsVertexPointer(false, + meshState().bindTexCoordsVertexPointer( vertexData + kMeshTextureOffset, vertices.stride); } @@ -339,6 +376,8 @@ void RenderState::render(const Glop& glop) { glDrawArrays(mesh.primitiveMode, 0, mesh.elementCount); } + GL_CHECKPOINT(MODERATE); + // ----------------------------------- // ---------- Mesh teardown ---------- // ----------------------------------- @@ -348,6 +387,8 @@ void RenderState::render(const Glop& glop) { if (vertices.attribFlags & VertexAttribFlags::Color) { glDisableVertexAttribArray(colorLocation); } + + GL_CHECKPOINT(MODERATE); } void RenderState::dump() { diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 4fd792c1b503..731d9bbb3355 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -16,24 +16,26 @@ #ifndef RENDERSTATE_H #define RENDERSTATE_H -#include <set> -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> -#include <utils/Mutex.h> -#include <utils/Functor.h> -#include <utils/RefBase.h> -#include <private/hwui/DrawGlInfo.h> -#include <renderstate/Blend.h> - #include "AssetAtlas.h" #include "Caches.h" #include "Glop.h" +#include "renderstate/Blend.h" #include "renderstate/MeshState.h" +#include "renderstate/OffscreenBufferPool.h" #include "renderstate/PixelBufferState.h" #include "renderstate/Scissor.h" #include "renderstate/Stencil.h" #include "utils/Macros.h" +#include <set> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <ui/Region.h> +#include <utils/Mutex.h> +#include <utils/Functor.h> +#include <utils/RefBase.h> +#include <private/hwui/DrawGlInfo.h> + namespace android { namespace uirenderer { @@ -49,15 +51,21 @@ class RenderThread; // wrapper of Caches for users to migrate to. class RenderState { PREVENT_COPY_AND_ASSIGN(RenderState); + friend class renderthread::RenderThread; + friend class Caches; public: void onGLContextCreated(); void onGLContextDestroyed(); + void flush(Caches::FlushMode flushMode); + void setViewport(GLsizei width, GLsizei height); void getViewport(GLsizei* outWidth, GLsizei* outHeight); void bindFramebuffer(GLuint fbo); - GLint getFramebuffer() { return mFramebuffer; } + GLuint getFramebuffer() { return mFramebuffer; } + GLuint createFramebuffer(); + void deleteFramebuffer(GLuint fbo); void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info); @@ -78,13 +86,11 @@ public: mRegisteredContexts.erase(context); } - void requireGLContext(); - // TODO: This system is a little clunky feeling, this could use some // more thinking... void postDecStrong(VirtualLightRefBase* object); - void render(const Glop& glop); + void render(const Glop& glop, const Matrix4& orthoMatrix); AssetAtlas& assetAtlas() { return mAssetAtlas; } Blend& blend() { return *mBlend; } @@ -92,14 +98,13 @@ public: Scissor& scissor() { return *mScissor; } Stencil& stencil() { return *mStencil; } + OffscreenBufferPool& layerPool() { return mLayerPool; } + void dump(); -private: - friend class renderthread::RenderThread; - friend class Caches; +private: void interruptForFunctorInvoke(); void resumeFromFunctorInvoke(); - void assertOnGLThread(); RenderState(renderthread::RenderThread& thread); ~RenderState(); @@ -113,6 +118,8 @@ private: Scissor* mScissor = nullptr; Stencil* mStencil = nullptr; + OffscreenBufferPool mLayerPool; + AssetAtlas mAssetAtlas; std::set<Layer*> mActiveLayers; std::set<renderthread::CanvasContext*> mRegisteredContexts; diff --git a/libs/hwui/renderstate/Scissor.cpp b/libs/hwui/renderstate/Scissor.cpp index 95dcd18867d9..61dd8c3200a4 100644 --- a/libs/hwui/renderstate/Scissor.cpp +++ b/libs/hwui/renderstate/Scissor.cpp @@ -15,6 +15,8 @@ */ #include "renderstate/Scissor.h" +#include "Rect.h" + #include <utils/Log.h> namespace android { @@ -71,6 +73,26 @@ bool Scissor::set(GLint x, GLint y, GLint width, GLint height) { return false; } +void Scissor::set(int viewportHeight, const Rect& clip) { + // transform to Y-flipped GL space, and prevent negatives + GLint x = std::max(0, (int)clip.left); + GLint y = std::max(0, viewportHeight - (int)clip.bottom); + GLint width = std::max(0, ((int)clip.right) - x); + GLint height = std::max(0, (viewportHeight - (int)clip.top) - y); + + if (x != mScissorX + || y != mScissorY + || width != mScissorWidth + || height != mScissorHeight) { + glScissor(x, y, width, height); + + mScissorX = x; + mScissorY = y; + mScissorWidth = width; + mScissorHeight = height; + } +} + void Scissor::reset() { mScissorX = mScissorY = mScissorWidth = mScissorHeight = 0; } diff --git a/libs/hwui/renderstate/Scissor.h b/libs/hwui/renderstate/Scissor.h index b37ec583686f..f30224470059 100644 --- a/libs/hwui/renderstate/Scissor.h +++ b/libs/hwui/renderstate/Scissor.h @@ -22,11 +22,14 @@ namespace android { namespace uirenderer { +class Rect; + class Scissor { friend class RenderState; public: bool setEnabled(bool enabled); bool set(GLint x, GLint y, GLint width, GLint height); + void set(int viewportHeight, const Rect& clip); void reset(); bool isEnabled() { return mEnabled; } void dump(); diff --git a/libs/hwui/renderstate/Stencil.cpp b/libs/hwui/renderstate/Stencil.cpp index 319cfe4ba0d0..d25ad514e892 100644 --- a/libs/hwui/renderstate/Stencil.cpp +++ b/libs/hwui/renderstate/Stencil.cpp @@ -34,10 +34,6 @@ namespace uirenderer { #define STENCIL_MASK_VALUE 0x1 #endif -Stencil::Stencil() - : mState(kDisabled) { -} - uint8_t Stencil::getStencilSize() { return STENCIL_BUFFER_SIZE; } @@ -64,14 +60,14 @@ void Stencil::clear() { glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); - if (mState == kTest) { + if (mState == StencilState::Test) { // reset to test state, with immutable stencil glStencilMask(0); } } void Stencil::enableTest(int incrementThreshold) { - if (mState != kTest) { + if (mState != StencilState::Test) { enable(); if (incrementThreshold > 0) { glStencilFunc(GL_EQUAL, incrementThreshold, 0xff); @@ -82,12 +78,12 @@ void Stencil::enableTest(int incrementThreshold) { glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glStencilMask(0); - mState = kTest; + mState = StencilState::Test; } } void Stencil::enableWrite(int incrementThreshold) { - if (mState != kWrite) { + if (mState != StencilState::Write) { enable(); if (incrementThreshold > 0) { glStencilFunc(GL_ALWAYS, 1, 0xff); @@ -100,7 +96,7 @@ void Stencil::enableWrite(int incrementThreshold) { } glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); glStencilMask(0xff); - mState = kWrite; + mState = StencilState::Write; } } @@ -109,7 +105,7 @@ void Stencil::enableDebugTest(GLint value, bool greater) { glStencilFunc(greater ? GL_LESS : GL_EQUAL, value, 0xffffffff); // We only want to test, let's keep everything glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - mState = kTest; + mState = StencilState::Test; glStencilMask(0); } @@ -119,20 +115,20 @@ void Stencil::enableDebugWrite() { // The test always passes so the first two values are meaningless glStencilOp(GL_KEEP, GL_KEEP, GL_INCR); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - mState = kWrite; + mState = StencilState::Write; glStencilMask(0xff); } void Stencil::enable() { - if (mState == kDisabled) { + if (mState == StencilState::Disabled) { glEnable(GL_STENCIL_TEST); } } void Stencil::disable() { - if (mState != kDisabled) { + if (mState != StencilState::Disabled) { glDisable(GL_STENCIL_TEST); - mState = kDisabled; + mState = StencilState::Disabled; } } diff --git a/libs/hwui/renderstate/Stencil.h b/libs/hwui/renderstate/Stencil.h index 3a8f8ebad48d..5f7d4056b51d 100644 --- a/libs/hwui/renderstate/Stencil.h +++ b/libs/hwui/renderstate/Stencil.h @@ -17,10 +17,6 @@ #ifndef ANDROID_HWUI_STENCIL_H #define ANDROID_HWUI_STENCIL_H -#ifndef LOG_TAG - #define LOG_TAG "OpenGLRenderer" -#endif - #include <GLES2/gl2.h> #include <cutils/compiler.h> @@ -34,8 +30,6 @@ namespace uirenderer { class ANDROID_API Stencil { public: - Stencil(); - /** * Returns the desired size for the stencil buffer. If the returned value * is 0, then no stencil buffer is required. @@ -85,32 +79,31 @@ public: * Indicates whether either test or write is enabled. */ bool isEnabled() { - return mState != kDisabled; + return mState != StencilState::Disabled; } /** * Indicates whether testing only is enabled. */ bool isTestEnabled() { - return mState == kTest; + return mState == StencilState::Test; } bool isWriteEnabled() { - return mState == kWrite; + return mState == StencilState::Write; } void dump(); private: - void enable(); - - enum StencilState { - kDisabled, - kTest, - kWrite + enum class StencilState { + Disabled, + Test, + Write }; - StencilState mState; + void enable(); + StencilState mState = StencilState::Disabled; }; // class Stencil diff --git a/libs/hwui/renderstate/TextureState.cpp b/libs/hwui/renderstate/TextureState.cpp index 987d4cd55a5e..78b8edae2eed 100644 --- a/libs/hwui/renderstate/TextureState.cpp +++ b/libs/hwui/renderstate/TextureState.cpp @@ -15,6 +15,14 @@ */ #include "renderstate/TextureState.h" +#include "Caches.h" +#include "utils/TraceUtils.h" + +#include <GLES3/gl3.h> +#include <memory> +#include <SkCanvas.h> +#include <SkBitmap.h> + namespace android { namespace uirenderer { @@ -35,6 +43,7 @@ TextureState::TextureState() glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxTextureUnits); LOG_ALWAYS_FATAL_IF(maxTextureUnits < kTextureUnitsCount, "At least %d texture units are required!", kTextureUnitsCount); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); } void TextureState::activateTexture(GLuint textureUnit) { diff --git a/libs/hwui/renderstate/TextureState.h b/libs/hwui/renderstate/TextureState.h index d3c014c00bce..ec94d7e9e267 100644 --- a/libs/hwui/renderstate/TextureState.h +++ b/libs/hwui/renderstate/TextureState.h @@ -23,9 +23,13 @@ #include <SkXfermode.h> #include <memory> +class SkBitmap; + namespace android { namespace uirenderer { +class Texture; + class TextureState { friend class Caches; // TODO: move to RenderState public: @@ -71,6 +75,7 @@ public: * Clear the cache of bound textures. */ void unbindTexture(GLuint texture); + private: // total number of texture units available for use static const int kTextureUnitsCount = 4; diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 4cf8b152ed40..e6399d4ec789 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -14,27 +14,49 @@ * limitations under the License. */ +#include <GpuMemoryTracker.h> #include "CanvasContext.h" #include "AnimationContext.h" #include "Caches.h" #include "DeferredLayerUpdater.h" #include "EglManager.h" +#include "LayerUpdateQueue.h" #include "LayerRenderer.h" #include "OpenGLRenderer.h" #include "Properties.h" #include "RenderThread.h" +#include "hwui/Canvas.h" #include "renderstate/RenderState.h" #include "renderstate/Stencil.h" +#include "protos/hwui.pb.h" +#include "utils/GLUtils.h" +#include "utils/TimeUtils.h" -#include <algorithm> -#include <strings.h> #include <cutils/properties.h> +#include <google/protobuf/io/zero_copy_stream_impl.h> #include <private/hwui/DrawGlInfo.h> +#include <strings.h> + +#include <algorithm> +#include <fcntl.h> +#include <sys/stat.h> + +#include <cstdlib> #define TRIM_MEMORY_COMPLETE 80 #define TRIM_MEMORY_UI_HIDDEN 20 +#define ENABLE_RENDERNODE_SERIALIZATION false + +#define LOG_FRAMETIME_MMA 0 + +#if LOG_FRAMETIME_MMA +static float sBenchMma = 0; +static int sFrameCount = 0; +static const float NANOS_PER_MILLIS_F = 1000000.0f; +#endif + namespace android { namespace uirenderer { namespace renderthread { @@ -45,104 +67,121 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, , mEglManager(thread.eglManager()) , mOpaque(!translucent) , mAnimationContext(contextFactory->createAnimationContext(mRenderThread.timeLord())) - , mRootRenderNode(rootRenderNode) , mJankTracker(thread.timeLord().frameIntervalNanos()) - , mProfiler(mFrames) { + , mProfiler(mFrames) + , mContentDrawBounds(0, 0, 0, 0) { + mRenderNodes.emplace_back(rootRenderNode); mRenderThread.renderState().registerCanvasContext(this); mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); } CanvasContext::~CanvasContext() { - destroy(); + destroy(nullptr); mRenderThread.renderState().unregisterCanvasContext(this); } -void CanvasContext::destroy() { +void CanvasContext::destroy(TreeObserver* observer) { stopDrawing(); setSurface(nullptr); - freePrefetechedLayers(); - destroyHardwareResources(); + freePrefetchedLayers(observer); + destroyHardwareResources(observer); mAnimationContext->destroy(); +#if !HWUI_NEW_OPS if (mCanvas) { delete mCanvas; mCanvas = nullptr; } +#endif } -void CanvasContext::setSurface(ANativeWindow* window) { +void CanvasContext::setSurface(Surface* surface) { ATRACE_CALL(); - mNativeWindow = window; + mNativeSurface = surface; if (mEglSurface != EGL_NO_SURFACE) { mEglManager.destroySurface(mEglSurface); mEglSurface = EGL_NO_SURFACE; } - if (window) { - mEglSurface = mEglManager.createSurface(window); + if (surface) { + mEglSurface = mEglManager.createSurface(surface); } + mFrameNumber = -1; + if (mEglSurface != EGL_NO_SURFACE) { const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer); mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); mHaveNewSurface = true; - makeCurrent(); + mSwapHistory.clear(); } else { mRenderThread.removeFrameCallback(this); } } -void CanvasContext::swapBuffers(const SkRect& dirty, EGLint width, EGLint height) { - if (CC_UNLIKELY(!mEglManager.swapBuffers(mEglSurface, dirty, width, height))) { - setSurface(nullptr); - } - mHaveNewSurface = false; -} - -void CanvasContext::requireSurface() { - LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, - "requireSurface() called but no surface set!"); - makeCurrent(); -} - void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) { mSwapBehavior = swapBehavior; } -bool CanvasContext::initialize(ANativeWindow* window) { - setSurface(window); - if (mCanvas) return false; +void CanvasContext::initialize(Surface* surface) { + setSurface(surface); +#if !HWUI_NEW_OPS + if (mCanvas) return; mCanvas = new OpenGLRenderer(mRenderThread.renderState()); mCanvas->initProperties(); - return true; +#endif } -void CanvasContext::updateSurface(ANativeWindow* window) { - setSurface(window); +void CanvasContext::updateSurface(Surface* surface) { + setSurface(surface); } -bool CanvasContext::pauseSurface(ANativeWindow* window) { +bool CanvasContext::pauseSurface(Surface* surface) { return mRenderThread.removeFrameCallback(this); } +void CanvasContext::setStopped(bool stopped) { + if (mStopped != stopped) { + mStopped = stopped; + if (mStopped) { + mRenderThread.removeFrameCallback(this); + if (mEglManager.isCurrent(mEglSurface)) { + mEglManager.makeCurrent(EGL_NO_SURFACE); + } + } + } +} + // TODO: don't pass viewport size, it's automatic via EGL void CanvasContext::setup(int width, int height, float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { +#if HWUI_NEW_OPS + mLightGeometry.radius = lightRadius; + mLightInfo.ambientShadowAlpha = ambientShadowAlpha; + mLightInfo.spotShadowAlpha = spotShadowAlpha; +#else if (!mCanvas) return; mCanvas->initLight(lightRadius, ambientShadowAlpha, spotShadowAlpha); +#endif } void CanvasContext::setLightCenter(const Vector3& lightCenter) { +#if HWUI_NEW_OPS + mLightGeometry.center = lightCenter; +#else if (!mCanvas) return; mCanvas->setLightCenter(lightCenter); +#endif } void CanvasContext::setOpaque(bool opaque) { mOpaque = opaque; } -void CanvasContext::makeCurrent() { +bool CanvasContext::makeCurrent() { + if (mStopped) return false; + // TODO: Figure out why this workaround is needed, see b/13913604 // In the meantime this matches the behavior of GLRenderer, so it is not a regression EGLint error = 0; @@ -150,21 +189,15 @@ void CanvasContext::makeCurrent() { if (error) { setSurface(nullptr); } -} - -void CanvasContext::processLayerUpdate(DeferredLayerUpdater* layerUpdater) { - bool success = layerUpdater->apply(); - LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!"); - if (layerUpdater->backingLayer()->deferredUpdateScheduled) { - mCanvas->pushLayerUpdate(layerUpdater->backingLayer()); - } + return !error; } static bool wasSkipped(FrameInfo* info) { return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame); } -void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued) { +void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, + int64_t syncQueued, RenderNode* target) { mRenderThread.removeFrameCallback(this); // If the previous frame was dropped we don't need to hold onto it, so @@ -177,28 +210,58 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy mCurrentFrameInfo->markSyncStart(); info.damageAccumulator = &mDamageAccumulator; +#if HWUI_NEW_OPS + info.layerUpdateQueue = &mLayerUpdateQueue; +#else info.renderer = mCanvas; - info.canvasContext = this; +#endif mAnimationContext->startFrame(info.mode); - mRootRenderNode->prepareTree(info); + for (const sp<RenderNode>& node : mRenderNodes) { + // Only the primary target node will be drawn full - all other nodes would get drawn in + // real time mode. In case of a window, the primary node is the window content and the other + // node(s) are non client / filler nodes. + info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY); + node->prepareTree(info); + GL_CHECKPOINT(MODERATE); + } mAnimationContext->runRemainingAnimations(info); + GL_CHECKPOINT(MODERATE); - freePrefetechedLayers(); + freePrefetchedLayers(info.observer); + GL_CHECKPOINT(MODERATE); - if (CC_UNLIKELY(!mNativeWindow.get())) { + if (CC_UNLIKELY(!mNativeSurface.get())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); info.out.canDrawThisFrame = false; return; } - int runningBehind = 0; - // TODO: This query is moderately expensive, investigate adding some sort - // of fast-path based off when we last called eglSwapBuffers() as well as - // last vsync time. Or something. - mNativeWindow->query(mNativeWindow.get(), - NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); - info.out.canDrawThisFrame = !runningBehind; + if (CC_LIKELY(mSwapHistory.size())) { + nsecs_t latestVsync = mRenderThread.timeLord().latestVsync(); + const SwapHistory& lastSwap = mSwapHistory.back(); + nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync); + // The slight fudge-factor is to deal with cases where + // the vsync was estimated due to being slow handling the signal. + // See the logic in TimeLord#computeFrameTimeNanos or in + // Choreographer.java for details on when this happens + if (vsyncDelta < 2_ms) { + // Already drew for this vsync pulse, UI draw request missed + // the deadline for RT animations + info.out.canDrawThisFrame = false; + } else if (lastSwap.swapTime < latestVsync) { + info.out.canDrawThisFrame = true; + } else { + // We're maybe behind? Find out for sure + int runningBehind = 0; + // TODO: Have this method be on Surface, too, not just ANativeWindow... + ANativeWindow* window = mNativeSurface.get(); + window->query(window, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); + info.out.canDrawThisFrame = !runningBehind; + } + } else { + info.out.canDrawThisFrame = true; + } if (!info.out.canDrawThisFrame) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); @@ -223,8 +286,10 @@ void CanvasContext::notifyFramePending() { } void CanvasContext::draw() { +#if !HWUI_NEW_OPS LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE, "drawRenderNode called on a context with no canvas or surface!"); +#endif SkRect dirty; mDamageAccumulator.finish(&dirty); @@ -237,56 +302,258 @@ void CanvasContext::draw() { mCurrentFrameInfo->markIssueDrawCommandsStart(); - EGLint width, height; - mEglManager.beginFrame(mEglSurface, &width, &height); - if (width != mCanvas->getViewportWidth() || height != mCanvas->getViewportHeight()) { - mCanvas->setViewport(width, height); + Frame frame = mEglManager.beginFrame(mEglSurface); + + if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) { + // can't rely on prior content of window if viewport size changes dirty.setEmpty(); - } else if (!mBufferPreserved || mHaveNewSurface) { + mLastFrameWidth = frame.width(); + mLastFrameHeight = frame.height(); + } else if (mHaveNewSurface || frame.bufferAge() == 0) { + // New surface needs a full draw dirty.setEmpty(); } else { - if (!dirty.isEmpty() && !dirty.intersect(0, 0, width, height)) { + if (!dirty.isEmpty() && !dirty.intersect(0, 0, frame.width(), frame.height())) { ALOGW("Dirty " RECT_STRING " doesn't intersect with 0 0 %d %d ?", - SK_RECT_ARGS(dirty), width, height); + SK_RECT_ARGS(dirty), frame.width(), frame.height()); dirty.setEmpty(); } profiler().unionDirty(&dirty); } - if (!dirty.isEmpty()) { - mCanvas->prepareDirty(dirty.fLeft, dirty.fTop, - dirty.fRight, dirty.fBottom, mOpaque); - } else { - mCanvas->prepare(mOpaque); + if (dirty.isEmpty()) { + dirty.set(0, 0, frame.width(), frame.height()); } + // At this point dirty is the area of the screen to update. However, + // the area of the frame we need to repaint is potentially different, so + // stash the screen area for later + SkRect screenDirty(dirty); + + // If the buffer age is 0 we do a full-screen repaint (handled above) + // If the buffer age is 1 the buffer contents are the same as they were + // last frame so there's nothing to union() against + // Therefore we only care about the > 1 case. + if (frame.bufferAge() > 1) { + if (frame.bufferAge() > (int) mSwapHistory.size()) { + // We don't have enough history to handle this old of a buffer + // Just do a full-draw + dirty.set(0, 0, frame.width(), frame.height()); + } else { + // At this point we haven't yet added the latest frame + // to the damage history (happens below) + // So we need to damage + for (int i = mSwapHistory.size() - 1; + i > ((int) mSwapHistory.size()) - frame.bufferAge(); i--) { + dirty.join(mSwapHistory[i].damage); + } + } + } + + mEglManager.damageFrame(frame, dirty); + +#if HWUI_NEW_OPS + auto& caches = Caches::getInstance(); + FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), mLightGeometry, caches); + + frameBuilder.deferLayers(mLayerUpdateQueue); + mLayerUpdateQueue.clear(); + + frameBuilder.deferRenderNodeScene(mRenderNodes, mContentDrawBounds); + + BakedOpRenderer renderer(caches, mRenderThread.renderState(), + mOpaque, mLightInfo); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); + profiler().draw(&renderer); + bool drew = renderer.didDraw(); + + // post frame cleanup + caches.clearGarbage(); + caches.pathCache.trim(); + caches.tessellationCache.trim(); + +#if DEBUG_MEMORY_USAGE + mCaches.dumpMemoryUsage(); +#else + if (CC_UNLIKELY(Properties::debugLevel & kDebugMemory)) { + caches.dumpMemoryUsage(); + } +#endif + +#else + mCanvas->prepareDirty(frame.width(), frame.height(), + dirty.fLeft, dirty.fTop, dirty.fRight, dirty.fBottom, mOpaque); + Rect outBounds; - mCanvas->drawRenderNode(mRootRenderNode.get(), outBounds); + // It there are multiple render nodes, they are laid out as follows: + // #0 - backdrop (content + caption) + // #1 - content (positioned at (0,0) and clipped to - its bounds mContentDrawBounds) + // #2 - additional overlay nodes + // Usually the backdrop cannot be seen since it will be entirely covered by the content. While + // resizing however it might become partially visible. The following render loop will crop the + // backdrop against the content and draw the remaining part of it. It will then draw the content + // cropped to the backdrop (since that indicates a shrinking of the window). + // + // Additional nodes will be drawn on top with no particular clipping semantics. + + // The bounds of the backdrop against which the content should be clipped. + Rect backdropBounds = mContentDrawBounds; + // Usually the contents bounds should be mContentDrawBounds - however - we will + // move it towards the fixed edge to give it a more stable appearance (for the moment). + Rect contentBounds; + // If there is no content bounds we ignore the layering as stated above and start with 2. + int layer = (mContentDrawBounds.isEmpty() || mRenderNodes.size() == 1) ? 2 : 0; + // Draw all render nodes. Note that + for (const sp<RenderNode>& node : mRenderNodes) { + if (layer == 0) { // Backdrop. + // Draw the backdrop clipped to the inverse content bounds, but assume that the content + // was moved to the upper left corner. + const RenderProperties& properties = node->properties(); + Rect targetBounds(properties.getLeft(), properties.getTop(), + properties.getRight(), properties.getBottom()); + // Move the content bounds towards the fixed corner of the backdrop. + const int x = targetBounds.left; + const int y = targetBounds.top; + contentBounds.set(x, y, x + mContentDrawBounds.getWidth(), + y + mContentDrawBounds.getHeight()); + // Remember the intersection of the target bounds and the intersection bounds against + // which we have to crop the content. + backdropBounds.set(x, y, x + backdropBounds.getWidth(), y + backdropBounds.getHeight()); + backdropBounds.doIntersect(targetBounds); + // Check if we have to draw something on the left side ... + if (targetBounds.left < contentBounds.left) { + mCanvas->save(SaveFlags::Clip); + if (mCanvas->clipRect(targetBounds.left, targetBounds.top, + contentBounds.left, targetBounds.bottom, + SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + // Reduce the target area by the area we have just painted. + targetBounds.left = std::min(contentBounds.left, targetBounds.right); + mCanvas->restore(); + } + // ... or on the right side ... + if (targetBounds.right > contentBounds.right && + !targetBounds.isEmpty()) { + mCanvas->save(SaveFlags::Clip); + if (mCanvas->clipRect(contentBounds.right, targetBounds.top, + targetBounds.right, targetBounds.bottom, + SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + // Reduce the target area by the area we have just painted. + targetBounds.right = std::max(targetBounds.left, contentBounds.right); + mCanvas->restore(); + } + // ... or at the top ... + if (targetBounds.top < contentBounds.top && + !targetBounds.isEmpty()) { + mCanvas->save(SaveFlags::Clip); + if (mCanvas->clipRect(targetBounds.left, targetBounds.top, targetBounds.right, + contentBounds.top, + SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + // Reduce the target area by the area we have just painted. + targetBounds.top = std::min(contentBounds.top, targetBounds.bottom); + mCanvas->restore(); + } + // ... or at the bottom. + if (targetBounds.bottom > contentBounds.bottom && + !targetBounds.isEmpty()) { + mCanvas->save(SaveFlags::Clip); + if (mCanvas->clipRect(targetBounds.left, contentBounds.bottom, targetBounds.right, + targetBounds.bottom, SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + mCanvas->restore(); + } + } else if (layer == 1) { // Content + // It gets cropped against the bounds of the backdrop to stay inside. + mCanvas->save(SaveFlags::MatrixClip); + + // We shift and clip the content to match its final location in the window. + const float left = mContentDrawBounds.left; + const float top = mContentDrawBounds.top; + const float dx = backdropBounds.left - left; + const float dy = backdropBounds.top - top; + const float width = backdropBounds.getWidth(); + const float height = backdropBounds.getHeight(); + + mCanvas->translate(dx, dy); + if (mCanvas->clipRect(left, top, left + width, top + height, SkRegion::kIntersect_Op)) { + mCanvas->drawRenderNode(node.get(), outBounds); + } + mCanvas->restore(); + } else { // draw the rest on top at will! + mCanvas->drawRenderNode(node.get(), outBounds); + } + layer++; + } profiler().draw(mCanvas); bool drew = mCanvas->finish(); +#endif + + waitOnFences(); + + GL_CHECKPOINT(LOW); // Even if we decided to cancel the frame, from the perspective of jank // metrics the frame was swapped at this point mCurrentFrameInfo->markSwapBuffers(); - if (drew) { - swapBuffers(dirty, width, height); + if (drew || mEglManager.damageRequiresSwap()) { + if (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty))) { + setSurface(nullptr); + } + SwapHistory& swap = mSwapHistory.next(); + swap.damage = screenDirty; + swap.swapTime = systemTime(CLOCK_MONOTONIC); + swap.vsyncTime = mRenderThread.timeLord().latestVsync(); + mHaveNewSurface = false; + mFrameNumber = -1; } // TODO: Use a fence for real completion? mCurrentFrameInfo->markFrameCompleted(); + +#if LOG_FRAMETIME_MMA + float thisFrame = mCurrentFrameInfo->duration( + FrameInfoIndex::IssueDrawCommandsStart, + FrameInfoIndex::FrameCompleted) / NANOS_PER_MILLIS_F; + if (sFrameCount) { + sBenchMma = ((9 * sBenchMma) + thisFrame) / 10; + } else { + sBenchMma = thisFrame; + } + if (++sFrameCount == 10) { + sFrameCount = 1; + ALOGD("Average frame time: %.4f", sBenchMma); + } +#endif + mJankTracker.addFrame(*mCurrentFrameInfo); mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo); + if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) { + mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data()); + } + + GpuMemoryTracker::onFrameCompleted(); } // Called by choreographer to do an RT-driven animation void CanvasContext::doFrame() { - if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) { - return; - } +#if HWUI_NEW_OPS + if (CC_UNLIKELY(mEglSurface == EGL_NO_SURFACE)) return; +#else + if (CC_UNLIKELY(!mCanvas || mEglSurface == EGL_NO_SURFACE)) return; +#endif + prepareAndDraw(nullptr); +} +void CanvasContext::prepareAndDraw(RenderNode* node) { ATRACE_CALL(); nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos(); @@ -295,8 +562,8 @@ void CanvasContext::doFrame() { .addFlag(FrameInfoFlags::RTAnimation) .setVsync(vsync, vsync); - TreeInfo info(TreeInfo::MODE_RT_ONLY, mRenderThread.renderState()); - prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC)); + TreeInfo info(TreeInfo::MODE_RT_ONLY, *this); + prepareTree(info, frameInfo, systemTime(CLOCK_MONOTONIC), node); if (info.out.canDrawThisFrame) { draw(); } @@ -313,35 +580,41 @@ void CanvasContext::invokeFunctor(RenderThread& thread, Functor* functor) { } void CanvasContext::markLayerInUse(RenderNode* node) { - if (mPrefetechedLayers.erase(node)) { + if (mPrefetchedLayers.erase(node)) { node->decStrong(nullptr); } } -static void destroyPrefetechedNode(RenderNode* node) { - ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...", node->getName()); - node->destroyHardwareResources(); - node->decStrong(nullptr); -} - -void CanvasContext::freePrefetechedLayers() { - if (mPrefetechedLayers.size()) { - std::for_each(mPrefetechedLayers.begin(), mPrefetechedLayers.end(), destroyPrefetechedNode); - mPrefetechedLayers.clear(); +void CanvasContext::freePrefetchedLayers(TreeObserver* observer) { + if (mPrefetchedLayers.size()) { + for (auto& node : mPrefetchedLayers) { + ALOGW("Incorrectly called buildLayer on View: %s, destroying layer...", + node->getName()); + node->destroyHardwareResources(observer); + node->decStrong(observer); + } + mPrefetchedLayers.clear(); } } -void CanvasContext::buildLayer(RenderNode* node) { +void CanvasContext::buildLayer(RenderNode* node, TreeObserver* observer) { ATRACE_CALL(); - if (!mEglManager.hasEglContext() || !mCanvas) { - return; - } + if (!mEglManager.hasEglContext()) return; +#if !HWUI_NEW_OPS + if (!mCanvas) return; +#endif + // buildLayer() will leave the tree in an unknown state, so we must stop drawing stopDrawing(); - TreeInfo info(TreeInfo::MODE_FULL, mRenderThread.renderState()); + TreeInfo info(TreeInfo::MODE_FULL, *this); info.damageAccumulator = &mDamageAccumulator; + info.observer = observer; +#if HWUI_NEW_OPS + info.layerUpdateQueue = &mLayerUpdateQueue; +#else info.renderer = mCanvas; +#endif info.runAnimations = false; node->prepareTree(info); SkRect ignore; @@ -350,11 +623,22 @@ void CanvasContext::buildLayer(RenderNode* node) { // purposes when the frame is actually drawn node->setPropertyFieldsDirty(RenderNode::GENERIC); +#if HWUI_NEW_OPS + static const std::vector< sp<RenderNode> > emptyNodeList; + auto& caches = Caches::getInstance(); + FrameBuilder frameBuilder(mLayerUpdateQueue, mLightGeometry, caches); + mLayerUpdateQueue.clear(); + BakedOpRenderer renderer(caches, mRenderThread.renderState(), + mOpaque, mLightInfo); + LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case"); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); +#else mCanvas->markLayersAsBuildLayers(); mCanvas->flushLayerUpdates(); +#endif node->incStrong(nullptr); - mPrefetechedLayers.insert(node); + mPrefetchedLayers.insert(node); } bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { @@ -362,16 +646,18 @@ bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) return LayerRenderer::copyLayer(mRenderThread.renderState(), layer->backingLayer(), bitmap); } -void CanvasContext::destroyHardwareResources() { +void CanvasContext::destroyHardwareResources(TreeObserver* observer) { stopDrawing(); if (mEglManager.hasEglContext()) { - freePrefetechedLayers(); - mRootRenderNode->destroyHardwareResources(); + freePrefetchedLayers(observer); + for (const sp<RenderNode>& node : mRenderNodes) { + node->destroyHardwareResources(observer); + } Caches& caches = Caches::getInstance(); // Make sure to release all the textures we were owning as there won't // be another draw caches.textureCache.resetMarkInUse(this); - caches.flush(Caches::kFlushMode_Layers); + mRenderThread.renderState().flush(Caches::FlushMode::Layers); } } @@ -381,10 +667,10 @@ void CanvasContext::trimMemory(RenderThread& thread, int level) { ATRACE_CALL(); if (level >= TRIM_MEMORY_COMPLETE) { - Caches::getInstance().flush(Caches::kFlushMode_Full); + thread.renderState().flush(Caches::FlushMode::Full); thread.eglManager().destroy(); } else if (level >= TRIM_MEMORY_UI_HIDDEN) { - Caches::getInstance().flush(Caches::kFlushMode_Moderate); + thread.renderState().flush(Caches::FlushMode::Moderate); } } @@ -395,7 +681,7 @@ void CanvasContext::runWithGlContext(RenderTask* task) { } Layer* CanvasContext::createTextureLayer() { - requireSurface(); + mEglManager.initialize(); return LayerRenderer::createTextureLayer(mRenderThread.renderState()); } @@ -430,6 +716,79 @@ void CanvasContext::resetFrameStats() { mRenderThread.jankTracker().reset(); } +void CanvasContext::serializeDisplayListTree() { +#if ENABLE_RENDERNODE_SERIALIZATION + using namespace google::protobuf::io; + char package[128]; + // Check whether tracing is enabled for this process. + FILE * file = fopen("/proc/self/cmdline", "r"); + if (file) { + if (!fgets(package, 128, file)) { + ALOGE("Error reading cmdline: %s (%d)", strerror(errno), errno); + fclose(file); + return; + } + fclose(file); + } else { + ALOGE("Error opening /proc/self/cmdline: %s (%d)", strerror(errno), + errno); + return; + } + char path[1024]; + snprintf(path, 1024, "/data/data/%s/cache/rendertree_dump", package); + int fd = open(path, O_CREAT | O_WRONLY, S_IRWXU | S_IRGRP | S_IROTH); + if (fd == -1) { + ALOGD("Failed to open '%s'", path); + return; + } + proto::RenderNode tree; + // TODO: Streaming writes? + mRootRenderNode->copyTo(&tree); + std::string data = tree.SerializeAsString(); + write(fd, data.c_str(), data.length()); + close(fd); +#endif +} + +void CanvasContext::waitOnFences() { + if (mFrameFences.size()) { + ATRACE_CALL(); + for (auto& fence : mFrameFences) { + fence->getResult(); + } + mFrameFences.clear(); + } +} + +class CanvasContext::FuncTaskProcessor : public TaskProcessor<bool> { +public: + FuncTaskProcessor(Caches& caches) + : TaskProcessor<bool>(&caches.tasks) {} + + virtual void onProcess(const sp<Task<bool> >& task) override { + FuncTask* t = static_cast<FuncTask*>(task.get()); + t->func(); + task->setResult(true); + } +}; + +void CanvasContext::enqueueFrameWork(std::function<void()>&& func) { + if (!mFrameWorkProcessor.get()) { + mFrameWorkProcessor = new FuncTaskProcessor(Caches::getInstance()); + } + sp<FuncTask> task(new FuncTask()); + task->func = func; + mFrameWorkProcessor->add(task); +} + +int64_t CanvasContext::getFrameNumber() { + // mFrameNumber is reset to -1 when the surface changes or we swap buffers + if (mFrameNumber == -1 && mNativeSurface.get()) { + mFrameNumber = static_cast<int64_t>(mNativeSurface->getNextFrameNumber()); + } + return mFrameNumber; +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index f2fa9cdcbefd..e739b2949cf9 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -18,23 +18,35 @@ #define CANVASCONTEXT_H_ #include "DamageAccumulator.h" -#include "IContextFactory.h" #include "FrameInfo.h" #include "FrameInfoVisualizer.h" +#include "FrameMetricsReporter.h" +#include "IContextFactory.h" +#include "LayerUpdateQueue.h" #include "RenderNode.h" +#include "thread/Task.h" +#include "thread/TaskProcessor.h" #include "utils/RingBuffer.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" +#if HWUI_NEW_OPS +#include "BakedOpDispatcher.h" +#include "BakedOpRenderer.h" +#include "FrameBuilder.h" +#endif + #include <cutils/compiler.h> #include <EGL/egl.h> #include <SkBitmap.h> #include <SkRect.h> #include <utils/Functor.h> -#include <utils/Vector.h> +#include <gui/Surface.h> +#include <functional> #include <set> #include <string> +#include <vector> namespace android { namespace uirenderer { @@ -67,29 +79,31 @@ public: // Won't take effect until next EGLSurface creation void setSwapBehavior(SwapBehavior swapBehavior); - bool initialize(ANativeWindow* window); - void updateSurface(ANativeWindow* window); - bool pauseSurface(ANativeWindow* window); - bool hasSurface() { return mNativeWindow.get(); } + void initialize(Surface* surface); + void updateSurface(Surface* surface); + bool pauseSurface(Surface* surface); + void setStopped(bool stopped); + bool hasSurface() { return mNativeSurface.get(); } void setup(int width, int height, float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); void setLightCenter(const Vector3& lightCenter); void setOpaque(bool opaque); - void makeCurrent(); - void processLayerUpdate(DeferredLayerUpdater* layerUpdater); - void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued); + bool makeCurrent(); + void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, + int64_t syncQueued, RenderNode* target); void draw(); - void destroy(); + void destroy(TreeObserver* observer); - // IFrameCallback, Chroreographer-driven frame callback entry point + // IFrameCallback, Choreographer-driven frame callback entry point virtual void doFrame() override; + void prepareAndDraw(RenderNode* node); - void buildLayer(RenderNode* node); + void buildLayer(RenderNode* node, TreeObserver* observer); bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); void markLayerInUse(RenderNode* node); - void destroyHardwareResources(); + void destroyHardwareResources(TreeObserver* observer); static void trimMemory(RenderThread& thread, int level); static void invokeFunctor(RenderThread& thread, Functor* functor); @@ -112,32 +126,93 @@ public: void setName(const std::string&& name) { mName = name; } const std::string& name() { return mName; } + void serializeDisplayListTree(); + + void addRenderNode(RenderNode* node, bool placeFront) { + int pos = placeFront ? 0 : static_cast<int>(mRenderNodes.size()); + mRenderNodes.emplace(mRenderNodes.begin() + pos, node); + } + + void removeRenderNode(RenderNode* node) { + mRenderNodes.erase(std::remove(mRenderNodes.begin(), mRenderNodes.end(), node), + mRenderNodes.end()); + } + + void setContentDrawBounds(int left, int top, int right, int bottom) { + mContentDrawBounds.set(left, top, right, bottom); + } + + RenderState& getRenderState() { + return mRenderThread.renderState(); + } + + void addFrameMetricsObserver(FrameMetricsObserver* observer) { + if (mFrameMetricsReporter.get() == nullptr) { + mFrameMetricsReporter.reset(new FrameMetricsReporter()); + } + + mFrameMetricsReporter->addObserver(observer); + } + + void removeFrameMetricsObserver(FrameMetricsObserver* observer) { + if (mFrameMetricsReporter.get() != nullptr) { + mFrameMetricsReporter->removeObserver(observer); + if (!mFrameMetricsReporter->hasObservers()) { + mFrameMetricsReporter.reset(nullptr); + } + } + } + + // Used to queue up work that needs to be completed before this frame completes + ANDROID_API void enqueueFrameWork(std::function<void()>&& func); + + ANDROID_API int64_t getFrameNumber(); + private: friend class RegisterFrameCallbackTask; // TODO: Replace with something better for layer & other GL object // lifecycle tracking friend class android::uirenderer::RenderState; - void setSurface(ANativeWindow* window); - void swapBuffers(const SkRect& dirty, EGLint width, EGLint height); - void requireSurface(); + void setSurface(Surface* window); + + void freePrefetchedLayers(TreeObserver* observer); + + void waitOnFences(); - void freePrefetechedLayers(); + EGLint mLastFrameWidth = 0; + EGLint mLastFrameHeight = 0; RenderThread& mRenderThread; EglManager& mEglManager; - sp<ANativeWindow> mNativeWindow; + sp<Surface> mNativeSurface; EGLSurface mEglSurface = EGL_NO_SURFACE; + bool mStopped = false; bool mBufferPreserved = false; SwapBehavior mSwapBehavior = kSwap_default; + struct SwapHistory { + SkRect damage; + nsecs_t vsyncTime; + nsecs_t swapTime; + }; + + RingBuffer<SwapHistory, 3> mSwapHistory; + int64_t mFrameNumber = -1; bool mOpaque; +#if HWUI_NEW_OPS + BakedOpRenderer::LightInfo mLightInfo; + FrameBuilder::LightGeometry mLightGeometry = { {0, 0, 0}, 0 }; +#else OpenGLRenderer* mCanvas = nullptr; +#endif + bool mHaveNewSurface = false; DamageAccumulator mDamageAccumulator; + LayerUpdateQueue mLayerUpdateQueue; std::unique_ptr<AnimationContext> mAnimationContext; - const sp<RenderNode> mRootRenderNode; + std::vector< sp<RenderNode> > mRenderNodes; FrameInfo* mCurrentFrameInfo = nullptr; // Ring buffer large enough for 2 seconds worth of frames @@ -145,8 +220,22 @@ private: std::string mName; JankTracker mJankTracker; FrameInfoVisualizer mProfiler; + std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter; + + std::set<RenderNode*> mPrefetchedLayers; + + // Stores the bounds of the main content. + Rect mContentDrawBounds; + + // TODO: This is really a Task<void> but that doesn't really work + // when Future<> expects to be able to get/set a value + struct FuncTask : public Task<bool> { + std::function<void()> func; + }; + class FuncTaskProcessor; - std::set<RenderNode*> mPrefetechedLayers; + std::vector< sp<FuncTask> > mFrameFences; + sp<TaskProcessor<bool> > mFrameWorkProcessor; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index a4ac13bb95a1..c9c07b3df292 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define ATRACE_TAG ATRACE_TAG_VIEW - #include "DrawFrameTask.h" #include <utils/Log.h> @@ -34,15 +32,17 @@ namespace renderthread { DrawFrameTask::DrawFrameTask() : mRenderThread(nullptr) , mContext(nullptr) - , mSyncResult(kSync_OK) { + , mSyncResult(SyncResult::OK) { } DrawFrameTask::~DrawFrameTask() { } -void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context) { +void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, + RenderNode* targetNode) { mRenderThread = thread; mContext = context; + mTargetNode = targetNode; } void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) { @@ -65,11 +65,12 @@ void DrawFrameTask::removeLayerUpdate(DeferredLayerUpdater* layer) { } } -int DrawFrameTask::drawFrame() { +int DrawFrameTask::drawFrame(TreeObserver* observer) { LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!"); - mSyncResult = kSync_OK; + mSyncResult = SyncResult::OK; mSyncQueued = systemTime(CLOCK_MONOTONIC); + mObserver = observer; postAndWait(); return mSyncResult; @@ -87,7 +88,8 @@ void DrawFrameTask::run() { bool canUnblockUiThread; bool canDrawThisFrame; { - TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState()); + TreeInfo info(TreeInfo::MODE_FULL, *mContext); + info.observer = mObserver; canUnblockUiThread = syncFrameState(info); canDrawThisFrame = info.out.canDrawThisFrame; } @@ -113,24 +115,30 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { ATRACE_CALL(); int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)]; mRenderThread->timeLord().vsyncReceived(vsync); - mContext->makeCurrent(); + bool canDraw = mContext->makeCurrent(); Caches::getInstance().textureCache.resetMarkInUse(mContext); for (size_t i = 0; i < mLayers.size(); i++) { - mContext->processLayerUpdate(mLayers[i].get()); + mLayers[i]->apply(); } mLayers.clear(); - mContext->prepareTree(info, mFrameInfo, mSyncQueued); + mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode); // This is after the prepareTree so that any pending operations // (RenderNode tree state, prefetched layers, etc...) will be flushed. - if (CC_UNLIKELY(!mContext->hasSurface())) { - mSyncResult |= kSync_LostSurfaceRewardIfFound; + if (CC_UNLIKELY(!mContext->hasSurface() || !canDraw)) { + if (!mContext->hasSurface()) { + mSyncResult |= SyncResult::LostSurfaceRewardIfFound; + } else { + // If we have a surface but can't draw we must be stopped + mSyncResult |= SyncResult::ContextIsStopped; + } + info.out.canDrawThisFrame = false; } if (info.out.hasAnimations) { if (info.out.requiresUiRedraw) { - mSyncResult |= kSync_UIRedrawRequired; + mSyncResult |= SyncResult::UIRedrawRequired; } } // If prepareTextures is false, we ran out of texture cache space diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index ebefcba9f6a5..c02d376098a6 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -32,7 +32,7 @@ namespace android { namespace uirenderer { class DeferredLayerUpdater; -class DisplayListData; +class DisplayList; class RenderNode; namespace renderthread { @@ -40,15 +40,18 @@ namespace renderthread { class CanvasContext; class RenderThread; -enum SyncResult { - kSync_OK = 0, - kSync_UIRedrawRequired = 1 << 0, - kSync_LostSurfaceRewardIfFound = 1 << 1, +namespace SyncResult { +enum { + OK = 0, + UIRedrawRequired = 1 << 0, + LostSurfaceRewardIfFound = 1 << 1, + ContextIsStopped = 1 << 2, }; +} /* * This is a special Super Task. It is re-used multiple times by RenderProxy, - * and contains state (such as layer updaters & new DisplayListDatas) that is + * and contains state (such as layer updaters & new DisplayLists) that is * tracked across many frames not just a single frame. * It is the sync-state task, and will kick off the post-sync draw */ @@ -57,12 +60,12 @@ public: DrawFrameTask(); virtual ~DrawFrameTask(); - void setContext(RenderThread* thread, CanvasContext* context); + void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode); void pushLayerUpdate(DeferredLayerUpdater* layer); void removeLayerUpdate(DeferredLayerUpdater* layer); - int drawFrame(); + int drawFrame(TreeObserver* observer); int64_t* frameInfo() { return mFrameInfo; } @@ -78,6 +81,7 @@ private: RenderThread* mRenderThread; CanvasContext* mContext; + RenderNode* mTargetNode = nullptr; /********************************************* * Single frame data @@ -86,6 +90,7 @@ private: int mSyncResult; int64_t mSyncQueued; + TreeObserver* mObserver; int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE]; }; diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index eb332d59fee3..ac6a28fe6289 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -17,19 +17,18 @@ #include "EglManager.h" #include "Caches.h" +#include "DeviceInfo.h" #include "Properties.h" #include "RenderThread.h" #include "renderstate/RenderState.h" - +#include "utils/StringUtils.h" #include <cutils/log.h> #include <cutils/properties.h> #include <EGL/eglext.h> +#include <string> -#define PROPERTY_RENDER_DIRTY_REGIONS "debug.hwui.render_dirty_regions" #define GLES_VERSION 2 -#define WAIT_FOR_GPU_COMPLETION 0 - // Android-specific addition that is used to show when frames began in systrace EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface); @@ -63,10 +62,27 @@ static const char* egl_error_str() { return egl_error_str(eglGetError()); } -static bool load_dirty_regions_property() { - char buf[PROPERTY_VALUE_MAX]; - int len = property_get(PROPERTY_RENDER_DIRTY_REGIONS, buf, "true"); - return !strncasecmp("true", buf, len); +static struct { + bool bufferAge = false; + bool setDamage = false; +} EglExtensions; + +void Frame::map(const SkRect& in, EGLint* out) const { + /* The rectangles are specified relative to the bottom-left of the surface + * and the x and y components of each rectangle specify the bottom-left + * position of that rectangle. + * + * HWUI does everything with 0,0 being top-left, so need to map + * the rect + */ + SkIRect idirty; + in.roundOut(&idirty); + EGLint y = mHeight - (idirty.y() + idirty.height()); + // layout: {x, y, width, height} + out[0] = idirty.x(); + out[1] = y; + out[2] = idirty.width(); + out[3] = idirty.height(); } EglManager::EglManager(RenderThread& thread) @@ -75,12 +91,9 @@ EglManager::EglManager(RenderThread& thread) , mEglConfig(nullptr) , mEglContext(EGL_NO_CONTEXT) , mPBufferSurface(EGL_NO_SURFACE) - , mAllowPreserveBuffer(load_dirty_regions_property()) , mCurrentSurface(EGL_NO_SURFACE) , mAtlasMap(nullptr) , mAtlasMapSize(0) { - mCanSetPreserveBuffer = mAllowPreserveBuffer; - ALOGD("Use EGL_SWAP_BEHAVIOR_PRESERVED: %s", mAllowPreserveBuffer ? "true" : "false"); } void EglManager::initialize() { @@ -98,20 +111,43 @@ void EglManager::initialize() { ALOGI("Initialized EGL, version %d.%d", (int)major, (int)minor); + initExtensions(); + + // Now that extensions are loaded, pick a swap behavior + if (Properties::enablePartialUpdates) { + if (Properties::useBufferAge && EglExtensions.bufferAge) { + mSwapBehavior = SwapBehavior::BufferAge; + } else { + mSwapBehavior = SwapBehavior::Preserved; + } + } + loadConfig(); createContext(); createPBufferSurface(); makeCurrent(mPBufferSurface); + DeviceInfo::initialize(); mRenderThread.renderState().onGLContextCreated(); initAtlas(); } +void EglManager::initExtensions() { + auto extensions = StringUtils::split( + eglQueryString(mEglDisplay, EGL_EXTENSIONS)); + EglExtensions.bufferAge = extensions.has("EGL_EXT_buffer_age"); + EglExtensions.setDamage = extensions.has("EGL_KHR_partial_update"); + LOG_ALWAYS_FATAL_IF(!extensions.has("EGL_KHR_swap_buffers_with_damage"), + "Missing required extension EGL_KHR_swap_buffers_with_damage"); +} + bool EglManager::hasEglContext() { return mEglDisplay != EGL_NO_DISPLAY; } void EglManager::loadConfig() { - EGLint swapBehavior = mCanSetPreserveBuffer ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior)); + EGLint swapBehavior = (mSwapBehavior == SwapBehavior::Preserved) + ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; EGLint attribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, @@ -128,20 +164,23 @@ void EglManager::loadConfig() { EGLint num_configs = 1; if (!eglChooseConfig(mEglDisplay, attribs, &mEglConfig, num_configs, &num_configs) || num_configs != 1) { - // Failed to get a valid config - if (mCanSetPreserveBuffer) { - ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without..."); + if (mSwapBehavior == SwapBehavior::Preserved) { // Try again without dirty regions enabled - mCanSetPreserveBuffer = false; + ALOGW("Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without..."); + mSwapBehavior = SwapBehavior::Discard; loadConfig(); } else { + // Failed to get a valid config LOG_ALWAYS_FATAL("Failed to choose config, error = %s", egl_error_str()); } } } void EglManager::createContext() { - EGLint attribs[] = { EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, EGL_NONE }; + EGLint attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, GLES_VERSION, + EGL_NONE + }; mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attribs); LOG_ALWAYS_FATAL_IF(mEglContext == EGL_NO_CONTEXT, "Failed to create context, error = %s", egl_error_str()); @@ -189,6 +228,13 @@ EGLSurface EglManager::createSurface(EGLNativeWindowType window) { LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE, "Failed to create EGLSurface for window %p, eglErr = %s", (void*) window, egl_error_str()); + + if (mSwapBehavior != SwapBehavior::Preserved) { + LOG_ALWAYS_FATAL_IF(eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_DESTROYED) == EGL_FALSE, + "Failed to set swap behavior to destroyed for window %p, eglErr = %s", + (void*) window, egl_error_str()); + } + return surface; } @@ -238,64 +284,72 @@ bool EglManager::makeCurrent(EGLSurface surface, EGLint* errOut) { return true; } -void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) { +EGLint EglManager::queryBufferAge(EGLSurface surface) { + switch (mSwapBehavior) { + case SwapBehavior::Discard: + return 0; + case SwapBehavior::Preserved: + return 1; + case SwapBehavior::BufferAge: + EGLint bufferAge; + eglQuerySurface(mEglDisplay, surface, EGL_BUFFER_AGE_EXT, &bufferAge); + return bufferAge; + } + return 0; +} + +Frame EglManager::beginFrame(EGLSurface surface) { LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE, "Tried to beginFrame on EGL_NO_SURFACE!"); makeCurrent(surface); - if (width) { - eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, width); - } - if (height) { - eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, height); - } + Frame frame; + frame.mSurface = surface; + eglQuerySurface(mEglDisplay, surface, EGL_WIDTH, &frame.mWidth); + eglQuerySurface(mEglDisplay, surface, EGL_HEIGHT, &frame.mHeight); + frame.mBufferAge = queryBufferAge(surface); eglBeginFrame(mEglDisplay, surface); + return frame; } -bool EglManager::swapBuffers(EGLSurface surface, const SkRect& dirty, - EGLint width, EGLint height) { +void EglManager::damageFrame(const Frame& frame, const SkRect& dirty) { +#ifdef EGL_KHR_partial_update + if (EglExtensions.setDamage && mSwapBehavior == SwapBehavior::BufferAge) { + EGLint rects[4]; + frame.map(dirty, rects); + if (!eglSetDamageRegionKHR(mEglDisplay, frame.mSurface, rects, 1)) { + LOG_ALWAYS_FATAL("Failed to set damage region on surface %p, error=%s", + (void*)frame.mSurface, egl_error_str()); + } + } +#endif +} -#if WAIT_FOR_GPU_COMPLETION - { +bool EglManager::damageRequiresSwap() { + return EglExtensions.setDamage && mSwapBehavior == SwapBehavior::BufferAge; +} + +bool EglManager::swapBuffers(const Frame& frame, const SkRect& screenDirty) { + + if (CC_UNLIKELY(Properties::waitForGpuCompletion)) { ATRACE_NAME("Finishing GPU work"); fence(); } -#endif -#ifdef EGL_KHR_swap_buffers_with_damage - if (CC_LIKELY(Properties::swapBuffersWithDamage)) { - SkIRect idirty; - dirty.roundOut(&idirty); - /* - * EGL_KHR_swap_buffers_with_damage spec states: - * - * The rectangles are specified relative to the bottom-left of the surface - * and the x and y components of each rectangle specify the bottom-left - * position of that rectangle. - * - * HWUI does everything with 0,0 being top-left, so need to map - * the rect - */ - EGLint y = height - (idirty.y() + idirty.height()); - // layout: {x, y, width, height} - EGLint rects[4] = { idirty.x(), y, idirty.width(), idirty.height() }; - EGLint numrects = dirty.isEmpty() ? 0 : 1; - eglSwapBuffersWithDamageKHR(mEglDisplay, surface, rects, numrects); - } else { - eglSwapBuffers(mEglDisplay, surface); - } -#else - eglSwapBuffers(mEglDisplay, surface); -#endif + EGLint rects[4]; + frame.map(screenDirty, rects); + eglSwapBuffersWithDamageKHR(mEglDisplay, frame.mSurface, rects, + screenDirty.isEmpty() ? 0 : 1); EGLint err = eglGetError(); if (CC_LIKELY(err == EGL_SUCCESS)) { return true; } - if (err == EGL_BAD_SURFACE) { + if (err == EGL_BAD_SURFACE || err == EGL_BAD_NATIVE_WINDOW) { // For some reason our surface was destroyed out from under us // This really shouldn't happen, but if it does we can recover easily // by just not trying to use the surface anymore - ALOGW("swapBuffers encountered EGL_BAD_SURFACE on %p, halting rendering...", surface); + ALOGW("swapBuffers encountered EGL error %d on %p, halting rendering...", + err, frame.mSurface); return false; } LOG_ALWAYS_FATAL("Encountered EGL error %d %s during rendering", @@ -312,18 +366,13 @@ void EglManager::fence() { } bool EglManager::setPreserveBuffer(EGLSurface surface, bool preserve) { - if (CC_UNLIKELY(!mAllowPreserveBuffer)) return false; - - bool preserved = false; - if (mCanSetPreserveBuffer) { - preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, - preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED); - if (CC_UNLIKELY(!preserved)) { - ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s", - (void*) surface, egl_error_str()); - } - } - if (CC_UNLIKELY(!preserved)) { + if (mSwapBehavior != SwapBehavior::Preserved) return false; + + bool preserved = eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, + preserve ? EGL_BUFFER_PRESERVED : EGL_BUFFER_DESTROYED); + if (!preserved) { + ALOGW("Failed to set EGL_SWAP_BEHAVIOR on surface %p, error=%s", + (void*) surface, egl_error_str()); // Maybe it's already set? EGLint swapBehavior; if (eglQuerySurface(mEglDisplay, surface, EGL_SWAP_BEHAVIOR, &swapBehavior)) { diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index 0a8cfd30df71..459baed70e40 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -27,6 +27,29 @@ namespace uirenderer { namespace renderthread { class RenderThread; +class EglManager; + +class Frame { +public: + EGLint width() const { return mWidth; } + EGLint height() const { return mHeight; } + + // See: https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_buffer_age.txt + // for what this means + EGLint bufferAge() const { return mBufferAge; } + +private: + friend class EglManager; + + EGLSurface mSurface; + EGLint mWidth; + EGLint mHeight; + EGLint mBufferAge; + + // Maps from 0,0 in top-left to 0,0 in bottom-left + // If out is not an EGLint[4] you're going to have a bad time + void map(const SkRect& in, EGLint* out) const; +}; // This class contains the shared global EGL objects, such as EGLDisplay // and EGLConfig, which are re-used by CanvasContext @@ -45,8 +68,13 @@ public: bool isCurrent(EGLSurface surface) { return mCurrentSurface == surface; } // Returns true if the current surface changed, false if it was already current bool makeCurrent(EGLSurface surface, EGLint* errOut = nullptr); - void beginFrame(EGLSurface surface, EGLint* width, EGLint* height); - bool swapBuffers(EGLSurface surface, const SkRect& dirty, EGLint width, EGLint height); + Frame beginFrame(EGLSurface surface); + void damageFrame(const Frame& frame, const SkRect& dirty); + // If this returns true it is mandatory that swapBuffers is called + // if damageFrame is called without subsequent calls to damageFrame(). + // See EGL_KHR_partial_update for more information + bool damageRequiresSwap(); + bool swapBuffers(const Frame& frame, const SkRect& screenDirty); // Returns true iff the surface is now preserving buffers. bool setPreserveBuffer(EGLSurface surface, bool preserve); @@ -62,10 +90,12 @@ private: // EglContext is never destroyed, method is purposely not implemented ~EglManager(); + void initExtensions(); void createPBufferSurface(); void loadConfig(); void createContext(); void initAtlas(); + EGLint queryBufferAge(EGLSurface surface); RenderThread& mRenderThread; @@ -74,14 +104,18 @@ private: EGLContext mEglContext; EGLSurface mPBufferSurface; - const bool mAllowPreserveBuffer; - bool mCanSetPreserveBuffer; - EGLSurface mCurrentSurface; sp<GraphicBuffer> mAtlasBuffer; int64_t* mAtlasMap; size_t mAtlasMapSize; + + enum class SwapBehavior { + Discard, + Preserved, + BufferAge, + }; + SwapBehavior mSwapBehavior = SwapBehavior::Discard; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 6d9acd429279..54af2829cf40 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -19,6 +19,7 @@ #include "DeferredLayerUpdater.h" #include "DisplayList.h" #include "LayerRenderer.h" +#include "Readback.h" #include "Rect.h" #include "renderthread/CanvasContext.h" #include "renderthread/RenderTask.h" @@ -52,13 +53,6 @@ namespace renderthread { MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \ ARGS(method) *args = (ARGS(method) *) task->payload() -namespace DumpFlags { - enum { - FrameStats = 1 << 0, - Reset = 1 << 1, - }; -}; - CREATE_BRIDGE4(createContext, RenderThread* thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) { return new CanvasContext(*args->thread, args->translucent, @@ -74,7 +68,7 @@ RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode, IContextF args->thread = &mRenderThread; args->contextFactory = contextFactory; mContext = (CanvasContext*) postAndWait(task); - mDrawFrameTask.setContext(&mRenderThread, mContext); + mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode); } RenderProxy::~RenderProxy() { @@ -91,7 +85,7 @@ void RenderProxy::destroyContext() { SETUP_TASK(destroyContext); args->context = mContext; mContext = nullptr; - mDrawFrameTask.setContext(nullptr, nullptr); + mDrawFrameTask.setContext(nullptr, nullptr, nullptr); // This is also a fence as we need to be certain that there are no // outstanding mDrawFrame tasks posted before it is destroyed postAndWait(task); @@ -139,40 +133,53 @@ void RenderProxy::setName(const char* name) { postAndWait(task); // block since name/value pointers owned by caller } -CREATE_BRIDGE2(initialize, CanvasContext* context, ANativeWindow* window) { - return (void*) args->context->initialize(args->window); +CREATE_BRIDGE2(initialize, CanvasContext* context, Surface* surface) { + args->context->initialize(args->surface); + return nullptr; } -bool RenderProxy::initialize(const sp<ANativeWindow>& window) { +void RenderProxy::initialize(const sp<Surface>& surface) { SETUP_TASK(initialize); args->context = mContext; - args->window = window.get(); - return (bool) postAndWait(task); + args->surface = surface.get(); + post(task); } -CREATE_BRIDGE2(updateSurface, CanvasContext* context, ANativeWindow* window) { - args->context->updateSurface(args->window); +CREATE_BRIDGE2(updateSurface, CanvasContext* context, Surface* surface) { + args->context->updateSurface(args->surface); return nullptr; } -void RenderProxy::updateSurface(const sp<ANativeWindow>& window) { +void RenderProxy::updateSurface(const sp<Surface>& surface) { SETUP_TASK(updateSurface); args->context = mContext; - args->window = window.get(); + args->surface = surface.get(); postAndWait(task); } -CREATE_BRIDGE2(pauseSurface, CanvasContext* context, ANativeWindow* window) { - return (void*) args->context->pauseSurface(args->window); +CREATE_BRIDGE2(pauseSurface, CanvasContext* context, Surface* surface) { + return (void*) args->context->pauseSurface(args->surface); } -bool RenderProxy::pauseSurface(const sp<ANativeWindow>& window) { +bool RenderProxy::pauseSurface(const sp<Surface>& surface) { SETUP_TASK(pauseSurface); args->context = mContext; - args->window = window.get(); + args->surface = surface.get(); return (bool) postAndWait(task); } +CREATE_BRIDGE2(setStopped, CanvasContext* context, bool stopped) { + args->context->setStopped(args->stopped); + return nullptr; +} + +void RenderProxy::setStopped(bool stopped) { + SETUP_TASK(setStopped); + args->context = mContext; + args->stopped = stopped; + postAndWait(task); +} + CREATE_BRIDGE6(setup, CanvasContext* context, int width, int height, float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { args->context->setup(args->width, args->height, args->lightRadius, @@ -220,18 +227,19 @@ int64_t* RenderProxy::frameInfo() { return mDrawFrameTask.frameInfo(); } -int RenderProxy::syncAndDrawFrame() { - return mDrawFrameTask.drawFrame(); +int RenderProxy::syncAndDrawFrame(TreeObserver* observer) { + return mDrawFrameTask.drawFrame(observer); } -CREATE_BRIDGE1(destroy, CanvasContext* context) { - args->context->destroy(); +CREATE_BRIDGE2(destroy, CanvasContext* context, TreeObserver* observer) { + args->context->destroy(args->observer); return nullptr; } -void RenderProxy::destroy() { +void RenderProxy::destroy(TreeObserver* observer) { SETUP_TASK(destroy); args->context = mContext; + args->observer = observer; // destroyCanvasAndSurface() needs a fence as when it returns the // underlying BufferQueue is going to be released from under // the render thread. @@ -271,30 +279,30 @@ void RenderProxy::runWithGlContext(RenderTask* gltask) { postAndWait(task); } -CREATE_BRIDGE2(createTextureLayer, RenderThread* thread, CanvasContext* context) { +CREATE_BRIDGE1(createTextureLayer, CanvasContext* context) { Layer* layer = args->context->createTextureLayer(); if (!layer) return nullptr; - return new DeferredLayerUpdater(*args->thread, layer); + return new DeferredLayerUpdater(layer); } DeferredLayerUpdater* RenderProxy::createTextureLayer() { SETUP_TASK(createTextureLayer); args->context = mContext; - args->thread = &mRenderThread; void* retval = postAndWait(task); DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(retval); return layer; } -CREATE_BRIDGE2(buildLayer, CanvasContext* context, RenderNode* node) { - args->context->buildLayer(args->node); +CREATE_BRIDGE3(buildLayer, CanvasContext* context, RenderNode* node, TreeObserver* observer) { + args->context->buildLayer(args->node, args->observer); return nullptr; } -void RenderProxy::buildLayer(RenderNode* node) { +void RenderProxy::buildLayer(RenderNode* node, TreeObserver* observer) { SETUP_TASK(buildLayer); args->context = mContext; args->node = node; + args->observer = observer; postAndWait(task); } @@ -331,15 +339,16 @@ void RenderProxy::detachSurfaceTexture(DeferredLayerUpdater* layer) { postAndWait(task); } -CREATE_BRIDGE1(destroyHardwareResources, CanvasContext* context) { - args->context->destroyHardwareResources(); +CREATE_BRIDGE2(destroyHardwareResources, CanvasContext* context, TreeObserver* observer) { + args->context->destroyHardwareResources(args->observer); return nullptr; } -void RenderProxy::destroyHardwareResources() { +void RenderProxy::destroyHardwareResources(TreeObserver* observer) { SETUP_TASK(destroyHardwareResources); args->context = mContext; - post(task); + args->observer = observer; + postAndWait(task); } CREATE_BRIDGE2(trimMemory, RenderThread* thread, int level) { @@ -384,6 +393,12 @@ void RenderProxy::fence() { postAndWait(task); } +void RenderProxy::staticFence() { + SETUP_TASK(fence); + UNUSED(args); + staticPostAndWait(task); +} + CREATE_BRIDGE1(stopDrawing, CanvasContext* context) { args->context->stopDrawing(); return nullptr; @@ -409,13 +424,15 @@ void RenderProxy::notifyFramePending() { CREATE_BRIDGE4(dumpProfileInfo, CanvasContext* context, RenderThread* thread, int fd, int dumpFlags) { args->context->profiler().dumpData(args->fd); - args->thread->jankTracker().dump(args->fd); if (args->dumpFlags & DumpFlags::FrameStats) { args->context->dumpFrames(args->fd); } if (args->dumpFlags & DumpFlags::Reset) { args->context->resetFrameStats(); } + if (args->dumpFlags & DumpFlags::JankStats) { + args->thread->jankTracker().dump(args->fd); + } return nullptr; } @@ -450,6 +467,11 @@ CREATE_BRIDGE2(dumpGraphicsMemory, int fd, RenderThread* thread) { } else { fprintf(file, "\nNo caches instance.\n"); } +#if HWUI_NEW_OPS + fprintf(file, "\nPipeline=FrameBuilder\n"); +#else + fprintf(file, "\nPipeline=OpenGLRenderer\n"); +#endif fflush(file); return nullptr; } @@ -462,7 +484,8 @@ void RenderProxy::dumpGraphicsMemory(int fd) { staticPostAndWait(task); } -CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, size_t size) { +CREATE_BRIDGE4(setTextureAtlas, RenderThread* thread, GraphicBuffer* buffer, int64_t* map, + size_t size) { CanvasContext::setTextureAtlas(*args->thread, args->buffer, args->map, args->size); args->buffer->decStrong(nullptr); return nullptr; @@ -491,6 +514,124 @@ void RenderProxy::setProcessStatsBuffer(int fd) { post(task); } +CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) { + args->context->addRenderNode(args->node, args->placeFront); + return nullptr; +} + +void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) { + SETUP_TASK(addRenderNode); + args->context = mContext; + args->node = node; + args->placeFront = placeFront; + post(task); +} + +CREATE_BRIDGE2(removeRenderNode, CanvasContext* context, RenderNode* node) { + args->context->removeRenderNode(args->node); + return nullptr; +} + +void RenderProxy::removeRenderNode(RenderNode* node) { + SETUP_TASK(removeRenderNode); + args->context = mContext; + args->node = node; + post(task); +} + +CREATE_BRIDGE2(drawRenderNode, CanvasContext* context, RenderNode* node) { + args->context->prepareAndDraw(args->node); + return nullptr; +} + +void RenderProxy::drawRenderNode(RenderNode* node) { + SETUP_TASK(drawRenderNode); + args->context = mContext; + args->node = node; + // Be pseudo-thread-safe and don't use any member variables + staticPostAndWait(task); +} + +CREATE_BRIDGE5(setContentDrawBounds, CanvasContext* context, int left, int top, + int right, int bottom) { + args->context->setContentDrawBounds(args->left, args->top, args->right, args->bottom); + return nullptr; +} + +void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) { + SETUP_TASK(setContentDrawBounds); + args->context = mContext; + args->left = left; + args->top = top; + args->right = right; + args->bottom = bottom; + staticPostAndWait(task); +} + +CREATE_BRIDGE1(serializeDisplayListTree, CanvasContext* context) { + args->context->serializeDisplayListTree(); + return nullptr; +} + +void RenderProxy::serializeDisplayListTree() { + SETUP_TASK(serializeDisplayListTree); + args->context = mContext; + post(task); +} + +CREATE_BRIDGE2(addFrameMetricsObserver, CanvasContext* context, + FrameMetricsObserver* frameStatsObserver) { + args->context->addFrameMetricsObserver(args->frameStatsObserver); + if (args->frameStatsObserver != nullptr) { + args->frameStatsObserver->decStrong(args->context); + } + return nullptr; +} + +void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observer) { + SETUP_TASK(addFrameMetricsObserver); + args->context = mContext; + args->frameStatsObserver = observer; + if (observer != nullptr) { + observer->incStrong(mContext); + } + post(task); +} + +CREATE_BRIDGE2(removeFrameMetricsObserver, CanvasContext* context, + FrameMetricsObserver* frameStatsObserver) { + args->context->removeFrameMetricsObserver(args->frameStatsObserver); + if (args->frameStatsObserver != nullptr) { + args->frameStatsObserver->decStrong(args->context); + } + return nullptr; +} + +void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observer) { + SETUP_TASK(removeFrameMetricsObserver); + args->context = mContext; + args->frameStatsObserver = observer; + if (observer != nullptr) { + observer->incStrong(mContext); + } + post(task); +} + +CREATE_BRIDGE3(copySurfaceInto, RenderThread* thread, + Surface* surface, SkBitmap* bitmap) { + return (void*) Readback::copySurfaceInto(*args->thread, + *args->surface, args->bitmap); +} + +int RenderProxy::copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap) { + SETUP_TASK(copySurfaceInto); + args->bitmap = bitmap; + args->surface = surface.get(); + args->thread = &RenderThread::getInstance(); + return static_cast<int>( + reinterpret_cast<intptr_t>( staticPostAndWait(task) )); +} + void RenderProxy::post(RenderTask* task) { mRenderThread.queue(task); } @@ -509,12 +650,7 @@ void* RenderProxy::staticPostAndWait(MethodInvokeRenderTask* task) { RenderThread& thread = RenderThread::getInstance(); void* retval; task->setReturnPtr(&retval); - Mutex mutex; - Condition condition; - SignalingRenderTask syncTask(task, &mutex, &condition); - AutoMutex _lock(mutex); - thread.queue(&syncTask); - condition.wait(mutex); + thread.queueAndWait(task); return retval; } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 5febbe0ab26c..898b31421aad 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -27,9 +27,9 @@ #include <utils/Mutex.h> #include <utils/Timers.h> #include <utils/StrongPointer.h> -#include <utils/Vector.h> #include "../Caches.h" +#include "../FrameMetricsObserver.h" #include "../IContextFactory.h" #include "CanvasContext.h" #include "DrawFrameTask.h" @@ -39,9 +39,10 @@ namespace uirenderer { class DeferredLayerUpdater; class RenderNode; -class DisplayListData; +class DisplayList; class Layer; class Rect; +class TreeObserver; namespace renderthread { @@ -49,6 +50,14 @@ class ErrorChannel; class RenderThread; class RenderProxyBridge; +namespace DumpFlags { + enum { + FrameStats = 1 << 0, + Reset = 1 << 1, + JankStats = 1 << 2, + }; +}; + /* * RenderProxy is strictly single threaded. All methods must be invoked on the owning * thread. It is important to note that RenderProxy may be deleted while it has @@ -67,33 +76,35 @@ public: ANDROID_API bool loadSystemProperties(); ANDROID_API void setName(const char* name); - ANDROID_API bool initialize(const sp<ANativeWindow>& window); - ANDROID_API void updateSurface(const sp<ANativeWindow>& window); - ANDROID_API bool pauseSurface(const sp<ANativeWindow>& window); + ANDROID_API void initialize(const sp<Surface>& surface); + ANDROID_API void updateSurface(const sp<Surface>& surface); + ANDROID_API bool pauseSurface(const sp<Surface>& surface); + ANDROID_API void setStopped(bool stopped); ANDROID_API void setup(int width, int height, float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); ANDROID_API void setLightCenter(const Vector3& lightCenter); ANDROID_API void setOpaque(bool opaque); ANDROID_API int64_t* frameInfo(); - ANDROID_API int syncAndDrawFrame(); - ANDROID_API void destroy(); + ANDROID_API int syncAndDrawFrame(TreeObserver* observer); + ANDROID_API void destroy(TreeObserver* observer); ANDROID_API static void invokeFunctor(Functor* functor, bool waitForCompletion); ANDROID_API void runWithGlContext(RenderTask* task); ANDROID_API DeferredLayerUpdater* createTextureLayer(); - ANDROID_API void buildLayer(RenderNode* node); + ANDROID_API void buildLayer(RenderNode* node, TreeObserver* observer); ANDROID_API bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap); ANDROID_API void pushLayerUpdate(DeferredLayerUpdater* layer); ANDROID_API void cancelLayerUpdate(DeferredLayerUpdater* layer); ANDROID_API void detachSurfaceTexture(DeferredLayerUpdater* layer); - ANDROID_API void destroyHardwareResources(); + ANDROID_API void destroyHardwareResources(TreeObserver* observer); ANDROID_API static void trimMemory(int level); ANDROID_API static void overrideProperty(const char* name, const char* value); ANDROID_API void fence(); + ANDROID_API static void staticFence(); ANDROID_API void stopDrawing(); ANDROID_API void notifyFramePending(); @@ -105,6 +116,19 @@ public: ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size); ANDROID_API void setProcessStatsBuffer(int fd); + ANDROID_API void serializeDisplayListTree(); + + ANDROID_API void addRenderNode(RenderNode* node, bool placeFront); + ANDROID_API void removeRenderNode(RenderNode* node); + ANDROID_API void drawRenderNode(RenderNode* node); + ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom); + + ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer); + ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer); + ANDROID_API long getDroppedFrameReportCount(); + + ANDROID_API static int copySurfaceInto(sp<Surface>& surface, SkBitmap* bitmap); + private: RenderThread& mRenderThread; CanvasContext* mContext; diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 64075f1c346a..9fb30c928c00 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -25,12 +25,11 @@ #include <gui/ISurfaceComposer.h> #include <gui/SurfaceComposerClient.h> #include <sys/resource.h> +#include <utils/Condition.h> #include <utils/Log.h> +#include <utils/Mutex.h> namespace android { -using namespace uirenderer::renderthread; -ANDROID_SINGLETON_STATIC_INSTANCE(RenderThread); - namespace uirenderer { namespace renderthread { @@ -136,7 +135,22 @@ public: } }; -RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>() +static bool gHasRenderThreadInstance = false; + +bool RenderThread::hasInstance() { + return gHasRenderThreadInstance; +} + +RenderThread& RenderThread::getInstance() { + // This is a pointer because otherwise __cxa_finalize + // will try to delete it like a Good Citizen but that causes us to crash + // because we don't want to delete the RenderThread normally. + static RenderThread* sInstance = new RenderThread(); + gHasRenderThreadInstance = true; + return *sInstance; +} + +RenderThread::RenderThread() : Thread(true) , mNextWakeup(LLONG_MAX) , mDisplayEventReceiver(nullptr) , mVsyncRequested(false) @@ -312,6 +326,19 @@ void RenderThread::queue(RenderTask* task) { } } +void RenderThread::queueAndWait(RenderTask* task) { + // These need to be local to the thread to avoid the Condition + // signaling the wrong thread. The easiest way to achieve that is to just + // make this on the stack, although that has a slight cost to it + Mutex mutex; + Condition condition; + SignalingRenderTask syncTask(task, &mutex, &condition); + + AutoMutex _lock(mutex); + queue(&syncTask); + condition.wait(mutex); +} + void RenderThread::queueAtFront(RenderTask* task) { AutoMutex _lock(mLock); mQueue.queueAtFront(task); diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index 80960999ef53..076e3d43a2c9 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -25,8 +25,6 @@ #include <cutils/compiler.h> #include <ui/DisplayInfo.h> #include <utils/Looper.h> -#include <utils/Mutex.h> -#include <utils/Singleton.h> #include <utils/Thread.h> #include <memory> @@ -39,6 +37,7 @@ class DisplayEventReceiver; namespace uirenderer { class RenderState; +class TestUtils; namespace renderthread { @@ -71,11 +70,12 @@ protected: ~IFrameCallback() {} }; -class ANDROID_API RenderThread : public Thread, protected Singleton<RenderThread> { +class ANDROID_API RenderThread : public Thread { public: // RenderThread takes complete ownership of tasks that are queued // and will delete them after they are run ANDROID_API void queue(RenderTask* task); + ANDROID_API void queueAndWait(RenderTask* task); ANDROID_API void queueAtFront(RenderTask* task); void queueAt(RenderTask* task, nsecs_t runAtNs); void remove(RenderTask* task); @@ -98,13 +98,16 @@ protected: virtual bool threadLoop() override; private: - friend class Singleton<RenderThread>; friend class DispatchFrameCallbacks; friend class RenderProxy; + friend class android::uirenderer::TestUtils; RenderThread(); virtual ~RenderThread(); + static bool hasInstance(); + static RenderThread& getInstance(); + void initThreadLocals(); void initializeDisplayEventReceiver(); static int displayEventReceiverCallback(int fd, int events, void* data); diff --git a/libs/hwui/tests/Android.mk b/libs/hwui/tests/Android.mk deleted file mode 100644 index b6f0baf4bf3e..000000000000 --- a/libs/hwui/tests/Android.mk +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright (C) 2014 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. -# - -local_target_dir := $(TARGET_OUT_DATA)/local/tmp -LOCAL_PATH:= $(call my-dir)/.. -include $(CLEAR_VARS) - -LOCAL_MODULE_PATH := $(local_target_dir) -LOCAL_MODULE:= hwuitest -LOCAL_MODULE_TAGS := tests -LOCAL_MULTILIB := both -LOCAL_MODULE_STEM_32 := hwuitest -LOCAL_MODULE_STEM_64 := hwuitest64 - -HWUI_NULL_GPU := false - -include $(LOCAL_PATH)/Android.common.mk - -LOCAL_SRC_FILES += \ - tests/TestContext.cpp \ - tests/main.cpp - -include $(BUILD_EXECUTABLE) diff --git a/libs/hwui/tests/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp index 3687a5003471..146e735839d1 100644 --- a/libs/hwui/tests/TestContext.cpp +++ b/libs/hwui/tests/common/TestContext.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "TestContext.h" +#include "tests/common/TestContext.h" namespace android { namespace uirenderer { @@ -22,16 +22,35 @@ namespace test { static const int IDENT_DISPLAYEVENT = 1; -static DisplayInfo getBuiltInDisplay() { +static android::DisplayInfo DUMMY_DISPLAY { + 1080, //w + 1920, //h + 320.0, // xdpi + 320.0, // ydpi + 60.0, // fps + 2.0, // density + 0, // orientation + false, // secure? + 0, // appVsyncOffset + 0, // presentationDeadline + 0, // colorTransform +}; + +DisplayInfo getBuiltInDisplay() { +#if !HWUI_NULL_GPU DisplayInfo display; sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay( ISurfaceComposer::eDisplayIdMain)); status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &display); LOG_ALWAYS_FATAL_IF(status, "Failed to get display info\n"); return display; +#else + return DUMMY_DISPLAY; +#endif } -android::DisplayInfo gDisplay = getBuiltInDisplay(); +// Initialize to a dummy default +android::DisplayInfo gDisplay = DUMMY_DISPLAY; TestContext::TestContext() { mLooper = new Looper(true); @@ -57,10 +76,7 @@ sp<Surface> TestContext::surface() { } void TestContext::waitForVsync() { -#if HWUI_NULL_GPU - return; -#endif - +#if !HWUI_NULL_GPU // Request vsync mDisplayEventReceiver.requestNextVsync(); @@ -70,6 +86,7 @@ void TestContext::waitForVsync() { // Drain it DisplayEventReceiver::Event buf[100]; while (mDisplayEventReceiver.getEvents(buf, 100) > 0) { } +#endif } } // namespace test diff --git a/libs/hwui/tests/TestContext.h b/libs/hwui/tests/common/TestContext.h index 7b30fc1dc7ce..2bbe5dffd9b8 100644 --- a/libs/hwui/tests/TestContext.h +++ b/libs/hwui/tests/common/TestContext.h @@ -32,6 +32,8 @@ namespace test { extern DisplayInfo gDisplay; #define dp(x) ((x) * android::uirenderer::test::gDisplay.density) +DisplayInfo getBuiltInDisplay(); + class TestContext { public: TestContext(); diff --git a/libs/hwui/tests/common/TestScene.cpp b/libs/hwui/tests/common/TestScene.cpp new file mode 100644 index 000000000000..02bcd4768a65 --- /dev/null +++ b/libs/hwui/tests/common/TestScene.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015 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 "tests/common/TestScene.h" + +namespace android { +namespace uirenderer { +namespace test { + +// Not a static global because we need to force the map to be constructed +// before we try to add things to it. +std::unordered_map<std::string, TestScene::Info>& TestScene::testMap() { + static std::unordered_map<std::string, TestScene::Info> testMap; + return testMap; +} + +void TestScene::registerScene(const TestScene::Info& info) { + testMap()[info.name] = info; +} + +} /* namespace test */ +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h new file mode 100644 index 000000000000..706f2ff75222 --- /dev/null +++ b/libs/hwui/tests/common/TestScene.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TESTS_TESTSCENE_H +#define TESTS_TESTSCENE_H + +#include <string> +#include <unordered_map> + +namespace android { +namespace uirenderer { +class RenderNode; + +#if HWUI_NEW_OPS +class RecordingCanvas; +typedef RecordingCanvas TestCanvas; +#else +class DisplayListCanvas; +typedef DisplayListCanvas TestCanvas; +#endif + +namespace test { + +class TestScene { +public: + struct Options { + int count = 0; + int reportFrametimeWeight = 0; + }; + + template <class T> + static test::TestScene* simpleCreateScene(const TestScene::Options&) { + return new T(); + } + + typedef test::TestScene* (*CreateScene)(const TestScene::Options&); + + struct Info { + std::string name; + std::string description; + CreateScene createScene; + }; + + class Registrar { + public: + Registrar(const TestScene::Info& info) { + TestScene::registerScene(info); + } + private: + Registrar() = delete; + Registrar(const Registrar&) = delete; + Registrar& operator=(const Registrar&) = delete; + }; + + virtual ~TestScene() {} + virtual void createContent(int width, int height, TestCanvas& renderer) = 0; + virtual void doFrame(int frameNr) = 0; + + static std::unordered_map<std::string, Info>& testMap(); + static void registerScene(const Info& info); +}; + +} // namespace test +} // namespace uirenderer +} // namespace android + +#endif /* TESTS_TESTSCENE_H */ diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp new file mode 100644 index 000000000000..c762eed616e4 --- /dev/null +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2015 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 "TestUtils.h" + +#include "hwui/Paint.h" +#include "DeferredLayerUpdater.h" +#include "LayerRenderer.h" + +namespace android { +namespace uirenderer { + +SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) { + int startA = (start >> 24) & 0xff; + int startR = (start >> 16) & 0xff; + int startG = (start >> 8) & 0xff; + int startB = start & 0xff; + + int endA = (end >> 24) & 0xff; + int endR = (end >> 16) & 0xff; + int endG = (end >> 8) & 0xff; + int endB = end & 0xff; + + return (int)((startA + (int)(fraction * (endA - startA))) << 24) + | (int)((startR + (int)(fraction * (endR - startR))) << 16) + | (int)((startG + (int)(fraction * (endG - startG))) << 8) + | (int)((startB + (int)(fraction * (endB - startB)))); +} + +sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater( + renderthread::RenderThread& renderThread, uint32_t width, uint32_t height, + const SkMatrix& transform) { + Layer* layer = LayerRenderer::createTextureLayer(renderThread.renderState()); + layer->getTransform().load(transform); + + sp<DeferredLayerUpdater> layerUpdater = new DeferredLayerUpdater(layer); + layerUpdater->setSize(width, height); + layerUpdater->setTransform(&transform); + + // updateLayer so it's ready to draw + bool isOpaque = true; + bool forceFilter = true; + GLenum renderTarget = GL_TEXTURE_EXTERNAL_OES; + LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, forceFilter, + renderTarget, Matrix4::identity().data); + + return layerUpdater; +} + +void TestUtils::layoutTextUnscaled(const SkPaint& paint, const char* text, + std::vector<glyph_t>* outGlyphs, std::vector<float>* outPositions, + float* outTotalAdvance, Rect* outBounds) { + Rect bounds; + float totalAdvance = 0; + SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry); + SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I()); + while (*text != '\0') { + SkUnichar unichar = SkUTF8_NextUnichar(&text); + glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar); + autoCache.getCache()->unicharToGlyph(unichar); + + // push glyph and its relative position + outGlyphs->push_back(glyph); + outPositions->push_back(totalAdvance); + outPositions->push_back(0); + + // compute bounds + SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar); + Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight); + glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop); + bounds.unionWith(glyphBounds); + + // advance next character + SkScalar skWidth; + paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL); + totalAdvance += skWidth; + } + *outBounds = bounds; + *outTotalAdvance = totalAdvance; +} + + +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, + const SkPaint& paint, float x, float y) { + auto utf16 = asciiToUtf16(text); + canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, 0, paint, nullptr); +} + +void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, + const SkPaint& paint, const SkPath& path) { + auto utf16 = asciiToUtf16(text); + canvas->drawTextOnPath(utf16.get(), strlen(text), 0, path, 0, 0, paint, nullptr); +} + +void TestUtils::TestTask::run() { + // RenderState only valid once RenderThread is running, so queried here + RenderState& renderState = renderthread::RenderThread::getInstance().renderState(); + + renderState.onGLContextCreated(); + rtCallback(renderthread::RenderThread::getInstance()); + renderState.flush(Caches::FlushMode::Full); + renderState.onGLContextDestroyed(); +} + +std::unique_ptr<uint16_t[]> TestUtils::asciiToUtf16(const char* str) { + const int length = strlen(str); + std::unique_ptr<uint16_t[]> utf16(new uint16_t[length]); + for (int i = 0; i < length; i++) { + utf16.get()[i] = str[i]; + } + return utf16; +} + +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h new file mode 100644 index 000000000000..dbaefa470b50 --- /dev/null +++ b/libs/hwui/tests/common/TestUtils.h @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TEST_UTILS_H +#define TEST_UTILS_H + +#include <DeviceInfo.h> +#include <DisplayList.h> +#include <Matrix.h> +#include <Rect.h> +#include <RenderNode.h> +#include <renderstate/RenderState.h> +#include <renderthread/RenderThread.h> +#include <Snapshot.h> + +#if HWUI_NEW_OPS +#include <RecordedOp.h> +#include <RecordingCanvas.h> +#else +#include <DisplayListOp.h> +#include <DisplayListCanvas.h> +#endif + +#include <memory> + +namespace android { +namespace uirenderer { + +#if HWUI_NEW_OPS +typedef RecordingCanvas TestCanvas; +#else +typedef DisplayListCanvas TestCanvas; +#endif + +#define EXPECT_MATRIX_APPROX_EQ(a, b) \ + EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b)) + +#define EXPECT_RECT_APPROX_EQ(a, b) \ + EXPECT_TRUE(MathUtils::areEqual(a.left, b.left) \ + && MathUtils::areEqual(a.top, b.top) \ + && MathUtils::areEqual(a.right, b.right) \ + && MathUtils::areEqual(a.bottom, b.bottom)); + +#define EXPECT_CLIP_RECT(expRect, clipStatePtr) \ + EXPECT_NE(nullptr, (clipStatePtr)) << "Op is unclipped"; \ + if ((clipStatePtr)->mode == ClipMode::Rectangle) { \ + EXPECT_EQ((expRect), reinterpret_cast<const ClipRect*>(clipStatePtr)->rect); \ + } else { \ + ADD_FAILURE() << "ClipState not a rect"; \ + } +/** + * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope + * (for e.g. accessing its RenderState) + */ +#define RENDERTHREAD_TEST(test_case_name, test_name) \ + class test_case_name##_##test_name##_RenderThreadTest { \ + public: \ + static void doTheThing(renderthread::RenderThread& renderThread); \ + }; \ + TEST(test_case_name, test_name) { \ + TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \ + }; \ + void test_case_name##_##test_name##_RenderThreadTest::doTheThing(renderthread::RenderThread& renderThread) + +class TestUtils { +public: + class SignalingDtor { + public: + SignalingDtor() + : mSignal(nullptr) {} + SignalingDtor(int* signal) + : mSignal(signal) {} + void setSignal(int* signal) { + mSignal = signal; + } + ~SignalingDtor() { + if (mSignal) { + (*mSignal)++; + } + } + private: + int* mSignal; + }; + + static bool matricesAreApproxEqual(const Matrix4& a, const Matrix4& b) { + for (int i = 0; i < 16; i++) { + if (!MathUtils::areEqual(a[i], b[i])) { + return false; + } + } + return true; + } + + static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) { + std::unique_ptr<Snapshot> snapshot(new Snapshot()); + snapshot->clip(clip, SkRegion::kReplace_Op); // store clip first, so it isn't transformed + *(snapshot->transform) = transform; + return snapshot; + } + + static SkBitmap createSkBitmap(int width, int height, + SkColorType colorType = kN32_SkColorType) { + SkBitmap bitmap; + SkImageInfo info = SkImageInfo::Make(width, height, + colorType, kPremul_SkAlphaType); + bitmap.setInfo(info); + bitmap.allocPixels(info); + return bitmap; + } + + static sp<DeferredLayerUpdater> createTextureLayerUpdater( + renderthread::RenderThread& renderThread, uint32_t width, uint32_t height, + const SkMatrix& transform); + + template<class CanvasType> + static std::unique_ptr<DisplayList> createDisplayList(int width, int height, + std::function<void(CanvasType& canvas)> canvasCallback) { + CanvasType canvas(width, height); + canvasCallback(canvas); + return std::unique_ptr<DisplayList>(canvas.finishRecording()); + } + + static sp<RenderNode> createNode(int left, int top, int right, int bottom, + std::function<void(RenderProperties& props, TestCanvas& canvas)> setup) { +#if HWUI_NULL_GPU + // if RenderNodes are being sync'd/used, device info will be needed, since + // DeviceInfo::maxTextureSize() affects layer property + DeviceInfo::initialize(); +#endif + + sp<RenderNode> node = new RenderNode(); + RenderProperties& props = node->mutateStagingProperties(); + props.setLeftTopRightBottom(left, top, right, bottom); + if (setup) { + TestCanvas canvas(props.getWidth(), props.getHeight()); + setup(props, canvas); + node->setStagingDisplayList(canvas.finishRecording(), nullptr); + } + node->setPropertyFieldsDirty(0xFFFFFFFF); + return node; + } + + static void recordNode(RenderNode& node, + std::function<void(TestCanvas&)> contentCallback) { + TestCanvas canvas(node.stagingProperties().getWidth(), + node.stagingProperties().getHeight()); + contentCallback(canvas); + node.setStagingDisplayList(canvas.finishRecording(), nullptr); + } + + /** + * Forces a sync of a tree of RenderNode, such that every descendant will have its staging + * properties and DisplayList moved to the render copies. + * + * Note: does not check dirtiness bits, so any non-staging DisplayLists will be discarded. + * For this reason, this should generally only be called once on a tree. + */ + static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) { + syncHierarchyPropertiesAndDisplayListImpl(node.get()); + } + + static sp<RenderNode>& getSyncedNode(sp<RenderNode>& node) { + syncHierarchyPropertiesAndDisplayList(node); + return node; + } + + typedef std::function<void(renderthread::RenderThread& thread)> RtCallback; + + class TestTask : public renderthread::RenderTask { + public: + TestTask(RtCallback rtCallback) + : rtCallback(rtCallback) {} + virtual ~TestTask() {} + virtual void run() override; + RtCallback rtCallback; + }; + + /** + * NOTE: requires surfaceflinger to run, otherwise this method will wait indefinitely. + */ + static void runOnRenderThread(RtCallback rtCallback) { + TestTask task(rtCallback); + renderthread::RenderThread::getInstance().queueAndWait(&task); + } + + static bool isRenderThreadRunning() { + return renderthread::RenderThread::hasInstance(); + } + + static SkColor interpolateColor(float fraction, SkColor start, SkColor end); + + static void layoutTextUnscaled(const SkPaint& paint, const char* text, + std::vector<glyph_t>* outGlyphs, std::vector<float>* outPositions, + float* outTotalAdvance, Rect* outBounds); + + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, + const SkPaint& paint, float x, float y); + + static void drawUtf8ToCanvas(Canvas* canvas, const char* text, + const SkPaint& paint, const SkPath& path); + + static std::unique_ptr<uint16_t[]> asciiToUtf16(const char* str); + +private: + static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { + node->syncProperties(); + node->syncDisplayList(nullptr); + auto displayList = node->getDisplayList(); + if (displayList) { + for (auto&& childOp : displayList->getChildren()) { + syncHierarchyPropertiesAndDisplayListImpl(childOp->renderNode); + } + } + } + +}; // class TestUtils + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* TEST_UTILS_H */ diff --git a/libs/hwui/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp new file mode 100644 index 000000000000..a5fd71266314 --- /dev/null +++ b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" + +class ClippingAnimation; + +static TestScene::Registrar _RectGrid(TestScene::Info{ + "clip", + "Complex clip cases" + "Low CPU/GPU load.", + TestScene::simpleCreateScene<ClippingAnimation> +}); + +class ClippingAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode); + card = TestUtils::createNode(0, 0, 200, 400, + [](RenderProperties& props, TestCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + { + canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op); + canvas.translate(100, 100); + canvas.rotate(45); + canvas.translate(-100, -100); + canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op); + canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode); + } + canvas.restore(); + + canvas.save(SaveFlags::MatrixClip); + { + SkPath clipCircle; + clipCircle.addCircle(100, 300, 100); + canvas.clipPath(&clipCircle, SkRegion::kIntersect_Op); + canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode); + } + canvas.restore(); + + // put on a layer, to test stencil attachment + props.mutateLayerProperties().setType(LayerType::RenderLayer); + props.setAlpha(0.9f); + }); + canvas.drawRenderNode(card.get()); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp new file mode 100644 index 000000000000..f184411b4139 --- /dev/null +++ b/libs/hwui/tests/common/scenes/GlyphStressAnimation.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" +#include "utils/Color.h" + +#include <minikin/Layout.h> +#include <hwui/Paint.h> + +#include <cstdio> + +class GlyphStressAnimation; + +static TestScene::Registrar _GlyphStress(TestScene::Info{ + "glyphstress", + "A stress test for both the glyph cache, and glyph rendering.", + TestScene::simpleCreateScene<GlyphStressAnimation> +}); + +class GlyphStressAnimation : public TestScene { +public: + sp<RenderNode> container; + void createContent(int width, int height, TestCanvas& canvas) override { + container = TestUtils::createNode(0, 0, width, height, nullptr); + doFrame(0); // update container + + canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode); + canvas.drawRenderNode(container.get()); + } + + void doFrame(int frameNr) override { + std::unique_ptr<uint16_t[]> text = TestUtils::asciiToUtf16( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + ssize_t textLength = 26 * 2; + + TestCanvas canvas( + container->stagingProperties().getWidth(), + container->stagingProperties().getHeight()); + Paint paint; + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setAntiAlias(true); + paint.setColor(Color::Black); + for (int i = 0; i < 5; i++) { + paint.setTextSize(10 + (frameNr % 20) + i * 20); + canvas.drawText(text.get(), 0, textLength, textLength, + 0, 100 * (i + 2), kBidi_Force_LTR, paint, nullptr); + } + + container->setStagingDisplayList(canvas.finishRecording(), nullptr); + } +}; diff --git a/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp new file mode 100644 index 000000000000..c212df4f978d --- /dev/null +++ b/libs/hwui/tests/common/scenes/HwLayerAnimation.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" + +class HwLayerAnimation; + +static TestScene::Registrar _HwLayer(TestScene::Info{ + "hwlayer", + "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. " + "Tests the hardware layer codepath.", + TestScene::simpleCreateScene<HwLayerAnimation> +}); + +class HwLayerAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + card = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, TestCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); + canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); + }); + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background + canvas.drawRenderNode(card.get()); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp new file mode 100644 index 000000000000..8035dc45f23c --- /dev/null +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" +#include "utils/Color.h" + +#include <cstdio> + +class ListViewAnimation; + +static TestScene::Registrar _ListView(TestScene::Info{ + "listview", + "A mock ListView of scrolling content. Doesn't re-bind/re-record views as they are recycled, so" + "won't upload much content (either glyphs, or bitmaps).", + TestScene::simpleCreateScene<ListViewAnimation> +}); + +class ListViewAnimation : public TestScene { +public: + int cardHeight; + int cardSpacing; + int cardWidth; + int cardLeft; + sp<RenderNode> listView; + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, TestCanvas& canvas) override { + srand(0); + cardHeight = dp(60); + cardSpacing = dp(16); + cardWidth = std::min((height - cardSpacing * 2), (int)dp(300)); + cardLeft = (width - cardWidth) / 2; + + for (int y = 0; y < height + (cardHeight + cardSpacing - 1); y += (cardHeight + cardSpacing)) { + cards.push_back(createCard(cards.size(), y)); + } + listView = TestUtils::createNode(0, 0, width, height, + [this](RenderProperties& props, TestCanvas& canvas) { + for (size_t ci = 0; ci < cards.size(); ci++) { + canvas.drawRenderNode(cards[ci].get()); + } + }); + + canvas.drawColor(Color::Grey_500, SkXfermode::kSrcOver_Mode); + canvas.drawRenderNode(listView.get()); + } + + void doFrame(int frameNr) override { + int scrollPx = dp(frameNr) * 3; + int cardIndexOffset = scrollPx / (cardSpacing + cardHeight); + int pxOffset = -(scrollPx % (cardSpacing + cardHeight)); + + TestCanvas canvas( + listView->stagingProperties().getWidth(), + listView->stagingProperties().getHeight()); + for (size_t ci = 0; ci < cards.size(); ci++) { + // update card position + auto card = cards[(ci + cardIndexOffset) % cards.size()]; + int top = ((int)ci) * (cardSpacing + cardHeight) + pxOffset; + card->mutateStagingProperties().setLeftTopRightBottom( + cardLeft, top, cardLeft + cardWidth, top + cardHeight); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + // draw it to parent DisplayList + canvas.drawRenderNode(cards[ci].get()); + } + listView->setStagingDisplayList(canvas.finishRecording(), nullptr); + } +private: + SkBitmap createRandomCharIcon() { + int size = cardHeight - (dp(10) * 2); + SkBitmap bitmap = TestUtils::createSkBitmap(size, size); + SkCanvas canvas(bitmap); + canvas.clear(0); + + SkPaint paint; + paint.setAntiAlias(true); + SkColor randomColor = BrightColors[rand() % BrightColorsCount]; + paint.setColor(randomColor); + canvas.drawCircle(size / 2, size / 2, size / 2, paint); + + bool bgDark = SkColorGetR(randomColor) + SkColorGetG(randomColor) + SkColorGetB(randomColor) + < 128 * 3; + paint.setColor(bgDark ? Color::White : Color::Grey_700); + paint.setTextAlign(SkPaint::kCenter_Align); + paint.setTextSize(size / 2); + char charToShow = 'A' + (rand() % 26); + canvas.drawText(&charToShow, 1, size / 2, /*approximate centering*/ size * 0.7, paint); + return bitmap; + } + + static SkBitmap createBoxBitmap(bool filled) { + int size = dp(20); + int stroke = dp(2); + SkBitmap bitmap = TestUtils::createSkBitmap(size, size); + SkCanvas canvas(bitmap); + canvas.clear(Color::Transparent); + + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(filled ? Color::Yellow_500 : Color::Grey_700); + paint.setStyle(filled ? SkPaint::kStrokeAndFill_Style : SkPaint::kStroke_Style); + paint.setStrokeWidth(stroke); + canvas.drawRect(SkRect::MakeLTRB(stroke, stroke, size - stroke, size - stroke), paint); + return bitmap; + } + + sp<RenderNode> createCard(int cardId, int top) { + return TestUtils::createNode(cardLeft, top, cardLeft + cardWidth, top + cardHeight, + [this, cardId](RenderProperties& props, TestCanvas& canvas) { + static SkBitmap filledBox = createBoxBitmap(true); + static SkBitmap strokedBox = createBoxBitmap(false); + + // TODO: switch to using round rect clipping, once merging correctly handles that + SkPaint roundRectPaint; + roundRectPaint.setAntiAlias(true); + roundRectPaint.setColor(Color::White); + canvas.drawRoundRect(0, 0, cardWidth, cardHeight, dp(6), dp(6), roundRectPaint); + + SkPaint textPaint; + textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + textPaint.setColor(rand() % 2 ? Color::Black : Color::Grey_500); + textPaint.setTextSize(dp(20)); + textPaint.setAntiAlias(true); + char buf[256]; + snprintf(buf, sizeof(buf), "This card is #%d", cardId); + TestUtils::drawUtf8ToCanvas(&canvas, buf, textPaint, cardHeight, dp(25)); + textPaint.setTextSize(dp(15)); + TestUtils::drawUtf8ToCanvas(&canvas, "This is some more text on the card", textPaint, + cardHeight, dp(45)); + + canvas.drawBitmap(createRandomCharIcon(), dp(10), dp(10), nullptr); + + const SkBitmap& boxBitmap = rand() % 2 ? filledBox : strokedBox; + canvas.drawBitmap(boxBitmap, cardWidth - dp(10) - boxBitmap.width(), dp(10), nullptr); + }); + } +}; diff --git a/libs/hwui/tests/common/scenes/OpPropAnimation.cpp b/libs/hwui/tests/common/scenes/OpPropAnimation.cpp new file mode 100644 index 000000000000..5dfb2b4a8b33 --- /dev/null +++ b/libs/hwui/tests/common/scenes/OpPropAnimation.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" +#include "utils/Color.h" + +class OpPropAnimation; + +static TestScene::Registrar _Shapes(TestScene::Info{ + "opprops", + "A minimal demonstration of CanvasProperty drawing operations.", + TestScene::simpleCreateScene<OpPropAnimation> +}); + +class OpPropAnimation : public TestScene { +public: + sp<CanvasPropertyPaint> mPaint = new CanvasPropertyPaint(SkPaint()); + + sp<CanvasPropertyPrimitive> mRoundRectLeft = new CanvasPropertyPrimitive(0); + sp<CanvasPropertyPrimitive> mRoundRectTop = new CanvasPropertyPrimitive(0); + sp<CanvasPropertyPrimitive> mRoundRectRight = new CanvasPropertyPrimitive(0); + sp<CanvasPropertyPrimitive> mRoundRectBottom = new CanvasPropertyPrimitive(0); + sp<CanvasPropertyPrimitive> mRoundRectRx = new CanvasPropertyPrimitive(0); + sp<CanvasPropertyPrimitive> mRoundRectRy = new CanvasPropertyPrimitive(0); + + sp<CanvasPropertyPrimitive> mCircleX = new CanvasPropertyPrimitive(0); + sp<CanvasPropertyPrimitive> mCircleY = new CanvasPropertyPrimitive(0); + sp<CanvasPropertyPrimitive> mCircleRadius = new CanvasPropertyPrimitive(0); + + sp<RenderNode> content; + void createContent(int width, int height, TestCanvas& canvas) override { + content = TestUtils::createNode(0, 0, width, height, + [this, width, height](RenderProperties& props, TestCanvas& canvas) { + mPaint->value.setAntiAlias(true); + mPaint->value.setColor(Color::Blue_500); + + mRoundRectRight->value = width / 2; + mRoundRectBottom->value = height / 2; + + mCircleX->value = width * 0.75; + mCircleY->value = height * 0.75; + + canvas.drawColor(Color::White, SkXfermode::Mode::kSrcOver_Mode); + canvas.drawRoundRect(mRoundRectLeft.get(), mRoundRectTop.get(), + mRoundRectRight.get(), mRoundRectBottom.get(), + mRoundRectRx.get(), mRoundRectRy.get(), mPaint.get()); + canvas.drawCircle(mCircleX.get(), mCircleY.get(), mCircleRadius.get(), mPaint.get()); + }); + canvas.drawRenderNode(content.get()); + } + + void doFrame(int frameNr) override { + float value = (abs((frameNr % 200) - 100)) / 100.0f; + mRoundRectRx->value = dp(10) + value * dp(40); + mRoundRectRy->value = dp(10) + value * dp(80); + mCircleRadius->value = value * dp(200); + content->setPropertyFieldsDirty(RenderNode::GENERIC); + } +}; diff --git a/libs/hwui/tests/common/scenes/OvalAnimation.cpp b/libs/hwui/tests/common/scenes/OvalAnimation.cpp new file mode 100644 index 000000000000..e56f2f97007e --- /dev/null +++ b/libs/hwui/tests/common/scenes/OvalAnimation.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" +#include "utils/Color.h" + +class OvalAnimation; + +static TestScene::Registrar _Oval(TestScene::Info{ + "oval", + "Draws 1 oval.", + TestScene::simpleCreateScene<OvalAnimation> +}); + +class OvalAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode); + card = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, TestCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(Color::Black); + canvas.drawOval(0, 0, 200, 200, paint); + }); + canvas.drawRenderNode(card.get()); + } + + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp new file mode 100644 index 000000000000..84265a4774a5 --- /dev/null +++ b/libs/hwui/tests/common/scenes/PartialDamageAnimation.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" + +class PartialDamageAnimation; + +static TestScene::Registrar _PartialDamage(TestScene::Info{ + "partialdamage", + "Tests the partial invalidation path. Draws a grid of rects and animates 1 " + "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or " + "EGL_KHR_partial_update is supported by the device & are enabled in hwui.", + TestScene::simpleCreateScene<PartialDamageAnimation> +}); + +class PartialDamageAnimation : public TestScene { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, TestCanvas& canvas) override { + static SkColor COLORS[] = { + 0xFFF44336, + 0xFF9C27B0, + 0xFF2196F3, + 0xFF4CAF50, + }; + + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + + for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { + for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { + SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4]; + sp<RenderNode> card = TestUtils::createNode(x, y, + x + dp(100), y + dp(100), + [color](RenderProperties& props, TestCanvas& canvas) { + canvas.drawColor(color, SkXfermode::kSrcOver_Mode); + }); + canvas.drawRenderNode(card.get()); + cards.push_back(card); + } + } + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + cards[0]->mutateStagingProperties().setTranslationX(curFrame); + cards[0]->mutateStagingProperties().setTranslationY(curFrame); + cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + + TestUtils::recordNode(*cards[0], [curFrame](TestCanvas& canvas) { + SkColor color = TestUtils::interpolateColor( + curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0); + canvas.drawColor(color, SkXfermode::kSrcOver_Mode); + }); + } +}; diff --git a/libs/hwui/tests/common/scenes/RecentsAnimation.cpp b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp new file mode 100644 index 000000000000..6509edd4077f --- /dev/null +++ b/libs/hwui/tests/common/scenes/RecentsAnimation.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" +#include "utils/Color.h" + +class RecentsAnimation; + +static TestScene::Registrar _Recents(TestScene::Info{ + "recents", + "A recents-like scrolling list of textures. " + "Consists of updating a texture every frame", + TestScene::simpleCreateScene<RecentsAnimation> +}); + +class RecentsAnimation : public TestScene { +public: + void createContent(int width, int height, TestCanvas& renderer) override { + static SkColor COLORS[] = { + Color::Red_500, + Color::Purple_500, + Color::Blue_500, + Color::Green_500, + }; + + thumbnailSize = std::min(std::min(width, height) / 2, 720); + int cardsize = std::min(width, height) - dp(64); + + renderer.drawColor(Color::White, SkXfermode::kSrcOver_Mode); + renderer.insertReorderBarrier(true); + + int x = dp(32); + for (int i = 0; i < 4; i++) { + int y = (height / 4) * i; + SkBitmap thumb = TestUtils::createSkBitmap(thumbnailSize, thumbnailSize); + thumb.eraseColor(COLORS[i]); + sp<RenderNode> card = createCard(x, y, cardsize, cardsize, thumb); + card->mutateStagingProperties().setElevation(i * dp(8)); + renderer.drawRenderNode(card.get()); + mThumbnail = thumb; + mCards.push_back(card); + } + + renderer.insertReorderBarrier(false); + } + + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < mCards.size(); ci++) { + mCards[ci]->mutateStagingProperties().setTranslationY(curFrame); + mCards[ci]->setPropertyFieldsDirty(RenderNode::Y); + } + mThumbnail.eraseColor(TestUtils::interpolateColor( + curFrame / 150.0f, Color::Green_500, Color::DeepOrange_500)); + } + +private: + sp<RenderNode> createCard(int x, int y, int width, int height, + const SkBitmap& thumb) { + return TestUtils::createNode(x, y, x + width, y + height, + [&thumb, width, height](RenderProperties& props, TestCanvas& canvas) { + props.setElevation(dp(16)); + props.mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1); + props.mutableOutline().setShouldClip(true); + + canvas.drawColor(Color::Grey_200, SkXfermode::kSrcOver_Mode); + canvas.drawBitmap(thumb, 0, 0, thumb.width(), thumb.height(), + 0, 0, width, height, nullptr); + }); + } + + SkBitmap mThumbnail; + std::vector< sp<RenderNode> > mCards; + int thumbnailSize; +}; diff --git a/libs/hwui/tests/common/scenes/RectGridAnimation.cpp b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp new file mode 100644 index 000000000000..a9293ab244dd --- /dev/null +++ b/libs/hwui/tests/common/scenes/RectGridAnimation.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" + +class RectGridAnimation; + +static TestScene::Registrar _RectGrid(TestScene::Info{ + "rectgrid", + "A dense grid of 1x1 rects that should visually look like a single rect. " + "Low CPU/GPU load.", + TestScene::simpleCreateScene<RectGridAnimation> +}); + +class RectGridAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas.insertReorderBarrier(true); + + card = TestUtils::createNode(50, 50, 250, 250, + [](RenderProperties& props, TestCanvas& canvas) { + canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); + + SkRegion region; + for (int xOffset = 0; xOffset < 200; xOffset+=2) { + for (int yOffset = 0; yOffset < 200; yOffset+=2) { + region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op); + } + } + + SkPaint paint; + paint.setColor(0xff00ffff); + canvas.drawRegion(region, paint); + }); + canvas.drawRenderNode(card.get()); + + canvas.insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp new file mode 100644 index 000000000000..6904bec304e3 --- /dev/null +++ b/libs/hwui/tests/common/scenes/SaveLayerAnimation.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" + +class SaveLayerAnimation; + +static TestScene::Registrar _SaveLayer(TestScene::Info{ + "savelayer", + "A nested pair of clipped saveLayer operations. " + "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.", + TestScene::simpleCreateScene<SaveLayerAnimation> +}); + +class SaveLayerAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode); // background + + card = TestUtils::createNode(0, 0, 400, 800, + [](RenderProperties& props, TestCanvas& canvas) { + // nested clipped saveLayers + canvas.saveLayerAlpha(0, 0, 400, 400, 200, SaveFlags::ClipToLayer); + canvas.drawColor(Color::Green_700, SkXfermode::kSrcOver_Mode); + canvas.clipRect(50, 50, 350, 350, SkRegion::kIntersect_Op); + canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer); + canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode); + canvas.restore(); + canvas.restore(); + + // single unclipped saveLayer + canvas.save(SaveFlags::MatrixClip); + canvas.translate(0, 400); + canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::Flags(0)); // unclipped + SkPaint paint; + paint.setAntiAlias(true); + paint.setColor(Color::Green_700); + canvas.drawCircle(200, 200, 200, paint); + canvas.restore(); + canvas.restore(); + }); + + canvas.drawRenderNode(card.get()); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp new file mode 100644 index 000000000000..d3249b8f585a --- /dev/null +++ b/libs/hwui/tests/common/scenes/ShadowGrid2Animation.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" + +class ShadowGrid2Animation; + +static TestScene::Registrar _ShadowGrid2(TestScene::Info{ + "shadowgrid2", + "A dense grid of rounded rects that cast a shadow. This is a higher CPU load " + "variant of shadowgrid. Very high CPU load, high GPU load.", + TestScene::simpleCreateScene<ShadowGrid2Animation> +}); + +class ShadowGrid2Animation : public TestScene { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas.insertReorderBarrier(true); + + for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { + for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { + sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); + canvas.drawRenderNode(card.get()); + cards.push_back(card); + } + } + + canvas.insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < cards.size(); ci++) { + cards[ci]->mutateStagingProperties().setTranslationX(curFrame); + cards[ci]->mutateStagingProperties().setTranslationY(curFrame); + cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + return TestUtils::createNode(x, y, x + width, y + height, + [width, height](RenderProperties& props, TestCanvas& canvas) { + props.setElevation(dp(16)); + props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); + props.mutableOutline().setShouldClip(true); + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + }); + } +}; diff --git a/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp new file mode 100644 index 000000000000..5ffedf09bc70 --- /dev/null +++ b/libs/hwui/tests/common/scenes/ShadowGridAnimation.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" + +class ShadowGridAnimation; + +static TestScene::Registrar _ShadowGrid(TestScene::Info{ + "shadowgrid", + "A grid of rounded rects that cast a shadow. Simplified scenario of an " + "Android TV-style launcher interface. High CPU/GPU load.", + TestScene::simpleCreateScene<ShadowGridAnimation> +}); + +class ShadowGridAnimation : public TestScene { +public: + std::vector< sp<RenderNode> > cards; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); + canvas.insertReorderBarrier(true); + + for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { + for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { + sp<RenderNode> card = createCard(x, y, dp(100), dp(100)); + canvas.drawRenderNode(card.get()); + cards.push_back(card); + } + } + + canvas.insertReorderBarrier(false); + } + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + for (size_t ci = 0; ci < cards.size(); ci++) { + cards[ci]->mutateStagingProperties().setTranslationX(curFrame); + cards[ci]->mutateStagingProperties().setTranslationY(curFrame); + cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } + } +private: + sp<RenderNode> createCard(int x, int y, int width, int height) { + return TestUtils::createNode(x, y, x + width, y + height, + [width, height](RenderProperties& props, TestCanvas& canvas) { + props.setElevation(dp(16)); + props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); + props.mutableOutline().setShouldClip(true); + canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); + }); + } +}; diff --git a/libs/hwui/tests/common/scenes/ShapeAnimation.cpp b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp new file mode 100644 index 000000000000..6d27c9d61a29 --- /dev/null +++ b/libs/hwui/tests/common/scenes/ShapeAnimation.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" +#include "utils/Color.h" + +#include <cstdio> + +class ShapeAnimation; + +static TestScene::Registrar _Shapes(TestScene::Info{ + "shapes", + "A grid of shape drawing test cases.", + TestScene::simpleCreateScene<ShapeAnimation> +}); + +class ShapeAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + card = TestUtils::createNode(0, 0, width, height, + [width](RenderProperties& props, TestCanvas& canvas) { + std::function<void(TestCanvas&, float, const SkPaint&)> ops[] = { + [](TestCanvas& canvas, float size, const SkPaint& paint) { + canvas.drawArc(0, 0, size, size, 50, 189, true, paint); + }, + [](TestCanvas& canvas, float size, const SkPaint& paint) { + canvas.drawOval(0, 0, size, size, paint); + }, + [](TestCanvas& canvas, float size, const SkPaint& paint) { + SkPath diamondPath; + diamondPath.moveTo(size / 2, 0); + diamondPath.lineTo(size, size / 2); + diamondPath.lineTo(size / 2, size); + diamondPath.lineTo(0, size / 2); + diamondPath.close(); + canvas.drawPath(diamondPath, paint); + }, + [](TestCanvas& canvas, float size, const SkPaint& paint) { + float data[] = {0, 0, size, size, 0, size, size, 0 }; + canvas.drawLines(data, sizeof(data) / sizeof(float), paint); + }, + [](TestCanvas& canvas, float size, const SkPaint& paint) { + float data[] = {0, 0, size, size, 0, size, size, 0 }; + canvas.drawPoints(data, sizeof(data) / sizeof(float), paint); + }, + [](TestCanvas& canvas, float size, const SkPaint& paint) { + canvas.drawRect(0, 0, size, size, paint); + }, + [](TestCanvas& canvas, float size, const SkPaint& paint) { + float rad = size / 4; + canvas.drawRoundRect(0, 0, size, size, rad, rad, paint); + } + }; + float cellSpace = dp(4); + float cellSize = floorf(width / 7 - cellSpace); + + // each combination of strokeWidth + style gets a column + int outerCount = canvas.save(SaveFlags::MatrixClip); + SkPaint paint; + paint.setAntiAlias(true); + SkPaint::Style styles[] = { + SkPaint::kStroke_Style, SkPaint::kFill_Style, SkPaint::kStrokeAndFill_Style }; + for (auto style : styles) { + paint.setStyle(style); + for (auto strokeWidth : { 0.0f, 0.5f, 8.0f }) { + paint.setStrokeWidth(strokeWidth); + // fill column with each op + int middleCount = canvas.save(SaveFlags::MatrixClip); + for (auto op : ops) { + int innerCount = canvas.save(SaveFlags::MatrixClip); + canvas.clipRect(0, 0, cellSize, cellSize, SkRegion::kIntersect_Op); + canvas.drawColor(Color::White, SkXfermode::Mode::kSrcOver_Mode); + op(canvas, cellSize, paint); + canvas.restoreToCount(innerCount); + canvas.translate(cellSize + cellSpace, 0); + } + canvas.restoreToCount(middleCount); + canvas.translate(0, cellSize + cellSpace); + } + } + canvas.restoreToCount(outerCount); + }); + canvas.drawColor(Color::Grey_500, SkXfermode::Mode::kSrcOver_Mode); + canvas.drawRenderNode(card.get()); + } + + void doFrame(int frameNr) override { + card->mutateStagingProperties().setTranslationY(frameNr % 150); + card->setPropertyFieldsDirty(RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/common/scenes/TestSceneBase.h b/libs/hwui/tests/common/scenes/TestSceneBase.h new file mode 100644 index 000000000000..935ddcf9212d --- /dev/null +++ b/libs/hwui/tests/common/scenes/TestSceneBase.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TESTS_SCENES_TESTSCENEBASE_H +#define TESTS_SCENES_TESTSCENEBASE_H + +#include "DisplayListCanvas.h" +#include "RecordingCanvas.h" +#include "RenderNode.h" +#include "tests/common/TestContext.h" +#include "tests/common/TestScene.h" +#include "tests/common/TestUtils.h" +#include "utils/Color.h" + +#include <functional> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::test; + +#endif /* TESTS_SCENES_TESTSCENEBASE_H_ */ diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp new file mode 100644 index 000000000000..be8f48b9fd17 --- /dev/null +++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 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 "TestSceneBase.h" +#include "utils/Color.h" + +class TextAnimation; + +static TestScene::Registrar _Text(TestScene::Info{ + "text", + "Draws a bunch of text.", + TestScene::simpleCreateScene<TextAnimation> +}); + +class TextAnimation : public TestScene { +public: + sp<RenderNode> card; + void createContent(int width, int height, TestCanvas& canvas) override { + canvas.drawColor(Color::White, SkXfermode::kSrcOver_Mode); + card = TestUtils::createNode(0, 0, width, height, + [](RenderProperties& props, TestCanvas& canvas) { + SkPaint paint; + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setAntiAlias(true); + paint.setTextSize(50); + + paint.setColor(Color::Black); + for (int i = 0; i < 10; i++) { + TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 400, i * 100); + } + + SkPath path; + path.addOval(SkRect::MakeLTRB(100, 100, 300, 300)); + + paint.setColor(Color::Blue_500); + TestUtils::drawUtf8ToCanvas(&canvas, "This is a neat circle of text!", paint, path); + }); + canvas.drawRenderNode(card.get()); + } + + void doFrame(int frameNr) override { + int curFrame = frameNr % 150; + card->mutateStagingProperties().setTranslationX(curFrame); + card->mutateStagingProperties().setTranslationY(curFrame); + card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); + } +}; diff --git a/libs/hwui/tests/how_to_run.txt b/libs/hwui/tests/how_to_run.txt deleted file mode 100644 index 686cd7878c50..000000000000 --- a/libs/hwui/tests/how_to_run.txt +++ /dev/null @@ -1,17 +0,0 @@ -mmm -j8 frameworks/base/libs/hwui/tests/ && - adb push $OUT/data/local/tmp/hwuitest /data/local/tmp/hwuitest && - adb shell /data/local/tmp/hwuitest - - -Command arguments: -hwuitest [testname] - -Default test is 'shadowgrid' - -List of tests: - -shadowgrid: creates a grid of rounded rects that cast shadows, high CPU & GPU load - -rectgrid: creates a grid of 1x1 rects - -oval: draws 1 oval diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp new file mode 100644 index 000000000000..c5af06160b62 --- /dev/null +++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2015 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 "AnimationContext.h" +#include "RenderNode.h" +#include "tests/common/TestContext.h" +#include "tests/common/TestScene.h" +#include "tests/common/scenes/TestSceneBase.h" +#include "renderthread/RenderProxy.h" +#include "renderthread/RenderTask.h" + +#include <cutils/log.h> +#include <gui/Surface.h> +#include <ui/PixelFormat.h> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::test; + +class ContextFactory : public IContextFactory { +public: + virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { + return new AnimationContext(clock); + } +}; + +template<class T> +class ModifiedMovingAverage { +public: + ModifiedMovingAverage(int weight) : mWeight(weight) {} + + T add(T today) { + if (!mHasValue) { + mAverage = today; + } else { + mAverage = (((mWeight - 1) * mAverage) + today) / mWeight; + } + return mAverage; + } + + T average() { + return mAverage; + } + +private: + bool mHasValue = false; + int mWeight; + T mAverage; +}; + +void run(const TestScene::Info& info, const TestScene::Options& opts) { + // Switch to the real display + gDisplay = getBuiltInDisplay(); + + std::unique_ptr<TestScene> scene(info.createScene(opts)); + + TestContext testContext; + + // create the native surface + const int width = gDisplay.w; + const int height = gDisplay.h; + sp<Surface> surface = testContext.surface(); + + sp<RenderNode> rootNode = TestUtils::createNode(0, 0, width, height, + [&scene, width, height](RenderProperties& props, TestCanvas& canvas) { + props.setClipToBounds(false); + scene->createContent(width, height, canvas); + }); + + ContextFactory factory; + std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, + rootNode.get(), &factory)); + proxy->loadSystemProperties(); + proxy->initialize(surface); + float lightX = width / 2.0; + proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15); + proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); + + // Do a few cold runs then reset the stats so that the caches are all hot + for (int i = 0; i < 5; i++) { + testContext.waitForVsync(); + nsecs_t vsync = systemTime(CLOCK_MONOTONIC); + UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync); + proxy->syncAndDrawFrame(nullptr); + } + + proxy->resetProfileInfo(); + proxy->fence(); + + ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight); + + for (int i = 0; i < opts.count; i++) { + testContext.waitForVsync(); + nsecs_t vsync = systemTime(CLOCK_MONOTONIC); + { + ATRACE_NAME("UI-Draw Frame"); + UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync); + scene->doFrame(i); + proxy->syncAndDrawFrame(nullptr); + } + if (opts.reportFrametimeWeight) { + proxy->fence(); + nsecs_t done = systemTime(CLOCK_MONOTONIC); + avgMs.add((done - vsync) / 1000000.0); + if (i % 10 == 9) { + printf("Average frametime %.3fms\n", avgMs.average()); + } + } + } + + proxy->dumpProfileInfo(STDOUT_FILENO, DumpFlags::JankStats); +} diff --git a/libs/hwui/tests/macrobench/how_to_run.txt b/libs/hwui/tests/macrobench/how_to_run.txt new file mode 100644 index 000000000000..b051768f3262 --- /dev/null +++ b/libs/hwui/tests/macrobench/how_to_run.txt @@ -0,0 +1,5 @@ +mmm -j8 frameworks/base/libs/hwui/ && + adb push $OUT/data/local/tmp/hwuitest /data/local/tmp/hwuitest && + adb shell /data/local/tmp/hwuitest + +Pass --help to get help diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp new file mode 100644 index 000000000000..02a39501e647 --- /dev/null +++ b/libs/hwui/tests/macrobench/main.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2014 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 "tests/common/TestScene.h" + +#include "protos/hwui.pb.h" +#include "Properties.h" + +#include <getopt.h> +#include <stdio.h> +#include <string> +#include <unistd.h> +#include <unordered_map> +#include <vector> +#include <pthread.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::test; + +static int gRepeatCount = 1; +static std::vector<TestScene::Info> gRunTests; +static TestScene::Options gOpts; + +void run(const TestScene::Info& info, const TestScene::Options& opts); + +static void printHelp() { + printf(R"( +USAGE: hwuitest [OPTIONS] <TESTNAME> + +OPTIONS: + -c, --count=NUM NUM loops a test should run (example, number of frames) + -r, --runs=NUM Repeat the test(s) NUM times + -h, --help Display this help + --list List all tests + --wait-for-gpu Set this to wait for the GPU before producing the + next frame. Note that without locked clocks this will + pathologically bad performance due to large idle time + --report-frametime[=weight] If set, the test will print to stdout the + moving average frametime. Weight is optional, default is 10 + --cpuset=name Adds the test to the specified cpuset before running + Not supported on all devices and needs root +)"); +} + +static void listTests() { + printf("Tests: \n"); + for (auto&& test : TestScene::testMap()) { + auto&& info = test.second; + const char* col1 = info.name.c_str(); + int dlen = info.description.length(); + const char* col2 = info.description.c_str(); + // World's best line breaking algorithm. + do { + int toPrint = dlen; + if (toPrint > 50) { + char* found = (char*) memrchr(col2, ' ', 50); + if (found) { + toPrint = found - col2; + } else { + toPrint = 50; + } + } + printf("%-20s %.*s\n", col1, toPrint, col2); + col1 = ""; + col2 += toPrint; + dlen -= toPrint; + while (*col2 == ' ') { + col2++; dlen--; + } + } while (dlen > 0); + printf("\n"); + } +} + +static void moveToCpuSet(const char* cpusetName) { + if (access("/dev/cpuset/tasks", F_OK)) { + fprintf(stderr, "don't have access to cpusets, skipping...\n"); + return; + } + static const int BUF_SIZE = 100; + char buffer[BUF_SIZE]; + + if (snprintf(buffer, BUF_SIZE, "/dev/cpuset/%s/tasks", cpusetName) >= BUF_SIZE) { + fprintf(stderr, "Error, cpusetName too large to fit in buffer '%s'\n", cpusetName); + return; + } + int fd = open(buffer, O_WRONLY | O_CLOEXEC); + if (fd == -1) { + fprintf(stderr, "Error opening file %d\n", errno); + return; + } + pid_t pid = getpid(); + + int towrite = snprintf(buffer, BUF_SIZE, "%ld", (long) pid); + if (towrite >= BUF_SIZE) { + fprintf(stderr, "Buffer wasn't large enough?\n"); + } else { + if (write(fd, buffer, towrite) != towrite) { + fprintf(stderr, "Failed to write, errno=%d", errno); + } + } + close(fd); +} + +// For options that only exist in long-form. Anything in the +// 0-255 range is reserved for short options (which just use their ASCII value) +namespace LongOpts { +enum { + Reserved = 255, + List, + WaitForGpu, + ReportFrametime, + CpuSet, +}; +} + +static const struct option LONG_OPTIONS[] = { + { "frames", required_argument, nullptr, 'f' }, + { "repeat", required_argument, nullptr, 'r' }, + { "help", no_argument, nullptr, 'h' }, + { "list", no_argument, nullptr, LongOpts::List }, + { "wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu }, + { "report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime }, + { "cpuset", required_argument, nullptr, LongOpts::CpuSet }, + { 0, 0, 0, 0 } +}; + +static const char* SHORT_OPTIONS = "c:r:h"; + +void parseOptions(int argc, char* argv[]) { + int c; + bool error = false; + opterr = 0; + + while (true) { + + /* getopt_long stores the option index here. */ + int option_index = 0; + + c = getopt_long(argc, argv, SHORT_OPTIONS, LONG_OPTIONS, &option_index); + + if (c == -1) + break; + + switch (c) { + case 0: + // Option set a flag, don't need to do anything + // (although none of the current LONG_OPTIONS do this...) + break; + + case LongOpts::List: + listTests(); + exit(EXIT_SUCCESS); + break; + + case 'c': + gOpts.count = atoi(optarg); + if (!gOpts.count) { + fprintf(stderr, "Invalid frames argument '%s'\n", optarg); + error = true; + } + break; + + case 'r': + gRepeatCount = atoi(optarg); + if (!gRepeatCount) { + fprintf(stderr, "Invalid repeat argument '%s'\n", optarg); + error = true; + } else { + gRepeatCount = (gRepeatCount > 0 ? gRepeatCount : INT_MAX); + } + break; + + case LongOpts::ReportFrametime: + if (optarg) { + gOpts.reportFrametimeWeight = atoi(optarg); + if (!gOpts.reportFrametimeWeight) { + fprintf(stderr, "Invalid report frametime weight '%s'\n", optarg); + error = true; + } + } else { + gOpts.reportFrametimeWeight = 10; + } + break; + + case LongOpts::WaitForGpu: + Properties::waitForGpuCompletion = true; + break; + + case LongOpts::CpuSet: + if (!optarg) { + error = true; + break; + } + moveToCpuSet(optarg); + break; + + case 'h': + printHelp(); + exit(EXIT_SUCCESS); + break; + + case '?': + fprintf(stderr, "Unrecognized option '%s'\n", argv[optind - 1]); + // fall-through + default: + error = true; + break; + } + } + + if (error) { + fprintf(stderr, "Try 'hwuitest --help' for more information.\n"); + exit(EXIT_FAILURE); + } + + /* Print any remaining command line arguments (not options). */ + if (optind < argc) { + do { + const char* test = argv[optind++]; + auto pos = TestScene::testMap().find(test); + if (pos == TestScene::testMap().end()) { + fprintf(stderr, "Unknown test '%s'\n", test); + exit(EXIT_FAILURE); + } else { + gRunTests.push_back(pos->second); + } + } while (optind < argc); + } else { + gRunTests.push_back(TestScene::testMap()["shadowgrid"]); + } +} + +int main(int argc, char* argv[]) { + // set defaults + gOpts.count = 150; + + parseOptions(argc, argv); + + for (int i = 0; i < gRepeatCount; i++) { + for (auto&& test : gRunTests) { + run(test, gOpts); + } + } + printf("Success!\n"); + return 0; +} diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp deleted file mode 100644 index 80d7029857c4..000000000000 --- a/libs/hwui/tests/main.cpp +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2014 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 <cutils/log.h> -#include <gui/Surface.h> -#include <ui/PixelFormat.h> - -#include <AnimationContext.h> -#include <DisplayListCanvas.h> -#include <RenderNode.h> -#include <renderthread/RenderProxy.h> -#include <renderthread/RenderTask.h> - -#include "TestContext.h" - -#include <stdio.h> -#include <unistd.h> - -using namespace android; -using namespace android::uirenderer; -using namespace android::uirenderer::renderthread; -using namespace android::uirenderer::test; - -class ContextFactory : public IContextFactory { -public: - virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override { - return new AnimationContext(clock); - } -}; - -static DisplayListCanvas* startRecording(RenderNode* node) { - DisplayListCanvas* renderer = new DisplayListCanvas(); - renderer->setViewport(node->stagingProperties().getWidth(), - node->stagingProperties().getHeight()); - renderer->prepare(); - return renderer; -} - -static void endRecording(DisplayListCanvas* renderer, RenderNode* node) { - renderer->finish(); - node->setStagingDisplayList(renderer->finishRecording()); - delete renderer; -} - -class TreeContentAnimation { -public: - virtual ~TreeContentAnimation() {} - int frameCount = 150; - virtual int getFrameCount() { return frameCount; } - virtual void setFrameCount(int fc) { - if (fc > 0) { - frameCount = fc; - } - } - virtual void createContent(int width, int height, DisplayListCanvas* renderer) = 0; - virtual void doFrame(int frameNr) = 0; - - template <class T> - static void run(int frameCount) { - T animation; - animation.setFrameCount(frameCount); - - TestContext testContext; - - // create the native surface - const int width = gDisplay.w; - const int height = gDisplay.h; - sp<Surface> surface = testContext.surface(); - - RenderNode* rootNode = new RenderNode(); - rootNode->incStrong(nullptr); - rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height); - rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - rootNode->mutateStagingProperties().setClipToBounds(false); - rootNode->setPropertyFieldsDirty(RenderNode::GENERIC); - - ContextFactory factory; - std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory)); - proxy->loadSystemProperties(); - proxy->initialize(surface); - float lightX = width / 2.0; - proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15); - proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); - - android::uirenderer::Rect DUMMY; - - DisplayListCanvas* renderer = startRecording(rootNode); - animation.createContent(width, height, renderer); - endRecording(renderer, rootNode); - - // Do a few cold runs then reset the stats so that the caches are all hot - for (int i = 0; i < 3; i++) { - testContext.waitForVsync(); - proxy->syncAndDrawFrame(); - } - proxy->resetProfileInfo(); - - for (int i = 0; i < animation.getFrameCount(); i++) { - testContext.waitForVsync(); - - ATRACE_NAME("UI-Draw Frame"); - nsecs_t vsync = systemTime(CLOCK_MONOTONIC); - UiFrameInfoBuilder(proxy->frameInfo()) - .setVsync(vsync, vsync); - animation.doFrame(i); - proxy->syncAndDrawFrame(); - } - - proxy->dumpProfileInfo(STDOUT_FILENO, 0); - rootNode->decStrong(nullptr); - } -}; - -class ShadowGridAnimation : public TreeContentAnimation { -public: - std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - for (int x = dp(16); x < (width - dp(116)); x += dp(116)) { - for (int y = dp(16); y < (height - dp(116)); y += dp(116)) { - sp<RenderNode> card = createCard(x, y, dp(100), dp(100)); - renderer->drawRenderNode(card.get()); - cards.push_back(card); - } - } - - renderer->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - for (size_t ci = 0; ci < cards.size(); ci++) { - cards[ci]->mutateStagingProperties().setTranslationX(curFrame); - cards[ci]->mutateStagingProperties().setTranslationY(curFrame); - cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->mutateStagingProperties().setElevation(dp(16)); - node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1); - node->mutateStagingProperties().mutableOutline().setShouldClip(true); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - - DisplayListCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); - return node; - } -}; - -class ShadowGrid2Animation : public TreeContentAnimation { -public: - std::vector< sp<RenderNode> > cards; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - for (int x = dp(8); x < (width - dp(58)); x += dp(58)) { - for (int y = dp(8); y < (height - dp(58)); y += dp(58)) { - sp<RenderNode> card = createCard(x, y, dp(50), dp(50)); - renderer->drawRenderNode(card.get()); - cards.push_back(card); - } - } - - renderer->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - for (size_t ci = 0; ci < cards.size(); ci++) { - cards[ci]->mutateStagingProperties().setTranslationX(curFrame); - cards[ci]->mutateStagingProperties().setTranslationY(curFrame); - cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->mutateStagingProperties().setElevation(dp(16)); - node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1); - node->mutateStagingProperties().mutableOutline().setShouldClip(true); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z); - - DisplayListCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode); - endRecording(renderer, node.get()); - return node; - } -}; - -class RectGridAnimation : public TreeContentAnimation { -public: - sp<RenderNode> card; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - card = createCard(40, 40, 200, 200); - renderer->drawRenderNode(card.get()); - - renderer->insertReorderBarrier(false); - } - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - card->mutateStagingProperties().setTranslationX(curFrame); - card->mutateStagingProperties().setTranslationY(curFrame); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - DisplayListCanvas* renderer = startRecording(node.get()); - renderer->drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode); - - float rects[width * height]; - int index = 0; - for (int xOffset = 0; xOffset < width; xOffset+=2) { - for (int yOffset = 0; yOffset < height; yOffset+=2) { - rects[index++] = xOffset; - rects[index++] = yOffset; - rects[index++] = xOffset + 1; - rects[index++] = yOffset + 1; - } - } - int count = width * height; - - SkPaint paint; - paint.setColor(0xff00ffff); - renderer->drawRects(rects, count, &paint); - - endRecording(renderer, node.get()); - return node; - } -}; - -class OvalAnimation : public TreeContentAnimation { -public: - sp<RenderNode> card; - void createContent(int width, int height, DisplayListCanvas* renderer) override { - renderer->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); - renderer->insertReorderBarrier(true); - - card = createCard(40, 40, 400, 400); - renderer->drawRenderNode(card.get()); - - renderer->insertReorderBarrier(false); - } - - void doFrame(int frameNr) override { - int curFrame = frameNr % 150; - card->mutateStagingProperties().setTranslationX(curFrame); - card->mutateStagingProperties().setTranslationY(curFrame); - card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - } -private: - sp<RenderNode> createCard(int x, int y, int width, int height) { - sp<RenderNode> node = new RenderNode(); - node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height); - node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y); - - DisplayListCanvas* renderer = startRecording(node.get()); - - SkPaint paint; - paint.setAntiAlias(true); - paint.setColor(0xFF000000); - renderer->drawOval(0, 0, width, height, paint); - - endRecording(renderer, node.get()); - return node; - } -}; - -struct cstr_cmp { - bool operator()(const char *a, const char *b) const { - return std::strcmp(a, b) < 0; - } -}; - -typedef void (*testProc)(int); - -std::map<const char*, testProc, cstr_cmp> gTestMap { - {"shadowgrid", TreeContentAnimation::run<ShadowGridAnimation>}, - {"shadowgrid2", TreeContentAnimation::run<ShadowGrid2Animation>}, - {"rectgrid", TreeContentAnimation::run<RectGridAnimation> }, - {"oval", TreeContentAnimation::run<OvalAnimation> }, -}; - -int main(int argc, char* argv[]) { - const char* testName = argc > 1 ? argv[1] : "shadowgrid"; - testProc proc = gTestMap[testName]; - if(!proc) { - printf("Error: couldn't find test %s\n", testName); - return 1; - } - int loopCount = 1; - if (argc > 2) { - loopCount = atoi(argv[2]); - if (!loopCount) { - printf("Invalid loop count!\n"); - return 1; - } - } - int frameCount = 150; - if (argc > 3) { - frameCount = atoi(argv[3]); - if (frameCount < 1) { - printf("Invalid frame count!\n"); - return 1; - } - } - if (loopCount < 0) { - loopCount = INT_MAX; - } - for (int i = 0; i < loopCount; i++) { - proc(frameCount); - } - printf("Success!\n"); - return 0; -} diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp new file mode 100644 index 000000000000..06b68d1dea8f --- /dev/null +++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2015 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 <benchmark/benchmark.h> + +#include "DisplayList.h" +#if HWUI_NEW_OPS +#include "RecordingCanvas.h" +#else +#include "DisplayListCanvas.h" +#endif +#include "tests/common/TestUtils.h" + +using namespace android; +using namespace android::uirenderer; + +#if HWUI_NEW_OPS +typedef RecordingCanvas TestCanvas; +#else +typedef DisplayListCanvas TestCanvas; +#endif + +void BM_DisplayList_alloc(benchmark::State& benchState) { + while (benchState.KeepRunning()) { + auto displayList = new DisplayList(); + benchmark::DoNotOptimize(displayList); + delete displayList; + } +} +BENCHMARK(BM_DisplayList_alloc); + +void BM_DisplayList_alloc_theoretical(benchmark::State& benchState) { + while (benchState.KeepRunning()) { + auto displayList = new char[sizeof(DisplayList)]; + benchmark::DoNotOptimize(displayList); + delete[] displayList; + } +} +BENCHMARK(BM_DisplayList_alloc_theoretical); + +void BM_DisplayListCanvas_record_empty(benchmark::State& benchState) { + TestCanvas canvas(100, 100); + delete canvas.finishRecording(); + + while (benchState.KeepRunning()) { + canvas.resetRecording(100, 100); + benchmark::DoNotOptimize(&canvas); + delete canvas.finishRecording(); + } +} +BENCHMARK(BM_DisplayListCanvas_record_empty); + +void BM_DisplayListCanvas_record_saverestore(benchmark::State& benchState) { + TestCanvas canvas(100, 100); + delete canvas.finishRecording(); + + while (benchState.KeepRunning()) { + canvas.resetRecording(100, 100); + canvas.save(SaveFlags::MatrixClip); + canvas.save(SaveFlags::MatrixClip); + benchmark::DoNotOptimize(&canvas); + canvas.restore(); + canvas.restore(); + delete canvas.finishRecording(); + } +} +BENCHMARK(BM_DisplayListCanvas_record_saverestore); + +void BM_DisplayListCanvas_record_translate(benchmark::State& benchState) { + TestCanvas canvas(100, 100); + delete canvas.finishRecording(); + + while (benchState.KeepRunning()) { + canvas.resetRecording(100, 100); + canvas.scale(10, 10); + benchmark::DoNotOptimize(&canvas); + delete canvas.finishRecording(); + } +} +BENCHMARK(BM_DisplayListCanvas_record_translate); + +/** + * Simulate a simple view drawing a background, overlapped by an image. + * + * Note that the recording commands are intentionally not perfectly efficient, as the + * View system frequently produces unneeded save/restores. + */ +void BM_DisplayListCanvas_record_simpleBitmapView(benchmark::State& benchState) { + TestCanvas canvas(100, 100); + delete canvas.finishRecording(); + + SkPaint rectPaint; + SkBitmap iconBitmap = TestUtils::createSkBitmap(80, 80); + + while (benchState.KeepRunning()) { + canvas.resetRecording(100, 100); + { + canvas.save(SaveFlags::MatrixClip); + canvas.drawRect(0, 0, 100, 100, rectPaint); + canvas.restore(); + } + { + canvas.save(SaveFlags::MatrixClip); + canvas.translate(10, 10); + canvas.drawBitmap(iconBitmap, 0, 0, nullptr); + canvas.restore(); + } + benchmark::DoNotOptimize(&canvas); + delete canvas.finishRecording(); + } +} +BENCHMARK(BM_DisplayListCanvas_record_simpleBitmapView); + +class NullClient: public CanvasStateClient { + void onViewportInitialized() override {} + void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + GLuint getTargetFbo() const override { return 0; } +}; + +void BM_CanvasState_saverestore(benchmark::State& benchState) { + NullClient client; + CanvasState state(client); + state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); + + while (benchState.KeepRunning()) { + state.save(SaveFlags::MatrixClip); + state.save(SaveFlags::MatrixClip); + benchmark::DoNotOptimize(&state); + state.restore(); + state.restore(); + } +} +BENCHMARK(BM_CanvasState_saverestore); + +void BM_CanvasState_init(benchmark::State& benchState) { + NullClient client; + CanvasState state(client); + state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); + + while (benchState.KeepRunning()) { + state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); + benchmark::DoNotOptimize(&state); + } +} +BENCHMARK(BM_CanvasState_init); + +void BM_CanvasState_translate(benchmark::State& benchState) { + NullClient client; + CanvasState state(client); + state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); + + while (benchState.KeepRunning()) { + state.translate(5, 5, 0); + benchmark::DoNotOptimize(&state); + state.translate(-5, -5, 0); + } +} +BENCHMARK(BM_CanvasState_translate); diff --git a/libs/hwui/tests/microbench/FontBench.cpp b/libs/hwui/tests/microbench/FontBench.cpp new file mode 100644 index 000000000000..df3d041e722d --- /dev/null +++ b/libs/hwui/tests/microbench/FontBench.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 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 <benchmark/benchmark.h> + +#include "GammaFontRenderer.h" +#include "tests/common/TestUtils.h" + +#include <SkPaint.h> + +using namespace android; +using namespace android::uirenderer; + +void BM_FontRenderer_precache_cachehits(benchmark::State& state) { + TestUtils::runOnRenderThread([&state](renderthread::RenderThread& thread) { + SkPaint paint; + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + GammaFontRenderer gammaFontRenderer; + FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); + fontRenderer.setFont(&paint, SkMatrix::I()); + + std::vector<glyph_t> glyphs; + std::vector<float> positions; + float totalAdvance; + uirenderer::Rect bounds; + TestUtils::layoutTextUnscaled(paint, "This is a test", + &glyphs, &positions, &totalAdvance, &bounds); + + fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I()); + + while (state.KeepRunning()) { + fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I()); + } + }); +} +BENCHMARK(BM_FontRenderer_precache_cachehits); diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp new file mode 100644 index 000000000000..84ef9c2575f5 --- /dev/null +++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2016 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 <benchmark/benchmark.h> + +#include "BakedOpState.h" +#include "BakedOpDispatcher.h" +#include "BakedOpRenderer.h" +#include "FrameBuilder.h" +#include "LayerUpdateQueue.h" +#include "RecordedOp.h" +#include "RecordingCanvas.h" +#include "tests/common/TestContext.h" +#include "tests/common/TestScene.h" +#include "tests/common/TestUtils.h" +#include "Vector.h" + +#include <vector> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::test; + +const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50}; +const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 }; + +static sp<RenderNode> createTestNode() { + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkBitmap bitmap = TestUtils::createSkBitmap(10, 10); + SkPaint paint; + + // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects. + // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group. + canvas.save(SaveFlags::MatrixClip); + for (int i = 0; i < 30; i++) { + canvas.translate(0, 10); + canvas.drawRect(0, 0, 10, 10, paint); + canvas.drawBitmap(bitmap, 5, 0, nullptr); + } + canvas.restore(); + }); + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + return node; +} + +void BM_FrameBuilder_defer(benchmark::State& state) { + TestUtils::runOnRenderThread([&state](RenderThread& thread) { + auto node = createTestNode(); + while (state.KeepRunning()) { + FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*node); + benchmark::DoNotOptimize(&frameBuilder); + } + }); +} +BENCHMARK(BM_FrameBuilder_defer); + +void BM_FrameBuilder_deferAndRender(benchmark::State& state) { + TestUtils::runOnRenderThread([&state](RenderThread& thread) { + auto node = createTestNode(); + + RenderState& renderState = thread.renderState(); + Caches& caches = Caches::getInstance(); + + while (state.KeepRunning()) { + FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, + sLightGeometry, caches); + frameBuilder.deferRenderNode(*node); + + BakedOpRenderer renderer(caches, renderState, true, sLightInfo); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); + benchmark::DoNotOptimize(&renderer); + } + }); +} +BENCHMARK(BM_FrameBuilder_deferAndRender); + +static sp<RenderNode> getSyncedSceneNode(const char* sceneName) { + gDisplay = getBuiltInDisplay(); // switch to real display if present + + TestContext testContext; + TestScene::Options opts; + std::unique_ptr<TestScene> scene(TestScene::testMap()[sceneName].createScene(opts)); + + sp<RenderNode> rootNode = TestUtils::createNode(0, 0, gDisplay.w, gDisplay.h, + [&scene](RenderProperties& props, TestCanvas& canvas) { + scene->createContent(gDisplay.w, gDisplay.h, canvas); + }); + + TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode); + return rootNode; +} + +static auto SCENES = { + "listview", +}; + +void BM_FrameBuilder_defer_scene(benchmark::State& state) { + TestUtils::runOnRenderThread([&state](RenderThread& thread) { + const char* sceneName = *(SCENES.begin() + state.range_x()); + state.SetLabel(sceneName); + auto node = getSyncedSceneNode(sceneName); + while (state.KeepRunning()) { + FrameBuilder frameBuilder(SkRect::MakeWH(gDisplay.w, gDisplay.h), + gDisplay.w, gDisplay.h, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*node); + benchmark::DoNotOptimize(&frameBuilder); + } + }); +} +BENCHMARK(BM_FrameBuilder_defer_scene)->DenseRange(0, SCENES.size() - 1); + +void BM_FrameBuilder_deferAndRender_scene(benchmark::State& state) { + TestUtils::runOnRenderThread([&state](RenderThread& thread) { + const char* sceneName = *(SCENES.begin() + state.range_x()); + state.SetLabel(sceneName); + auto node = getSyncedSceneNode(sceneName); + + RenderState& renderState = thread.renderState(); + Caches& caches = Caches::getInstance(); + + while (state.KeepRunning()) { + FrameBuilder frameBuilder(SkRect::MakeWH(gDisplay.w, gDisplay.h), + gDisplay.w, gDisplay.h, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*node); + + BakedOpRenderer renderer(caches, renderState, true, sLightInfo); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); + benchmark::DoNotOptimize(&renderer); + } + }); +} +BENCHMARK(BM_FrameBuilder_deferAndRender_scene)->DenseRange(0, SCENES.size() - 1); diff --git a/libs/hwui/tests/microbench/LinearAllocatorBench.cpp b/libs/hwui/tests/microbench/LinearAllocatorBench.cpp new file mode 100644 index 000000000000..3c0a6c5ae8ac --- /dev/null +++ b/libs/hwui/tests/microbench/LinearAllocatorBench.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 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 <benchmark/benchmark.h> + +#include "utils/LinearAllocator.h" + +#include <vector> + +using namespace android; +using namespace android::uirenderer; + +static void BM_LinearStdAllocator_vectorBaseline(benchmark::State& state) { + while (state.KeepRunning()) { + std::vector<char> v; + for (int j = 0; j < 200; j++) { + v.push_back(j); + } + benchmark::DoNotOptimize(&v); + } +} +BENCHMARK(BM_LinearStdAllocator_vectorBaseline); + +static void BM_LinearStdAllocator_vector(benchmark::State& state) { + while (state.KeepRunning()) { + LinearAllocator la; + LinearStdAllocator<void*> stdAllocator(la); + std::vector<char, LinearStdAllocator<char> > v(stdAllocator); + for (int j = 0; j < 200; j++) { + v.push_back(j); + } + benchmark::DoNotOptimize(&v); + } +} +BENCHMARK(BM_LinearStdAllocator_vector); diff --git a/libs/hwui/tests/microbench/PathParserBench.cpp b/libs/hwui/tests/microbench/PathParserBench.cpp new file mode 100644 index 000000000000..b43c4c3b63c1 --- /dev/null +++ b/libs/hwui/tests/microbench/PathParserBench.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 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 <benchmark/benchmark.h> + +#include "PathParser.h" +#include "VectorDrawable.h" + +#include <SkPath.h> + +using namespace android; +using namespace android::uirenderer; + +static const char* sPathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10"; + +void BM_PathParser_parseStringPathForSkPath(benchmark::State& state) { + SkPath skPath; + size_t length = strlen(sPathString); + PathParser::ParseResult result; + while (state.KeepRunning()) { + PathParser::parseAsciiStringForSkPath(&skPath, &result, sPathString, length); + benchmark::DoNotOptimize(&result); + benchmark::DoNotOptimize(&skPath); + } +} +BENCHMARK(BM_PathParser_parseStringPathForSkPath); + +void BM_PathParser_parseStringPathForPathData(benchmark::State& state) { + size_t length = strlen(sPathString); + PathData outData; + PathParser::ParseResult result; + while (state.KeepRunning()) { + PathParser::getPathDataFromAsciiString(&outData, &result, sPathString, length); + benchmark::DoNotOptimize(&result); + benchmark::DoNotOptimize(&outData); + } +} +BENCHMARK(BM_PathParser_parseStringPathForPathData); diff --git a/libs/hwui/tests/microbench/ShadowBench.cpp b/libs/hwui/tests/microbench/ShadowBench.cpp new file mode 100644 index 000000000000..a0fc6e8f9f53 --- /dev/null +++ b/libs/hwui/tests/microbench/ShadowBench.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2015 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 <benchmark/benchmark.h> + +#include "Matrix.h" +#include "Rect.h" +#include "Vector.h" +#include "VertexBuffer.h" +#include "TessellationCache.h" + +#include <SkPath.h> + +#include <memory> + +using namespace android; +using namespace android::uirenderer; + +struct ShadowTestData { + Matrix4 drawTransform; + Rect localClip; + Matrix4 casterTransformXY; + Matrix4 casterTransformZ; + Vector3 lightCenter; + float lightRadius; +}; + +void createShadowTestData(ShadowTestData* out) { + static float SAMPLE_DRAW_TRANSFORM[] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + }; + static float SAMPLE_CASTERXY[] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 32, 32, 0, 1, + }; + static float SAMPLE_CASTERZ[] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 32, 32, 32, 1, + }; + static Rect SAMPLE_CLIP(0, 0, 1536, 2048); + static Vector3 SAMPLE_LIGHT_CENTER{768, -400, 1600}; + static float SAMPLE_LIGHT_RADIUS = 1600; + + out->drawTransform.load(SAMPLE_DRAW_TRANSFORM); + out->localClip = SAMPLE_CLIP; + out->casterTransformXY.load(SAMPLE_CASTERXY); + out->casterTransformZ.load(SAMPLE_CASTERZ); + out->lightCenter = SAMPLE_LIGHT_CENTER; + out->lightRadius = SAMPLE_LIGHT_RADIUS; +} + +static inline void tessellateShadows(ShadowTestData& testData, bool opaque, + const SkPath& shape, VertexBuffer* ambient, VertexBuffer* spot) { + tessellateShadows(&testData.drawTransform, &testData.localClip, + opaque, &shape, &testData.casterTransformXY, + &testData.casterTransformZ, testData.lightCenter, + testData.lightRadius, *ambient, *spot); +} + +void BM_TessellateShadows_roundrect_opaque(benchmark::State& state) { + ShadowTestData shadowData; + createShadowTestData(&shadowData); + SkPath path; + path.addRoundRect(SkRect::MakeWH(100, 100), 5, 5); + + while (state.KeepRunning()) { + VertexBuffer ambient; + VertexBuffer spot; + tessellateShadows(shadowData, true, path, &ambient, &spot); + benchmark::DoNotOptimize(&ambient); + benchmark::DoNotOptimize(&spot); + } +} +BENCHMARK(BM_TessellateShadows_roundrect_opaque); + +void BM_TessellateShadows_roundrect_translucent(benchmark::State& state) { + ShadowTestData shadowData; + createShadowTestData(&shadowData); + SkPath path; + path.reset(); + path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5); + + while (state.KeepRunning()) { + std::unique_ptr<VertexBuffer> ambient(new VertexBuffer); + std::unique_ptr<VertexBuffer> spot(new VertexBuffer); + tessellateShadows(shadowData, false, path, ambient.get(), spot.get()); + benchmark::DoNotOptimize(ambient.get()); + benchmark::DoNotOptimize(spot.get()); + } +} +BENCHMARK(BM_TessellateShadows_roundrect_translucent); diff --git a/libs/hwui/tests/microbench/TaskManagerBench.cpp b/libs/hwui/tests/microbench/TaskManagerBench.cpp new file mode 100644 index 000000000000..c6b9f3bca55f --- /dev/null +++ b/libs/hwui/tests/microbench/TaskManagerBench.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 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 <benchmark/benchmark.h> + +#include "thread/Task.h" +#include "thread/TaskManager.h" +#include "thread/TaskProcessor.h" + +#include <vector> + +using namespace android; +using namespace android::uirenderer; + +class TrivialTask : public Task<char> {}; + +class TrivialProcessor : public TaskProcessor<char> { +public: + TrivialProcessor(TaskManager* manager) + : TaskProcessor(manager) {} + virtual ~TrivialProcessor() {} + virtual void onProcess(const sp<Task<char> >& task) override { + TrivialTask* t = static_cast<TrivialTask*>(task.get()); + t->setResult(reinterpret_cast<intptr_t>(t) % 16 == 0 ? 'a' : 'b'); + } +}; + +void BM_TaskManager_allocateTask(benchmark::State& state) { + std::vector<sp<TrivialTask> > tasks; + tasks.reserve(state.max_iterations); + + while (state.KeepRunning()) { + tasks.emplace_back(new TrivialTask); + benchmark::DoNotOptimize(tasks.back()); + } +} +BENCHMARK(BM_TaskManager_allocateTask); + +void BM_TaskManager_enqueueTask(benchmark::State& state) { + TaskManager taskManager; + sp<TrivialProcessor> processor(new TrivialProcessor(&taskManager)); + std::vector<sp<TrivialTask> > tasks; + tasks.reserve(state.max_iterations); + + while (state.KeepRunning()) { + tasks.emplace_back(new TrivialTask); + benchmark::DoNotOptimize(tasks.back()); + processor->add(tasks.back()); + } + + for (sp<TrivialTask>& task : tasks) { + task->getResult(); + } +} +BENCHMARK(BM_TaskManager_enqueueTask); + +void BM_TaskManager_enqueueRunDeleteTask(benchmark::State& state) { + TaskManager taskManager; + sp<TrivialProcessor> processor(new TrivialProcessor(&taskManager)); + std::vector<sp<TrivialTask> > tasks; + tasks.reserve(state.max_iterations); + + while (state.KeepRunning()) { + tasks.emplace_back(new TrivialTask); + benchmark::DoNotOptimize(tasks.back()); + processor->add(tasks.back()); + } + state.ResumeTiming(); + for (sp<TrivialTask>& task : tasks) { + benchmark::DoNotOptimize(task->getResult()); + } + tasks.clear(); + state.PauseTiming(); +} +BENCHMARK(BM_TaskManager_enqueueRunDeleteTask); diff --git a/libs/hwui/tests/microbench/how_to_run.txt b/libs/hwui/tests/microbench/how_to_run.txt new file mode 100755 index 000000000000..e6f80b278276 --- /dev/null +++ b/libs/hwui/tests/microbench/how_to_run.txt @@ -0,0 +1,4 @@ +mmm -j8 frameworks/base/libs/hwui && +adb push $ANDROID_PRODUCT_OUT/data/local/tmp/hwuimicro \ + /data/local/tmp/hwuimicro && + adb shell /data/local/tmp/hwuimicro diff --git a/libs/hwui/unit_tests/main.cpp b/libs/hwui/tests/microbench/main.cpp index c9b96360b36b..a0157bc4f9ef 100644 --- a/libs/hwui/unit_tests/main.cpp +++ b/libs/hwui/tests/microbench/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 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,9 +14,6 @@ * limitations under the License. */ -#include <gtest/gtest.h> +#include <benchmark/benchmark.h> -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} +BENCHMARK_MAIN(); diff --git a/libs/hwui/tests/scripts/prep_volantis.sh b/libs/hwui/tests/scripts/prep_volantis.sh new file mode 100755 index 000000000000..0572ee55c9b9 --- /dev/null +++ b/libs/hwui/tests/scripts/prep_volantis.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Copyright (C) 2015 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. + +adb root +adb wait-for-device +adb shell stop mpdecision +adb shell stop perfd +adb shell stop +for pid in $( adb shell ps | awk '{ if ( $9 == "surfaceflinger" ) { print $2 } }' ); do + adb shell kill $pid +done +adb shell setprop debug.egl.traceGpuCompletion 1 +adb shell daemonize surfaceflinger +sleep 3 +adb shell setprop service.bootanim.exit 1 + +# cpu possible frequencies +# 204000 229500 255000 280500 306000 331500 357000 382500 408000 433500 459000 +# 484500 510000 535500 561000 586500 612000 637500 663000 688500 714000 739500 +# 765000 790500 816000 841500 867000 892500 918000 943500 969000 994500 1020000 +# 1122000 1224000 1326000 1428000 1530000 1632000 1734000 1836000 1938000 +# 2014500 2091000 2193000 2295000 2397000 2499000 + +S=1326000 +echo "set cpu $cpu to $S hz"; +adb shell "echo userspace > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor" +adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" +adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq" +adb shell "echo $S > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed" + +#disable hotplug +adb shell "echo 0 > /sys/devices/system/cpu/cpuquiet/tegra_cpuquiet/enable" + +# gbus possible rates +# 72000 108000 180000 252000 324000 396000 468000 540000 612000 648000 +# 684000 708000 756000 804000 852000 (kHz) + +S=324000000 +echo "set gpu to $S hz" +adb shell "echo 1 > /d/clock/override.gbus/state" +adb shell "echo $S > /d/clock/override.gbus/rate" diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp new file mode 100644 index 000000000000..de57cd1e9650 --- /dev/null +++ b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include <RecordedOp.h> +#include <BakedOpDispatcher.h> +#include <BakedOpRenderer.h> +#include <tests/common/TestUtils.h> + +#include <SkDashPathEffect.h> + +using namespace android::uirenderer; + +static BakedOpRenderer::LightInfo sLightInfo; +static Rect sBaseClip(100, 100); + +class ValidatingBakedOpRenderer : public BakedOpRenderer { +public: + ValidatingBakedOpRenderer(RenderState& renderState, std::function<void(const Glop& glop)> validator) + : BakedOpRenderer(Caches::getInstance(), renderState, true, sLightInfo) + , mValidator(validator) { + mGlopReceiver = ValidatingGlopReceiver; + } +private: + static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds, + const ClipBase* clip, const Glop& glop) { + + auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer); + vbor->mValidator(glop); + } + std::function<void(const Glop& glop)> mValidator; +}; + +typedef void (*BakedOpReceiver)(BakedOpRenderer&, const BakedOpState&); + +static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op, + std::function<void(const Glop& glop)> glopVerifier) { + // Create op, and wrap with basic state. + LinearAllocator allocator; + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), sBaseClip); + auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op); + ASSERT_NE(nullptr, state); + + int glopCount = 0; + auto glopReceiver = [&glopVerifier, &glopCount] (const Glop& glop) { + ASSERT_EQ(glopCount++, 0) << "Only one Glop expected"; + glopVerifier(glop); + }; + ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver); + + // Dispatch based on op type created, similar to Frame/LayerBuilder dispatch behavior +#define X(Type) \ + [](BakedOpRenderer& renderer, const BakedOpState& state) { \ + BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \ + }, + static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X); +#undef X + unmergedReceivers[op->opId](renderer, *state); + ASSERT_EQ(1, glopCount) << "Exactly one Glop expected"; +} + +RENDERTHREAD_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) { + SkPaint strokePaint; + strokePaint.setStyle(SkPaint::kStroke_Style); + strokePaint.setStrokeWidth(4); + + float intervals[] = {1.0f, 1.0f}; + auto dashEffect = SkDashPathEffect::Create(intervals, 2, 0); + strokePaint.setPathEffect(dashEffect); + dashEffect->unref(); + + auto textureGlopVerifier = [] (const Glop& glop) { + // validate glop produced by renderPathTexture (so texture, unit quad) + auto texture = glop.fill.texture.texture; + ASSERT_NE(nullptr, texture); + float expectedOffset = floor(4 * 1.5f + 0.5f); + EXPECT_EQ(expectedOffset, reinterpret_cast<PathTexture*>(texture)->offset) + << "Should see conservative offset from PathCache::computeBounds"; + Rect expectedBounds(10, 15, 20, 25); + expectedBounds.outset(expectedOffset); + + Matrix4 expectedModelView; + expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0); + expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1); + EXPECT_EQ(expectedModelView, glop.transform.modelView) + << "X and Y offsets, and scale both applied to model view"; + }; + + // Arc and Oval will render functionally the same glop, differing only in texture content + ArcOp arcOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint, 0, 270, true); + testUnmergedGlopDispatch(renderThread, &arcOp, textureGlopVerifier); + + OvalOp ovalOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint); + testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier); +} + +RENDERTHREAD_TEST(BakedOpDispatcher, onLayerOp_bufferless) { + SkPaint layerPaint; + layerPaint.setAlpha(128); + OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case + LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer); + testUnmergedGlopDispatch(renderThread, &op, [&renderThread] (const Glop& glop) { + // rect glop is dispatched with paint props applied + EXPECT_EQ(renderThread.renderState().meshState().getUnitQuadVBO(), + glop.mesh.vertices.bufferObject) << "Unit quad should be drawn"; + EXPECT_EQ(nullptr, glop.fill.texture.texture) << "Should be no texture when layer is null"; + EXPECT_FLOAT_EQ(128 / 255.0f, glop.fill.color.a) << "Rect quad should use op alpha"; + }); +} + +static int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) { + int result = 0; + testUnmergedGlopDispatch(renderThread, op, [&result] (const Glop& glop) { + result = glop.transform.transformFlags; + }); + return result; +} + +RENDERTHREAD_TEST(BakedOpDispatcher, offsetFlags) { + Rect bounds(10, 15, 20, 25); + SkPaint paint; + SkPaint aaPaint; + aaPaint.setAntiAlias(true); + + RoundRectOp roundRectOp(bounds, Matrix4::identity(), nullptr, &paint, 0, 270); + EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &roundRectOp)) + << "Expect no offset for round rect op."; + + const float points[4] = {0.5, 0.5, 1.0, 1.0}; + PointsOp antiAliasedPointsOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4); + EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedPointsOp)) + << "Expect no offset for AA points."; + PointsOp pointsOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4); + EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &pointsOp)) + << "Expect an offset for non-AA points."; + + LinesOp antiAliasedLinesOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4); + EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedLinesOp)) + << "Expect no offset for AA lines."; + LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4); + EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp)) + << "Expect an offset for non-AA lines."; +}
\ No newline at end of file diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp new file mode 100644 index 000000000000..59bd75ef6f62 --- /dev/null +++ b/libs/hwui/tests/unit/BakedOpRendererTests.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include <BakedOpRenderer.h> +#include <tests/common/TestUtils.h> + +using namespace android::uirenderer; + +const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 }; + +RENDERTHREAD_TEST(BakedOpRenderer, startRepaintLayer_clear) { + BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), true, sLightInfo); + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200u, 200u); + + layer.dirty(Rect(200, 200)); + { + renderer.startRepaintLayer(&layer, Rect(200, 200)); + EXPECT_TRUE(layer.region.isEmpty()) << "Repaint full layer should clear region"; + renderer.endLayer(); + } + + layer.dirty(Rect(200, 200)); + { + renderer.startRepaintLayer(&layer, Rect(100, 200)); // repainting left side + EXPECT_TRUE(layer.region.isRect()); + //ALOGD("bounds %d %d %d %d", RECT_ARGS(layer.region.getBounds())); + EXPECT_EQ(android::Rect(100, 0, 200, 200), layer.region.getBounds()) + << "Left side being repainted, so right side should be clear"; + renderer.endLayer(); + } + + // right side is now only dirty portion + { + renderer.startRepaintLayer(&layer, Rect(100, 0, 200, 200)); // repainting right side + EXPECT_TRUE(layer.region.isEmpty()) + << "Now right side being repainted, so region should be entirely clear"; + renderer.endLayer(); + } +} diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp new file mode 100644 index 000000000000..0f8e0471556f --- /dev/null +++ b/libs/hwui/tests/unit/BakedOpStateTests.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> + +#include <BakedOpState.h> +#include <ClipArea.h> +#include <RecordedOp.h> +#include <tests/common/TestUtils.h> + +namespace android { +namespace uirenderer { + +TEST(ResolvedRenderState, construct) { + LinearAllocator allocator; + Matrix4 translate10x20; + translate10x20.loadTranslate(10, 20, 0); + + SkPaint paint; + ClipRect clip(Rect(100, 200)); + RectOp recordedOp(Rect(30, 40, 100, 200), translate10x20, &clip, &paint); + { + // recorded with transform, no parent transform + auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false); + EXPECT_MATRIX_APPROX_EQ(state.transform, translate10x20); + EXPECT_EQ(Rect(100, 200), state.clipRect()); + EXPECT_EQ(Rect(40, 60, 100, 200), state.clippedBounds); // translated and also clipped + EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags); + } + { + // recorded with transform and parent transform + auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200)); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false); + + Matrix4 expectedTranslate; + expectedTranslate.loadTranslate(20, 40, 0); + EXPECT_MATRIX_APPROX_EQ(expectedTranslate, state.transform); + + // intersection of parent & transformed child clip + EXPECT_EQ(Rect(10, 20, 100, 200), state.clipRect()); + + // translated and also clipped + EXPECT_EQ(Rect(50, 80, 100, 200), state.clippedBounds); + EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags); + } +} + +TEST(ResolvedRenderState, computeLocalSpaceClip) { + LinearAllocator allocator; + Matrix4 translate10x20; + translate10x20.loadTranslate(10, 20, 0); + + SkPaint paint; + ClipRect clip(Rect(100, 200)); + RectOp recordedOp(Rect(1000, 1000), translate10x20, &clip, &paint); + { + // recorded with transform, no parent transform + auto parentSnapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false); + EXPECT_EQ(Rect(-10, -20, 90, 180), state.computeLocalSpaceClip()) + << "Local clip rect should be 100x200, offset by -10,-20"; + } + { + // recorded with transform + parent transform + auto parentSnapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200)); + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, false); + EXPECT_EQ(Rect(-10, -20, 80, 160), state.computeLocalSpaceClip()) + << "Local clip rect should be 90x190, offset by -10,-20"; + } +} + +const float HAIRLINE = 0.0f; + +// Note: bounds will be conservative, but not precise for non-hairline +// - use approx bounds checks for these +const float SEMI_HAIRLINE = 0.3f; + +struct StrokeTestCase { + float scale; + float strokeWidth; + const std::function<void(const ResolvedRenderState&)> validator; +}; + +const static StrokeTestCase sStrokeTestCases[] = { + { + 1, HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_EQ(Rect(49.5f, 49.5f, 150.5f, 150.5f), state.clippedBounds); + } + }, + { + 1, SEMI_HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(49.5f, 49.5f, 150.5f, 150.5f)); + EXPECT_TRUE(Rect(49, 49, 151, 151).contains(state.clippedBounds)); + } + }, + { + 1, 20, [](const ResolvedRenderState& state) { + EXPECT_EQ(Rect(40, 40, 160, 160), state.clippedBounds); + } + }, + + // 3x3 scale: + { + 3, HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_EQ(Rect(149.5f, 149.5f, 200, 200), state.clippedBounds); + EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, state.clipSideFlags); + } + }, + { + 3, SEMI_HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(149.5f, 149.5f, 200, 200)); + EXPECT_TRUE(Rect(149, 149, 200, 200).contains(state.clippedBounds)); + } + }, + { + 3, 20, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(120, 120, 200, 200)); + EXPECT_TRUE(Rect(119, 119, 200, 200).contains(state.clippedBounds)); + } + }, + + // 0.5f x 0.5f scale + { + 0.5f, HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_EQ(Rect(24.5f, 24.5f, 75.5f, 75.5f), state.clippedBounds); + } + }, + { + 0.5f, SEMI_HAIRLINE, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(24.5f, 24.5f, 75.5f, 75.5f)); + EXPECT_TRUE(Rect(24, 24, 76, 76).contains(state.clippedBounds)); + } + }, + { + 0.5f, 20, [](const ResolvedRenderState& state) { + EXPECT_TRUE(state.clippedBounds.contains(19.5f, 19.5f, 80.5f, 80.5f)); + EXPECT_TRUE(Rect(19, 19, 81, 81).contains(state.clippedBounds)); + } + } +}; + +TEST(ResolvedRenderState, construct_expandForStroke) { + LinearAllocator allocator; + // Loop over table of test cases and verify different combinations of stroke width and transform + for (auto&& testCase : sStrokeTestCases) { + SkPaint strokedPaint; + strokedPaint.setAntiAlias(true); + strokedPaint.setStyle(SkPaint::kStroke_Style); + strokedPaint.setStrokeWidth(testCase.strokeWidth); + + ClipRect clip(Rect(200, 200)); + RectOp recordedOp(Rect(50, 50, 150, 150), + Matrix4::identity(), &clip, &strokedPaint); + + Matrix4 snapshotMatrix; + snapshotMatrix.loadScale(testCase.scale, testCase.scale, 1); + auto parentSnapshot = TestUtils::makeSnapshot(snapshotMatrix, Rect(200, 200)); + + ResolvedRenderState state(allocator, *parentSnapshot, recordedOp, true); + testCase.validator(state); + } +} + +TEST(BakedOpState, tryConstruct) { + Matrix4 translate100x0; + translate100x0.loadTranslate(100, 0, 0); + + SkPaint paint; + ClipRect clip(Rect(100, 200)); + + LinearAllocator allocator; + RectOp successOp(Rect(30, 40, 100, 200), Matrix4::identity(), &clip, &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 200)); + EXPECT_NE(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, successOp)) + << "successOp NOT rejected by clip, so should be constructed"; + size_t successAllocSize = allocator.usedSize(); + EXPECT_LE(64u, successAllocSize) << "relatively large alloc for non-rejected op"; + + RectOp rejectOp(Rect(30, 40, 100, 200), translate100x0, &clip, &paint); + EXPECT_EQ(nullptr, BakedOpState::tryConstruct(allocator, *snapshot, rejectOp)) + << "rejectOp rejected by clip, so should not be constructed"; + + // NOTE: this relies on the clip having already been serialized by the op above + EXPECT_EQ(successAllocSize, allocator.usedSize()) << "no extra allocation used for rejected op"; +} + +TEST(BakedOpState, tryShadowOpConstruct) { + Matrix4 translate10x20; + translate10x20.loadTranslate(10, 20, 0); + + LinearAllocator allocator; + { + auto snapshot = TestUtils::makeSnapshot(translate10x20, Rect()); // Note: empty clip + BakedOpState* bakedState = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234); + + EXPECT_EQ(nullptr, bakedState) << "op should be rejected by clip, so not constructed"; + EXPECT_EQ(0u, allocator.usedSize()) << "no serialization, even for clip," + "since op is quick rejected based on snapshot clip"; + } + { + auto snapshot = TestUtils::makeSnapshot(translate10x20, Rect(100, 200)); + BakedOpState* bakedState = BakedOpState::tryShadowOpConstruct(allocator, *snapshot, (ShadowOp*)0x1234); + + ASSERT_NE(nullptr, bakedState) << "NOT rejected by clip, so op should be constructed"; + EXPECT_LE(64u, allocator.usedSize()) << "relatively large alloc for non-rejected op"; + + EXPECT_MATRIX_APPROX_EQ(translate10x20, bakedState->computedState.transform); + EXPECT_EQ(Rect(100, 200), bakedState->computedState.clippedBounds); + } +} + +TEST(BakedOpState, tryStrokeableOpConstruct) { + LinearAllocator allocator; + { + // check regular rejection + SkPaint paint; + paint.setStyle(SkPaint::kStrokeAndFill_Style); + paint.setStrokeWidth(0.0f); + ClipRect clip(Rect(100, 200)); + RectOp rejectOp(Rect(100, 200), Matrix4::identity(), &clip, &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect()); // Note: empty clip + auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, + BakedOpState::StrokeBehavior::StyleDefined); + + EXPECT_EQ(nullptr, bakedState); + EXPECT_GT(8u, allocator.usedSize()); // no significant allocation space used for rejected op + } + { + // check simple unscaled expansion + SkPaint paint; + paint.setStyle(SkPaint::kStrokeAndFill_Style); + paint.setStrokeWidth(10.0f); + ClipRect clip(Rect(200, 200)); + RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200)); + auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, + BakedOpState::StrokeBehavior::StyleDefined); + + ASSERT_NE(nullptr, bakedState); + EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds); + EXPECT_EQ(0, bakedState->computedState.clipSideFlags); + } + { + // check simple unscaled expansion, and fill style with stroke forced + SkPaint paint; + paint.setStyle(SkPaint::kFill_Style); + paint.setStrokeWidth(10.0f); + ClipRect clip(Rect(200, 200)); + RectOp rejectOp(Rect(50, 50, 150, 150), Matrix4::identity(), &clip, &paint); + auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(200, 200)); + auto bakedState = BakedOpState::tryStrokeableOpConstruct(allocator, *snapshot, rejectOp, + BakedOpState::StrokeBehavior::Forced); + + ASSERT_NE(nullptr, bakedState); + EXPECT_EQ(Rect(45, 45, 155, 155), bakedState->computedState.clippedBounds); + EXPECT_EQ(0, bakedState->computedState.clipSideFlags); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/tests/unit/CanvasStateTests.cpp b/libs/hwui/tests/unit/CanvasStateTests.cpp new file mode 100644 index 000000000000..0afabd83f5cc --- /dev/null +++ b/libs/hwui/tests/unit/CanvasStateTests.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2015 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 "CanvasState.h" + +#include "Matrix.h" +#include "Rect.h" +#include "hwui/Canvas.h" +#include "utils/LinearAllocator.h" + +#include <gtest/gtest.h> +#include <SkPath.h> +#include <SkRegion.h> + +namespace android { +namespace uirenderer { + +class NullClient: public CanvasStateClient { + void onViewportInitialized() override {} + void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + GLuint getTargetFbo() const override { return 0; } +}; + +static NullClient sNullClient; + +static bool approxEqual(const Matrix4& a, const Matrix4& b) { + for (int i = 0; i < 16; i++) { + if (!MathUtils::areEqual(a[i], b[i])) { + return false; + } + } + return true; +} + +TEST(CanvasState, gettersAndSetters) { + CanvasState state(sNullClient); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); + + ASSERT_EQ(state.getWidth(), 200); + ASSERT_EQ(state.getHeight(), 200); + + Matrix4 simpleTranslate; + simpleTranslate.loadTranslate(10, 20, 0); + state.setMatrix(simpleTranslate); + + ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(200, 200)); + ASSERT_EQ(state.getLocalClipBounds(), Rect(-10, -20, 190, 180)); + EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate)); + EXPECT_TRUE(state.clipIsSimple()); +} + +TEST(CanvasState, simpleClipping) { + CanvasState state(sNullClient); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); + + state.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op); + ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(100, 100)); + + state.clipRect(10, 10, 200, 200, SkRegion::kIntersect_Op); + ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10, 100, 100)); + + state.clipRect(50, 50, 150, 150, SkRegion::kReplace_Op); + ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(50, 50, 150, 150)); +} + +TEST(CanvasState, complexClipping) { + CanvasState state(sNullClient); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); + + state.save(SaveFlags::MatrixClip); + { + // rotated clip causes complex clip + state.rotate(10); + EXPECT_TRUE(state.clipIsSimple()); + state.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op); + EXPECT_FALSE(state.clipIsSimple()); + } + state.restore(); + + state.save(SaveFlags::MatrixClip); + { + // subtracted clip causes complex clip + EXPECT_TRUE(state.clipIsSimple()); + state.clipRect(50, 50, 150, 150, SkRegion::kDifference_Op); + EXPECT_FALSE(state.clipIsSimple()); + } + state.restore(); + + state.save(SaveFlags::MatrixClip); + { + // complex path causes complex clip + SkPath path; + path.addOval(SkRect::MakeWH(200, 200)); + EXPECT_TRUE(state.clipIsSimple()); + state.clipPath(&path, SkRegion::kDifference_Op); + EXPECT_FALSE(state.clipIsSimple()); + } + state.restore(); +} + +TEST(CanvasState, saveAndRestore) { + CanvasState state(sNullClient); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); + + state.save(SaveFlags::Clip); + { + state.clipRect(0, 0, 10, 10, SkRegion::kIntersect_Op); + ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); + } + state.restore(); + ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(200, 200)); // verify restore + + Matrix4 simpleTranslate; + simpleTranslate.loadTranslate(10, 10, 0); + state.save(SaveFlags::Matrix); + { + state.translate(10, 10, 0); + EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate)); + } + state.restore(); + EXPECT_FALSE(approxEqual(*state.currentTransform(), simpleTranslate)); +} + +TEST(CanvasState, saveAndRestoreButNotTooMuch) { + CanvasState state(sNullClient); + state.initializeSaveStack(200, 200, + 0, 0, 200, 200, Vector3()); + + state.save(SaveFlags::Matrix); // NOTE: clip not saved + { + state.clipRect(0, 0, 10, 10, SkRegion::kIntersect_Op); + ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); + } + state.restore(); + ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); // verify not restored + + Matrix4 simpleTranslate; + simpleTranslate.loadTranslate(10, 10, 0); + state.save(SaveFlags::Clip); // NOTE: matrix not saved + { + state.translate(10, 10, 0); + EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate)); + } + state.restore(); + EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate)); // verify not restored +} + +} +} diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp new file mode 100644 index 000000000000..54ca68d63dbe --- /dev/null +++ b/libs/hwui/tests/unit/ClipAreaTests.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> +#include <SkPath.h> +#include <SkRegion.h> + +#include "ClipArea.h" + +#include "Matrix.h" +#include "Rect.h" +#include "utils/LinearAllocator.h" + +namespace android { +namespace uirenderer { + +static Rect kViewportBounds(2048, 2048); + +static ClipArea createClipArea() { + ClipArea area; + area.setViewportDimensions(kViewportBounds.getWidth(), kViewportBounds.getHeight()); + return area; +} + +TEST(TransformedRectangle, basics) { + Rect r(0, 0, 100, 100); + Matrix4 minus90; + minus90.loadRotate(-90); + minus90.mapRect(r); + Rect r2(20, 40, 120, 60); + + Matrix4 m90; + m90.loadRotate(90); + TransformedRectangle tr(r, m90); + EXPECT_TRUE(tr.canSimplyIntersectWith(tr)); + + Matrix4 m0; + TransformedRectangle tr0(r2, m0); + EXPECT_FALSE(tr.canSimplyIntersectWith(tr0)); + + Matrix4 m45; + m45.loadRotate(45); + TransformedRectangle tr2(r, m45); + EXPECT_FALSE(tr2.canSimplyIntersectWith(tr)); +} + +TEST(RectangleList, basics) { + RectangleList list; + EXPECT_TRUE(list.isEmpty()); + + Rect r(0, 0, 100, 100); + Matrix4 m45; + m45.loadRotate(45); + list.set(r, m45); + EXPECT_FALSE(list.isEmpty()); + + Rect r2(20, 20, 200, 200); + list.intersectWith(r2, m45); + EXPECT_FALSE(list.isEmpty()); + EXPECT_EQ(1, list.getTransformedRectanglesCount()); + + Rect r3(20, 20, 200, 200); + Matrix4 m30; + m30.loadRotate(30); + list.intersectWith(r2, m30); + EXPECT_FALSE(list.isEmpty()); + EXPECT_EQ(2, list.getTransformedRectanglesCount()); + + SkRegion clip; + clip.setRect(0, 0, 2000, 2000); + SkRegion rgn(list.convertToRegion(clip)); + EXPECT_FALSE(rgn.isEmpty()); +} + +TEST(ClipArea, basics) { + ClipArea area(createClipArea()); + EXPECT_FALSE(area.isEmpty()); +} + +TEST(ClipArea, paths) { + ClipArea area(createClipArea()); + SkPath path; + SkScalar r = 100; + path.addCircle(r, r, r); + area.clipPathWithTransform(path, &Matrix4::identity(), SkRegion::kIntersect_Op); + EXPECT_FALSE(area.isEmpty()); + EXPECT_FALSE(area.isSimple()); + EXPECT_FALSE(area.isRectangleList()); + + Rect clipRect(area.getClipRect()); + Rect expected(0, 0, r * 2, r * 2); + EXPECT_EQ(expected, clipRect); + SkRegion clipRegion(area.getClipRegion()); + auto skRect(clipRegion.getBounds()); + Rect regionBounds; + regionBounds.set(skRect); + EXPECT_EQ(expected, regionBounds); +} + +TEST(ClipArea, replaceNegative) { + ClipArea area(createClipArea()); + area.setClip(0, 0, 100, 100); + + Rect expected(-50, -50, 50, 50); + area.clipRectWithTransform(expected, &Matrix4::identity(), SkRegion::kReplace_Op); + EXPECT_EQ(expected, area.getClipRect()); +} + +TEST(ClipArea, serializeClip) { + ClipArea area(createClipArea()); + LinearAllocator allocator; + + // unset clip + EXPECT_EQ(nullptr, area.serializeClip(allocator)); + + // rect clip + area.setClip(0, 0, 200, 200); + { + auto serializedClip = area.serializeClip(allocator); + ASSERT_NE(nullptr, serializedClip); + ASSERT_EQ(ClipMode::Rectangle, serializedClip->mode); + ASSERT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot"; + EXPECT_EQ(Rect(200, 200), serializedClip->rect); + EXPECT_EQ(serializedClip, area.serializeClip(allocator)) + << "Requery of clip on unmodified ClipArea must return same pointer."; + } + + // rect list + Matrix4 rotate; + rotate.loadRotate(5.0f); + area.clipRectWithTransform(Rect(50, 50, 150, 150), &rotate, SkRegion::kIntersect_Op); + { + auto serializedClip = area.serializeClip(allocator); + ASSERT_NE(nullptr, serializedClip); + ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode); + ASSERT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot"; + auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip); + EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount()); + EXPECT_EQ(Rect(37, 54, 145, 163), clipRectList->rect); + EXPECT_EQ(serializedClip, area.serializeClip(allocator)) + << "Requery of clip on unmodified ClipArea must return same pointer."; + } + + // region + SkPath circlePath; + circlePath.addCircle(100, 100, 100); + area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op); + { + auto serializedClip = area.serializeClip(allocator); + ASSERT_NE(nullptr, serializedClip); + ASSERT_EQ(ClipMode::Region, serializedClip->mode); + ASSERT_TRUE(serializedClip->intersectWithRoot) << "Replace op, so expect intersectWithRoot"; + auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip); + EXPECT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds()) + << "Clip region should be 200x200"; + EXPECT_EQ(Rect(200, 200), clipRegion->rect); + EXPECT_EQ(serializedClip, area.serializeClip(allocator)) + << "Requery of clip on unmodified ClipArea must return same pointer."; + } +} + +TEST(ClipArea, serializeClip_pathIntersectWithRoot) { + ClipArea area(createClipArea()); + LinearAllocator allocator; + SkPath circlePath; + circlePath.addCircle(100, 100, 100); + area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kIntersect_Op); + + auto serializedClip = area.serializeClip(allocator); + ASSERT_NE(nullptr, serializedClip); + EXPECT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot"; +} + +TEST(ClipArea, serializeIntersectedClip) { + ClipArea area(createClipArea()); + LinearAllocator allocator; + + // simple state; + EXPECT_EQ(nullptr, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity())); + area.setClip(0, 0, 200, 200); + { + auto origRectClip = area.serializeClip(allocator); + ASSERT_NE(nullptr, origRectClip); + EXPECT_EQ(origRectClip, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity())); + } + + // rect + { + ClipRect recordedClip(Rect(100, 100)); + Matrix4 translateScale; + translateScale.loadTranslate(100, 100, 0); + translateScale.scale(2, 3, 1); + auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale); + ASSERT_NE(nullptr, resolvedClip); + ASSERT_EQ(ClipMode::Rectangle, resolvedClip->mode); + EXPECT_EQ(Rect(100, 100, 200, 200), resolvedClip->rect); + + EXPECT_EQ(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip, translateScale)) + << "Must return previous serialization, since input is same"; + + ClipRect recordedClip2(Rect(100, 100)); + EXPECT_NE(resolvedClip, area.serializeIntersectedClip(allocator, &recordedClip2, translateScale)) + << "Shouldn't return previous serialization, since matrix location is different"; + } + + // rect list + Matrix4 rotate; + rotate.loadRotate(2.0f); + area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op); + { + ClipRect recordedClip(Rect(100, 100)); + auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, Matrix4::identity()); + ASSERT_NE(nullptr, resolvedClip); + ASSERT_EQ(ClipMode::RectangleList, resolvedClip->mode); + auto clipRectList = reinterpret_cast<const ClipRectList*>(resolvedClip); + EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount()); + } + + // region + SkPath circlePath; + circlePath.addCircle(100, 100, 100); + area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op); + { + SkPath ovalPath; + ovalPath.addOval(SkRect::MakeLTRB(50, 0, 150, 200)); + + ClipRegion recordedClip; + recordedClip.region.setPath(ovalPath, SkRegion(SkIRect::MakeWH(200, 200))); + recordedClip.rect = Rect(200, 200); + + Matrix4 translate10x20; + translate10x20.loadTranslate(10, 20, 0); + auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, + translate10x20); // Note: only translate for now, others not handled correctly + ASSERT_NE(nullptr, resolvedClip); + ASSERT_EQ(ClipMode::Region, resolvedClip->mode); + auto clipRegion = reinterpret_cast<const ClipRegion*>(resolvedClip); + EXPECT_EQ(SkIRect::MakeLTRB(60, 20, 160, 200), clipRegion->region.getBounds()); + } +} + +TEST(ClipArea, serializeIntersectedClip_snap) { + ClipArea area(createClipArea()); + area.setClip(100.2, 100.4, 500.6, 500.8); + LinearAllocator allocator; + + { + // no recorded clip case + auto resolvedClip = area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()); + EXPECT_EQ(Rect(100, 100, 501, 501), resolvedClip->rect); + } + { + // recorded clip case + ClipRect recordedClip(Rect(100.12, 100.74)); + Matrix4 translateScale; + translateScale.loadTranslate(100, 100, 0); + translateScale.scale(2, 3, 1); // recorded clip will have non-int coords, even after transform + auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale); + ASSERT_NE(nullptr, resolvedClip); + EXPECT_EQ(ClipMode::Rectangle, resolvedClip->mode); + EXPECT_EQ(Rect(100, 100, 300, 402), resolvedClip->rect); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/tests/unit/DamageAccumulatorTests.cpp b/libs/hwui/tests/unit/DamageAccumulatorTests.cpp new file mode 100644 index 000000000000..77001382115a --- /dev/null +++ b/libs/hwui/tests/unit/DamageAccumulatorTests.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> + +#include <DamageAccumulator.h> +#include <Matrix.h> +#include <RenderNode.h> +#include <utils/LinearAllocator.h> + +#include <SkRect.h> + +using namespace android; +using namespace android::uirenderer; + +// Test that push & pop are propegating the dirty rect +// There is no transformation of the dirty rect, the input is the same +// as the output. +TEST(DamageAccumulator, identity) { + DamageAccumulator da; + SkRect curDirty; + da.pushTransform(&Matrix4::identity()); + da.dirty(50, 50, 100, 100); + { + da.pushTransform(&Matrix4::identity()); + da.peekAtDirty(&curDirty); + ASSERT_EQ(SkRect(), curDirty); + da.popTransform(); + } + da.peekAtDirty(&curDirty); + ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty); + da.popTransform(); + da.finish(&curDirty); + ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty); +} + +// Test that transformation is happening at the correct levels via +// peekAtDirty & popTransform. Just uses a simple translate to test this +TEST(DamageAccumulator, translate) { + DamageAccumulator da; + Matrix4 translate; + SkRect curDirty; + translate.loadTranslate(25, 25, 0); + da.pushTransform(&translate); + da.dirty(50, 50, 100, 100); + da.peekAtDirty(&curDirty); + ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty); + da.popTransform(); + da.finish(&curDirty); + ASSERT_EQ(SkRect::MakeLTRB(75, 75, 125, 125), curDirty); +} + +// Test that dirty rectangles are being unioned across "siblings +TEST(DamageAccumulator, union) { + DamageAccumulator da; + SkRect curDirty; + da.pushTransform(&Matrix4::identity()); + { + da.pushTransform(&Matrix4::identity()); + da.dirty(50, 50, 100, 100); + da.popTransform(); + da.pushTransform(&Matrix4::identity()); + da.dirty(150, 50, 200, 125); + da.popTransform(); + } + da.popTransform(); + da.finish(&curDirty); + ASSERT_EQ(SkRect::MakeLTRB(50, 50, 200, 125), curDirty); +} + +TEST(DamageAccumulator, basicRenderNode) { + DamageAccumulator da; + RenderNode node1; + node1.animatorProperties().setLeftTopRightBottom(50, 50, 500, 500); + node1.animatorProperties().updateMatrix(); + da.pushTransform(&node1); + { + RenderNode node2; + node2.animatorProperties().setLeftTopRightBottom(50, 50, 100, 100); + node2.animatorProperties().updateMatrix(); + da.pushTransform(&node2); + da.dirty(0, 0, 25, 25); + da.popTransform(); + } + da.popTransform(); + SkRect dirty; + da.finish(&dirty); + ASSERT_EQ(SkRect::MakeLTRB(100, 100, 125, 125), dirty); +} + +TEST(DamageAccumulator, perspectiveTransform) { + DamageAccumulator da; + RenderNode node1; + node1.animatorProperties().setLeftTopRightBottom(50, 50, 500, 500); + node1.animatorProperties().setClipToBounds(true); + node1.animatorProperties().updateMatrix(); + da.pushTransform(&node1); + { + RenderNode node2; + node2.animatorProperties().setLeftTopRightBottom(50, 50, 100, 100); + node2.animatorProperties().setClipToBounds(false); + node2.animatorProperties().setRotationX(1.0f); + node2.animatorProperties().setRotationY(1.0f); + node2.animatorProperties().setRotation(20.0f); + node2.animatorProperties().setCameraDistance(500.0f); + node2.animatorProperties().setTranslationZ(30.0f); + node2.animatorProperties().updateMatrix(); + da.pushTransform(&node2); + da.dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX); + da.popTransform(); + } + da.popTransform(); + SkRect dirty; + da.finish(&dirty); + ASSERT_EQ(SkRect::MakeLTRB(50, 50, 500, 500), dirty); +} diff --git a/libs/hwui/tests/unit/DeviceInfoTests.cpp b/libs/hwui/tests/unit/DeviceInfoTests.cpp new file mode 100644 index 000000000000..17236bdf0bf7 --- /dev/null +++ b/libs/hwui/tests/unit/DeviceInfoTests.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015 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 <DeviceInfo.h> + +#include <gtest/gtest.h> + +using namespace android; +using namespace android::uirenderer; + +TEST(DeviceInfo, basic) { + // can't assert state before init - another test may have initialized the singleton + DeviceInfo::initialize(); + const DeviceInfo* di = DeviceInfo::get(); + ASSERT_NE(nullptr, di) << "DeviceInfo initialization failed"; + EXPECT_EQ(2048, di->maxTextureSize()) << "Max texture size didn't match"; +} diff --git a/libs/hwui/tests/unit/FatVectorTests.cpp b/libs/hwui/tests/unit/FatVectorTests.cpp new file mode 100644 index 000000000000..64b0ba13562d --- /dev/null +++ b/libs/hwui/tests/unit/FatVectorTests.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> +#include <utils/FatVector.h> + +#include <tests/common/TestUtils.h> + +using namespace android; +using namespace android::uirenderer; + +template<class VectorType> +static bool allocationIsInternal(VectorType& v) { + // allocation array (from &v[0] to &v[0] + v.capacity) is + // located within the vector object itself + return (char*)(&v) <= (char*)(&v[0]) + && (char*)(&v + 1) >= (char*)(&v[0] + v.capacity()); +} + +TEST(FatVector, baseline) { + // Verify allocation behavior FatVector contrasts against - allocations are always external + std::vector<int> v; + for (int i = 0; i < 50; i++) { + v.push_back(i); + EXPECT_FALSE(allocationIsInternal(v)); + } +} + +TEST(FatVector, simpleAllocate) { + FatVector<int, 4> v; + EXPECT_EQ(4u, v.capacity()); + + // can insert 4 items into internal buffer + for (int i = 0; i < 4; i++) { + v.push_back(i); + EXPECT_TRUE(allocationIsInternal(v)); + } + + // then will fall back to external allocation + for (int i = 5; i < 50; i++) { + v.push_back(i); + EXPECT_FALSE(allocationIsInternal(v)); + } +} + +TEST(FatVector, preSizeConstructor) { + { + FatVector<int, 4> v(32); + EXPECT_EQ(32u, v.capacity()); + EXPECT_EQ(32u, v.size()); + EXPECT_FALSE(allocationIsInternal(v)); + } + { + FatVector<int, 4> v(4); + EXPECT_EQ(4u, v.capacity()); + EXPECT_EQ(4u, v.size()); + EXPECT_TRUE(allocationIsInternal(v)); + } + { + FatVector<int, 4> v(2); + EXPECT_EQ(4u, v.capacity()); + EXPECT_EQ(2u, v.size()); + EXPECT_TRUE(allocationIsInternal(v)); + } +} + +TEST(FatVector, shrink) { + FatVector<int, 10> v; + EXPECT_TRUE(allocationIsInternal(v)); + + // push into external alloc + v.resize(11); + EXPECT_FALSE(allocationIsInternal(v)); + + // shrinking back to internal alloc succeeds + // note that shrinking further will succeed, but is a waste + v.resize(10); + v.shrink_to_fit(); + EXPECT_TRUE(allocationIsInternal(v)); +} + +TEST(FatVector, destructorInternal) { + int count = 0; + { + // push 1 into external allocation, verify destruction happens once + FatVector<TestUtils::SignalingDtor, 0> v; + v.emplace_back(&count); + EXPECT_FALSE(allocationIsInternal(v)); + EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet"; + } + EXPECT_EQ(1, count) << "Destruction should happen exactly once"; +} + +TEST(FatVector, destructorExternal) { + int count = 0; + { + // push 10 into internal allocation, verify 10 destructors called + FatVector<TestUtils::SignalingDtor, 10> v; + for (int i = 0; i < 10; i++) { + v.emplace_back(&count); + EXPECT_TRUE(allocationIsInternal(v)); + } + EXPECT_EQ(0, count) << "Destruction shouldn't have happened yet"; + } + EXPECT_EQ(10, count) << "Destruction should happen exactly once"; +} diff --git a/libs/hwui/tests/unit/FontRendererTests.cpp b/libs/hwui/tests/unit/FontRendererTests.cpp new file mode 100644 index 000000000000..99080ac938e7 --- /dev/null +++ b/libs/hwui/tests/unit/FontRendererTests.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "GammaFontRenderer.h" +#include "tests/common/TestUtils.h" + +using namespace android::uirenderer; + +static bool isZero(uint8_t* data, int size) { + for (int i = 0; i < size; i++) { + if (data[i]) return false; + } + return true; +} + +RENDERTHREAD_TEST(FontRenderer, renderDropShadow) { + SkPaint paint; + paint.setTextSize(10); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + GammaFontRenderer gammaFontRenderer; + FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); + fontRenderer.setFont(&paint, SkMatrix::I()); + + std::vector<glyph_t> glyphs; + std::vector<float> positions; + float totalAdvance; + Rect bounds; + TestUtils::layoutTextUnscaled(paint, "This is a test", + &glyphs, &positions, &totalAdvance, &bounds); + + for (int radius : {28, 20, 2}) { + auto result = fontRenderer.renderDropShadow(&paint, glyphs.data(), glyphs.size(), + radius, positions.data()); + ASSERT_NE(nullptr, result.image); + EXPECT_FALSE(isZero(result.image, result.width * result.height)); + EXPECT_LE(bounds.getWidth() + radius * 2, (int) result.width); + EXPECT_LE(bounds.getHeight() + radius * 2, (int) result.height); + delete result.image; + } +} diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp new file mode 100644 index 000000000000..0f16b1512586 --- /dev/null +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -0,0 +1,2172 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include <BakedOpState.h> +#include <DeferredLayerUpdater.h> +#include <FrameBuilder.h> +#include <LayerUpdateQueue.h> +#include <RecordedOp.h> +#include <RecordingCanvas.h> +#include <tests/common/TestUtils.h> + +#include <unordered_map> + +namespace android { +namespace uirenderer { + +const FrameBuilder::LightGeometry sLightGeometry = { {100, 100, 100}, 50}; + +/** + * Virtual class implemented by each test to redirect static operation / state transitions to + * virtual methods. + * + * Virtual dispatch allows for default behaviors to be specified (very common case in below tests), + * and allows Renderer vs Dispatching behavior to be merged. + * + * onXXXOp methods fail by default - tests should override ops they expect + * startRepaintLayer fails by default - tests should override if expected + * startFrame/endFrame do nothing by default - tests should override to intercept + */ +class TestRendererBase { +public: + virtual ~TestRendererBase() {} + virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) { + ADD_FAILURE() << "Temporary layers not expected in this test"; + return nullptr; + } + virtual void recycleTemporaryLayer(OffscreenBuffer*) { + ADD_FAILURE() << "Temporary layers not expected in this test"; + } + virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) { + ADD_FAILURE() << "Layer repaint not expected in this test"; + } + virtual void endLayer() { + ADD_FAILURE() << "Layer updates not expected in this test"; + } + virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {} + virtual void endFrame(const Rect& repaintRect) {} + + // define virtual defaults for single draw methods +#define X(Type) \ + virtual void on##Type(const Type&, const BakedOpState&) { \ + ADD_FAILURE() << #Type " not expected in this test"; \ + } + MAP_RENDERABLE_OPS(X) +#undef X + + // define virtual defaults for merged draw methods +#define X(Type) \ + virtual void onMerged##Type##s(const MergedBakedOpList& opList) { \ + ADD_FAILURE() << "Merged " #Type "s not expected in this test"; \ + } + MAP_MERGEABLE_OPS(X) +#undef X + + int getIndex() { return mIndex; } + +protected: + int mIndex = 0; +}; + +/** + * Dispatches all static methods to similar formed methods on renderer, which fail by default but + * are overridden by subclasses per test. + */ +class TestDispatcher { +public: + // define single op methods, which redirect to TestRendererBase +#define X(Type) \ + static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \ + renderer.on##Type(op, state); \ + } + MAP_RENDERABLE_OPS(X); +#undef X + + // define merged op methods, which redirect to TestRendererBase +#define X(Type) \ + static void onMerged##Type##s(TestRendererBase& renderer, const MergedBakedOpList& opList) { \ + renderer.onMerged##Type##s(opList); \ + } + MAP_MERGEABLE_OPS(X); +#undef X +}; + +class FailRenderer : public TestRendererBase {}; + +RENDERTHREAD_TEST(FrameBuilder, simple) { + class SimpleTestRenderer : public TestRendererBase { + public: + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(100u, width); + EXPECT_EQ(200u, height); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + } + void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { + EXPECT_EQ(2, mIndex++); + } + void endFrame(const Rect& repaintRect) override { + EXPECT_EQ(3, mIndex++); + } + }; + + auto node = TestUtils::createNode(0, 0, 100, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkBitmap bitmap = TestUtils::createSkBitmap(25, 25); + canvas.drawRect(0, 0, 100, 200, SkPaint()); + canvas.drawBitmap(bitmap, 10, 10, nullptr); + }); + FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SimpleTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end +} + +RENDERTHREAD_TEST(FrameBuilder, simpleStroke) { + class SimpleStrokeTestRenderer : public TestRendererBase { + public: + void onPointsOp(const PointsOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + // even though initial bounds are empty... + EXPECT_TRUE(op.unmappedBounds.isEmpty()) + << "initial bounds should be empty, since they're unstroked"; + EXPECT_EQ(Rect(45, 45, 55, 55), state.computedState.clippedBounds) + << "final bounds should account for stroke"; + } + }; + + auto node = TestUtils::createNode(0, 0, 100, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkPaint strokedPaint; + strokedPaint.setStrokeWidth(10); + canvas.drawPoint(50, 50, strokedPaint); + }); + FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SimpleStrokeTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, simpleRejection) { + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty + canvas.drawRect(0, 0, 400, 400, SkPaint()); + canvas.restore(); + }); + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + FailRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +RENDERTHREAD_TEST(FrameBuilder, simpleBatching) { + const int LOOPS = 5; + class SimpleBatchingTestRenderer : public TestRendererBase { + public: + void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { + EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects"; + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps"; + } + }; + + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkBitmap bitmap = TestUtils::createSkBitmap(10, 10, + kAlpha_8_SkColorType); // Disable merging by using alpha 8 bitmap + + // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects. + // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group. + canvas.save(SaveFlags::MatrixClip); + for (int i = 0; i < LOOPS; i++) { + canvas.translate(0, 10); + canvas.drawRect(0, 0, 10, 10, SkPaint()); + canvas.drawBitmap(bitmap, 5, 0, nullptr); + } + canvas.restore(); + }); + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SimpleBatchingTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2 * LOOPS, renderer.getIndex()) + << "Expect number of ops = 2 * loop count"; +} + +RENDERTHREAD_TEST(FrameBuilder, deferRenderNode_translateClip) { + class DeferRenderNodeTranslateClipTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(Rect(5, 10, 55, 60), state.computedState.clippedBounds); + EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, + state.computedState.clipSideFlags); + } + }; + + auto node = TestUtils::createNode(0, 0, 100, 100, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 100, 100, SkPaint()); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(5, 10, Rect(50, 50), // translate + clip node + *TestUtils::getSyncedNode(node)); + + DeferRenderNodeTranslateClipTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, deferRenderNodeScene) { + class DeferRenderNodeSceneTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + const Rect& clippedBounds = state.computedState.clippedBounds; + Matrix4 expected; + switch (mIndex++) { + case 0: + // background - left side + EXPECT_EQ(Rect(600, 100, 700, 500), clippedBounds); + expected.loadTranslate(100, 100, 0); + break; + case 1: + // background - top side + EXPECT_EQ(Rect(100, 400, 600, 500), clippedBounds); + expected.loadTranslate(100, 100, 0); + break; + case 2: + // content + EXPECT_EQ(Rect(100, 100, 700, 500), clippedBounds); + expected.loadTranslate(-50, -50, 0); + break; + case 3: + // overlay + EXPECT_EQ(Rect(0, 0, 800, 200), clippedBounds); + break; + default: + ADD_FAILURE() << "Too many rects observed"; + } + EXPECT_EQ(expected, state.computedState.transform); + } + }; + + std::vector<sp<RenderNode>> nodes; + SkPaint transparentPaint; + transparentPaint.setAlpha(128); + + // backdrop + nodes.push_back(TestUtils::createNode(100, 100, 700, 500, // 600x400 + [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 600, 400, transparentPaint); + })); + + // content + Rect contentDrawBounds(150, 150, 650, 450); // 500x300 + nodes.push_back(TestUtils::createNode(0, 0, 800, 600, + [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 800, 600, transparentPaint); + })); + + // overlay + nodes.push_back(TestUtils::createNode(0, 0, 800, 600, + [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 800, 200, transparentPaint); + })); + + for (auto& node : nodes) { + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + } + + FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds); + + DeferRenderNodeSceneTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, empty_noFbo0) { + class EmptyNoFbo0TestRenderer : public TestRendererBase { + public: + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { + ADD_FAILURE() << "Primary frame draw not expected in this test"; + } + void endFrame(const Rect& repaintRect) override { + ADD_FAILURE() << "Primary frame draw not expected in this test"; + } + }; + + // Use layer update constructor, so no work is enqueued for Fbo0 + LayerUpdateQueue emptyLayerUpdateQueue; + FrameBuilder frameBuilder(emptyLayerUpdateQueue, sLightGeometry, Caches::getInstance()); + EmptyNoFbo0TestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +RENDERTHREAD_TEST(FrameBuilder, empty_withFbo0) { + class EmptyWithFbo0TestRenderer : public TestRendererBase { + public: + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { + EXPECT_EQ(0, mIndex++); + } + void endFrame(const Rect& repaintRect) override { + EXPECT_EQ(1, mIndex++); + } + }; + auto node = TestUtils::createNode(10, 10, 110, 110, + [](RenderProperties& props, RecordingCanvas& canvas) { + // no drawn content + }); + + // Draw, but pass node without draw content, so no work is done for primary frame + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + EmptyWithFbo0TestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()) << "No drawing content produced," + " but fbo0 update lifecycle should still be observed"; +} + +RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_rects) { + class AvoidOverdrawRectsTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(mIndex++, 0) << "Should be one rect"; + EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds) + << "Last rect should occlude others."; + } + }; + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.drawRect(10, 10, 190, 190, SkPaint()); + }); + + // Damage (and therefore clip) is same as last draw, subset of renderable area. + // This means last op occludes other contents, and they'll be rejected to avoid overdraw. + FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 190, 190), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + EXPECT_EQ(3u, node->getDisplayList()->getOps().size()) + << "Recording must not have rejected ops, in order for this test to be valid"; + + AvoidOverdrawRectsTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op"; +} + +RENDERTHREAD_TEST(FrameBuilder, avoidOverdraw_bitmaps) { + static SkBitmap opaqueBitmap = TestUtils::createSkBitmap(50, 50, + SkColorType::kRGB_565_SkColorType); + static SkBitmap transpBitmap = TestUtils::createSkBitmap(50, 50, + SkColorType::kAlpha_8_SkColorType); + class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase { + public: + void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { + switch(mIndex++) { + case 0: + EXPECT_EQ(opaqueBitmap.pixelRef(), op.bitmap->pixelRef()); + break; + case 1: + EXPECT_EQ(transpBitmap.pixelRef(), op.bitmap->pixelRef()); + break; + default: + ADD_FAILURE() << "Only two ops expected."; + } + } + }; + + auto node = TestUtils::createNode(0, 0, 50, 50, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 50, 50, SkPaint()); + canvas.drawRect(0, 0, 50, 50, SkPaint()); + canvas.drawBitmap(transpBitmap, 0, 0, nullptr); + + // only the below draws should remain, since they're + canvas.drawBitmap(opaqueBitmap, 0, 0, nullptr); + canvas.drawBitmap(transpBitmap, 0, 0, nullptr); + }); + FrameBuilder frameBuilder(SkRect::MakeWH(50, 50), 50, 50, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + EXPECT_EQ(5u, node->getDisplayList()->getOps().size()) + << "Recording must not have rejected ops, in order for this test to be valid"; + + AvoidOverdrawBitmapsTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly two ops"; +} + +RENDERTHREAD_TEST(FrameBuilder, clippedMerging) { + class ClippedMergingTestRenderer : public TestRendererBase { + public: + void onMergedBitmapOps(const MergedBakedOpList& opList) override { + EXPECT_EQ(0, mIndex); + mIndex += opList.count; + EXPECT_EQ(4u, opList.count); + EXPECT_EQ(Rect(10, 10, 90, 90), opList.clip); + EXPECT_EQ(OpClipSideFlags::Left | OpClipSideFlags::Top | OpClipSideFlags::Right, + opList.clipSideFlags); + } + }; + auto node = TestUtils::createNode(0, 0, 100, 100, + [](RenderProperties& props, TestCanvas& canvas) { + SkBitmap bitmap = TestUtils::createSkBitmap(20, 20); + + // left side clipped (to inset left half) + canvas.clipRect(10, 0, 50, 100, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 0, 40, nullptr); + + // top side clipped (to inset top half) + canvas.clipRect(0, 10, 100, 50, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 40, 0, nullptr); + + // right side clipped (to inset right half) + canvas.clipRect(50, 0, 90, 100, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 80, 40, nullptr); + + // bottom not clipped, just abutting (inset bottom half) + canvas.clipRect(0, 50, 100, 90, SkRegion::kReplace_Op); + canvas.drawBitmap(bitmap, 40, 70, nullptr); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + ClippedMergingTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, textMerging) { + class TextMergingTestRenderer : public TestRendererBase { + public: + void onMergedTextOps(const MergedBakedOpList& opList) override { + EXPECT_EQ(0, mIndex); + mIndex += opList.count; + EXPECT_EQ(2u, opList.count); + EXPECT_EQ(OpClipSideFlags::Top, opList.clipSideFlags); + EXPECT_EQ(OpClipSideFlags::Top, opList.states[0]->computedState.clipSideFlags); + EXPECT_EQ(OpClipSideFlags::None, opList.states[1]->computedState.clipSideFlags); + } + }; + auto node = TestUtils::createNode(0, 0, 400, 400, + [](RenderProperties& props, TestCanvas& canvas) { + SkPaint paint; + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setAntiAlias(true); + paint.setTextSize(50); + TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped + TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped + }); + FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + TextMergingTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops"; +} + +RENDERTHREAD_TEST(FrameBuilder, textStrikethrough) { + const int LOOPS = 5; + class TextStrikethroughTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text"; + } + void onMergedTextOps(const MergedBakedOpList& opList) override { + EXPECT_EQ(0, mIndex); + mIndex += opList.count; + EXPECT_EQ(5u, opList.count); + } + }; + auto node = TestUtils::createNode(0, 0, 200, 2000, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkPaint textPaint; + textPaint.setAntiAlias(true); + textPaint.setTextSize(20); + textPaint.setStrikeThruText(true); + textPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + for (int i = 0; i < LOOPS; i++) { + TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1)); + } + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 2000), 200, 2000, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + TextStrikethroughTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2 * LOOPS, renderer.getIndex()) + << "Expect number of ops = 2 * loop count"; +} + +static auto styles = { + SkPaint::kFill_Style, SkPaint::kStroke_Style, SkPaint::kStrokeAndFill_Style }; + +RENDERTHREAD_TEST(FrameBuilder, textStyle) { + class TextStyleTestRenderer : public TestRendererBase { + public: + void onMergedTextOps(const MergedBakedOpList& opList) override { + ASSERT_EQ(0, mIndex); + ASSERT_EQ(3u, opList.count); + mIndex += opList.count; + + int index = 0; + for (auto style : styles) { + auto state = opList.states[index++]; + ASSERT_EQ(style, state->op->paint->getStyle()) + << "Remainder of validation relies upon stable merged order"; + ASSERT_EQ(0, state->computedState.clipSideFlags) + << "Clipped bounds validation requires unclipped ops"; + } + + Rect fill = opList.states[0]->computedState.clippedBounds; + Rect stroke = opList.states[1]->computedState.clippedBounds; + EXPECT_EQ(stroke, opList.states[2]->computedState.clippedBounds) + << "Stroke+Fill should be same as stroke"; + + EXPECT_TRUE(stroke.contains(fill)); + EXPECT_FALSE(fill.contains(stroke)); + + // outset by half the stroke width + Rect outsetFill(fill); + outsetFill.outset(5); + EXPECT_EQ(stroke, outsetFill); + } + }; + auto node = TestUtils::createNode(0, 0, 400, 400, + [](RenderProperties& props, TestCanvas& canvas) { + SkPaint paint; + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setAntiAlias(true); + paint.setTextSize(50); + paint.setStrokeWidth(10); + + // draw 3 copies of the same text overlapping, each with a different style. + // They'll get merged, but with + for (auto style : styles) { + paint.setStyle(style); + TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); + } + }); + FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + TextStyleTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(3, renderer.getIndex()) << "Expect 3 ops"; +} + +RENDERTHREAD_TEST(FrameBuilder, textureLayer_clipLocalMatrix) { + class TextureLayerClipLocalMatrixTestRenderer : public TestRendererBase { + public: + void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect()); + EXPECT_EQ(Rect(50, 50, 105, 105), state.computedState.clippedBounds); + + Matrix4 expected; + expected.loadTranslate(5, 5, 0); + EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); + } + }; + + auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100, + SkMatrix::MakeTrans(5, 5)); + + auto node = TestUtils::createNode(0, 0, 200, 200, + [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.clipRect(50, 50, 150, 150, SkRegion::kIntersect_Op); + canvas.drawLayer(layerUpdater.get()); + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + TextureLayerClipLocalMatrixTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, textureLayer_combineMatrices) { + class TextureLayerCombineMatricesTestRenderer : public TestRendererBase { + public: + void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + + Matrix4 expected; + expected.loadTranslate(35, 45, 0); + EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); + } + }; + + auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100, + SkMatrix::MakeTrans(5, 5)); + + auto node = TestUtils::createNode(0, 0, 200, 200, + [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.translate(30, 40); + canvas.drawLayer(layerUpdater.get()); + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + TextureLayerCombineMatricesTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, textureLayer_reject) { + auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100, + SkMatrix::MakeTrans(5, 5)); + layerUpdater->backingLayer()->setRenderTarget(GL_NONE); // Should be rejected + + auto node = TestUtils::createNode(0, 0, 200, 200, + [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { + canvas.drawLayer(layerUpdater.get()); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + FailRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +RENDERTHREAD_TEST(FrameBuilder, functor_reject) { + class FunctorTestRenderer : public TestRendererBase { + public: + void onFunctorOp(const FunctorOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + } + }; + Functor noopFunctor; + + // 1 million pixel tall view, scrolled down 80% + auto scrolledFunctorView = TestUtils::createNode(0, 0, 400, 1000000, + [&noopFunctor](RenderProperties& props, RecordingCanvas& canvas) { + canvas.translate(0, -800000); + canvas.callDrawGLFunction(&noopFunctor, nullptr); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(scrolledFunctorView)); + + FunctorTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected"; +} + +RENDERTHREAD_TEST(FrameBuilder, deferColorOp_unbounded) { + class ColorTestRenderer : public TestRendererBase { + public: + void onColorOp(const ColorOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds) + << "Color op should be expanded to bounds of surrounding"; + } + }; + + auto unclippedColorView = TestUtils::createNode(0, 0, 10, 10, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.setClipToBounds(false); + canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(unclippedColorView)); + + ColorTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()) << "ColorOp should not be rejected"; +} + +TEST(FrameBuilder, renderNode) { + class RenderNodeTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + switch(mIndex++) { + case 0: + EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds); + EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); + break; + case 1: + EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds); + EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); + break; + default: + ADD_FAILURE(); + } + } + }; + + auto child = TestUtils::createNode(10, 10, 110, 110, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + + auto parent = TestUtils::createNode(0, 0, 200, 200, + [&child](RenderProperties& props, RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorDKGRAY); + canvas.drawRect(0, 0, 200, 200, paint); + + canvas.save(SaveFlags::MatrixClip); + canvas.translate(40, 40); + canvas.drawRenderNode(child.get()); + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); + + RenderNodeTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, clipped) { + class ClippedTestRenderer : public TestRendererBase { + public: + void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clippedBounds); + EXPECT_EQ(Rect(10, 20, 30, 40), state.computedState.clipRect()); + EXPECT_TRUE(state.computedState.transform.isIdentity()); + } + }; + + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkBitmap bitmap = TestUtils::createSkBitmap(200, 200); + canvas.drawBitmap(bitmap, 0, 0, nullptr); + }); + + // clip to small area, should see in receiver + FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 20, 30, 40), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + ClippedTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +RENDERTHREAD_TEST(FrameBuilder, saveLayer_simple) { + class SaveLayerSimpleTestRenderer : public TestRendererBase { + public: + OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(180u, width); + EXPECT_EQ(180u, height); + return nullptr; + } + void endLayer() override { + EXPECT_EQ(2, mIndex++); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds); + EXPECT_EQ(Rect(180, 180), state.computedState.clippedBounds); + EXPECT_EQ(Rect(180, 180), state.computedState.clipRect()); + + Matrix4 expectedTransform; + expectedTransform.loadTranslate(-10, -10, 0); + EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(3, mIndex++); + EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); + EXPECT_EQ(Rect(200, 200), state.computedState.clipRect()); + EXPECT_TRUE(state.computedState.transform.isIdentity()); + } + void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { + EXPECT_EQ(4, mIndex++); + EXPECT_EQ(nullptr, offscreenBuffer); + } + }; + + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10, 10, 190, 190, 128, SaveFlags::ClipToLayer); + canvas.drawRect(10, 10, 190, 190, SkPaint()); + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SaveLayerSimpleTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(5, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, saveLayer_nested) { + /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as: + * - startTemporaryLayer2, rect2 endLayer2 + * - startTemporaryLayer1, rect1, drawLayer2, endLayer1 + * - startFrame, layerOp1, endFrame + */ + class SaveLayerNestedTestRenderer : public TestRendererBase { + public: + OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { + const int index = mIndex++; + if (index == 0) { + EXPECT_EQ(400u, width); + EXPECT_EQ(400u, height); + return (OffscreenBuffer*) 0x400; + } else if (index == 3) { + EXPECT_EQ(800u, width); + EXPECT_EQ(800u, height); + return (OffscreenBuffer*) 0x800; + } else { ADD_FAILURE(); } + return (OffscreenBuffer*) nullptr; + } + void endLayer() override { + int index = mIndex++; + EXPECT_TRUE(index == 2 || index == 6); + } + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { + EXPECT_EQ(7, mIndex++); + } + void endFrame(const Rect& repaintRect) override { + EXPECT_EQ(9, mIndex++); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + const int index = mIndex++; + if (index == 1) { + EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner rect + } else if (index == 4) { + EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer rect + } else { ADD_FAILURE(); } + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + const int index = mIndex++; + if (index == 5) { + EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle); + EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner layer + } else if (index == 8) { + EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle); + EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer layer + } else { ADD_FAILURE(); } + } + void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { + const int index = mIndex++; + // order isn't important, but we need to see both + if (index == 10) { + EXPECT_EQ((OffscreenBuffer*)0x400, offscreenBuffer); + } else if (index == 11) { + EXPECT_EQ((OffscreenBuffer*)0x800, offscreenBuffer); + } else { ADD_FAILURE(); } + } + }; + + auto node = TestUtils::createNode(0, 0, 800, 800, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 800, 800, 128, SaveFlags::ClipToLayer); + { + canvas.drawRect(0, 0, 800, 800, SkPaint()); + canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer); + { + canvas.drawRect(0, 0, 400, 400, SkPaint()); + } + canvas.restore(); + } + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(800, 800), 800, 800, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SaveLayerNestedTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(12, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, saveLayer_contentRejection) { + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); + canvas.saveLayerAlpha(200, 200, 400, 400, 128, SaveFlags::ClipToLayer); + + // draw within save layer may still be recorded, but shouldn't be drawn + canvas.drawRect(200, 200, 400, 400, SkPaint()); + + canvas.restore(); + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + FailRenderer renderer; + // should see no ops, even within the layer, since the layer should be rejected + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_simple) { + class SaveLayerUnclippedSimpleTestRenderer : public TestRendererBase { + public: + void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); + EXPECT_CLIP_RECT(Rect(200, 200), state.computedState.clipState); + EXPECT_TRUE(state.computedState.transform.isIdentity()); + } + void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + ASSERT_NE(nullptr, op.paint); + ASSERT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint)); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(2, mIndex++); + EXPECT_EQ(Rect(200, 200), op.unmappedBounds); + EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds); + EXPECT_EQ(Rect(200, 200), state.computedState.clipRect()); + EXPECT_TRUE(state.computedState.transform.isIdentity()); + } + void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(3, mIndex++); + EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); + EXPECT_CLIP_RECT(Rect(200, 200), state.computedState.clipState); + EXPECT_TRUE(state.computedState.transform.isIdentity()); + } + }; + + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0)); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SaveLayerUnclippedSimpleTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_mergedClears) { + class SaveLayerUnclippedMergedClearsTestRenderer : public TestRendererBase { + public: + void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { + int index = mIndex++; + EXPECT_GT(4, index); + EXPECT_EQ(5, op.unmappedBounds.getWidth()); + EXPECT_EQ(5, op.unmappedBounds.getHeight()); + if (index == 0) { + EXPECT_EQ(Rect(10, 10), state.computedState.clippedBounds); + } else if (index == 1) { + EXPECT_EQ(Rect(190, 0, 200, 10), state.computedState.clippedBounds); + } else if (index == 2) { + EXPECT_EQ(Rect(0, 190, 10, 200), state.computedState.clippedBounds); + } else if (index == 3) { + EXPECT_EQ(Rect(190, 190, 200, 200), state.computedState.clippedBounds); + } + } + void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { + EXPECT_EQ(4, mIndex++); + ASSERT_EQ(op.vertexCount, 16u); + for (size_t i = 0; i < op.vertexCount; i++) { + auto v = op.vertices[i]; + EXPECT_TRUE(v.x == 0 || v.x == 10 || v.x == 190 || v.x == 200); + EXPECT_TRUE(v.y == 0 || v.y == 10 || v.y == 190 || v.y == 200); + } + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(5, mIndex++); + } + void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { + EXPECT_LT(5, mIndex++); + } + }; + + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + + int restoreTo = canvas.save(SaveFlags::MatrixClip); + canvas.scale(2, 2); + canvas.saveLayerAlpha(0, 0, 5, 5, 128, SaveFlags::MatrixClip); + canvas.saveLayerAlpha(95, 0, 100, 5, 128, SaveFlags::MatrixClip); + canvas.saveLayerAlpha(0, 95, 5, 100, 128, SaveFlags::MatrixClip); + canvas.saveLayerAlpha(95, 95, 100, 100, 128, SaveFlags::MatrixClip); + canvas.drawRect(0, 0, 100, 100, SkPaint()); + canvas.restoreToCount(restoreTo); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SaveLayerUnclippedMergedClearsTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(10, renderer.getIndex()) + << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect."; +} + +RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_clearClip) { + class SaveLayerUnclippedClearClipTestRenderer : public TestRendererBase { + public: + void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + } + void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + ASSERT_NE(nullptr, op.paint); + EXPECT_EQ(SkXfermode::kClear_Mode, PaintUtils::getXfermodeDirect(op.paint)); + EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds) + << "Expect dirty rect as clip"; + ASSERT_NE(nullptr, state.computedState.clipState); + EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipState->rect); + EXPECT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(2, mIndex++); + } + void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(3, mIndex++); + } + }; + + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + // save smaller than clip, so we get unclipped behavior + canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0)); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.restore(); + }); + + // draw with partial screen dirty, and assert we see that rect later + FrameBuilder frameBuilder(SkRect::MakeLTRB(50, 50, 150, 150), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SaveLayerUnclippedClearClipTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_reject) { + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + // unclipped savelayer + rect both in area that won't intersect with dirty + canvas.saveLayerAlpha(100, 100, 200, 200, 128, (SaveFlags::Flags)(0)); + canvas.drawRect(100, 100, 200, 200, SkPaint()); + canvas.restore(); + }); + + // draw with partial screen dirty that doesn't intersect with savelayer + FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + FailRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +/* saveLayerUnclipped { saveLayer { saveLayerUnclipped { rect } } } will play back as: + * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer + * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe + */ +RENDERTHREAD_TEST(FrameBuilder, saveLayerUnclipped_complex) { + class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase { + public: + OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) { + EXPECT_EQ(0, mIndex++); // savelayer first + return (OffscreenBuffer*)0xabcd; + } + void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { + int index = mIndex++; + EXPECT_TRUE(index == 1 || index == 7); + } + void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { + int index = mIndex++; + EXPECT_TRUE(index == 2 || index == 8); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(3, mIndex++); + Matrix4 expected; + expected.loadTranslate(-100, -100, 0); + EXPECT_EQ(Rect(100, 100, 200, 200), state.computedState.clippedBounds); + EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); + } + void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { + int index = mIndex++; + EXPECT_TRUE(index == 4 || index == 10); + } + void endLayer() override { + EXPECT_EQ(5, mIndex++); + } + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { + EXPECT_EQ(6, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(9, mIndex++); + EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle); + } + void endFrame(const Rect& repaintRect) override { + EXPECT_EQ(11, mIndex++); + } + void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { + EXPECT_EQ(12, mIndex++); + EXPECT_EQ((OffscreenBuffer*)0xabcd, offscreenBuffer); + } + }; + + auto node = TestUtils::createNode(0, 0, 600, 600, // 500x500 triggers clipping + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SaveFlags::Flags)0); // unclipped + canvas.saveLayerAlpha(100, 100, 400, 400, 128, SaveFlags::ClipToLayer); // clipped + canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SaveFlags::Flags)0); // unclipped + canvas.drawRect(200, 200, 300, 300, SkPaint()); + canvas.restore(); + canvas.restore(); + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(600, 600), 600, 600, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + SaveLayerUnclippedComplexTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(13, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, hwLayer_simple) { + class HwLayerSimpleTestRenderer : public TestRendererBase { + public: + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(100u, offscreenBuffer->viewportWidth); + EXPECT_EQ(100u, offscreenBuffer->viewportHeight); + EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + + EXPECT_TRUE(state.computedState.transform.isIdentity()) + << "Transform should be reset within layer"; + + EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect()) + << "Damage rect should be used to clip layer content"; + } + void endLayer() override { + EXPECT_EQ(2, mIndex++); + } + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { + EXPECT_EQ(3, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(4, mIndex++); + } + void endFrame(const Rect& repaintRect) override { + EXPECT_EQ(5, mIndex++); + } + }; + + auto node = TestUtils::createNode(10, 10, 110, 110, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + OffscreenBuffer** layerHandle = node->getLayerHandle(); + + // create RenderNode's layer here in same way prepareTree would + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); + *layerHandle = &layer; + + auto syncedNode = TestUtils::getSyncedNode(node); + + // only enqueue partial damage + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid + layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferLayers(layerUpdateQueue); + frameBuilder.deferRenderNode(*syncedNode); + + HwLayerSimpleTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(6, renderer.getIndex()); + + // clean up layer pointer, so we can safely destruct RenderNode + *layerHandle = nullptr; +} + +RENDERTHREAD_TEST(FrameBuilder, hwLayer_complex) { + /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as: + * - startRepaintLayer(child), rect(grey), endLayer + * - startTemporaryLayer, drawLayer(child), endLayer + * - startRepaintLayer(parent), rect(white), drawLayer(saveLayer), endLayer + * - startFrame, drawLayer(parent), endLayerb + */ + class HwLayerComplexTestRenderer : public TestRendererBase { + public: + OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) { + EXPECT_EQ(3, mIndex++); // savelayer first + return (OffscreenBuffer*)0xabcd; + } + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { + int index = mIndex++; + if (index == 0) { + // starting inner layer + EXPECT_EQ(100u, offscreenBuffer->viewportWidth); + EXPECT_EQ(100u, offscreenBuffer->viewportHeight); + } else if (index == 6) { + // starting outer layer + EXPECT_EQ(200u, offscreenBuffer->viewportWidth); + EXPECT_EQ(200u, offscreenBuffer->viewportHeight); + } else { ADD_FAILURE(); } + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + int index = mIndex++; + if (index == 1) { + // inner layer's rect (white) + EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); + } else if (index == 7) { + // outer layer's rect (grey) + EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); + } else { ADD_FAILURE(); } + } + void endLayer() override { + int index = mIndex++; + EXPECT_TRUE(index == 2 || index == 5 || index == 9); + } + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { + EXPECT_EQ(10, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + OffscreenBuffer* layer = *op.layerHandle; + int index = mIndex++; + if (index == 4) { + EXPECT_EQ(100u, layer->viewportWidth); + EXPECT_EQ(100u, layer->viewportHeight); + } else if (index == 8) { + EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle); + } else if (index == 11) { + EXPECT_EQ(200u, layer->viewportWidth); + EXPECT_EQ(200u, layer->viewportHeight); + } else { ADD_FAILURE(); } + } + void endFrame(const Rect& repaintRect) override { + EXPECT_EQ(12, mIndex++); + } + void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { + EXPECT_EQ(13, mIndex++); + } + }; + + auto child = TestUtils::createNode(50, 50, 150, 150, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100); + *(child->getLayerHandle()) = &childLayer; + + RenderNode* childPtr = child.get(); + auto parent = TestUtils::createNode(0, 0, 200, 200, + [childPtr](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); + SkPaint paint; + paint.setColor(SK_ColorDKGRAY); + canvas.drawRect(0, 0, 200, 200, paint); + + canvas.saveLayerAlpha(50, 50, 150, 150, 128, SaveFlags::ClipToLayer); + canvas.drawRenderNode(childPtr); + canvas.restore(); + }); + OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200); + *(parent->getLayerHandle()) = &parentLayer; + + auto syncedNode = TestUtils::getSyncedNode(parent); + + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid + layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100)); + layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200)); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferLayers(layerUpdateQueue); + frameBuilder.deferRenderNode(*syncedNode); + + HwLayerComplexTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(14, renderer.getIndex()); + + // clean up layer pointers, so we can safely destruct RenderNodes + *(child->getLayerHandle()) = nullptr; + *(parent->getLayerHandle()) = nullptr; +} + + +RENDERTHREAD_TEST(FrameBuilder, buildLayer) { + class BuildLayerTestRenderer : public TestRendererBase { + public: + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(100u, offscreenBuffer->viewportWidth); + EXPECT_EQ(100u, offscreenBuffer->viewportHeight); + EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect); + } + void onColorOp(const ColorOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + + EXPECT_TRUE(state.computedState.transform.isIdentity()) + << "Transform should be reset within layer"; + + EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect()) + << "Damage rect should be used to clip layer content"; + } + void endLayer() override { + EXPECT_EQ(2, mIndex++); + } + void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { + ADD_FAILURE() << "Primary frame draw not expected in this test"; + } + void endFrame(const Rect& repaintRect) override { + ADD_FAILURE() << "Primary frame draw not expected in this test"; + } + }; + + auto node = TestUtils::createNode(10, 10, 110, 110, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); + canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode); + }); + OffscreenBuffer** layerHandle = node->getLayerHandle(); + + // create RenderNode's layer here in same way prepareTree would + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); + *layerHandle = &layer; + + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + + // only enqueue partial damage + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid + layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); + + // Draw, but pass empty node list, so no work is done for primary frame + FrameBuilder frameBuilder(layerUpdateQueue, sLightGeometry, Caches::getInstance()); + BuildLayerTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(3, renderer.getIndex()); + + // clean up layer pointer, so we can safely destruct RenderNode + *layerHandle = nullptr; +} + +static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) { + SkPaint paint; + paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel + canvas->drawRect(0, 0, 100, 100, paint); +} +static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) { + auto node = TestUtils::createNode(0, 0, 100, 100, + [expectedDrawOrder](RenderProperties& props, RecordingCanvas& canvas) { + drawOrderedRect(&canvas, expectedDrawOrder); + }); + node->mutateStagingProperties().setTranslationZ(z); + node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z); + canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership +} +RENDERTHREAD_TEST(FrameBuilder, zReorder) { + class ZReorderTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel + EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order"; + } + }; + + auto parent = TestUtils::createNode(0, 0, 100, 100, + [](RenderProperties& props, RecordingCanvas& canvas) { + drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder + drawOrderedRect(&canvas, 1); + canvas.insertReorderBarrier(true); + drawOrderedNode(&canvas, 6, 2.0f); + drawOrderedRect(&canvas, 3); + drawOrderedNode(&canvas, 4, 0.0f); + drawOrderedRect(&canvas, 5); + drawOrderedNode(&canvas, 2, -2.0f); + drawOrderedNode(&canvas, 7, 2.0f); + canvas.insertReorderBarrier(false); + drawOrderedRect(&canvas, 8); + drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder + }); + FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); + + ZReorderTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(10, renderer.getIndex()); +}; + +RENDERTHREAD_TEST(FrameBuilder, projectionReorder) { + static const int scrollX = 5; + static const int scrollY = 10; + class ProjectionReorderTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + const int index = mIndex++; + + Matrix4 expectedMatrix; + switch (index) { + case 0: + EXPECT_EQ(Rect(100, 100), op.unmappedBounds); + EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); + expectedMatrix.loadIdentity(); + EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask); + break; + case 1: + EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds); + EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); + expectedMatrix.loadTranslate(50 - scrollX, 50 - scrollY, 0); + ASSERT_NE(nullptr, state.computedState.localProjectionPathMask); + EXPECT_EQ(Rect(-35, -30, 45, 50), + Rect(state.computedState.localProjectionPathMask->getBounds())); + break; + case 2: + EXPECT_EQ(Rect(100, 50), op.unmappedBounds); + EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); + expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0); + EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask); + break; + default: + ADD_FAILURE(); + } + EXPECT_EQ(expectedMatrix, state.computedState.transform); + } + }; + + /** + * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C) + * with a projecting child (P) of its own. P would normally draw between B and C's "background" + * draw, but because it is projected backwards, it's drawn in between B and C. + * + * The parent is scrolled by scrollX/scrollY, but this does not affect the background + * (which isn't affected by scroll). + */ + auto receiverBackground = TestUtils::createNode(0, 0, 100, 100, + [](RenderProperties& properties, RecordingCanvas& canvas) { + properties.setProjectionReceiver(true); + // scroll doesn't apply to background, so undone via translationX/Y + // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver! + properties.setTranslationX(scrollX); + properties.setTranslationY(scrollY); + + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + auto projectingRipple = TestUtils::createNode(50, 0, 100, 50, + [](RenderProperties& properties, RecordingCanvas& canvas) { + properties.setProjectBackwards(true); + properties.setClipToBounds(false); + SkPaint paint; + paint.setColor(SK_ColorDKGRAY); + canvas.drawRect(-10, -10, 60, 60, paint); + }); + auto child = TestUtils::createNode(0, 50, 100, 100, + [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorBLUE); + canvas.drawRect(0, 0, 100, 50, paint); + canvas.drawRenderNode(projectingRipple.get()); + }); + auto parent = TestUtils::createNode(0, 0, 100, 100, + [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { + // Set a rect outline for the projecting ripple to be masked against. + properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f); + + canvas.save(SaveFlags::MatrixClip); + canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally) + canvas.drawRenderNode(receiverBackground.get()); + canvas.drawRenderNode(child.get()); + canvas.restore(); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); + + ProjectionReorderTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(3, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, projectionHwLayer) { + static const int scrollX = 5; + static const int scrollY = 10; + class ProjectionHwLayerTestRenderer : public TestRendererBase { + public: + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { + EXPECT_EQ(0, mIndex++); + } + void onArcOp(const ArcOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); + } + void endLayer() override { + EXPECT_EQ(2, mIndex++); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(3, mIndex++); + ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); + } + void onOvalOp(const OvalOp& op, const BakedOpState& state) override { + EXPECT_EQ(4, mIndex++); + ASSERT_NE(nullptr, state.computedState.localProjectionPathMask); + Matrix4 expected; + expected.loadTranslate(100 - scrollX, 100 - scrollY, 0); + EXPECT_EQ(expected, state.computedState.transform); + EXPECT_EQ(Rect(-85, -80, 295, 300), + Rect(state.computedState.localProjectionPathMask->getBounds())); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(5, mIndex++); + ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); + } + }; + auto receiverBackground = TestUtils::createNode(0, 0, 400, 400, + [](RenderProperties& properties, RecordingCanvas& canvas) { + properties.setProjectionReceiver(true); + // scroll doesn't apply to background, so undone via translationX/Y + // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver! + properties.setTranslationX(scrollX); + properties.setTranslationY(scrollY); + + canvas.drawRect(0, 0, 400, 400, SkPaint()); + }); + auto projectingRipple = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& properties, RecordingCanvas& canvas) { + properties.setProjectBackwards(true); + properties.setClipToBounds(false); + canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds + }); + auto child = TestUtils::createNode(100, 100, 300, 300, + [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { + properties.mutateLayerProperties().setType(LayerType::RenderLayer); + canvas.drawRenderNode(projectingRipple.get()); + canvas.drawArc(0, 0, 200, 200, 0.0f, 280.0f, true, SkPaint()); + }); + auto parent = TestUtils::createNode(0, 0, 400, 400, + [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { + // Set a rect outline for the projecting ripple to be masked against. + properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f); + canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally) + canvas.drawRenderNode(receiverBackground.get()); + canvas.drawRenderNode(child.get()); + }); + + OffscreenBuffer** layerHandle = child->getLayerHandle(); + + // create RenderNode's layer here in same way prepareTree would, setting windowTransform + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200, 200); + Matrix4 windowTransform; + windowTransform.loadTranslate(100, 100, 0); // total transform of layer's origin + layer.setWindowTransform(windowTransform); + *layerHandle = &layer; + + auto syncedNode = TestUtils::getSyncedNode(parent); + + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid + layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200)); + + FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferLayers(layerUpdateQueue); + frameBuilder.deferRenderNode(*syncedNode); + + ProjectionHwLayerTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(6, renderer.getIndex()); + + // clean up layer pointer, so we can safely destruct RenderNode + *layerHandle = nullptr; +} + +RENDERTHREAD_TEST(FrameBuilder, projectionChildScroll) { + static const int scrollX = 500000; + static const int scrollY = 0; + class ProjectionChildScrollTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_TRUE(state.computedState.transform.isIdentity()); + } + void onOvalOp(const OvalOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + ASSERT_NE(nullptr, state.computedState.clipState); + ASSERT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode); + ASSERT_EQ(Rect(400, 400), state.computedState.clipState->rect); + EXPECT_TRUE(state.computedState.transform.isIdentity()); + } + }; + auto receiverBackground = TestUtils::createNode(0, 0, 400, 400, + [](RenderProperties& properties, RecordingCanvas& canvas) { + properties.setProjectionReceiver(true); + canvas.drawRect(0, 0, 400, 400, SkPaint()); + }); + auto projectingRipple = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& properties, RecordingCanvas& canvas) { + // scroll doesn't apply to background, so undone via translationX/Y + // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver! + properties.setTranslationX(scrollX); + properties.setTranslationY(scrollY); + properties.setProjectBackwards(true); + properties.setClipToBounds(false); + canvas.drawOval(0, 0, 200, 200, SkPaint()); + }); + auto child = TestUtils::createNode(0, 0, 400, 400, + [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { + // Record time clip will be ignored by projectee + canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op); + + canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally) + canvas.drawRenderNode(projectingRipple.get()); + }); + auto parent = TestUtils::createNode(0, 0, 400, 400, + [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { + canvas.drawRenderNode(receiverBackground.get()); + canvas.drawRenderNode(child.get()); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); + + ProjectionChildScrollTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()); +} + +// creates a 100x100 shadow casting node with provided translationZ +static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) { + return TestUtils::createNode(0, 0, 100, 100, + [translationZ](RenderProperties& properties, RecordingCanvas& canvas) { + properties.setTranslationZ(translationZ); + properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f); + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); +} + +RENDERTHREAD_TEST(FrameBuilder, shadow) { + class ShadowTestRenderer : public TestRendererBase { + public: + void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_FLOAT_EQ(1.0f, op.casterAlpha); + EXPECT_TRUE(op.shadowTask->casterPerimeter.isRect(nullptr)); + EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowTask->transformXY); + + Matrix4 expectedZ; + expectedZ.loadTranslate(0, 0, 5); + EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowTask->transformZ); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + } + }; + + auto parent = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.insertReorderBarrier(true); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); + + ShadowTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, shadowSaveLayer) { + class ShadowSaveLayerTestRenderer : public TestRendererBase { + public: + OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { + EXPECT_EQ(0, mIndex++); + return nullptr; + } + void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x); + EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(2, mIndex++); + } + void endLayer() override { + EXPECT_EQ(3, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(4, mIndex++); + } + void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { + EXPECT_EQ(5, mIndex++); + } + }; + + auto parent = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + // save/restore outside of reorderBarrier, so they don't get moved out of place + canvas.translate(20, 10); + int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SaveFlags::ClipToLayer); + canvas.insertReorderBarrier(true); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + canvas.insertReorderBarrier(false); + canvas.restoreToCount(count); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); + + ShadowSaveLayerTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(6, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, shadowHwLayer) { + class ShadowHwLayerTestRenderer : public TestRendererBase { + public: + void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { + EXPECT_EQ(0, mIndex++); + } + void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x); + EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y); + EXPECT_FLOAT_EQ(30, op.shadowTask->lightRadius); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(2, mIndex++); + } + void endLayer() override { + EXPECT_EQ(3, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(4, mIndex++); + } + }; + + auto parent = TestUtils::createNode(50, 60, 150, 160, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); + canvas.insertReorderBarrier(true); + canvas.save(SaveFlags::MatrixClip); + canvas.translate(20, 10); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + canvas.restore(); + }); + OffscreenBuffer** layerHandle = parent->getLayerHandle(); + + // create RenderNode's layer here in same way prepareTree would, setting windowTransform + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); + Matrix4 windowTransform; + windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin + layer.setWindowTransform(windowTransform); + *layerHandle = &layer; + + auto syncedNode = TestUtils::getSyncedNode(parent); + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid + layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100)); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 30}, Caches::getInstance()); + frameBuilder.deferLayers(layerUpdateQueue); + frameBuilder.deferRenderNode(*syncedNode); + + ShadowHwLayerTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(5, renderer.getIndex()); + + // clean up layer pointer, so we can safely destruct RenderNode + *layerHandle = nullptr; +} + +RENDERTHREAD_TEST(FrameBuilder, shadowLayering) { + class ShadowLayeringTestRenderer : public TestRendererBase { + public: + void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { + int index = mIndex++; + EXPECT_TRUE(index == 0 || index == 1); + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + int index = mIndex++; + EXPECT_TRUE(index == 2 || index == 3); + } + }; + auto parent = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.insertReorderBarrier(true); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get()); + }); + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); + + ShadowLayeringTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + +RENDERTHREAD_TEST(FrameBuilder, shadowClipping) { + class ShadowClippingTestRenderer : public TestRendererBase { + public: + void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipState->rect) + << "Shadow must respect pre-barrier canvas clip value."; + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + } + }; + auto parent = TestUtils::createNode(0, 0, 100, 100, + [](RenderProperties& props, RecordingCanvas& canvas) { + // Apply a clip before the reorder barrier/shadow casting child is drawn. + // This clip must be applied to the shadow cast by the child. + canvas.clipRect(25, 25, 75, 75, SkRegion::kIntersect_Op); + canvas.insertReorderBarrier(true); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, + (FrameBuilder::LightGeometry) {{ 100, 100, 100 }, 50}, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); + + ShadowClippingTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()); +} + +static void testProperty(std::function<void(RenderProperties&)> propSetupCallback, + std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) { + class PropertyTestRenderer : public TestRendererBase { + public: + PropertyTestRenderer(std::function<void(const RectOp&, const BakedOpState&)> callback) + : mCallback(callback) {} + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(mIndex++, 0); + mCallback(op, state); + } + std::function<void(const RectOp&, const BakedOpState&)> mCallback; + }; + + auto node = TestUtils::createNode(0, 0, 100, 100, + [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) { + propSetupCallback(props); + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + + FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + PropertyTestRenderer renderer(opValidateCallback); + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op"; +} + +RENDERTHREAD_TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) { + testProperty([](RenderProperties& properties) { + properties.setAlpha(0.5f); + properties.setHasOverlappingRendering(false); + }, [](const RectOp& op, const BakedOpState& state) { + EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op"; + }); +} + +RENDERTHREAD_TEST(FrameBuilder, renderPropClipping) { + testProperty([](RenderProperties& properties) { + properties.setClipToBounds(true); + properties.setClipBounds(Rect(10, 20, 300, 400)); + }, [](const RectOp& op, const BakedOpState& state) { + EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds) + << "Clip rect should be intersection of node bounds and clip bounds"; + }); +} + +RENDERTHREAD_TEST(FrameBuilder, renderPropRevealClip) { + testProperty([](RenderProperties& properties) { + properties.mutableRevealClip().set(true, 50, 50, 25); + }, [](const RectOp& op, const BakedOpState& state) { + ASSERT_NE(nullptr, state.roundRectClipState); + EXPECT_TRUE(state.roundRectClipState->highPriority); + EXPECT_EQ(25, state.roundRectClipState->radius); + EXPECT_EQ(Rect(50, 50, 50, 50), state.roundRectClipState->innerRect); + }); +} + +RENDERTHREAD_TEST(FrameBuilder, renderPropOutlineClip) { + testProperty([](RenderProperties& properties) { + properties.mutableOutline().setShouldClip(true); + properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f); + }, [](const RectOp& op, const BakedOpState& state) { + ASSERT_NE(nullptr, state.roundRectClipState); + EXPECT_FALSE(state.roundRectClipState->highPriority); + EXPECT_EQ(5, state.roundRectClipState->radius); + EXPECT_EQ(Rect(15, 25, 25, 35), state.roundRectClipState->innerRect); + }); +} + +RENDERTHREAD_TEST(FrameBuilder, renderPropTransform) { + testProperty([](RenderProperties& properties) { + properties.setLeftTopRightBottom(10, 10, 110, 110); + + SkMatrix staticMatrix = SkMatrix::MakeScale(1.2f, 1.2f); + properties.setStaticMatrix(&staticMatrix); + + // ignored, since static overrides animation + SkMatrix animationMatrix = SkMatrix::MakeTrans(15, 15); + properties.setAnimationMatrix(&animationMatrix); + + properties.setTranslationX(10); + properties.setTranslationY(20); + properties.setScaleX(0.5f); + properties.setScaleY(0.7f); + }, [](const RectOp& op, const BakedOpState& state) { + Matrix4 matrix; + matrix.loadTranslate(10, 10, 0); // left, top + matrix.scale(1.2f, 1.2f, 1); // static matrix + // ignore animation matrix, since static overrides it + + // translation xy + matrix.translate(10, 20); + + // scale xy (from default pivot - center) + matrix.translate(50, 50); + matrix.scale(0.5f, 0.7f, 1); + matrix.translate(-50, -50); + EXPECT_MATRIX_APPROX_EQ(matrix, state.computedState.transform) + << "Op draw matrix must match expected combination of transformation properties"; + }); +} + +struct SaveLayerAlphaData { + uint32_t layerWidth = 0; + uint32_t layerHeight = 0; + Rect rectClippedBounds; + Matrix4 rectMatrix; +}; +/** + * Constructs a view to hit the temporary layer alpha property implementation: + * a) 0 < alpha < 1 + * b) too big for layer (larger than maxTextureSize) + * c) overlapping rendering content + * returning observed data about layer size and content clip/transform. + * + * Used to validate clipping behavior of temporary layer, where requested layer size is reduced + * (for efficiency, and to fit in layer size constraints) based on parent clip. + */ +void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData, + std::function<void(RenderProperties&)> propSetupCallback) { + class SaveLayerAlphaClipTestRenderer : public TestRendererBase { + public: + SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData) + : mOutData(outData) {} + + OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { + EXPECT_EQ(0, mIndex++); + mOutData->layerWidth = width; + mOutData->layerHeight = height; + return nullptr; + } + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_EQ(1, mIndex++); + + mOutData->rectClippedBounds = state.computedState.clippedBounds; + mOutData->rectMatrix = state.computedState.transform; + } + void endLayer() override { + EXPECT_EQ(2, mIndex++); + } + void onLayerOp(const LayerOp& op, const BakedOpState& state) override { + EXPECT_EQ(3, mIndex++); + } + void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { + EXPECT_EQ(4, mIndex++); + } + private: + SaveLayerAlphaData* mOutData; + }; + + ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize()) + << "Node must be bigger than max texture size to exercise saveLayer codepath"; + auto node = TestUtils::createNode(0, 0, 10000, 10000, + [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) { + properties.setHasOverlappingRendering(true); + properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer + // apply other properties + propSetupCallback(properties); + + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 10000, 10000, paint); + }); + auto syncedNode = TestUtils::getSyncedNode(node); // sync before querying height + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*syncedNode); + + SaveLayerAlphaClipTestRenderer renderer(outObservedData); + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + + // assert, since output won't be valid if we haven't seen a save layer triggered + ASSERT_EQ(5, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior."; +} + +RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaClipBig) { + SaveLayerAlphaData observedData; + testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { + properties.setTranslationX(10); // offset rendering content + properties.setTranslationY(-2000); // offset rendering content + }); + EXPECT_EQ(190u, observedData.layerWidth); + EXPECT_EQ(200u, observedData.layerHeight); + EXPECT_EQ(Rect(190, 200), observedData.rectClippedBounds) + << "expect content to be clipped to screen area"; + Matrix4 expected; + expected.loadTranslate(0, -2000, 0); + EXPECT_MATRIX_APPROX_EQ(expected, observedData.rectMatrix) + << "expect content to be translated as part of being clipped"; +} + +RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) { + SaveLayerAlphaData observedData; + testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { + // Translate and rotate the view so that the only visible part is the top left corner of + // the view. It will form an isosceles right triangle with a long side length of 200 at the + // bottom of the viewport. + properties.setTranslationX(100); + properties.setTranslationY(100); + properties.setPivotX(0); + properties.setPivotY(0); + properties.setRotation(45); + }); + // ceil(sqrt(2) / 2 * 200) = 142 + EXPECT_EQ(142u, observedData.layerWidth); + EXPECT_EQ(142u, observedData.layerHeight); + EXPECT_EQ(Rect(142, 142), observedData.rectClippedBounds); + EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); +} + +RENDERTHREAD_TEST(FrameBuilder, renderPropSaveLayerAlphaScale) { + SaveLayerAlphaData observedData; + testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { + properties.setPivotX(0); + properties.setPivotY(0); + properties.setScaleX(2); + properties.setScaleY(0.5f); + }); + EXPECT_EQ(100u, observedData.layerWidth); + EXPECT_EQ(400u, observedData.layerHeight); + EXPECT_EQ(Rect(100, 400), observedData.rectClippedBounds); + EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); +} + +RENDERTHREAD_TEST(FrameBuilder, clip_replace) { + class ClipReplaceTestRenderer : public TestRendererBase { + public: + void onColorOp(const ColorOp& op, const BakedOpState& state) override { + EXPECT_EQ(0, mIndex++); + EXPECT_TRUE(op.localClip->intersectWithRoot); + EXPECT_EQ(Rect(20, 10, 30, 40), state.computedState.clipState->rect) + << "Expect resolved clip to be intersection of viewport clip and clip op"; + } + }; + auto node = TestUtils::createNode(20, 20, 30, 30, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.clipRect(0, -20, 10, 30, SkRegion::kReplace_Op); + canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode); + }); + + FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 40, 40), 50, 50, + sLightGeometry, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + + ClipReplaceTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/tests/unit/GlopBuilderTests.cpp b/libs/hwui/tests/unit/GlopBuilderTests.cpp new file mode 100644 index 000000000000..95543d33b1ef --- /dev/null +++ b/libs/hwui/tests/unit/GlopBuilderTests.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "Glop.h" +#include "GlopBuilder.h" +#include "Rect.h" +#include "tests/common/TestUtils.h" +#include "utils/Color.h" + +#include <SkPaint.h> + +using namespace android::uirenderer; + +static void expectFillEq(Glop::Fill& expectedFill, Glop::Fill& builtFill) { + EXPECT_EQ(expectedFill.colorEnabled, builtFill.colorEnabled); + if (expectedFill.colorEnabled) + EXPECT_EQ(expectedFill.color, builtFill.color); + + EXPECT_EQ(expectedFill.filterMode, builtFill.filterMode); + if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Blend) { + EXPECT_EQ(expectedFill.filter.color, builtFill.filter.color); + } else if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Matrix) { + Glop::Fill::Filter::Matrix& expectedMatrix = expectedFill.filter.matrix; + Glop::Fill::Filter::Matrix& builtMatrix = expectedFill.filter.matrix; + EXPECT_TRUE(std::memcmp(expectedMatrix.matrix, builtMatrix.matrix, + sizeof(Glop::Fill::Filter::Matrix::matrix))); + EXPECT_TRUE(std::memcmp(expectedMatrix.vector, builtMatrix.vector, + sizeof(Glop::Fill::Filter::Matrix::vector))); + } + EXPECT_EQ(expectedFill.skiaShaderData.skiaShaderType, builtFill.skiaShaderData.skiaShaderType); + EXPECT_EQ(expectedFill.texture.clamp, builtFill.texture.clamp); + EXPECT_EQ(expectedFill.texture.filter, builtFill.texture.filter); + EXPECT_EQ(expectedFill.texture.target, builtFill.texture.target); + EXPECT_EQ(expectedFill.texture.textureTransform, builtFill.texture.textureTransform); +} + +static void expectBlendEq(Glop::Blend& expectedBlend, Glop::Blend& builtBlend) { + EXPECT_EQ(expectedBlend.src, builtBlend.src); + EXPECT_EQ(expectedBlend.dst, builtBlend.dst); +} + +static void expectMeshEq(Glop::Mesh& expectedMesh, Glop::Mesh& builtMesh) { + EXPECT_EQ(expectedMesh.elementCount, builtMesh.elementCount); + EXPECT_EQ(expectedMesh.primitiveMode, builtMesh.primitiveMode); + EXPECT_EQ(expectedMesh.indices.indices, builtMesh.indices.indices); + EXPECT_EQ(expectedMesh.indices.bufferObject, builtMesh.indices.bufferObject); + EXPECT_EQ(expectedMesh.vertices.attribFlags, builtMesh.vertices.attribFlags); + EXPECT_EQ(expectedMesh.vertices.bufferObject, builtMesh.vertices.bufferObject); + EXPECT_EQ(expectedMesh.vertices.color, builtMesh.vertices.color); + EXPECT_EQ(expectedMesh.vertices.position, builtMesh.vertices.position); + EXPECT_EQ(expectedMesh.vertices.stride, builtMesh.vertices.stride); + EXPECT_EQ(expectedMesh.vertices.texCoord, builtMesh.vertices.texCoord); + + if (builtMesh.vertices.position) { + for (int i = 0; i < 4; i++) { + TextureVertex& expectedVertex = expectedMesh.mappedVertices[i]; + TextureVertex& builtVertex = builtMesh.mappedVertices[i]; + EXPECT_EQ(expectedVertex.u, builtVertex.u); + EXPECT_EQ(expectedVertex.v, builtVertex.v); + EXPECT_EQ(expectedVertex.x, builtVertex.x); + EXPECT_EQ(expectedVertex.y, builtVertex.y); + } + } +} + +static void expectTransformEq(Glop::Transform& expectedTransform, Glop::Transform& builtTransform) { + EXPECT_EQ(expectedTransform.canvas, builtTransform.canvas); + EXPECT_EQ(expectedTransform.modelView, builtTransform.modelView); + EXPECT_EQ(expectedTransform.transformFlags, expectedTransform.transformFlags); +} + +static void expectGlopEq(Glop& expectedGlop, Glop& builtGlop) { +#if !HWUI_NEW_OPS + EXPECT_EQ(expectedGlop.bounds, builtGlop.bounds); +#endif + expectBlendEq(expectedGlop.blend, builtGlop.blend); + expectFillEq(expectedGlop.fill, builtGlop.fill); + expectMeshEq(expectedGlop.mesh, builtGlop.mesh); + expectTransformEq(expectedGlop.transform, builtGlop.transform); +} + +static std::unique_ptr<Glop> blackUnitQuadGlop(RenderState& renderState) { + std::unique_ptr<Glop> glop(new Glop()); + glop->blend = { GL_ZERO, GL_ZERO }; + glop->mesh.elementCount = 4; + glop->mesh.primitiveMode = GL_TRIANGLE_STRIP; + glop->mesh.indices.indices = nullptr; + glop->mesh.indices.bufferObject = GL_ZERO; + glop->mesh.vertices = { + renderState.meshState().getUnitQuadVBO(), + VertexAttribFlags::None, + nullptr, nullptr, nullptr, + kTextureVertexStride }; + glop->transform.modelView.loadIdentity(); + glop->fill.colorEnabled = true; + glop->fill.color.set(Color::Black); + glop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType; + glop->fill.filterMode = ProgramDescription::ColorFilterMode::None; + glop->fill.texture = { nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr }; + return glop; +} + +RENDERTHREAD_TEST(GlopBuilder, rectSnapTest) { + RenderState& renderState = renderThread.renderState(); + Caches& caches = Caches::getInstance(); + SkPaint paint; + Rect dest(1, 1, 100, 100); + Matrix4 simpleTranslate; + simpleTranslate.loadTranslate(0.7, 0.7, 0); + Glop glop; + GlopBuilder(renderState, caches, &glop) + .setRoundRectClipState(nullptr) + .setMeshUnitQuad() + .setFillPaint(paint, 1.0f) + .setTransform(simpleTranslate, TransformFlags::None) + .setModelViewMapUnitToRectSnap(dest) + .build(); + + std::unique_ptr<Glop> goldenGlop(blackUnitQuadGlop(renderState)); + // Rect(1,1,100,100) is the set destination, + // so unit quad should be translated by (1,1) and scaled by (99, 99) + // Tricky part: because translate (0.7, 0.7) and snapping were set in glopBuilder, + // unit quad also should be translate by additional (0.3, 0.3) to snap to exact pixels. + goldenGlop->transform.modelView.loadTranslate(1.3, 1.3, 0); + goldenGlop->transform.modelView.scale(99, 99, 1); +#if !HWUI_NEW_OPS + goldenGlop->bounds = android::uirenderer::Rect(1.70, 1.70, 100.70, 100.70); +#endif + goldenGlop->transform.canvas = simpleTranslate; + goldenGlop->fill.texture.filter = GL_NEAREST; + expectGlopEq(*goldenGlop, glop); +} diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp new file mode 100644 index 000000000000..aa1dcb2ea51b --- /dev/null +++ b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> +#include <GpuMemoryTracker.h> + +#include "renderthread/EglManager.h" +#include "renderthread/RenderThread.h" +#include "tests/common/TestUtils.h" + +#include <utils/StrongPointer.h> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; + +class TestGPUObject : public GpuMemoryTracker { +public: + TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {} + + void changeSize(int newSize) { + notifySizeChanged(newSize); + } +}; + +// Other tests may have created a renderthread and EGL context. +// This will destroy the EGLContext on RenderThread if it exists so that the +// current thread can spoof being a GPU thread +static void destroyEglContext() { + if (TestUtils::isRenderThreadRunning()) { + TestUtils::runOnRenderThread([](RenderThread& thread) { + thread.eglManager().destroy(); + }); + } +} + +TEST(GpuMemoryTracker, sizeCheck) { + destroyEglContext(); + + GpuMemoryTracker::onGLContextCreated(); + ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); + ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); + { + TestGPUObject myObj; + ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); + myObj.changeSize(500); + ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); + myObj.changeSize(1000); + ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); + myObj.changeSize(300); + ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); + } + ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture)); + ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture)); + GpuMemoryTracker::onGLContextDestroyed(); +} diff --git a/libs/hwui/tests/unit/GradientCacheTests.cpp b/libs/hwui/tests/unit/GradientCacheTests.cpp new file mode 100644 index 000000000000..0ee96470fc57 --- /dev/null +++ b/libs/hwui/tests/unit/GradientCacheTests.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "Extensions.h" +#include "GradientCache.h" +#include "tests/common/TestUtils.h" + +using namespace android; +using namespace android::uirenderer; + +RENDERTHREAD_TEST(GradientCache, addRemove) { + Extensions extensions; + GradientCache cache(extensions); + ASSERT_LT(1000u, cache.getMaxSize()) << "Expect non-trivial size"; + + SkColor colors[] = { 0xFF00FF00, 0xFFFF0000, 0xFF0000FF }; + float positions[] = { 1, 2, 3 }; + Texture* texture = cache.get(colors, positions, 3); + ASSERT_TRUE(texture); + ASSERT_FALSE(texture->cleanup); + ASSERT_EQ((uint32_t) texture->objectSize(), cache.getSize()); + ASSERT_TRUE(cache.getSize()); + cache.clear(); + ASSERT_EQ(cache.getSize(), 0u); +} diff --git a/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp new file mode 100644 index 000000000000..8b0e91c77396 --- /dev/null +++ b/libs/hwui/tests/unit/LayerUpdateQueueTests.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> + +#include <LayerUpdateQueue.h> +#include <RenderNode.h> + +#include <tests/common/TestUtils.h> + +namespace android { +namespace uirenderer { + +TEST(LayerUpdateQueue, construct) { + LayerUpdateQueue queue; + EXPECT_TRUE(queue.entries().empty()); +} + +// sync node properties, so properties() reflects correct width and height +static sp<RenderNode> createSyncedNode(uint32_t width, uint32_t height) { + sp<RenderNode> node = TestUtils::createNode(0, 0, width, height, nullptr); + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + return node; +} + +TEST(LayerUpdateQueue, enqueueSimple) { + sp<RenderNode> a = createSyncedNode(100, 100); + sp<RenderNode> b = createSyncedNode(200, 200); + + LayerUpdateQueue queue; + queue.enqueueLayerWithDamage(a.get(), Rect(25, 25, 75, 75)); + queue.enqueueLayerWithDamage(b.get(), Rect(100, 100, 300, 300)); + + EXPECT_EQ(2u, queue.entries().size()); + + EXPECT_EQ(a.get(), queue.entries()[0].renderNode); + EXPECT_EQ(Rect(25, 25, 75, 75), queue.entries()[0].damage); + EXPECT_EQ(b.get(), queue.entries()[1].renderNode); + EXPECT_EQ(Rect(100, 100, 200, 200), queue.entries()[1].damage); // clipped to bounds +} + +TEST(LayerUpdateQueue, enqueueUnion) { + sp<RenderNode> a = createSyncedNode(100, 100); + + LayerUpdateQueue queue; + queue.enqueueLayerWithDamage(a.get(), Rect(10, 10, 20, 20)); + queue.enqueueLayerWithDamage(a.get(), Rect(30, 30, 40, 40)); + + EXPECT_EQ(1u, queue.entries().size()); + + EXPECT_EQ(a.get(), queue.entries()[0].renderNode); + EXPECT_EQ(Rect(10, 10, 40, 40), queue.entries()[0].damage); +} + +TEST(LayerUpdateQueue, clear) { + sp<RenderNode> a = createSyncedNode(100, 100); + + LayerUpdateQueue queue; + queue.enqueueLayerWithDamage(a.get(), Rect(100, 100)); + + EXPECT_FALSE(queue.entries().empty()); + + queue.clear(); + + EXPECT_TRUE(queue.entries().empty()); +} + +}; +}; diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp new file mode 100644 index 000000000000..6148b33eceb8 --- /dev/null +++ b/libs/hwui/tests/unit/LeakCheckTests.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 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 "BakedOpRenderer.h" +#include "BakedOpDispatcher.h" +#include "FrameBuilder.h" +#include "LayerUpdateQueue.h" +#include "RecordingCanvas.h" +#include "tests/common/TestUtils.h" + +#include <gtest/gtest.h> + +using namespace android; +using namespace android::uirenderer; + +const FrameBuilder::LightGeometry sLightGeometery = { {100, 100, 100}, 50}; +const BakedOpRenderer::LightInfo sLightInfo = { 128, 128 }; + +RENDERTHREAD_TEST(LeakCheck, saveLayer_overdrawRejection) { + auto node = TestUtils::createNode(0, 0, 100, 100, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer); + canvas.drawRect(0, 0, 100, 100, SkPaint()); + canvas.restore(); + + // opaque draw, rejects saveLayer beneath + canvas.drawRect(0, 0, 100, 100, SkPaint()); + }); + RenderState& renderState = renderThread.renderState(); + Caches& caches = Caches::getInstance(); + + FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, + sLightGeometery, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + BakedOpRenderer renderer(caches, renderState, true, sLightInfo); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); +} + +RENDERTHREAD_TEST(LeakCheck, saveLayerUnclipped_simple) { + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0)); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.restore(); + }); + RenderState& renderState = renderThread.renderState(); + Caches& caches = Caches::getInstance(); + + FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, + sLightGeometery, Caches::getInstance()); + frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); + BakedOpRenderer renderer(caches, renderState, true, sLightInfo); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); +} diff --git a/libs/hwui/tests/unit/LinearAllocatorTests.cpp b/libs/hwui/tests/unit/LinearAllocatorTests.cpp new file mode 100644 index 000000000000..ffcbf128fc3a --- /dev/null +++ b/libs/hwui/tests/unit/LinearAllocatorTests.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> +#include <utils/LinearAllocator.h> + +#include <tests/common/TestUtils.h> + +using namespace android; +using namespace android::uirenderer; + +struct SimplePair { + int one = 1; + int two = 2; +}; + +TEST(LinearAllocator, create) { + LinearAllocator la; + EXPECT_EQ(0u, la.usedSize()); + la.alloc<char>(64); + // There's some internal tracking as well as padding + // so the usedSize isn't strictly defined + EXPECT_LE(64u, la.usedSize()); + EXPECT_GT(80u, la.usedSize()); + auto pair = la.create<SimplePair>(); + EXPECT_LE(64u + sizeof(SimplePair), la.usedSize()); + EXPECT_GT(80u + sizeof(SimplePair), la.usedSize()); + EXPECT_EQ(1, pair->one); + EXPECT_EQ(2, pair->two); +} + +TEST(LinearAllocator, dtor) { + int destroyed[10] = { 0 }; + { + LinearAllocator la; + for (int i = 0; i < 5; i++) { + la.create<TestUtils::SignalingDtor>()->setSignal(destroyed + i); + la.create<SimplePair>(); + } + la.alloc<char>(100); + for (int i = 0; i < 5; i++) { + la.create<TestUtils::SignalingDtor>(destroyed + 5 + i); + la.create_trivial<SimplePair>(); + } + la.alloc<char>(100); + for (int i = 0; i < 10; i++) { + EXPECT_EQ(0, destroyed[i]); + } + } + for (int i = 0; i < 10; i++) { + EXPECT_EQ(1, destroyed[i]); + } +} + +TEST(LinearAllocator, rewind) { + int destroyed = 0; + { + LinearAllocator la; + auto addr = la.alloc<char>(100); + EXPECT_LE(100u, la.usedSize()); + la.rewindIfLastAlloc(addr, 100); + EXPECT_GT(16u, la.usedSize()); + size_t emptySize = la.usedSize(); + auto sigdtor = la.create<TestUtils::SignalingDtor>(); + sigdtor->setSignal(&destroyed); + EXPECT_EQ(0, destroyed); + EXPECT_LE(emptySize, la.usedSize()); + la.rewindIfLastAlloc(sigdtor); + EXPECT_EQ(1, destroyed); + EXPECT_EQ(emptySize, la.usedSize()); + } + // Checking for a double-destroy case + EXPECT_EQ(1, destroyed); +} + +TEST(LinearStdAllocator, simpleAllocate) { + LinearAllocator la; + LinearStdAllocator<void*> stdAllocator(la); + + std::vector<char, LinearStdAllocator<char> > v(stdAllocator); + v.push_back(0); + char* initialLocation = &v[0]; + v.push_back(10); + v.push_back(20); + v.push_back(30); + + // expect to have allocated (since no space reserved), so [0] will have moved to + // slightly further down in the same LinearAllocator page + EXPECT_LT(initialLocation, &v[0]); + EXPECT_GT(initialLocation + 20, &v[0]); + + // expect to have allocated again inserting 4 more entries + char* lastLocation = &v[0]; + v.push_back(40); + v.push_back(50); + v.push_back(60); + v.push_back(70); + + EXPECT_LT(lastLocation, &v[0]); + EXPECT_GT(lastLocation + 20, &v[0]); + +} + +TEST(LsaVector, dtorCheck) { + LinearAllocator allocator; + LinearStdAllocator<void*> stdAllocator(allocator); + + for (int size : {1, 2, 3, 500}) { + int destroyed = 0; + { + LsaVector<std::unique_ptr<TestUtils::SignalingDtor> > vector(stdAllocator); + for (int i = 0; i < size; i++) { + vector.emplace_back(new TestUtils::SignalingDtor(&destroyed)); + } + EXPECT_EQ(0, destroyed); + EXPECT_EQ(size, (int) vector.size()); + } + EXPECT_EQ(size, destroyed); + } +} diff --git a/libs/hwui/tests/unit/MatrixTests.cpp b/libs/hwui/tests/unit/MatrixTests.cpp new file mode 100644 index 000000000000..eddab878a49d --- /dev/null +++ b/libs/hwui/tests/unit/MatrixTests.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "Matrix.h" +#include "Rect.h" + +using namespace android::uirenderer; + +TEST(Matrix, mapRect_emptyScaleSkew) { + // Skew, so we don't hit identity/translate/simple fast paths + Matrix4 scaleMatrix; + scaleMatrix.loadScale(10, 10, 1); + scaleMatrix.skew(0.1f, 0.1f); + + // non-zero empty rect, so sorting x/y would make rect non-empty + Rect empty(15, 20, 15, 100); + ASSERT_TRUE(empty.isEmpty()); + scaleMatrix.mapRect(empty); + EXPECT_EQ(Rect(170, 215, 250, 1015), empty); + EXPECT_FALSE(empty.isEmpty()) + << "Empty 'line' rect doesn't remain empty when skewed."; +} + +TEST(Matrix, mapRect_emptyRotate) { + // Skew, so we don't hit identity/translate/simple fast paths + Matrix4 skewMatrix; + skewMatrix.loadRotate(45); + + // non-zero empty rect, so sorting x/y would make rect non-empty + Rect lineRect(0, 100); + ASSERT_TRUE(lineRect.isEmpty()); + skewMatrix.mapRect(lineRect); + EXPECT_FALSE(lineRect.isEmpty()) + << "Empty 'line' rect doesn't remain empty when rotated."; +} diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp new file mode 100644 index 000000000000..b7950aab5662 --- /dev/null +++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> +#include <Rect.h> +#include <renderstate/OffscreenBufferPool.h> + +#include <tests/common/TestUtils.h> + +using namespace android::uirenderer; + +TEST(OffscreenBuffer, computeIdealDimension) { + EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(1)); + EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(31)); + EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(33)); + EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(64)); + EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000)); +} + +RENDERTHREAD_TEST(OffscreenBuffer, construct) { + OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u); + EXPECT_EQ(49u, layer.viewportWidth); + EXPECT_EQ(149u, layer.viewportHeight); + + EXPECT_EQ(64u, layer.texture.width()); + EXPECT_EQ(192u, layer.texture.height()); + + EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes()); +} + +RENDERTHREAD_TEST(OffscreenBuffer, getTextureCoordinates) { + OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u); + EXPECT_EQ(Rect(0, 1, 1, 0), + layerAligned.getTextureCoordinates()); + + OffscreenBuffer layerUnaligned(renderThread.renderState(), Caches::getInstance(), 200u, 225u); + EXPECT_EQ(Rect(0, 225.0f / 256.0f, 200.0f / 256.0f, 0), + layerUnaligned.getTextureCoordinates()); +} + +RENDERTHREAD_TEST(OffscreenBuffer, dirty) { + OffscreenBuffer buffer(renderThread.renderState(), Caches::getInstance(), 256u, 256u); + buffer.dirty(Rect(-100, -100, 100, 100)); + EXPECT_EQ(android::Rect(100, 100), buffer.region.getBounds()); +} + +RENDERTHREAD_TEST(OffscreenBufferPool, construct) { + OffscreenBufferPool pool; + EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty"; + EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty"; + EXPECT_EQ((uint32_t) Properties::layerPoolSize, pool.getMaxSize()) + << "pool must read size from Properties"; +} + +RENDERTHREAD_TEST(OffscreenBufferPool, getPutClear) { + OffscreenBufferPool pool; + + auto layer = pool.get(renderThread.renderState(), 100u, 200u); + EXPECT_EQ(100u, layer->viewportWidth); + EXPECT_EQ(200u, layer->viewportHeight); + + ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize()); + + pool.putOrDelete(layer); + ASSERT_EQ(layer->getSizeInBytes(), pool.getSize()); + + auto layer2 = pool.get(renderThread.renderState(), 102u, 202u); + EXPECT_EQ(layer, layer2) << "layer should be recycled"; + ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer"; + + pool.putOrDelete(layer); + EXPECT_EQ(1u, pool.getCount()); + pool.clear(); + EXPECT_EQ(0u, pool.getSize()); + EXPECT_EQ(0u, pool.getCount()); +} + +RENDERTHREAD_TEST(OffscreenBufferPool, resize) { + OffscreenBufferPool pool; + + auto layer = pool.get(renderThread.renderState(), 64u, 64u); + layer->dirty(Rect(64, 64)); + + // resize in place + ASSERT_EQ(layer, pool.resize(layer, 60u, 55u)); + EXPECT_TRUE(layer->region.isEmpty()) << "In place resize should clear usage region"; + EXPECT_EQ(60u, layer->viewportWidth); + EXPECT_EQ(55u, layer->viewportHeight); + EXPECT_EQ(64u, layer->texture.width()); + EXPECT_EQ(64u, layer->texture.height()); + + // resized to use different object in pool + auto layer2 = pool.get(renderThread.renderState(), 128u, 128u); + layer2->dirty(Rect(128, 128)); + EXPECT_FALSE(layer2->region.isEmpty()); + pool.putOrDelete(layer2); + ASSERT_EQ(1u, pool.getCount()); + + ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u)); + EXPECT_TRUE(layer2->region.isEmpty()) << "Swap resize should clear usage region"; + EXPECT_EQ(120u, layer2->viewportWidth); + EXPECT_EQ(125u, layer2->viewportHeight); + EXPECT_EQ(128u, layer2->texture.width()); + EXPECT_EQ(128u, layer2->texture.height()); + + // original allocation now only thing in pool + EXPECT_EQ(1u, pool.getCount()); + EXPECT_EQ(layer->getSizeInBytes(), pool.getSize()); + + pool.putOrDelete(layer2); +} + +RENDERTHREAD_TEST(OffscreenBufferPool, putAndDestroy) { + OffscreenBufferPool pool; + // layer too big to return to the pool + // Note: this relies on the fact that the pool won't reject based on max texture size + auto hugeLayer = pool.get(renderThread.renderState(), pool.getMaxSize() / 64, 64); + EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize()); + pool.putOrDelete(hugeLayer); + EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead) +} + +RENDERTHREAD_TEST(OffscreenBufferPool, clear) { + EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer)); + OffscreenBufferPool pool; + + // Create many buffers, with several at each size + std::vector<OffscreenBuffer*> buffers; + for (int size = 32; size <= 128; size += 32) { + for (int i = 0; i < 10; i++) { + buffers.push_back(pool.get(renderThread.renderState(), size, size)); + } + } + EXPECT_EQ(0u, pool.getCount()) << "Expect nothing inside"; + for (auto& buffer : buffers) pool.putOrDelete(buffer); + EXPECT_EQ(40u, pool.getCount()) << "Expect all items added"; + EXPECT_EQ(40, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer)); + pool.clear(); + EXPECT_EQ(0u, pool.getCount()) << "Expect all items cleared"; + + EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer)); +} diff --git a/libs/hwui/tests/unit/OpDumperTests.cpp b/libs/hwui/tests/unit/OpDumperTests.cpp new file mode 100644 index 000000000000..01840d72b766 --- /dev/null +++ b/libs/hwui/tests/unit/OpDumperTests.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "tests/common/TestUtils.h" +#include "OpDumper.h" + +using namespace android; +using namespace android::uirenderer; + +TEST(OpDumper, dump) { + SkPaint paint; + RectOp op(uirenderer::Rect(100, 100), Matrix4::identity(), nullptr, &paint); + + std::stringstream stream; + OpDumper::dump(op, stream); + EXPECT_STREQ("RectOp [100 x 100]", stream.str().c_str()); + + stream.str(""); + OpDumper::dump(op, stream, 2); + EXPECT_STREQ(" RectOp [100 x 100]", stream.str().c_str()); + + ClipRect clipRect(uirenderer::Rect(50, 50)); + op.localClip = &clipRect; + + stream.str(""); + OpDumper::dump(op, stream, 2); + EXPECT_STREQ(" RectOp [100 x 100] clip=[50 x 50] mode=0", stream.str().c_str()); +} diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp new file mode 100644 index 000000000000..18171de250d0 --- /dev/null +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> + +#include <DeferredLayerUpdater.h> +#include <RecordedOp.h> +#include <RecordingCanvas.h> +#include <hwui/Paint.h> +#include <minikin/Layout.h> +#include <tests/common/TestUtils.h> +#include <utils/Color.h> + +#include <SkGradientShader.h> +#include <SkShader.h> + +namespace android { +namespace uirenderer { + +static void playbackOps(const DisplayList& displayList, + std::function<void(const RecordedOp&)> opReceiver) { + for (auto& chunk : displayList.getChunks()) { + for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { + RecordedOp* op = displayList.getOps()[opIndex]; + opReceiver(*op); + } + } +} + +static void validateSingleOp(std::unique_ptr<DisplayList>& dl, + std::function<void(const RecordedOp& op)> opValidator) { + ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; + opValidator(*(dl->getOps()[0])); +} + +TEST(RecordingCanvas, emptyPlayback) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.restore(); + }); + playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); +} + +TEST(RecordingCanvas, clipRect) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op); + canvas.drawRect(0, 0, 50, 50, SkPaint()); + canvas.drawRect(50, 50, 100, 100, SkPaint()); + canvas.restore(); + }); + + ASSERT_EQ(2u, dl->getOps().size()) << "Must be exactly two ops"; + EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[0]->localClip); + EXPECT_CLIP_RECT(Rect(100, 100), dl->getOps()[1]->localClip); + EXPECT_EQ(dl->getOps()[0]->localClip, dl->getOps()[1]->localClip) + << "Clip should be serialized once"; +} + +TEST(RecordingCanvas, emptyClipRect) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.clipRect(0, 0, 100, 100, SkRegion::kIntersect_Op); + canvas.clipRect(100, 100, 200, 200, SkRegion::kIntersect_Op); + canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time + canvas.restore(); + }); + ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected."; +} + +TEST(RecordingCanvas, drawArc) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint()); + canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint()); + }); + + auto&& ops = dl->getOps(); + ASSERT_EQ(2u, ops.size()) << "Must be exactly two ops"; + EXPECT_EQ(RecordedOpId::ArcOp, ops[0]->opId); + EXPECT_EQ(Rect(200, 200), ops[0]->unmappedBounds); + + EXPECT_EQ(RecordedOpId::OvalOp, ops[1]->opId) + << "Circular arcs should be converted to ovals"; + EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds); +} + +TEST(RecordingCanvas, drawLines) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setStrokeWidth(20); // doesn't affect recorded bounds - would be resolved at bake time + float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line + canvas.drawLines(&points[0], 7, paint); + }); + + ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; + auto op = dl->getOps()[0]; + ASSERT_EQ(RecordedOpId::LinesOp, op->opId); + EXPECT_EQ(4, ((LinesOp*)op)->floatCount) + << "float count must be rounded down to closest multiple of 4"; + EXPECT_EQ(Rect(20, 10), op->unmappedBounds) + << "unmapped bounds must be size of line, and not outset for stroke width"; +} + +TEST(RecordingCanvas, drawRect) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + canvas.drawRect(10, 20, 90, 180, SkPaint()); + }); + + ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; + auto op = *(dl->getOps()[0]); + ASSERT_EQ(RecordedOpId::RectOp, op.opId); + EXPECT_EQ(nullptr, op.localClip); + EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds); +} + +TEST(RecordingCanvas, drawRoundRect) { + // Round case - stays rounded + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint()); + }); + ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; + ASSERT_EQ(RecordedOpId::RoundRectOp, dl->getOps()[0]->opId); + + // Non-rounded case - turned into drawRect + dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + canvas.drawRoundRect(0, 0, 100, 100, 0, -1, SkPaint()); + }); + ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; + ASSERT_EQ(RecordedOpId::RectOp, dl->getOps()[0]->opId) + << "Non-rounded rects should be converted"; +} + +TEST(RecordingCanvas, drawGlyphs) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); + }); + + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + count++; + ASSERT_EQ(RecordedOpId::TextOp, op.opId); + EXPECT_EQ(nullptr, op.localClip); + EXPECT_TRUE(op.localMatrix.isIdentity()); + EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25)) + << "Op expected to be 25+ pixels wide, 10+ pixels tall"; + }); + ASSERT_EQ(1, count); +} + +TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + paint.setUnderlineText(i != 0); + paint.setStrikeThruText(j != 0); + TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); + } + } + }); + + auto ops = dl->getOps(); + ASSERT_EQ(8u, ops.size()); + + int index = 0; + EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough + + EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); + EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only + + EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); + EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only + + EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); + EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline + EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough +} + +TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + paint.setTextAlign(SkPaint::kLeft_Align); + TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); + paint.setTextAlign(SkPaint::kCenter_Align); + TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); + paint.setTextAlign(SkPaint::kRight_Align); + TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); + }); + + int count = 0; + float lastX = FLT_MAX; + playbackOps(*dl, [&count, &lastX](const RecordedOp& op) { + count++; + ASSERT_EQ(RecordedOpId::TextOp, op.opId); + EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign()) + << "recorded drawText commands must force kLeft_Align on their paint"; + + // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class) + EXPECT_GT(lastX, ((const TextOp&)op).x) + << "x coordinate should reduce across each of the draw commands, from alignment"; + lastX = ((const TextOp&)op).x; + }); + ASSERT_EQ(3, count); +} + +TEST(RecordingCanvas, drawColor) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.drawColor(Color::Black, SkXfermode::kSrcOver_Mode); + }); + + ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; + auto op = *(dl->getOps()[0]); + EXPECT_EQ(RecordedOpId::ColorOp, op.opId); + EXPECT_EQ(nullptr, op.localClip); + EXPECT_TRUE(op.unmappedBounds.isEmpty()) << "Expect undefined recorded bounds"; +} + +TEST(RecordingCanvas, backgroundAndImage) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + SkBitmap bitmap; + bitmap.setInfo(SkImageInfo::MakeUnknown(25, 25)); + SkPaint paint; + paint.setColor(SK_ColorBLUE); + + canvas.save(SaveFlags::MatrixClip); + { + // a background! + canvas.save(SaveFlags::MatrixClip); + canvas.drawRect(0, 0, 100, 200, paint); + canvas.restore(); + } + { + // an image! + canvas.save(SaveFlags::MatrixClip); + canvas.translate(25, 25); + canvas.scale(2, 2); + canvas.drawBitmap(bitmap, 0, 0, nullptr); + canvas.restore(); + } + canvas.restore(); + }); + + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + if (count == 0) { + ASSERT_EQ(RecordedOpId::RectOp, op.opId); + ASSERT_NE(nullptr, op.paint); + EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); + EXPECT_EQ(Rect(100, 200), op.unmappedBounds); + EXPECT_EQ(nullptr, op.localClip); + + Matrix4 expectedMatrix; + expectedMatrix.loadIdentity(); + EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); + } else { + ASSERT_EQ(RecordedOpId::BitmapOp, op.opId); + EXPECT_EQ(nullptr, op.paint); + EXPECT_EQ(Rect(25, 25), op.unmappedBounds); + EXPECT_EQ(nullptr, op.localClip); + + Matrix4 expectedMatrix; + expectedMatrix.loadTranslate(25, 25, 0); + expectedMatrix.scale(2, 2, 1); + EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); + } + count++; + }); + ASSERT_EQ(2, count); +} + +RENDERTHREAD_TEST(RecordingCanvas, textureLayer) { + auto layerUpdater = TestUtils::createTextureLayerUpdater(renderThread, 100, 100, + SkMatrix::MakeTrans(5, 5)); + + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, + [&layerUpdater](RecordingCanvas& canvas) { + canvas.drawLayer(layerUpdater.get()); + }); + + validateSingleOp(dl, [] (const RecordedOp& op) { + ASSERT_EQ(RecordedOpId::TextureLayerOp, op.opId); + ASSERT_TRUE(op.localMatrix.isIdentity()) << "Op must not apply matrix at record time."; + }); +} + +TEST(RecordingCanvas, saveLayer_simple) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer); + canvas.drawRect(10, 20, 190, 180, SkPaint()); + canvas.restore(); + }); + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + Matrix4 expectedMatrix; + switch(count++) { + case 0: + EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId); + EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); + EXPECT_EQ(nullptr, op.localClip); + EXPECT_TRUE(op.localMatrix.isIdentity()); + break; + case 1: + EXPECT_EQ(RecordedOpId::RectOp, op.opId); + EXPECT_CLIP_RECT(Rect(180, 160), op.localClip); + EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); + expectedMatrix.loadTranslate(-10, -20, 0); + EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); + break; + case 2: + EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); + // Don't bother asserting recording state data - it's not used + break; + default: + ADD_FAILURE(); + } + }); + EXPECT_EQ(3, count); +} + +TEST(RecordingCanvas, saveLayer_missingRestore) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + // Note: restore omitted, shouldn't result in unmatched save + }); + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + if (count++ == 2) { + EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); + } + }); + EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer"; +} + +TEST(RecordingCanvas, saveLayer_simpleUnclipped) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped + canvas.drawRect(10, 20, 190, 180, SkPaint()); + canvas.restore(); + }); + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + switch(count++) { + case 0: + EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId); + EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); + EXPECT_EQ(nullptr, op.localClip); + EXPECT_TRUE(op.localMatrix.isIdentity()); + break; + case 1: + EXPECT_EQ(RecordedOpId::RectOp, op.opId); + EXPECT_EQ(nullptr, op.localClip); + EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); + EXPECT_TRUE(op.localMatrix.isIdentity()); + break; + case 2: + EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId); + // Don't bother asserting recording state data - it's not used + break; + default: + ADD_FAILURE(); + } + }); + EXPECT_EQ(3, count); +} + +TEST(RecordingCanvas, saveLayer_addClipFlag) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op); + canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped + canvas.drawRect(10, 20, 190, 180, SkPaint()); + canvas.restore(); + canvas.restore(); + }); + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + if (count++ == 0) { + EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId) + << "Clip + unclipped saveLayer should result in a clipped layer"; + } + }); + EXPECT_EQ(3, count); +} + +TEST(RecordingCanvas, saveLayer_viewportCrop) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + // shouldn't matter, since saveLayer will clip to its bounds + canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op); + + canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer); + canvas.drawRect(0, 0, 400, 400, SkPaint()); + canvas.restore(); + }); + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + if (count++ == 1) { + Matrix4 expectedMatrix; + EXPECT_EQ(RecordedOpId::RectOp, op.opId); + EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be + // intersection of viewport and saveLayer bounds, in layer space; + EXPECT_EQ(Rect(400, 400), op.unmappedBounds); + expectedMatrix.loadTranslate(-100, -100, 0); + EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); + } + }); + EXPECT_EQ(3, count); +} + +TEST(RecordingCanvas, saveLayer_rotateUnclipped) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.translate(100, 100); + canvas.rotate(45); + canvas.translate(-50, -50); + + canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer); + canvas.drawRect(0, 0, 100, 100, SkPaint()); + canvas.restore(); + + canvas.restore(); + }); + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + if (count++ == 1) { + EXPECT_EQ(RecordedOpId::RectOp, op.opId); + EXPECT_CLIP_RECT(Rect(100, 100), op.localClip); + EXPECT_EQ(Rect(100, 100), op.unmappedBounds); + EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix) + << "Recorded op shouldn't see any canvas transform before the saveLayer"; + } + }); + EXPECT_EQ(3, count); +} + +TEST(RecordingCanvas, saveLayer_rotateClipped) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.translate(100, 100); + canvas.rotate(45); + canvas.translate(-200, -200); + + // area of saveLayer will be clipped to parent viewport, so we ask for 400x400... + canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer); + canvas.drawRect(0, 0, 400, 400, SkPaint()); + canvas.restore(); + + canvas.restore(); + }); + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + if (count++ == 1) { + Matrix4 expectedMatrix; + EXPECT_EQ(RecordedOpId::RectOp, op.opId); + + // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by + // the parent 200x200 viewport, but prior to rotation + ASSERT_NE(nullptr, op.localClip); + ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode); + // NOTE: this check relies on saveLayer altering the clip post-viewport init. This + // causes the clip to be recorded by contained draw commands, though it's not necessary + // since the same clip will be computed at draw time. If such a change is made, this + // check could be done at record time by querying the clip, or the clip could be altered + // slightly so that it is serialized. + EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect); + EXPECT_EQ(Rect(400, 400), op.unmappedBounds); + expectedMatrix.loadIdentity(); + EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); + } + }); + EXPECT_EQ(3, count); +} + +TEST(RecordingCanvas, drawRenderNode_rejection) { + auto child = TestUtils::createNode(50, 50, 150, 150, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&child](RecordingCanvas& canvas) { + canvas.clipRect(0, 0, 0, 0, SkRegion::kIntersect_Op); // empty clip, reject node + canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node... + }); + ASSERT_TRUE(dl->isEmpty()); +} + +TEST(RecordingCanvas, drawRenderNode_projection) { + sp<RenderNode> background = TestUtils::createNode(50, 50, 150, 150, + [](RenderProperties& props, RecordingCanvas& canvas) { + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + }); + { + background->mutateStagingProperties().setProjectionReceiver(false); + + // NO RECEIVER PRESENT + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, + [&background](RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 100, 100, SkPaint()); + canvas.drawRenderNode(background.get()); + canvas.drawRect(0, 0, 100, 100, SkPaint()); + }); + EXPECT_EQ(-1, dl->projectionReceiveIndex) + << "no projection receiver should have been observed"; + } + { + background->mutateStagingProperties().setProjectionReceiver(true); + + // RECEIVER PRESENT + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, + [&background](RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 100, 100, SkPaint()); + canvas.drawRenderNode(background.get()); + canvas.drawRect(0, 0, 100, 100, SkPaint()); + }); + + ASSERT_EQ(3u, dl->getOps().size()) << "Must be three ops"; + auto op = dl->getOps()[1]; + EXPECT_EQ(RecordedOpId::RenderNodeOp, op->opId); + EXPECT_EQ(1, dl->projectionReceiveIndex) + << "correct projection receiver not identified"; + + // verify the behavior works even though projection receiver hasn't been sync'd yet + EXPECT_TRUE(background->stagingProperties().isProjectionReceiver()); + EXPECT_FALSE(background->properties().isProjectionReceiver()); + } +} + +TEST(RecordingCanvas, firstClipWillReplace) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + // since no explicit clip set on canvas, this should be the one observed on op: + canvas.clipRect(-100, -100, 300, 300, SkRegion::kIntersect_Op); + + SkPaint paint; + paint.setColor(SK_ColorWHITE); + canvas.drawRect(0, 0, 100, 100, paint); + + canvas.restore(); + }); + ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op"; + // first clip must be preserved, even if it extends beyond canvas bounds + EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip); +} + +TEST(RecordingCanvas, replaceClipIntersectWithRoot) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { + canvas.save(SaveFlags::MatrixClip); + canvas.clipRect(-10, -10, 110, 110, SkRegion::kReplace_Op); + canvas.drawColor(SK_ColorWHITE, SkXfermode::Mode::kSrcOver_Mode); + canvas.restore(); + }); + ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op"; + // first clip must be preserved, even if it extends beyond canvas bounds + EXPECT_CLIP_RECT(Rect(-10, -10, 110, 110), dl->getOps()[0]->localClip); + EXPECT_TRUE(dl->getOps()[0]->localClip->intersectWithRoot); +} + +TEST(RecordingCanvas, insertReorderBarrier) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.drawRect(0, 0, 400, 400, SkPaint()); + canvas.insertReorderBarrier(true); + canvas.insertReorderBarrier(false); + canvas.insertReorderBarrier(false); + canvas.insertReorderBarrier(true); + canvas.drawRect(0, 0, 400, 400, SkPaint()); + canvas.insertReorderBarrier(false); + }); + + auto chunks = dl->getChunks(); + EXPECT_EQ(0u, chunks[0].beginOpIndex); + EXPECT_EQ(1u, chunks[0].endOpIndex); + EXPECT_FALSE(chunks[0].reorderChildren); + + EXPECT_EQ(1u, chunks[1].beginOpIndex); + EXPECT_EQ(2u, chunks[1].endOpIndex); + EXPECT_TRUE(chunks[1].reorderChildren); +} + +TEST(RecordingCanvas, insertReorderBarrier_clip) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + // first chunk: no recorded clip + canvas.insertReorderBarrier(true); + canvas.drawRect(0, 0, 400, 400, SkPaint()); + + // second chunk: no recorded clip, since inorder region + canvas.clipRect(0, 0, 200, 200, SkRegion::kIntersect_Op); + canvas.insertReorderBarrier(false); + canvas.drawRect(0, 0, 400, 400, SkPaint()); + + // third chunk: recorded clip + canvas.insertReorderBarrier(true); + canvas.drawRect(0, 0, 400, 400, SkPaint()); + }); + + auto chunks = dl->getChunks(); + ASSERT_EQ(3u, chunks.size()); + + EXPECT_TRUE(chunks[0].reorderChildren); + EXPECT_EQ(nullptr, chunks[0].reorderClip); + + EXPECT_FALSE(chunks[1].reorderChildren); + EXPECT_EQ(nullptr, chunks[1].reorderClip); + + EXPECT_TRUE(chunks[2].reorderChildren); + ASSERT_NE(nullptr, chunks[2].reorderClip); + EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect); +} + +TEST(RecordingCanvas, refPaint) { + SkPaint paint; + + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) { + paint.setColor(SK_ColorBLUE); + // first two should use same paint + canvas.drawRect(0, 0, 200, 10, paint); + SkPaint paintCopy(paint); + canvas.drawRect(0, 10, 200, 20, paintCopy); + + // only here do we use different paint ptr + paint.setColor(SK_ColorRED); + canvas.drawRect(0, 20, 200, 30, paint); + }); + auto ops = dl->getOps(); + ASSERT_EQ(3u, ops.size()); + + // first two are the same + EXPECT_NE(nullptr, ops[0]->paint); + EXPECT_NE(&paint, ops[0]->paint); + EXPECT_EQ(ops[0]->paint, ops[1]->paint); + + // last is different, but still copied / non-null + EXPECT_NE(nullptr, ops[2]->paint); + EXPECT_NE(ops[0]->paint, ops[2]->paint); + EXPECT_NE(&paint, ops[2]->paint); +} + +TEST(RecordingCanvas, refBitmap) { + SkBitmap bitmap = TestUtils::createSkBitmap(100, 100); + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) { + canvas.drawBitmap(bitmap, 0, 0, nullptr); + }); + auto& bitmaps = dl->getBitmapResources(); + EXPECT_EQ(1u, bitmaps.size()); +} + +TEST(RecordingCanvas, refBitmapInShader_bitmapShader) { + SkBitmap bitmap = TestUtils::createSkBitmap(100, 100); + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) { + SkPaint paint; + SkAutoTUnref<SkShader> shader(SkShader::CreateBitmapShader(bitmap, + SkShader::TileMode::kClamp_TileMode, + SkShader::TileMode::kClamp_TileMode)); + paint.setShader(shader); + canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint); + }); + auto& bitmaps = dl->getBitmapResources(); + EXPECT_EQ(1u, bitmaps.size()); +} + +TEST(RecordingCanvas, refBitmapInShader_composeShader) { + SkBitmap bitmap = TestUtils::createSkBitmap(100, 100); + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [&bitmap](RecordingCanvas& canvas) { + SkPaint paint; + SkAutoTUnref<SkShader> shader1(SkShader::CreateBitmapShader(bitmap, + SkShader::TileMode::kClamp_TileMode, + SkShader::TileMode::kClamp_TileMode)); + + SkPoint center; + center.set(50, 50); + SkColor colors[2]; + colors[0] = Color::Black; + colors[1] = Color::White; + SkAutoTUnref<SkShader> shader2(SkGradientShader::CreateRadial(center, 50, colors, nullptr, 2, + SkShader::TileMode::kRepeat_TileMode)); + + SkAutoTUnref<SkShader> composeShader(SkShader::CreateComposeShader(shader1, shader2, + SkXfermode::Mode::kMultiply_Mode)); + paint.setShader(composeShader); + canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint); + }); + auto& bitmaps = dl->getBitmapResources(); + EXPECT_EQ(1u, bitmaps.size()); +} + +TEST(RecordingCanvas, drawText) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + Paint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO"); + canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL); + }); + + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + count++; + ASSERT_EQ(RecordedOpId::TextOp, op.opId); + EXPECT_EQ(nullptr, op.localClip); + EXPECT_TRUE(op.localMatrix.isIdentity()); + EXPECT_TRUE(op.unmappedBounds.getHeight() >= 10); + EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25); + }); + ASSERT_EQ(1, count); +} + +TEST(RecordingCanvas, drawTextInHighContrast) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.setHighContrastText(true); + Paint paint; + paint.setColor(SK_ColorWHITE); + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + std::unique_ptr<uint16_t[]> dst = TestUtils::asciiToUtf16("HELLO"); + canvas.drawText(dst.get(), 0, 5, 5, 25, 25, kBidi_Force_LTR, paint, NULL); + }); + + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + ASSERT_EQ(RecordedOpId::TextOp, op.opId); + if (count++ == 0) { + EXPECT_EQ(SK_ColorBLACK, op.paint->getColor()); + EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle()); + } else { + EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); + EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle()); + } + + }); + ASSERT_EQ(2, count); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp new file mode 100644 index 000000000000..b2997dfb357f --- /dev/null +++ b/libs/hwui/tests/unit/RenderNodeTests.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "RenderNode.h" +#include "TreeInfo.h" +#include "tests/common/TestUtils.h" +#include "utils/Color.h" + +using namespace android; +using namespace android::uirenderer; + +TEST(RenderNode, hasParents) { + auto child = TestUtils::createNode(0, 0, 200, 400, + [](RenderProperties& props, TestCanvas& canvas) { + canvas.drawColor(Color::Red_500, SkXfermode::kSrcOver_Mode); + }); + auto parent = TestUtils::createNode(0, 0, 200, 400, + [&child](RenderProperties& props, TestCanvas& canvas) { + canvas.drawRenderNode(child.get()); + }); + + TestUtils::syncHierarchyPropertiesAndDisplayList(parent); + + EXPECT_TRUE(child->hasParents()) << "Child node has no parent"; + EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents"; + + TestUtils::recordNode(*parent, [](TestCanvas& canvas) { + canvas.drawColor(Color::Amber_500, SkXfermode::kSrcOver_Mode); + }); + + EXPECT_TRUE(child->hasParents()) << "Child should still have a parent"; + EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents"; + + TestUtils::syncHierarchyPropertiesAndDisplayList(parent); + + EXPECT_FALSE(child->hasParents()) << "Child should be removed"; + EXPECT_FALSE(parent->hasParents()) << "Root node shouldn't have any parents"; +} + +TEST(RenderNode, releasedCallback) { + class DecRefOnReleased : public GlFunctorLifecycleListener { + public: + DecRefOnReleased(int* refcnt) : mRefCnt(refcnt) {} + void onGlFunctorReleased(Functor* functor) override { + *mRefCnt -= 1; + } + private: + int* mRefCnt; + }; + + int refcnt = 0; + sp<DecRefOnReleased> listener(new DecRefOnReleased(&refcnt)); + Functor noopFunctor; + + auto node = TestUtils::createNode(0, 0, 200, 400, + [&](RenderProperties& props, TestCanvas& canvas) { + refcnt++; + canvas.callDrawGLFunction(&noopFunctor, listener.get()); + }); + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + EXPECT_EQ(1, refcnt); + + TestUtils::recordNode(*node, [&](TestCanvas& canvas) { + refcnt++; + canvas.callDrawGLFunction(&noopFunctor, listener.get()); + }); + EXPECT_EQ(2, refcnt); + + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + EXPECT_EQ(1, refcnt); + + TestUtils::recordNode(*node, [](TestCanvas& canvas) {}); + EXPECT_EQ(1, refcnt); + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + EXPECT_EQ(0, refcnt); +} diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp new file mode 100644 index 000000000000..875e260c84cf --- /dev/null +++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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 "tests/common/TestUtils.h" + +#include <gtest/gtest.h> +#include <SkShader.h> + +using namespace android; +using namespace android::uirenderer; + +/** + * 1x1 bitmaps must not be optimized into solid color shaders, since HWUI can't + * compose/render color shaders + */ +TEST(SkiaBehavior, CreateBitmapShader1x1) { + SkBitmap origBitmap = TestUtils::createSkBitmap(1, 1); + std::unique_ptr<SkShader> s(SkShader::CreateBitmapShader( + origBitmap, + SkShader::kClamp_TileMode, + SkShader::kRepeat_TileMode)); + + SkBitmap bitmap; + SkShader::TileMode xy[2]; + ASSERT_TRUE(s->isABitmap(&bitmap, nullptr, xy)) + << "1x1 bitmap shader must query as bitmap shader"; + EXPECT_EQ(SkShader::kClamp_TileMode, xy[0]); + EXPECT_EQ(SkShader::kRepeat_TileMode, xy[1]); + EXPECT_EQ(origBitmap.pixelRef(), bitmap.pixelRef()); +} + +TEST(SkiaBehavior, genIds) { + SkBitmap bitmap = TestUtils::createSkBitmap(100, 100); + uint32_t genId = bitmap.getGenerationID(); + bitmap.notifyPixelsChanged(); + EXPECT_NE(genId, bitmap.getGenerationID()); +} diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp new file mode 100644 index 000000000000..5a011938e2bb --- /dev/null +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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 "tests/common/TestUtils.h" + +#include <gtest/gtest.h> +#include <RecordingCanvas.h> +#include <SkPicture.h> +#include <SkPictureRecorder.h> + +using namespace android; +using namespace android::uirenderer; + +/** + * Verify that we get the same culling bounds for text for (1) drawing glyphs + * directly to a Canvas or (2) going through a SkPicture as an intermediate step. + */ +TEST(SkiaCanvasProxy, drawGlyphsViaPicture) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + // setup test variables + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + static const char* text = "testing text bounds"; + + // draw text directly into Recording canvas + TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 25, 25); + + // record the same text draw into a SkPicture and replay it into a Recording canvas + SkPictureRecorder recorder; + SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0); + std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas)); + TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25); + SkAutoTUnref<const SkPicture> picture(recorder.endRecording()); + + canvas.asSkCanvas()->drawPicture(picture); + }); + + // verify that the text bounds and matrices match + ASSERT_EQ(2U, dl->getOps().size()); + auto directOp = dl->getOps()[0]; + auto pictureOp = dl->getOps()[1]; + ASSERT_EQ(RecordedOpId::TextOp, directOp->opId); + EXPECT_EQ(directOp->opId, pictureOp->opId); + EXPECT_EQ(directOp->unmappedBounds, pictureOp->unmappedBounds); + EXPECT_EQ(directOp->localMatrix, pictureOp->localMatrix); +} diff --git a/libs/hwui/tests/unit/SnapshotTests.cpp b/libs/hwui/tests/unit/SnapshotTests.cpp new file mode 100644 index 000000000000..11797a8fc1db --- /dev/null +++ b/libs/hwui/tests/unit/SnapshotTests.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include <Snapshot.h> + +#include <tests/common/TestUtils.h> + +using namespace android::uirenderer; + +TEST(Snapshot, serializeIntersectedClip) { + auto actualRoot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 100)); + auto root = TestUtils::makeSnapshot(Matrix4::identity(), Rect(10, 10, 90, 90)); + auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90)); + root->previous = actualRoot.get(); + child->previous = root.get(); + + LinearAllocator allocator; + ClipRect rect(Rect(0, 0, 75, 75)); + { + auto intersectWithChild = child->serializeIntersectedClip(allocator, + &rect, Matrix4::identity()); + ASSERT_NE(nullptr, intersectWithChild); + EXPECT_EQ(Rect(50, 50, 75, 75), intersectWithChild->rect) << "Expect intersect with child"; + } + + rect.intersectWithRoot = true; + { + auto intersectWithRoot = child->serializeIntersectedClip(allocator, + &rect, Matrix4::identity()); + ASSERT_NE(nullptr, intersectWithRoot); + EXPECT_EQ(Rect(10, 10, 75, 75), intersectWithRoot->rect) << "Expect intersect with root"; + } +} + +TEST(Snapshot, applyClip) { + auto actualRoot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 100)); + auto root = TestUtils::makeSnapshot(Matrix4::identity(), Rect(10, 10, 90, 90)); + root->previous = actualRoot.get(); + + ClipRect rect(Rect(0, 0, 75, 75)); + { + auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90)); + child->previous = root.get(); + child->applyClip(&rect, Matrix4::identity()); + + EXPECT_TRUE(child->getClipArea().isSimple()); + EXPECT_EQ(Rect(50, 50, 75, 75), child->getRenderTargetClip()); + } + + { + rect.intersectWithRoot = true; + auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90)); + child->previous = root.get(); + child->applyClip(&rect, Matrix4::identity()); + + EXPECT_TRUE(child->getClipArea().isSimple()); + EXPECT_EQ(Rect(10, 10, 75, 75), child->getRenderTargetClip()); + } +} diff --git a/libs/hwui/tests/unit/StringUtilsTests.cpp b/libs/hwui/tests/unit/StringUtilsTests.cpp new file mode 100644 index 000000000000..b60e96c756ec --- /dev/null +++ b/libs/hwui/tests/unit/StringUtilsTests.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> + +#include <utils/StringUtils.h> + +using namespace android; +using namespace android::uirenderer; + +TEST(StringUtils, simpleBuildSet) { + auto collection = StringUtils::split("a b c"); + + EXPECT_TRUE(collection.has("a")); + EXPECT_TRUE(collection.has("b")); + EXPECT_TRUE(collection.has("c")); + EXPECT_FALSE(collection.has("d")); +} + +TEST(StringUtils, advancedBuildSet) { + auto collection = StringUtils::split("GL_ext1 GL_ext2 GL_ext3"); + + EXPECT_TRUE(collection.has("GL_ext1")); + EXPECT_FALSE(collection.has("GL_ext")); // string present, but not in list +} + +TEST(StringUtils, sizePrinter) { + std::stringstream os; + os << SizePrinter{500}; + EXPECT_EQ("500.00B", os.str()); + os.str(""); + os << SizePrinter{46080}; + EXPECT_EQ("45.00KiB", os.str()); + os.str(""); + os << SizePrinter{5 * 1024 * 1024 + 520 * 1024}; + EXPECT_EQ("5.51MiB", os.str()); + os.str(""); + os << SizePrinter{2147483647}; + EXPECT_EQ("2048.00MiB", os.str()); +} diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp new file mode 100644 index 000000000000..0d26df203f02 --- /dev/null +++ b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 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 <gtest/gtest.h> + +#include "GammaFontRenderer.h" +#include "TextDropShadowCache.h" +#include "utils/Blur.h" +#include "tests/common/TestUtils.h" + +#include <SkPaint.h> + +using namespace android; +using namespace android::uirenderer; + +RENDERTHREAD_TEST(TextDropShadowCache, addRemove) { + SkPaint paint; + paint.setTextSize(20); + + GammaFontRenderer gammaFontRenderer; + FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); + fontRenderer.setFont(&paint, SkMatrix::I()); + TextDropShadowCache cache(MB(5)); + cache.setFontRenderer(fontRenderer); + + std::vector<glyph_t> glyphs; + std::vector<float> positions; + float totalAdvance; + uirenderer::Rect bounds; + TestUtils::layoutTextUnscaled(paint, "This is a test", + &glyphs, &positions, &totalAdvance, &bounds); + EXPECT_TRUE(bounds.contains(5, -10, 100, 0)) << "Expect input to be nontrivially sized"; + + ShadowTexture* texture = cache.get(&paint, glyphs.data(), glyphs.size(), 10, positions.data()); + + ASSERT_TRUE(texture); + ASSERT_FALSE(texture->cleanup); + ASSERT_EQ((uint32_t) texture->objectSize(), cache.getSize()); + ASSERT_TRUE(cache.getSize()); + cache.clear(); + ASSERT_EQ(cache.getSize(), 0u); +} diff --git a/libs/hwui/tests/unit/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp new file mode 100644 index 000000000000..83b485fa705e --- /dev/null +++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2015 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 <gtest/gtest.h> + +#include "PathParser.h" +#include "VectorDrawable.h" +#include "utils/MathUtils.h" +#include "utils/VectorDrawableUtils.h" + +#include <functional> + +namespace android { +namespace uirenderer { + +struct TestData { + const char* pathString; + const PathData pathData; + const std::function<void(SkPath*)> skPathLamda; +}; + +const static TestData sTestDataSet[] = { + // TestData with scientific notation -2e3 etc. + { + // Path + "M2.000000,22.000000l20.000000,0.000000 1e0-2e3z", + { + // Verbs + {'M', 'l', 'z'}, + // Verb sizes + {2, 4, 0}, + // Points + {2, 22, 20, 0, 1, -2000}, + }, + [](SkPath* outPath) { + outPath->moveTo(2, 22); + outPath->rLineTo(20, 0); + outPath->rLineTo(1, -2000); + outPath->close(); + outPath->moveTo(2, 22); + } + }, + + // Comprehensive data, containing all the verbs possible. + { + // Path + "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10", + { + // Verbs + {'M', 'm', 'l', 'L', 'H', 'h', 'V', 'v', 'Q', 'q', 't', 'T', 'C', 'c', 'S', 's', 'A', 'a'}, + // VerbSizes + {2, 2, 2, 2, 1, 1, 1, 1, 4, 4, 2, 2, 6, 6, 4, 4, 7, 7}, + // Points + {1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 6.0, 7.0, 7.0, 7.0, 7.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 8.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, } + }, + [](SkPath* outPath) { + outPath->moveTo(1.0, 1.0); + outPath->rMoveTo(2.0, 2.0); + outPath->rLineTo(3.0, 3.0); + outPath->lineTo(3.0, 3.0); + outPath->lineTo(4.0, 3.0); + outPath->rLineTo(4.0, 0); + outPath->lineTo(8.0, 5.0); + outPath->rLineTo(0, 5.0); + outPath->quadTo(6.0, 6.0, 6.0, 6.0); + outPath->rQuadTo(6.0, 6.0, 6.0, 6.0); + outPath->rQuadTo(0.0, 0.0, 7.0, 7.0); + outPath->quadTo(26.0, 26.0, 7.0, 7.0); + outPath->cubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0); + outPath->rCubicTo(8.0, 8.0, 8.0, 8.0, 8.0, 8.0); + outPath->cubicTo(16.0, 16.0, 9.0, 9.0, 9.0, 9.0); + outPath->rCubicTo(0.0, 0.0, 9.0, 9.0, 9.0, 9.0); + outPath->cubicTo(18.447775037328352, 20.404243860300607, 17.998389141249767, 22.8911717921705, 16.737515350332117, 24.986664170401575); + outPath->cubicTo(15.476641559414468, 27.08215654863265, 13.489843598291483, 28.644011882390082, 11.155893964798905, 29.37447073281729); + outPath->cubicTo(8.821944331306327, 30.1049295832445, 6.299226382436471, 29.954422532383525, 4.0686829203897235, 28.951642951534332); + outPath->cubicTo(1.838139458342976, 27.94886337068514, 0.05113662931485696, 26.161860541657013, -0.9516429515343354, 23.931317079610267); + outPath->cubicTo(-1.9544225323835278, 21.70077361756352, -2.1049295832444987, 19.178055668693663, -1.37447073281729, 16.844106035201087); + outPath->cubicTo(-0.6440118823900814, 14.51015640170851, 0.9178434513673546, 12.523358440585524, 3.0133358295984305, 11.262484649667876); + outPath->cubicTo(5.108828207829506, 10.001610858750228, 7.5957561396993984, 9.552224962671648, 10.000000000000005, 10.0); + outPath->cubicTo(10.0, 7.348852265086975, 11.054287646850167, 4.803576729418881, 12.928932188134523, 2.9289321881345254); + outPath->cubicTo(14.803576729418879, 1.0542876468501696, 17.348852265086972, 4.870079381441987E-16, 19.999999999999996, 0.0); + outPath->cubicTo(22.65114773491302, -4.870079381441987E-16, 25.19642327058112, 1.0542876468501678, 27.071067811865476, 2.9289321881345227); + outPath->cubicTo(28.94571235314983, 4.803576729418878, 30.0, 7.348852265086974, 30.0, 9.999999999999998); + outPath->cubicTo(30.0, 12.651147734913023, 28.94571235314983, 15.19642327058112, 27.071067811865476, 17.071067811865476); + outPath->cubicTo(25.19642327058112, 18.94571235314983, 22.651147734913028, 20.0, 20.000000000000004, 20.0); + } + }, + + // Check box VectorDrawable path data + { + // Path + "M 0.0,-1.0 l 0.0,0.0 c 0.5522847498,0.0 1.0,0.4477152502 1.0,1.0 l 0.0,0.0 c 0.0,0.5522847498 -0.4477152502,1.0 -1.0,1.0 l 0.0,0.0 c -0.5522847498,0.0 -1.0,-0.4477152502 -1.0,-1.0 l 0.0,0.0 c 0.0,-0.5522847498 0.4477152502,-1.0 1.0,-1.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z", + { + {'M', 'l', 'c', 'l', 'c', 'l', 'c', 'l', 'c', 'Z', 'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'Z'}, + {2, 2, 6, 2, 6, 2, 6, 2, 6, 0, 2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0}, + {0.0, -1.0, 0.0, 0.0, 0.5522848, 0.0, 1.0, 0.44771525, 1.0, 1.0, 0.0, 0.0, 0.0, 0.5522848, -0.44771525, 1.0, -1.0, 1.0, 0.0, 0.0, -0.5522848, 0.0, -1.0, -0.44771525, -1.0, -1.0, 0.0, 0.0, 0.0, -0.5522848, 0.44771525, -1.0, 1.0, -1.0, 7.0, -9.0, 0.0, 0.0, -14.0, 0.0, -14.0, 0.0, -1.1044922, 0.0, -2.0, 0.8955078, -2.0, 2.0, 0.0, 0.0, 0.0, 14.0, 0.0, 14.0, 0.0, 1.1044922, 0.8955078, 2.0, 2.0, 2.0, 0.0, 0.0, 14.0, 0.0, 14.0, 0.0, 1.1044922, 0.0, 2.0, -0.8955078, 2.0, -2.0, 0.0, 0.0, 0.0, -14.0, 0.0, -14.0, 0.0, -1.1044922, -0.8955078, -2.0, -2.0, -2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, + }, + [](SkPath* outPath) { + outPath->moveTo(0.0, -1.0); + outPath->rLineTo(0.0, 0.0); + outPath->rCubicTo(0.5522848, 0.0, 1.0, 0.44771525, 1.0, 1.0); + outPath->rLineTo(0.0, 0.0); + outPath->rCubicTo(0.0, 0.5522848, -0.44771525, 1.0, -1.0, 1.0); + outPath->rLineTo(0.0, 0.0); + outPath->rCubicTo(-0.5522848, 0.0, -1.0, -0.44771525, -1.0, -1.0); + outPath->rLineTo(0.0, 0.0); + outPath->rCubicTo(0.0, -0.5522848, 0.44771525, -1.0, 1.0, -1.0); + outPath->close(); + outPath->moveTo(0.0, -1.0); + outPath->moveTo(7.0, -9.0); + outPath->rCubicTo(0.0, 0.0, -14.0, 0.0, -14.0, 0.0); + outPath->rCubicTo(-1.1044922, 0.0, -2.0, 0.8955078, -2.0, 2.0); + outPath->rCubicTo(0.0, 0.0, 0.0, 14.0, 0.0, 14.0); + outPath->rCubicTo(0.0, 1.1044922, 0.8955078, 2.0, 2.0, 2.0); + outPath->rCubicTo(0.0, 0.0, 14.0, 0.0, 14.0, 0.0); + outPath->rCubicTo(1.1044922, 0.0, 2.0, -0.8955078, 2.0, -2.0); + outPath->rCubicTo(0.0, 0.0, 0.0, -14.0, 0.0, -14.0); + outPath->rCubicTo(0.0, -1.1044922, -0.8955078, -2.0, -2.0, -2.0); + outPath->rCubicTo(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + outPath->close(); + outPath->moveTo(7.0, -9.0); + } + }, + + // pie1 in progress bar + { + "M300,70 a230,230 0 1,0 1,0 z", + { + {'M', 'a', 'z', }, + {2, 7, 0, }, + {300.0, 70.0, 230.0, 230.0, 0.0, 1.0, 0.0, 1.0, 0.0, }, + }, + [](SkPath* outPath) { + outPath->moveTo(300.0, 70.0); + outPath->cubicTo(239.06697794203706, 70.13246340443499, 180.6164396449267, 94.47383115953485, 137.6004913602211, 137.6302781499585); + outPath->cubicTo(94.58454307551551, 180.78672514038215, 70.43390412842275, 239.3163266242308, 70.50013586976587, 300.2494566687817); + outPath->cubicTo(70.56636761110899, 361.1825867133326, 94.84418775550249, 419.65954850554147, 137.9538527586204, 462.72238058830936); + outPath->cubicTo(181.06351776173827, 505.7852126710772, 239.5668339599056, 529.999456521097, 300.49999999999994, 529.999456521097); + outPath->cubicTo(361.43316604009436, 529.999456521097, 419.93648223826176, 505.78521267107726, 463.0461472413797, 462.7223805883093); + outPath->cubicTo(506.1558122444976, 419.65954850554135, 530.433632388891, 361.1825867133324, 530.4998641302341, 300.2494566687815); + outPath->cubicTo(530.5660958715771, 239.31632662423056, 506.4154569244844, 180.7867251403819, 463.3995086397787, 137.6302781499583); + outPath->cubicTo(420.383560355073, 94.47383115953468, 361.93302205796255, 70.13246340443492, 300.9999999999996, 70.00000000000003); + outPath->close(); + outPath->moveTo(300.0, 70.0); + } + }, + + // Random long data + { + // Path + "M5.3,13.2c-0.1,0.0 -0.3,0.0 -0.4,-0.1c-0.3,-0.2 -0.4,-0.7 -0.2,-1.0c1.3,-1.9 2.9,-3.4 4.9,-4.5c4.1,-2.2 9.3,-2.2 13.4,0.0c1.9,1.1 3.6,2.5 4.9,4.4c0.2,0.3 0.1,0.8 -0.2,1.0c-0.3,0.2 -0.8,0.1 -1.0,-0.2c-1.2,-1.7 -2.6,-3.0 -4.3,-4.0c-3.7,-2.0 -8.3,-2.0 -12.0,0.0c-1.7,0.9 -3.2,2.3 -4.3,4.0C5.7,13.1 5.5,13.2 5.3,13.2z", + { + // Verbs + {'M', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'c', 'C', 'z'}, + // Verb sizes + {2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0}, + // Points + {5.3, 13.2, -0.1, 0, -0.3, 0, -0.4, -0.1, -0.3, -0.2, -0.4, -0.7, -0.2, -1, 1.3, -1.9, 2.9, -3.4, 4.9, -4.5, 4.1, -2.2, 9.3, -2.2, 13.4, 0, 1.9, 1.1, 3.6, 2.5, 4.9, 4.4, 0.2, 0.3, 0.1, 0.8, -0.2, 1, -0.3, 0.2, -0.8, 0.1, -1, -0.2, -1.2, -1.7, -2.6, -3, -4.3, -4, -3.7, -2, -8.3, -2, -12, 0, -1.7, 0.9, -3.2, 2.3, -4.3, 4, 5.7, 13.1, 5.5, 13.2, 5.3, 13.2}, + }, + [](SkPath* outPath) { + outPath->moveTo(5.3, 13.2); + outPath->rCubicTo(-0.1, 0.0, -0.3, 0.0, -0.4, -0.1); + outPath->rCubicTo(-0.3, -0.2, -0.4, -0.7, -0.2, -1.0); + outPath->rCubicTo(1.3, -1.9, 2.9, -3.4, 4.9, -4.5); + outPath->rCubicTo(4.1, -2.2, 9.3, -2.2, 13.4, 0.0); + outPath->rCubicTo(1.9, 1.1, 3.6, 2.5, 4.9, 4.4); + outPath->rCubicTo(0.2, 0.3, 0.1, 0.8, -0.2, 1.0); + outPath->rCubicTo(-0.3, 0.2, -0.8, 0.1, -1.0, -0.2); + outPath->rCubicTo(-1.2, -1.7, -2.6, -3.0, -4.3, -4.0); + outPath->rCubicTo(-3.7, -2.0, -8.3, -2.0, -12.0, 0.0); + outPath->rCubicTo(-1.7, 0.9, -3.2, 2.3, -4.3, 4.0); + outPath->cubicTo(5.7, 13.1, 5.5, 13.2, 5.3, 13.2); + outPath->close(); + outPath->moveTo(5.3, 13.2); + } + }, + + // Extreme case with numbers and decimal points crunched together + { + // Path + "l0.0.0.5.0.0.5-0.5.0.0-.5z", + { + // Verbs + {'l', 'z'}, + // Verb sizes + {10, 0}, + // Points + {0, 0, 0.5, 0, 0, 0.5, -0.5, 0, 0, -0.5}, + }, + [](SkPath* outPath) { + outPath->rLineTo(0.0, 0.0); + outPath->rLineTo(0.5, 0.0); + outPath->rLineTo(0.0, 0.5); + outPath->rLineTo(-0.5, 0.0); + outPath->rLineTo(0.0, -0.5); + outPath->close(); + outPath->moveTo(0.0, 0.0); + } + }, + + // Empty test data + { + "", + { + // Verbs + {}, + {}, + {}, + }, + [](SkPath* outPath) {} + } + +}; + +struct StringPath { + const char* stringPath; + bool isValid; +}; + +const StringPath sStringPaths[] = { + {"3e...3", false}, // Not starting with a verb and ill-formatted float + {"L.M.F.A.O", false}, // No floats following verbs + {"m 1 1", true}, // Valid path data + {"\n \t z", true}, // Valid path data with leading spaces + {"1-2e34567", false}, // Not starting with a verb and ill-formatted float + {"f 4 5", false}, // Invalid verb + {"\r ", false} // Empty string +}; + + +static bool hasSameVerbs(const PathData& from, const PathData& to) { + return from.verbs == to.verbs && from.verbSizes == to.verbSizes; +} + +TEST(PathParser, parseStringForData) { + for (TestData testData: sTestDataSet) { + PathParser::ParseResult result; + // Test generated path data against the given data. + PathData pathData; + size_t length = strlen(testData.pathString); + PathParser::getPathDataFromAsciiString(&pathData, &result, testData.pathString, length); + EXPECT_EQ(testData.pathData, pathData); + } + + for (StringPath stringPath : sStringPaths) { + PathParser::ParseResult result; + PathData pathData; + SkPath skPath; + PathParser::getPathDataFromAsciiString(&pathData, &result, + stringPath.stringPath, strlen(stringPath.stringPath)); + EXPECT_EQ(stringPath.isValid, !result.failureOccurred); + } +} + +TEST(VectorDrawableUtils, createSkPathFromPathData) { + for (TestData testData: sTestDataSet) { + SkPath expectedPath; + testData.skPathLamda(&expectedPath); + SkPath actualPath; + VectorDrawableUtils::verbsToPath(&actualPath, testData.pathData); + EXPECT_EQ(expectedPath, actualPath); + } +} + +TEST(PathParser, parseAsciiStringForSkPath) { + for (TestData testData: sTestDataSet) { + PathParser::ParseResult result; + size_t length = strlen(testData.pathString); + // Check the return value as well as the SkPath generated. + SkPath actualPath; + PathParser::parseAsciiStringForSkPath(&actualPath, &result, testData.pathString, length); + bool hasValidData = !result.failureOccurred; + EXPECT_EQ(hasValidData, testData.pathData.verbs.size() > 0); + SkPath expectedPath; + testData.skPathLamda(&expectedPath); + EXPECT_EQ(expectedPath, actualPath); + } + + for (StringPath stringPath : sStringPaths) { + PathParser::ParseResult result; + SkPath skPath; + PathParser::parseAsciiStringForSkPath(&skPath, &result, stringPath.stringPath, + strlen(stringPath.stringPath)); + EXPECT_EQ(stringPath.isValid, !result.failureOccurred); + } +} + +TEST(VectorDrawableUtils, morphPathData) { + for (TestData fromData: sTestDataSet) { + for (TestData toData: sTestDataSet) { + bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData); + if (fromData.pathData == toData.pathData) { + EXPECT_TRUE(canMorph); + } else { + bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData); + EXPECT_EQ(expectedToMorph, canMorph); + } + } + } +} + +TEST(VectorDrawableUtils, interpolatePathData) { + // Interpolate path data with itself and every other path data + for (TestData fromData: sTestDataSet) { + for (TestData toData: sTestDataSet) { + PathData outData; + bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData, + toData.pathData, 0.5); + bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData); + EXPECT_EQ(expectedToMorph, success); + } + } + + float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1}; + // Now try to interpolate with a slightly modified version of self and expect success + for (TestData fromData : sTestDataSet) { + PathData toPathData = fromData.pathData; + for (size_t i = 0; i < toPathData.points.size(); i++) { + toPathData.points[i]++; + } + const PathData& fromPathData = fromData.pathData; + PathData outData; + // Interpolate the two path data with different fractions + for (float fraction : fractions) { + bool success = VectorDrawableUtils::interpolatePathData( + &outData, fromPathData, toPathData, fraction); + EXPECT_TRUE(success); + for (size_t i = 0; i < outData.points.size(); i++) { + float expectedResult = fromPathData.points[i] * (1.0 - fraction) + + toPathData.points[i] * fraction; + EXPECT_TRUE(MathUtils::areEqual(expectedResult, outData.points[i])); + } + } + } +} + +TEST(VectorDrawable, matrixScale) { + struct MatrixAndScale { + float buffer[9]; + float matrixScale; + }; + + const MatrixAndScale sMatrixAndScales[] { + { + {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}, + 1.0 + }, + { + {1.0f, 0.0f, 240.0f, 0.0f, 1.0f, 240.0f, 0.0f, 0.0f, 1.0f}, + 1.0f, + }, + { + {1.5f, 0.0f, 24.0f, 0.0f, 1.5f, 24.0f, 0.0f, 0.0f, 1.0f}, + 1.5f, + }, + { + {0.99999994f, 0.0f, 300.0f, 0.0f, 0.99999994f, 158.57864f, 0.0f, 0.0f, 1.0f}, + 0.99999994f, + }, + { + {0.7071067f, 0.7071067f, 402.5305f, -0.7071067f, 0.7071067f, 169.18524f, 0.0f, 0.0f, 1.0f}, + 0.99999994f, + }, + { + {0.0f, 0.9999999f, 482.5305f, -0.9999999f, 0.0f, 104.18525f, 0.0f, 0.0f, 1.0f}, + 0.9999999f, + }, + { + {-0.35810637f, -0.93368083f, 76.55821f, 0.93368083f, -0.35810637f, 89.538506f, 0.0f, 0.0f, 1.0f}, + 1.0000001f, + }, + }; + + for (MatrixAndScale matrixAndScale : sMatrixAndScales) { + SkMatrix matrix; + matrix.set9(matrixAndScale.buffer); + float actualMatrixScale = VectorDrawable::Path::getMatrixScale(matrix); + EXPECT_EQ(matrixAndScale.matrixScale, actualMatrixScale); + } +} + +TEST(VectorDrawable, groupProperties) { + //TODO: Also need to test property sync and dirty flag when properties change. + VectorDrawable::Group group; + VectorDrawable::Group::GroupProperties* properties = group.mutateProperties(); + // Test default values, change values through setters and verify the change through getters. + EXPECT_EQ(0.0f, properties->getTranslateX()); + properties->setTranslateX(1.0f); + EXPECT_EQ(1.0f, properties->getTranslateX()); + + EXPECT_EQ(0.0f, properties->getTranslateY()); + properties->setTranslateY(1.0f); + EXPECT_EQ(1.0f, properties->getTranslateY()); + + EXPECT_EQ(0.0f, properties->getRotation()); + properties->setRotation(1.0f); + EXPECT_EQ(1.0f, properties->getRotation()); + + EXPECT_EQ(1.0f, properties->getScaleX()); + properties->setScaleX(0.0f); + EXPECT_EQ(0.0f, properties->getScaleX()); + + EXPECT_EQ(1.0f, properties->getScaleY()); + properties->setScaleY(0.0f); + EXPECT_EQ(0.0f, properties->getScaleY()); + + EXPECT_EQ(0.0f, properties->getPivotX()); + properties->setPivotX(1.0f); + EXPECT_EQ(1.0f, properties->getPivotX()); + + EXPECT_EQ(0.0f, properties->getPivotY()); + properties->setPivotY(1.0f); + EXPECT_EQ(1.0f, properties->getPivotY()); + +} +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/unit_tests/how_to_run.txt b/libs/hwui/tests/unit/how_to_run.txt index a2d6a34726df..c11d6eb33358 100755 --- a/libs/hwui/unit_tests/how_to_run.txt +++ b/libs/hwui/tests/unit/how_to_run.txt @@ -1,4 +1,4 @@ -mmm -j8 $ANDROID_BUILD_TOP/frameworks/base/libs/hwui/unit_tests && +mmm -j8 frameworks/base/libs/hwui && adb push $ANDROID_PRODUCT_OUT/data/nativetest/hwui_unit_tests/hwui_unit_tests \ /data/nativetest/hwui_unit_tests/hwui_unit_tests && adb shell /data/nativetest/hwui_unit_tests/hwui_unit_tests diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp new file mode 100644 index 000000000000..409a12d37693 --- /dev/null +++ b/libs/hwui/tests/unit/main.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 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 "gtest/gtest.h" + +#include "Caches.h" +#include "thread/TaskManager.h" +#include "tests/common/TestUtils.h" + +#include <memunreachable/memunreachable.h> + +#include <cstdio> +#include <iostream> +#include <map> +#include <unordered_set> +#include <signal.h> +#include <unistd.h> + +using namespace std; +using namespace android; +using namespace android::uirenderer; + +static auto CRASH_SIGNALS = { + SIGABRT, + SIGSEGV, + SIGBUS, +}; + +static map<int, struct sigaction> gSigChain; + +static void gtestSigHandler(int sig, siginfo_t* siginfo, void* context) { + auto testinfo = ::testing::UnitTest::GetInstance()->current_test_info(); + printf("[ FAILED ] %s.%s\n", testinfo->test_case_name(), + testinfo->name()); + printf("[ FATAL! ] Process crashed, aborting tests!\n"); + fflush(stdout); + + // restore the default sighandler and re-raise + struct sigaction sa = gSigChain[sig]; + sigaction(sig, &sa, nullptr); + raise(sig); +} + +static void logUnreachable(initializer_list<UnreachableMemoryInfo> infolist) { + // merge them all + UnreachableMemoryInfo merged; + unordered_set<uintptr_t> addrs; + merged.allocation_bytes = 0; + merged.leak_bytes = 0; + merged.num_allocations = 0; + merged.num_leaks = 0; + for (auto& info : infolist) { + // We'll be a little hazzy about these ones and just hope the biggest + // is the most accurate + merged.allocation_bytes = max(merged.allocation_bytes, info.allocation_bytes); + merged.num_allocations = max(merged.num_allocations, info.num_allocations); + for (auto& leak : info.leaks) { + if (addrs.find(leak.begin) == addrs.end()) { + merged.leaks.push_back(leak); + merged.num_leaks++; + merged.leak_bytes += leak.size; + addrs.insert(leak.begin); + } + } + } + + // Now log the result + if (merged.num_leaks) { + cout << endl << "Leaked memory!" << endl; + if (!merged.leaks[0].backtrace.num_frames) { + cout << "Re-run with 'setprop libc.debug.malloc.program hwui_unit_test'" + << endl << "and 'setprop libc.debug.malloc.options backtrace=8'" + << " to get backtraces" << endl; + } + cout << merged.ToString(false); + } +} + +static void checkForLeaks() { + // TODO: Until we can shutdown the RT thread we need to do this in + // two passes as GetUnreachableMemory has limited insight into + // thread-local caches so some leaks will not be properly tagged as leaks + nsecs_t before = systemTime(); + UnreachableMemoryInfo rtMemInfo; + TestUtils::runOnRenderThread([&rtMemInfo](renderthread::RenderThread& thread) { + if (Caches::hasInstance()) { + Caches::getInstance().tasks.stop(); + } + // Check for leaks + if (!GetUnreachableMemory(rtMemInfo)) { + cerr << "Failed to get unreachable memory!" << endl; + return; + } + }); + UnreachableMemoryInfo uiMemInfo; + if (!GetUnreachableMemory(uiMemInfo)) { + cerr << "Failed to get unreachable memory!" << endl; + return; + } + logUnreachable({rtMemInfo, uiMemInfo}); + nsecs_t after = systemTime(); + cout << "Leak check took " << ns2ms(after - before) << "ms" << endl; +} + +int main(int argc, char* argv[]) { + // Register a crash handler + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = >estSigHandler; + sa.sa_flags = SA_SIGINFO; + for (auto sig : CRASH_SIGNALS) { + struct sigaction old_sa; + sigaction(sig, &sa, &old_sa); + gSigChain.insert(pair<int, struct sigaction>(sig, old_sa)); + } + + // Run the tests + testing::InitGoogleTest(&argc, argv); + int ret = RUN_ALL_TESTS(); + checkForLeaks(); + return ret; +} + diff --git a/libs/hwui/thread/Barrier.h b/libs/hwui/thread/Barrier.h index 6cb23e54943b..0a7acb0fbbfd 100644 --- a/libs/hwui/thread/Barrier.h +++ b/libs/hwui/thread/Barrier.h @@ -33,11 +33,6 @@ public: mCondition.signal(mType); } - void close() { - Mutex::Autolock l(mLock); - mOpened = false; - } - void wait() const { Mutex::Autolock l(mLock); while (!mOpened) { diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp index e9dde294b2aa..d346b859526e 100644 --- a/libs/hwui/thread/TaskManager.cpp +++ b/libs/hwui/thread/TaskManager.cpp @@ -39,7 +39,7 @@ TaskManager::TaskManager() { for (int i = 0; i < workerCount; i++) { String8 name; name.appendFormat("hwuiTask%d", i + 1); - mThreads.add(new WorkerThread(name)); + mThreads.push_back(new WorkerThread(name)); } } @@ -89,36 +89,34 @@ status_t TaskManager::WorkerThread::readyToRun() { bool TaskManager::WorkerThread::threadLoop() { mSignal.wait(); - Vector<TaskWrapper> tasks; + std::vector<TaskWrapper> tasks; { Mutex::Autolock l(mLock); - tasks = mTasks; - mTasks.clear(); + tasks.swap(mTasks); } for (size_t i = 0; i < tasks.size(); i++) { - const TaskWrapper& task = tasks.itemAt(i); + const TaskWrapper& task = tasks[i]; task.mProcessor->process(task.mTask); } return true; } -bool TaskManager::WorkerThread::addTask(TaskWrapper task) { +bool TaskManager::WorkerThread::addTask(const TaskWrapper& task) { if (!isRunning()) { run(mName.string(), PRIORITY_DEFAULT); } else if (exitPending()) { return false; } - ssize_t index; { Mutex::Autolock l(mLock); - index = mTasks.add(task); + mTasks.push_back(task); } mSignal.signal(); - return index >= 0; + return true; } size_t TaskManager::WorkerThread::getTaskCount() const { diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h index 10e8b9e0bead..e4808f7b7181 100644 --- a/libs/hwui/thread/TaskManager.h +++ b/libs/hwui/thread/TaskManager.h @@ -20,10 +20,11 @@ #include <utils/Mutex.h> #include <utils/String8.h> #include <utils/Thread.h> -#include <utils/Vector.h> #include "Signal.h" +#include <vector> + namespace android { namespace uirenderer { @@ -79,7 +80,7 @@ private: public: WorkerThread(const String8 name): mSignal(Condition::WAKE_UP_ONE), mName(name) { } - bool addTask(TaskWrapper task); + bool addTask(const TaskWrapper& task); size_t getTaskCount() const; void exit(); @@ -89,7 +90,7 @@ private: // Lock for the list of tasks mutable Mutex mLock; - Vector<TaskWrapper> mTasks; + std::vector<TaskWrapper> mTasks; // Signal used to wake up the thread when a new // task is available in the list @@ -98,7 +99,7 @@ private: const String8 mName; }; - Vector<sp<WorkerThread> > mThreads; + std::vector<sp<WorkerThread> > mThreads; }; }; // namespace uirenderer diff --git a/libs/hwui/unit_tests/Android.mk b/libs/hwui/unit_tests/Android.mk deleted file mode 100644 index 917e646e2303..000000000000 --- a/libs/hwui/unit_tests/Android.mk +++ /dev/null @@ -1,35 +0,0 @@ -# -# Copyright (C) 2014 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. -# - -local_target_dir := $(TARGET_OUT_DATA)/local/tmp -LOCAL_PATH:= $(call my-dir)/.. - -include $(CLEAR_VARS) - -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.common.mk -LOCAL_MODULE := hwui_unit_tests -LOCAL_MODULE_TAGS := tests - -include $(LOCAL_PATH)/Android.common.mk - -LOCAL_SRC_FILES += \ - unit_tests/ClipAreaTests.cpp \ - unit_tests/DamageAccumulatorTests.cpp \ - unit_tests/LinearAllocatorTests.cpp \ - unit_tests/main.cpp - - -include $(BUILD_NATIVE_TEST) diff --git a/libs/hwui/unit_tests/ClipAreaTests.cpp b/libs/hwui/unit_tests/ClipAreaTests.cpp deleted file mode 100644 index 0c5e5e715dea..000000000000 --- a/libs/hwui/unit_tests/ClipAreaTests.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2015 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 <gtest/gtest.h> -#include <SkPath.h> -#include <SkRegion.h> - -#include "ClipArea.h" - -#include "Matrix.h" -#include "Rect.h" -#include "utils/LinearAllocator.h" - -namespace android { -namespace uirenderer { - -static Rect kViewportBounds(0, 0, 2048, 2048); - -static ClipArea createClipArea() { - ClipArea area; - area.setViewportDimensions(kViewportBounds.getWidth(), kViewportBounds.getHeight()); - return area; -} - -TEST(TransformedRectangle, basics) { - Rect r(0, 0, 100, 100); - Matrix4 minus90; - minus90.loadRotate(-90); - minus90.mapRect(r); - Rect r2(20, 40, 120, 60); - - Matrix4 m90; - m90.loadRotate(90); - TransformedRectangle tr(r, m90); - EXPECT_TRUE(tr.canSimplyIntersectWith(tr)); - - Matrix4 m0; - TransformedRectangle tr0(r2, m0); - EXPECT_FALSE(tr.canSimplyIntersectWith(tr0)); - - Matrix4 m45; - m45.loadRotate(45); - TransformedRectangle tr2(r, m45); - EXPECT_FALSE(tr2.canSimplyIntersectWith(tr)); -} - -TEST(RectangleList, basics) { - RectangleList list; - EXPECT_TRUE(list.isEmpty()); - - Rect r(0, 0, 100, 100); - Matrix4 m45; - m45.loadRotate(45); - list.set(r, m45); - EXPECT_FALSE(list.isEmpty()); - - Rect r2(20, 20, 200, 200); - list.intersectWith(r2, m45); - EXPECT_FALSE(list.isEmpty()); - EXPECT_EQ(1, list.getTransformedRectanglesCount()); - - Rect r3(20, 20, 200, 200); - Matrix4 m30; - m30.loadRotate(30); - list.intersectWith(r2, m30); - EXPECT_FALSE(list.isEmpty()); - EXPECT_EQ(2, list.getTransformedRectanglesCount()); - - SkRegion clip; - clip.setRect(0, 0, 2000, 2000); - SkRegion rgn(list.convertToRegion(clip)); - EXPECT_FALSE(rgn.isEmpty()); -} - -TEST(ClipArea, basics) { - ClipArea area(createClipArea()); - EXPECT_FALSE(area.isEmpty()); -} - -TEST(ClipArea, paths) { - ClipArea area(createClipArea()); - Matrix4 transform; - transform.loadIdentity(); - SkPath path; - SkScalar r = 100; - path.addCircle(r, r, r); - area.clipPathWithTransform(path, &transform, SkRegion::kIntersect_Op); - EXPECT_FALSE(area.isEmpty()); - EXPECT_FALSE(area.isSimple()); - EXPECT_FALSE(area.isRectangleList()); - Rect clipRect(area.getClipRect()); - clipRect.dump("clipRect"); - Rect expected(0, 0, r * 2, r * 2); - expected.dump("expected"); - EXPECT_EQ(expected, clipRect); - SkRegion clipRegion(area.getClipRegion()); - auto skRect(clipRegion.getBounds()); - Rect regionBounds; - regionBounds.set(skRect); - EXPECT_EQ(expected, regionBounds); -} - -TEST(ClipArea, replaceNegative) { - ClipArea area(createClipArea()); - area.setClip(0, 0, 100, 100); - - Matrix4 transform; - transform.loadIdentity(); - Rect expected(-50, -50, 50, 50); - area.clipRectWithTransform(expected, &transform, SkRegion::kReplace_Op); - EXPECT_EQ(expected, area.getClipRect()); -} -} -} diff --git a/libs/hwui/unit_tests/DamageAccumulatorTests.cpp b/libs/hwui/unit_tests/DamageAccumulatorTests.cpp deleted file mode 100644 index c8d6004e11ec..000000000000 --- a/libs/hwui/unit_tests/DamageAccumulatorTests.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2015 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 <gtest/gtest.h> - -#include <DamageAccumulator.h> -#include <Matrix.h> -#include <utils/LinearAllocator.h> - -#include <SkRect.h> - -using namespace android; -using namespace android::uirenderer; - -// Test that push & pop are propegating the dirty rect -// There is no transformation of the dirty rect, the input is the same -// as the output. -TEST(DamageAccumulator, identity) { - DamageAccumulator da; - Matrix4 identity; - SkRect curDirty; - identity.loadIdentity(); - da.pushTransform(&identity); - da.dirty(50, 50, 100, 100); - da.pushTransform(&identity); - da.peekAtDirty(&curDirty); - ASSERT_EQ(SkRect(), curDirty); - da.popTransform(); - da.peekAtDirty(&curDirty); - ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty); - da.popTransform(); - da.finish(&curDirty); - ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty); -} - -// Test that transformation is happening at the correct levels via -// peekAtDirty & popTransform. Just uses a simple translate to test this -TEST(DamageAccumulator, translate) { - DamageAccumulator da; - Matrix4 translate; - SkRect curDirty; - translate.loadTranslate(25, 25, 0); - da.pushTransform(&translate); - da.dirty(50, 50, 100, 100); - da.peekAtDirty(&curDirty); - ASSERT_EQ(SkRect::MakeLTRB(50, 50, 100, 100), curDirty); - da.popTransform(); - da.finish(&curDirty); - ASSERT_EQ(SkRect::MakeLTRB(75, 75, 125, 125), curDirty); -} - -// Test that dirty rectangles are being unioned across "siblings -TEST(DamageAccumulator, union) { - DamageAccumulator da; - Matrix4 identity; - SkRect curDirty; - identity.loadIdentity(); - da.pushTransform(&identity); - da.pushTransform(&identity); - da.dirty(50, 50, 100, 100); - da.popTransform(); - da.pushTransform(&identity); - da.dirty(150, 50, 200, 125); - da.popTransform(); - da.popTransform(); - da.finish(&curDirty); - ASSERT_EQ(SkRect::MakeLTRB(50, 50, 200, 125), curDirty); -} diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp deleted file mode 100644 index b3959d169e1d..000000000000 --- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2015 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 <gtest/gtest.h> -#include <utils/LinearAllocator.h> - -using namespace android; -using namespace android::uirenderer; - -struct SimplePair { - int one = 1; - int two = 2; -}; - -class SignalingDtor { -public: - SignalingDtor() { - mDestroyed = nullptr; - } - SignalingDtor(bool* destroyedSignal) { - mDestroyed = destroyedSignal; - *mDestroyed = false; - } - virtual ~SignalingDtor() { - if (mDestroyed) { - *mDestroyed = true; - } - } - void setSignal(bool* destroyedSignal) { - mDestroyed = destroyedSignal; - } -private: - bool* mDestroyed; -}; - -TEST(LinearAllocator, alloc) { - LinearAllocator la; - EXPECT_EQ(0u, la.usedSize()); - la.alloc(64); - // There's some internal tracking as well as padding - // so the usedSize isn't strictly defined - EXPECT_LE(64u, la.usedSize()); - EXPECT_GT(80u, la.usedSize()); - auto pair = la.alloc<SimplePair>(); - EXPECT_LE(64u + sizeof(SimplePair), la.usedSize()); - EXPECT_GT(80u + sizeof(SimplePair), la.usedSize()); - EXPECT_EQ(1, pair->one); - EXPECT_EQ(2, pair->two); -} - -TEST(LinearAllocator, dtor) { - bool destroyed[10]; - { - LinearAllocator la; - for (int i = 0; i < 5; i++) { - la.alloc<SignalingDtor>()->setSignal(destroyed + i); - la.alloc<SimplePair>(); - } - la.alloc(100); - for (int i = 0; i < 5; i++) { - auto sd = new (la) SignalingDtor(destroyed + 5 + i); - la.autoDestroy(sd); - new (la) SimplePair(); - } - la.alloc(100); - for (int i = 0; i < 10; i++) { - EXPECT_FALSE(destroyed[i]); - } - } - for (int i = 0; i < 10; i++) { - EXPECT_TRUE(destroyed[i]); - } -} - -TEST(LinearAllocator, rewind) { - bool destroyed; - { - LinearAllocator la; - auto addr = la.alloc(100); - EXPECT_LE(100u, la.usedSize()); - la.rewindIfLastAlloc(addr, 100); - EXPECT_GT(16u, la.usedSize()); - size_t emptySize = la.usedSize(); - auto sigdtor = la.alloc<SignalingDtor>(); - sigdtor->setSignal(&destroyed); - EXPECT_FALSE(destroyed); - EXPECT_LE(emptySize, la.usedSize()); - la.rewindIfLastAlloc(sigdtor); - EXPECT_TRUE(destroyed); - EXPECT_EQ(emptySize, la.usedSize()); - destroyed = false; - } - // Checking for a double-destroy case - EXPECT_EQ(destroyed, false); -} diff --git a/libs/hwui/utils/Blur.cpp b/libs/hwui/utils/Blur.cpp index 877a42216c27..9b70765ee8ad 100644 --- a/libs/hwui/utils/Blur.cpp +++ b/libs/hwui/utils/Blur.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include <math.h> #include "Blur.h" @@ -60,7 +58,9 @@ static float legacyConvertRadiusToSigma(float radius) { return radius > 0 ? 0.3f * radius + 0.6f : 0.0f; } -void Blur::generateGaussianWeights(float* weights, int32_t radius) { +void Blur::generateGaussianWeights(float* weights, float radius) { + int32_t intRadius = convertRadiusToInt(radius); + // Compute gaussian weights for the blur // e is the euler's number static float e = 2.718281828459045f; @@ -68,7 +68,7 @@ void Blur::generateGaussianWeights(float* weights, int32_t radius) { // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 ) // x is of the form [-radius .. 0 .. radius] // and sigma varies with radius. - float sigma = legacyConvertRadiusToSigma((float) radius); + float sigma = legacyConvertRadiusToSigma(radius); // Now compute the coefficints // We will store some redundant values to save some math during @@ -78,16 +78,16 @@ void Blur::generateGaussianWeights(float* weights, int32_t radius) { float coeff2 = - 1.0f / (2.0f * sigma * sigma); float normalizeFactor = 0.0f; - for (int32_t r = -radius; r <= radius; r ++) { + for (int32_t r = -intRadius; r <= intRadius; r ++) { float floatR = (float) r; - weights[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2); - normalizeFactor += weights[r + radius]; + weights[r + intRadius] = coeff1 * pow(e, floatR * floatR * coeff2); + normalizeFactor += weights[r + intRadius]; } //Now we need to normalize the weights because all our coefficients need to add up to one normalizeFactor = 1.0f / normalizeFactor; - for (int32_t r = -radius; r <= radius; r ++) { - weights[r + radius] *= normalizeFactor; + for (int32_t r = -intRadius; r <= intRadius; r ++) { + weights[r + intRadius] *= normalizeFactor; } } diff --git a/libs/hwui/utils/Blur.h b/libs/hwui/utils/Blur.h index b14533312719..3f21832bf2b5 100644 --- a/libs/hwui/utils/Blur.h +++ b/libs/hwui/utils/Blur.h @@ -34,7 +34,7 @@ public: // accounts for that error and snaps to the appropriate integer boundary. static uint32_t convertRadiusToInt(float radius); - static void generateGaussianWeights(float* weights, int32_t radius); + static void generateGaussianWeights(float* weights, float radius); static void horizontal(float* weights, int32_t radius, const uint8_t* source, uint8_t* dest, int32_t width, int32_t height); static void vertical(float* weights, int32_t radius, const uint8_t* source, diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h new file mode 100644 index 000000000000..b5157f401438 --- /dev/null +++ b/libs/hwui/utils/Color.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef COLOR_H +#define COLOR_H + +#include <SkColor.h> + +namespace android { +namespace uirenderer { + namespace Color { + enum Color { + Red_500 = 0xFFF44336, + Pink_500 = 0xFFE91E63, + Purple_500 = 0xFF9C27B0, + DeepPurple_500 = 0xFF673AB7, + Indigo_500 = 0xFF3F51B5, + Blue_500 = 0xFF2196F3, + LightBlue_300 = 0xFF4FC3F7, + LightBlue_500 = 0xFF03A9F4, + Cyan_500 = 0xFF00BCD4, + Teal_500 = 0xFF009688, + Teal_700 = 0xFF00796B, + Green_500 = 0xFF4CAF50, + Green_700 = 0xFF388E3C, + LightGreen_500 = 0xFF8BC34A, + LightGreen_700 = 0xFF689F38, + Lime_500 = 0xFFCDDC39, + Yellow_500 = 0xFFFFEB3B, + Amber_500 = 0xFFFFC107, + Orange_500 = 0xFFFF9800, + DeepOrange_500 = 0xFFFF5722, + Brown_500 = 0xFF795548, + Grey_200 = 0xFFEEEEEE, + Grey_500 = 0xFF9E9E9E, + Grey_700 = 0xFF616161, + BlueGrey_500 = 0xFF607D8B, + Transparent = 0x00000000, + Black = 0xFF000000, + White = 0xFFFFFFFF, + }; + } + + static_assert(Color::White == SK_ColorWHITE, "color format has changed"); + static_assert(Color::Black == SK_ColorBLACK, "color format has changed"); + + // Array of bright (500 intensity) colors for synthetic content + static const Color::Color BrightColors[] = { + Color::Red_500, + Color::Pink_500, + Color::Purple_500, + Color::DeepPurple_500, + Color::Indigo_500, + Color::Blue_500, + Color::LightBlue_500, + Color::Cyan_500, + Color::Teal_500, + Color::Green_500, + Color::LightGreen_500, + Color::Lime_500, + Color::Yellow_500, + Color::Amber_500, + Color::Orange_500, + Color::DeepOrange_500, + Color::Brown_500, + Color::Grey_500, + Color::BlueGrey_500, + }; + static constexpr int BrightColorsCount = sizeof(BrightColors) / sizeof(Color::Color); + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* TEST_UTILS_H */ diff --git a/libs/hwui/utils/FatVector.h b/libs/hwui/utils/FatVector.h new file mode 100644 index 000000000000..93d37c28f8a4 --- /dev/null +++ b/libs/hwui/utils/FatVector.h @@ -0,0 +1,107 @@ +/* + * Copyright 2015, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ANDROID_FAT_VECTOR_H +#define ANDROID_FAT_VECTOR_H + +#include "utils/Macros.h" + +#include <stddef.h> +#include <stdlib.h> +#include <type_traits> +#include <utils/Log.h> + +#include <vector> + +namespace android { +namespace uirenderer { + +template <typename T, size_t SIZE> +class InlineStdAllocator { +public: + struct Allocation { + PREVENT_COPY_AND_ASSIGN(Allocation); + public: + Allocation() {}; + // char array instead of T array, so memory is uninitialized, with no destructors run + char array[sizeof(T) * SIZE]; + bool inUse = false; + }; + + typedef T value_type; // needed to implement std::allocator + typedef T* pointer; // needed to implement std::allocator + + InlineStdAllocator(Allocation& allocation) + : mAllocation(allocation) {} + InlineStdAllocator(const InlineStdAllocator& other) + : mAllocation(other.mAllocation) {} + ~InlineStdAllocator() {} + + T* allocate(size_t num, const void* = 0) { + if (!mAllocation.inUse && num <= SIZE) { + mAllocation.inUse = true; + return (T*) mAllocation.array; + } else { + return (T*) malloc(num * sizeof(T)); + } + } + + void deallocate(pointer p, size_t num) { + if (p == (T*)mAllocation.array) { + mAllocation.inUse = false; + } else { + // 'free' instead of delete here - destruction handled separately + free(p); + } + } + Allocation& mAllocation; +}; + +/** + * std::vector with SIZE elements preallocated into an internal buffer. + * + * Useful for avoiding the cost of malloc in cases where only SIZE or + * fewer elements are needed in the common case. + */ +template <typename T, size_t SIZE> +class FatVector : public std::vector<T, InlineStdAllocator<T, SIZE>> { +public: + FatVector() : std::vector<T, InlineStdAllocator<T, SIZE>>( + InlineStdAllocator<T, SIZE>(mAllocation)) { + this->reserve(SIZE); + } + + FatVector(size_t capacity) : FatVector() { + this->resize(capacity); + } + +private: + typename InlineStdAllocator<T, SIZE>::Allocation mAllocation; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_FAT_VECTOR_H diff --git a/libs/hwui/utils/GLUtils.cpp b/libs/hwui/utils/GLUtils.cpp index 55104de5a9d8..332097593c86 100644 --- a/libs/hwui/utils/GLUtils.cpp +++ b/libs/hwui/utils/GLUtils.cpp @@ -21,10 +21,19 @@ #include "GLUtils.h" +#if DEBUG_OPENGL >= DEBUG_LEVEL_HIGH && !defined(HWUI_GLES_WRAP_ENABLED) +#error Setting DEBUG_OPENGL to HIGH requires setting HWUI_ENABLE_OPENGL_VALIDATION to true in the Android.mk! +#endif + namespace android { namespace uirenderer { bool GLUtils::dumpGLErrors() { +#if DEBUG_OPENGL >= DEBUG_LEVEL_HIGH + // If DEBUG_LEVEL_HIGH is set then every GLES call is already wrapped + // and asserts that there was no error. So this can just return success. + return false; +#else bool errorObserved = false; GLenum status = GL_NO_ERROR; while ((status = glGetError()) != GL_NO_ERROR) { @@ -47,6 +56,7 @@ bool GLUtils::dumpGLErrors() { } } return errorObserved; +#endif } }; // namespace uirenderer diff --git a/libs/hwui/utils/GLUtils.h b/libs/hwui/utils/GLUtils.h index 702046148ad8..b49c1eb1dc05 100644 --- a/libs/hwui/utils/GLUtils.h +++ b/libs/hwui/utils/GLUtils.h @@ -16,13 +16,29 @@ #ifndef GLUTILS_H #define GLUTILS_H +#include "Debug.h" + +#include <cutils/log.h> + namespace android { namespace uirenderer { + +#if DEBUG_OPENGL +#define GL_CHECKPOINT(LEVEL) \ + do { if (DEBUG_OPENGL >= DEBUG_LEVEL_##LEVEL) {\ + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(),\ + "GL errors! %s:%d", __FILE__, __LINE__);\ + } } while (0) +#else +#define GL_CHECKPOINT(LEVEL) +#endif + class GLUtils { public: /** * Print out any GL errors with ALOGE, returns true if any errors were found. + * You probably want to use GL_CHECKPOINT(LEVEL) instead of calling this directly */ static bool dumpGLErrors(); diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp index 59b12cf66a89..5bba420a258f 100644 --- a/libs/hwui/utils/LinearAllocator.cpp +++ b/libs/hwui/utils/LinearAllocator.cpp @@ -32,7 +32,7 @@ // The ideal size of a page allocation (these need to be multiples of 8) -#define INITIAL_PAGE_SIZE ((size_t)4096) // 4kb +#define INITIAL_PAGE_SIZE ((size_t)512) // 512b #define MAX_PAGE_SIZE ((size_t)131072) // 128kb // The maximum amount of wasted space we can have per page @@ -40,7 +40,7 @@ // If this is too low, we will malloc too much // Too high, and we may waste too much space // Must be smaller than INITIAL_PAGE_SIZE -#define MAX_WASTE_SIZE ((size_t)1024) +#define MAX_WASTE_RATIO (0.5f) #if ALIGN_DOUBLE #define ALIGN_SZ (sizeof(double)) @@ -52,8 +52,8 @@ #define ALIGN_PTR(p) ((void*)(ALIGN((size_t)p))) #if LOG_NDEBUG -#define ADD_ALLOCATION(size) -#define RM_ALLOCATION(size) +#define ADD_ALLOCATION() +#define RM_ALLOCATION() #else #include <utils/Thread.h> #include <utils/Timers.h> @@ -65,26 +65,22 @@ static void _logUsageLocked() { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); if (now > s_nextLog) { s_nextLog = now + milliseconds_to_nanoseconds(10); - ALOGV("Total memory usage: %zu kb", s_totalAllocations / 1024); + ALOGV("Total pages allocated: %zu", s_totalAllocations); } } -static void _addAllocation(size_t size) { +static void _addAllocation(int count) { android::AutoMutex lock(s_mutex); - s_totalAllocations += size; + s_totalAllocations += count; _logUsageLocked(); } -#define ADD_ALLOCATION(size) _addAllocation(size); -#define RM_ALLOCATION(size) _addAllocation(-size); +#define ADD_ALLOCATION(size) _addAllocation(1); +#define RM_ALLOCATION(size) _addAllocation(-1); #endif #define min(x,y) (((x) < (y)) ? (x) : (y)) -void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la) { - return la.alloc(size); -} - namespace android { namespace uirenderer { @@ -114,7 +110,7 @@ private: LinearAllocator::LinearAllocator() : mPageSize(INITIAL_PAGE_SIZE) - , mMaxAllocSize(MAX_WASTE_SIZE) + , mMaxAllocSize(INITIAL_PAGE_SIZE * MAX_WASTE_RATIO) , mNext(0) , mCurrentPage(0) , mPages(0) @@ -134,13 +130,13 @@ LinearAllocator::~LinearAllocator(void) { Page* next = p->next(); p->~Page(); free(p); - RM_ALLOCATION(mPageSize); + RM_ALLOCATION(); p = next; } } void* LinearAllocator::start(Page* p) { - return ALIGN_PTR(((size_t*)p) + sizeof(Page)); + return ALIGN_PTR((size_t)p + sizeof(Page)); } void* LinearAllocator::end(Page* p) { @@ -156,6 +152,7 @@ void LinearAllocator::ensureNext(size_t size) { if (mCurrentPage && mPageSize < MAX_PAGE_SIZE) { mPageSize = min(MAX_PAGE_SIZE, mPageSize * 2); + mMaxAllocSize = mPageSize * MAX_WASTE_RATIO; mPageSize = ALIGN(mPageSize); } mWastedSpace += mPageSize; @@ -170,7 +167,7 @@ void LinearAllocator::ensureNext(size_t size) { mNext = start(mCurrentPage); } -void* LinearAllocator::alloc(size_t size) { +void* LinearAllocator::allocImpl(size_t size) { size = ALIGN(size); if (size > mMaxAllocSize && !fitsInCurrentPage(size)) { ALOGV("Exceeded max size %zu > %zu", size, mMaxAllocSize); @@ -195,7 +192,7 @@ void LinearAllocator::addToDestructionList(Destructor dtor, void* addr) { "DestructorNode must have standard layout"); static_assert(std::is_trivially_destructible<DestructorNode>::value, "DestructorNode must be trivially destructable"); - auto node = new (*this) DestructorNode(); + auto node = new (allocImpl(sizeof(DestructorNode))) DestructorNode(); node->dtor = dtor; node->addr = addr; node->next = mDtorList; @@ -237,7 +234,7 @@ void LinearAllocator::rewindIfLastAlloc(void* ptr, size_t allocSize) { LinearAllocator::Page* LinearAllocator::newPage(size_t pageSize) { pageSize = ALIGN(pageSize + sizeof(LinearAllocator::Page)); - ADD_ALLOCATION(pageSize); + ADD_ALLOCATION(); mTotalAllocated += pageSize; mPageCount++; void* buf = malloc(pageSize); diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h index d90dd825ea1d..34c8c6beea81 100644 --- a/libs/hwui/utils/LinearAllocator.h +++ b/libs/hwui/utils/LinearAllocator.h @@ -29,6 +29,8 @@ #include <stddef.h> #include <type_traits> +#include <vector> + namespace android { namespace uirenderer { @@ -50,30 +52,43 @@ public: * The lifetime of the returned buffers is tied to that of the LinearAllocator. If calling * delete() on an object stored in a buffer is needed, it should be overridden to use * rewindIfLastAlloc() + * + * Note that unlike create, for alloc the type is purely for compile-time error + * checking and does not affect size. */ - void* alloc(size_t size); + template<class T> + void* alloc(size_t size) { + static_assert(std::is_trivially_destructible<T>::value, + "Error, type is non-trivial! did you mean to use create()?"); + return allocImpl(size); + } /** - * Allocates an instance of the template type with the default constructor + * Allocates an instance of the template type with the given construction parameters * and adds it to the automatic destruction list. */ - template<class T> - T* alloc() { - T* ret = new (*this) T; - autoDestroy(ret); + template<class T, typename... Params> + T* create(Params&&... params) { + T* ret = new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...); + if (!std::is_trivially_destructible<T>::value) { + auto dtor = [](void* ret) { ((T*)ret)->~T(); }; + addToDestructionList(dtor, ret); + } return ret; } - /** - * Adds the pointer to the tracking list to have its destructor called - * when the LinearAllocator is destroyed. - */ + template<class T, typename... Params> + T* create_trivial(Params&&... params) { + static_assert(std::is_trivially_destructible<T>::value, + "Error, called create_trivial on a non-trivial type"); + return new (allocImpl(sizeof(T))) T(std::forward<Params>(params)...); + } + template<class T> - void autoDestroy(T* addr) { - if (!std::is_trivially_destructible<T>::value) { - auto dtor = [](void* addr) { ((T*)addr)->~T(); }; - addToDestructionList(dtor, addr); - } + T* create_trivial_array(int count) { + static_assert(std::is_trivially_destructible<T>::value, + "Error, called create_trivial_array on a non-trivial type"); + return reinterpret_cast<T*>(allocImpl(sizeof(T) * count)); } /** @@ -112,6 +127,8 @@ private: DestructorNode* next = nullptr; }; + void* allocImpl(size_t size); + void addToDestructionList(Destructor, void* addr); void runDestructorFor(void* addr); Page* newPage(size_t pageSize); @@ -134,9 +151,55 @@ private: size_t mDedicatedPageCount; }; +template <class T> +class LinearStdAllocator { +public: + typedef T value_type; // needed to implement std::allocator + typedef T* pointer; // needed to implement std::allocator + + LinearStdAllocator(LinearAllocator& allocator) + : linearAllocator(allocator) {} + LinearStdAllocator(const LinearStdAllocator& other) + : linearAllocator(other.linearAllocator) {} + ~LinearStdAllocator() {} + + // rebind marks that allocators can be rebound to different types + template <class U> + struct rebind { + typedef LinearStdAllocator<U> other; + }; + // enable allocators to be constructed from other templated types + template <class U> + LinearStdAllocator(const LinearStdAllocator<U>& other) + : linearAllocator(other.linearAllocator) {} + + T* allocate(size_t num, const void* = 0) { + return (T*)(linearAllocator.alloc<void*>(num * sizeof(T))); + } + + void deallocate(pointer p, size_t num) { + // attempt to rewind, but no guarantees + linearAllocator.rewindIfLastAlloc(p, num * sizeof(T)); + } + + // public so template copy constructor can access + LinearAllocator& linearAllocator; +}; + +// return that all specializations of LinearStdAllocator are interchangeable +template <class T1, class T2> +bool operator== (const LinearStdAllocator<T1>&, const LinearStdAllocator<T2>&) { return true; } +template <class T1, class T2> +bool operator!= (const LinearStdAllocator<T1>&, const LinearStdAllocator<T2>&) { return false; } + +template <class T> +class LsaVector : public std::vector<T, LinearStdAllocator<T>> { +public: + LsaVector(const LinearStdAllocator<T>& allocator) + : std::vector<T, LinearStdAllocator<T>>(allocator) {} +}; + }; // namespace uirenderer }; // namespace android -void* operator new(std::size_t size, android::uirenderer::LinearAllocator& la); - #endif // ANDROID_LINEARALLOCATOR_H diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h index 5ca9083aab87..7212897bf5d3 100644 --- a/libs/hwui/utils/Macros.h +++ b/libs/hwui/utils/Macros.h @@ -23,16 +23,17 @@ Type(const Type&) = delete; \ void operator=(const Type&) = delete -#define DESCRIPTION_TYPE(Type) \ - int compare(const Type& rhs) const { return memcmp(this, &rhs, sizeof(Type));} \ - bool operator==(const Type& other) const { return compare(other) == 0; } \ - bool operator!=(const Type& other) const { return compare(other) != 0; } \ - friend inline int strictly_order_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs) < 0; } \ - friend inline int compare_type(const Type& lhs, const Type& rhs) { return lhs.compare(rhs); } \ +#define HASHABLE_TYPE(Type) \ + bool operator==(const Type& other) const; \ + hash_t hash() const; \ + bool operator!=(const Type& other) const { return !(*this == other); } \ friend inline hash_t hash_type(const Type& entry) { return entry.hash(); } #define REQUIRE_COMPATIBLE_LAYOUT(Type) \ static_assert(std::is_standard_layout<Type>::value, \ #Type " must have standard layout") +#define WARN_UNUSED_RESULT \ + __attribute__((warn_unused_result)) + #endif /* MACROS_H */ diff --git a/libs/hwui/utils/MathUtils.h b/libs/hwui/utils/MathUtils.h index 9c3787cd1e7f..8d20f2142e73 100644 --- a/libs/hwui/utils/MathUtils.h +++ b/libs/hwui/utils/MathUtils.h @@ -16,6 +16,7 @@ #ifndef MATHUTILS_H #define MATHUTILS_H +#include <algorithm> #include <math.h> namespace android { @@ -82,18 +83,8 @@ public: } template<typename T> - static inline T max(T a, T b) { - return a > b ? a : b; - } - - template<typename T> - static inline T min(T a, T b) { - return a < b ? a : b; - } - - template<typename T> static inline T clamp(T a, T minValue, T maxValue) { - return min(max(a, minValue), maxValue); + return std::min(std::max(a, minValue), maxValue); } inline static float lerp(float v1, float v2, float t) { diff --git a/libs/hwui/utils/NinePatch.h b/libs/hwui/utils/NinePatch.h new file mode 100644 index 000000000000..323e56312fb7 --- /dev/null +++ b/libs/hwui/utils/NinePatch.h @@ -0,0 +1,37 @@ +/* +** +** Copyright 2015, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +#ifndef ANDROID_GRAPHICS_NINEPATCH_H +#define ANDROID_GRAPHICS_NINEPATCH_H + +#include <androidfw/ResourceTypes.h> +#include <cutils/compiler.h> + +#include "SkCanvas.h" +#include "SkRegion.h" + +namespace android { + +class ANDROID_API NinePatch { +public: + static void Draw(SkCanvas* canvas, const SkRect& bounds, const SkBitmap& bitmap, + const Res_png_9patch& chunk, const SkPaint* paint, SkRegion** outRegion); +}; + +} // namespace android + +#endif // ANDROID_GRAPHICS_NINEPATCH_H diff --git a/libs/hwui/utils/NinePatchImpl.cpp b/libs/hwui/utils/NinePatchImpl.cpp new file mode 100644 index 000000000000..985f3fb66814 --- /dev/null +++ b/libs/hwui/utils/NinePatchImpl.cpp @@ -0,0 +1,326 @@ +/* +** +** Copyright 2006, 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 "utils/NinePatch.h" + +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkColorPriv.h" +#include "SkNinePatch.h" +#include "SkPaint.h" +#include "SkUnPreMultiply.h" + +#include <utils/Log.h> + +namespace android { + +static const bool kUseTrace = true; +static bool gTrace = false; + +static bool getColor(const SkBitmap& bitmap, int x, int y, SkColor* c) { + switch (bitmap.colorType()) { + case kN32_SkColorType: + *c = SkUnPreMultiply::PMColorToColor(*bitmap.getAddr32(x, y)); + break; + case kRGB_565_SkColorType: + *c = SkPixel16ToPixel32(*bitmap.getAddr16(x, y)); + break; + case kARGB_4444_SkColorType: + *c = SkUnPreMultiply::PMColorToColor( + SkPixel4444ToPixel32(*bitmap.getAddr16(x, y))); + break; + case kIndex_8_SkColorType: { + SkColorTable* ctable = bitmap.getColorTable(); + *c = SkUnPreMultiply::PMColorToColor( + (*ctable)[*bitmap.getAddr8(x, y)]); + break; + } + default: + return false; + } + return true; +} + +static SkColor modAlpha(SkColor c, int alpha) { + int scale = alpha + (alpha >> 7); + int a = SkColorGetA(c) * scale >> 8; + return SkColorSetA(c, a); +} + +static void drawStretchyPatch(SkCanvas* canvas, SkIRect& src, const SkRect& dst, + const SkBitmap& bitmap, const SkPaint& paint, + SkColor initColor, uint32_t colorHint, + bool hasXfer) { + if (colorHint != android::Res_png_9patch::NO_COLOR) { + ((SkPaint*)&paint)->setColor(modAlpha(colorHint, paint.getAlpha())); + canvas->drawRect(dst, paint); + ((SkPaint*)&paint)->setColor(initColor); + } else if (src.width() == 1 && src.height() == 1) { + SkColor c; + if (!getColor(bitmap, src.fLeft, src.fTop, &c)) { + goto SLOW_CASE; + } + if (0 != c || hasXfer) { + SkColor prev = paint.getColor(); + ((SkPaint*)&paint)->setColor(c); + canvas->drawRect(dst, paint); + ((SkPaint*)&paint)->setColor(prev); + } + } else { + SLOW_CASE: + canvas->drawBitmapRect(bitmap, SkRect::Make(src), dst, &paint); + } +} + +SkScalar calculateStretch(SkScalar boundsLimit, SkScalar startingPoint, + int srcSpace, int numStrechyPixelsRemaining, + int numFixedPixelsRemaining) { + SkScalar spaceRemaining = boundsLimit - startingPoint; + SkScalar stretchySpaceRemaining = + spaceRemaining - SkIntToScalar(numFixedPixelsRemaining); + return srcSpace * stretchySpaceRemaining / numStrechyPixelsRemaining; +} + +void NinePatch::Draw(SkCanvas* canvas, const SkRect& bounds, + const SkBitmap& bitmap, const Res_png_9patch& chunk, + const SkPaint* paint, SkRegion** outRegion) { + if (canvas && canvas->quickReject(bounds)) { + return; + } + + SkPaint defaultPaint; + if (NULL == paint) { + // matches default dither in NinePatchDrawable.java. + defaultPaint.setDither(true); + paint = &defaultPaint; + } + + const int32_t* xDivs = chunk.getXDivs(); + const int32_t* yDivs = chunk.getYDivs(); + // if our SkCanvas were back by GL we should enable this and draw this as + // a mesh, which will be faster in most cases. + if ((false)) { + SkNinePatch::DrawMesh(canvas, bounds, bitmap, + xDivs, chunk.numXDivs, + yDivs, chunk.numYDivs, + paint); + return; + } + + if (kUseTrace) { + gTrace = true; + } + + SkASSERT(canvas || outRegion); + + if (kUseTrace) { + if (canvas) { + const SkMatrix& m = canvas->getTotalMatrix(); + ALOGV("ninepatch [%g %g %g] [%g %g %g]\n", + SkScalarToFloat(m[0]), SkScalarToFloat(m[1]), SkScalarToFloat(m[2]), + SkScalarToFloat(m[3]), SkScalarToFloat(m[4]), SkScalarToFloat(m[5])); + } + + ALOGV("======== ninepatch bounds [%g %g]\n", SkScalarToFloat(bounds.width()), + SkScalarToFloat(bounds.height())); + ALOGV("======== ninepatch paint bm [%d,%d]\n", bitmap.width(), bitmap.height()); + ALOGV("======== ninepatch xDivs [%d,%d]\n", xDivs[0], xDivs[1]); + ALOGV("======== ninepatch yDivs [%d,%d]\n", yDivs[0], yDivs[1]); + } + + if (bounds.isEmpty() || + bitmap.width() == 0 || bitmap.height() == 0 || + (paint && paint->getXfermode() == NULL && paint->getAlpha() == 0)) + { + if (kUseTrace) { + ALOGV("======== abort ninepatch draw\n"); + } + return; + } + + // should try a quick-reject test before calling lockPixels + + SkAutoLockPixels alp(bitmap); + // after the lock, it is valid to check getPixels() + if (bitmap.getPixels() == NULL) + return; + + const bool hasXfer = paint->getXfermode() != NULL; + SkRect dst; + SkIRect src; + + const int32_t x0 = xDivs[0]; + const int32_t y0 = yDivs[0]; + const SkColor initColor = ((SkPaint*)paint)->getColor(); + const uint8_t numXDivs = chunk.numXDivs; + const uint8_t numYDivs = chunk.numYDivs; + int i; + int j; + int colorIndex = 0; + uint32_t color; + bool xIsStretchable; + const bool initialXIsStretchable = (x0 == 0); + bool yIsStretchable = (y0 == 0); + const int bitmapWidth = bitmap.width(); + const int bitmapHeight = bitmap.height(); + + // Number of bytes needed for dstRights array. + // Need to cast numXDivs to a larger type to avoid overflow. + const size_t dstBytes = ((size_t) numXDivs + 1) * sizeof(SkScalar); + SkScalar* dstRights = (SkScalar*) alloca(dstBytes); + bool dstRightsHaveBeenCached = false; + + int numStretchyXPixelsRemaining = 0; + for (i = 0; i < numXDivs; i += 2) { + numStretchyXPixelsRemaining += xDivs[i + 1] - xDivs[i]; + } + int numFixedXPixelsRemaining = bitmapWidth - numStretchyXPixelsRemaining; + int numStretchyYPixelsRemaining = 0; + for (i = 0; i < numYDivs; i += 2) { + numStretchyYPixelsRemaining += yDivs[i + 1] - yDivs[i]; + } + int numFixedYPixelsRemaining = bitmapHeight - numStretchyYPixelsRemaining; + + if (kUseTrace) { + ALOGV("NinePatch [%d %d] bounds [%g %g %g %g] divs [%d %d]\n", + bitmap.width(), bitmap.height(), + SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop), + SkScalarToFloat(bounds.width()), SkScalarToFloat(bounds.height()), + numXDivs, numYDivs); + } + + src.fTop = 0; + dst.fTop = bounds.fTop; + // The first row always starts with the top being at y=0 and the bottom + // being either yDivs[1] (if yDivs[0]=0) or yDivs[0]. In the former case + // the first row is stretchable along the Y axis, otherwise it is fixed. + // The last row always ends with the bottom being bitmap.height and the top + // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or + // yDivs[numYDivs-1]. In the former case the last row is stretchable along + // the Y axis, otherwise it is fixed. + // + // The first and last columns are similarly treated with respect to the X + // axis. + // + // The above is to help explain some of the special casing that goes on the + // code below. + + // The initial yDiv and whether the first row is considered stretchable or + // not depends on whether yDiv[0] was zero or not. + for (j = yIsStretchable ? 1 : 0; + j <= numYDivs && src.fTop < bitmapHeight; + j++, yIsStretchable = !yIsStretchable) { + src.fLeft = 0; + dst.fLeft = bounds.fLeft; + if (j == numYDivs) { + src.fBottom = bitmapHeight; + dst.fBottom = bounds.fBottom; + } else { + src.fBottom = yDivs[j]; + const int srcYSize = src.fBottom - src.fTop; + if (yIsStretchable) { + dst.fBottom = dst.fTop + calculateStretch(bounds.fBottom, dst.fTop, + srcYSize, + numStretchyYPixelsRemaining, + numFixedYPixelsRemaining); + numStretchyYPixelsRemaining -= srcYSize; + } else { + dst.fBottom = dst.fTop + SkIntToScalar(srcYSize); + numFixedYPixelsRemaining -= srcYSize; + } + } + + xIsStretchable = initialXIsStretchable; + // The initial xDiv and whether the first column is considered + // stretchable or not depends on whether xDiv[0] was zero or not. + const uint32_t* colors = chunk.getColors(); + for (i = xIsStretchable ? 1 : 0; + i <= numXDivs && src.fLeft < bitmapWidth; + i++, xIsStretchable = !xIsStretchable) { + color = colors[colorIndex++]; + if (i == numXDivs) { + src.fRight = bitmapWidth; + dst.fRight = bounds.fRight; + } else { + src.fRight = xDivs[i]; + if (dstRightsHaveBeenCached) { + dst.fRight = dstRights[i]; + } else { + const int srcXSize = src.fRight - src.fLeft; + if (xIsStretchable) { + dst.fRight = dst.fLeft + calculateStretch(bounds.fRight, dst.fLeft, + srcXSize, + numStretchyXPixelsRemaining, + numFixedXPixelsRemaining); + numStretchyXPixelsRemaining -= srcXSize; + } else { + dst.fRight = dst.fLeft + SkIntToScalar(srcXSize); + numFixedXPixelsRemaining -= srcXSize; + } + dstRights[i] = dst.fRight; + } + } + // If this horizontal patch is too small to be displayed, leave + // the destination left edge where it is and go on to the next patch + // in the source. + if (src.fLeft >= src.fRight) { + src.fLeft = src.fRight; + continue; + } + // Make sure that we actually have room to draw any bits + if (dst.fRight <= dst.fLeft || dst.fBottom <= dst.fTop) { + goto nextDiv; + } + // If this patch is transparent, skip and don't draw. + if (color == android::Res_png_9patch::TRANSPARENT_COLOR && !hasXfer) { + if (outRegion) { + if (*outRegion == NULL) { + *outRegion = new SkRegion(); + } + SkIRect idst; + dst.round(&idst); + //ALOGI("Adding trans rect: (%d,%d)-(%d,%d)\n", + // idst.fLeft, idst.fTop, idst.fRight, idst.fBottom); + (*outRegion)->op(idst, SkRegion::kUnion_Op); + } + goto nextDiv; + } + if (canvas) { + if (kUseTrace) { + ALOGV("-- src [%d %d %d %d] dst [%g %g %g %g]\n", + src.fLeft, src.fTop, src.width(), src.height(), + SkScalarToFloat(dst.fLeft), SkScalarToFloat(dst.fTop), + SkScalarToFloat(dst.width()), SkScalarToFloat(dst.height())); + if (2 == src.width() && SkIntToScalar(5) == dst.width()) { + ALOGV("--- skip patch\n"); + } + } + drawStretchyPatch(canvas, src, dst, bitmap, *paint, initColor, + color, hasXfer); + } + +nextDiv: + src.fLeft = src.fRight; + dst.fLeft = dst.fRight; + } + src.fTop = src.fBottom; + dst.fTop = dst.fBottom; + dstRightsHaveBeenCached = true; + } +} + +} // namespace android diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index ba02f5f1a77d..4faab9a5f648 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -16,12 +16,20 @@ #ifndef PAINT_UTILS_H #define PAINT_UTILS_H +#include <utils/Blur.h> + #include <SkColorFilter.h> +#include <SkDrawLooper.h> +#include <SkShader.h> #include <SkXfermode.h> namespace android { namespace uirenderer { +/** + * Utility methods for accessing data within SkPaint, and providing defaults + * with optional SkPaint pointers. + */ class PaintUtils { public: @@ -59,6 +67,21 @@ public: && getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode; } + static bool isOpaquePaint(const SkPaint* paint) { + if (!paint) return true; // default (paintless) behavior is SrcOver, black + + if (paint->getAlpha() != 0xFF + || PaintUtils::isBlendedShader(paint->getShader()) + || PaintUtils::isBlendedColorFilter(paint->getColorFilter())) { + return false; + } + + // Only let simple srcOver / src blending modes declare opaque, since behavior is clear. + SkXfermode::Mode mode = getXfermode(paint->getXfermode()); + return mode == SkXfermode::Mode::kSrcOver_Mode + || mode == SkXfermode::Mode::kSrc_Mode; + } + static bool isBlendedShader(const SkShader* shader) { if (shader == nullptr) { return false; @@ -73,6 +96,39 @@ public: return (filter->getFlags() & SkColorFilter::kAlphaUnchanged_Flag) == 0; } + struct TextShadow { + SkScalar radius; + float dx; + float dy; + SkColor color; + }; + + static inline bool getTextShadow(const SkPaint* paint, TextShadow* textShadow) { + SkDrawLooper::BlurShadowRec blur; + if (paint && paint->getLooper() && paint->getLooper()->asABlurShadow(&blur)) { + if (textShadow) { + textShadow->radius = Blur::convertSigmaToRadius(blur.fSigma); + textShadow->dx = blur.fOffset.fX; + textShadow->dy = blur.fOffset.fY; + textShadow->color = blur.fColor; + } + return true; + } + return false; + } + + static inline bool hasTextShadow(const SkPaint* paint) { + return getTextShadow(paint, nullptr); + } + + static inline SkXfermode::Mode getXfermodeDirect(const SkPaint* paint) { + return paint ? getXfermode(paint->getXfermode()) : SkXfermode::kSrcOver_Mode; + } + + static inline int getAlphaDirect(const SkPaint* paint) { + return paint ? paint->getAlpha() : 255; + } + }; // class PaintUtils } /* namespace uirenderer */ diff --git a/libs/hwui/utils/RingBuffer.h b/libs/hwui/utils/RingBuffer.h index 6895f07fd60a..06bcdcd7d84b 100644 --- a/libs/hwui/utils/RingBuffer.h +++ b/libs/hwui/utils/RingBuffer.h @@ -32,7 +32,7 @@ public: ~RingBuffer() {} constexpr size_t capacity() const { return SIZE; } - size_t size() { return mCount; } + size_t size() const { return mCount; } T& next() { mHead = (mHead + 1) % SIZE; @@ -54,6 +54,10 @@ public: return mBuffer[(mHead + index + 1) % mCount]; } + const T& operator[](size_t index) const { + return mBuffer[(mHead + index + 1) % mCount]; + } + void clear() { mCount = 0; mHead = -1; diff --git a/libs/hwui/utils/SortedList.h b/libs/hwui/utils/SortedList.h deleted file mode 100644 index a2c8c52fcbc7..000000000000 --- a/libs/hwui/utils/SortedList.h +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_SORTED_LIST_H -#define ANDROID_HWUI_SORTED_LIST_H - -#include <stdint.h> -#include <sys/types.h> - -#include <utils/Vector.h> -#include <utils/TypeHelpers.h> - -#include "SortedListImpl.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Sorted list -/////////////////////////////////////////////////////////////////////////////// - -template<class TYPE> -class SortedList: private SortedListImpl { -public: - typedef TYPE value_type; - - SortedList(); - SortedList(const SortedList<TYPE>& rhs); - virtual ~SortedList(); - - const SortedList<TYPE>& operator =(const SortedList<TYPE>& rhs) const; - SortedList<TYPE>& operator =(const SortedList<TYPE>& rhs); - - inline void clear() { - VectorImpl::clear(); - } - - inline size_t size() const { - return VectorImpl::size(); - } - - inline bool isEmpty() const { - return VectorImpl::isEmpty(); - } - - inline size_t capacity() const { - return VectorImpl::capacity(); - } - - inline ssize_t setCapacity(size_t size) { - return VectorImpl::setCapacity(size); - } - - inline const TYPE* array() const; - - TYPE* editArray(); - - ssize_t indexOf(const TYPE& item) const; - size_t orderOf(const TYPE& item) const; - - inline const TYPE& operator [](size_t index) const; - inline const TYPE& itemAt(size_t index) const; - const TYPE& top() const; - const TYPE& mirrorItemAt(ssize_t index) const; - - ssize_t add(const TYPE& item); - - TYPE& editItemAt(size_t index) { - return *(static_cast<TYPE *> (VectorImpl::editItemLocation(index))); - } - - ssize_t merge(const Vector<TYPE>& vector); - ssize_t merge(const SortedList<TYPE>& vector); - - ssize_t remove(const TYPE&); - - inline ssize_t removeItemsAt(size_t index, size_t count = 1); - inline ssize_t removeAt(size_t index) { - return removeItemsAt(index); - } - -protected: - virtual void do_construct(void* storage, size_t num) const override; - virtual void do_destroy(void* storage, size_t num) const override; - virtual void do_copy(void* dest, const void* from, size_t num) const override; - virtual void do_splat(void* dest, const void* item, size_t num) const override; - virtual void do_move_forward(void* dest, const void* from, size_t num) const override; - virtual void do_move_backward(void* dest, const void* from, size_t num) const override; - virtual int do_compare(const void* lhs, const void* rhs) const override; -}; // class SortedList - -/////////////////////////////////////////////////////////////////////////////// -// Implementation -/////////////////////////////////////////////////////////////////////////////// - -template<class TYPE> -inline SortedList<TYPE>::SortedList(): - SortedListImpl(sizeof(TYPE), ((traits<TYPE>::has_trivial_ctor ? HAS_TRIVIAL_CTOR : 0) - | (traits<TYPE>::has_trivial_dtor ? HAS_TRIVIAL_DTOR : 0) - | (traits<TYPE>::has_trivial_copy ? HAS_TRIVIAL_COPY : 0))) { -} - -template<class TYPE> -inline SortedList<TYPE>::SortedList(const SortedList<TYPE>& rhs): SortedListImpl(rhs) { -} - -template<class TYPE> inline SortedList<TYPE>::~SortedList() { - finish_vector(); -} - -template<class TYPE> -inline SortedList<TYPE>& SortedList<TYPE>::operator =(const SortedList<TYPE>& rhs) { - SortedListImpl::operator =(rhs); - return *this; -} - -template<class TYPE> -inline const SortedList<TYPE>& SortedList<TYPE>::operator =( - const SortedList<TYPE>& rhs) const { - SortedListImpl::operator =(rhs); - return *this; -} - -template<class TYPE> -inline const TYPE* SortedList<TYPE>::array() const { - return static_cast<const TYPE *> (arrayImpl()); -} - -template<class TYPE> -inline TYPE* SortedList<TYPE>::editArray() { - return static_cast<TYPE *> (editArrayImpl()); -} - -template<class TYPE> -inline const TYPE& SortedList<TYPE>::operator[](size_t index) const { - assert( index<size() ); - return *(array() + index); -} - -template<class TYPE> -inline const TYPE& SortedList<TYPE>::itemAt(size_t index) const { - return operator[](index); -} - -template<class TYPE> -inline const TYPE& SortedList<TYPE>::mirrorItemAt(ssize_t index) const { - assert( (index>0 ? index : -index)<size() ); - return *(array() + ((index < 0) ? (size() - index) : index)); -} - -template<class TYPE> -inline const TYPE& SortedList<TYPE>::top() const { - return *(array() + size() - 1); -} - -template<class TYPE> -inline ssize_t SortedList<TYPE>::add(const TYPE& item) { - return SortedListImpl::add(&item); -} - -template<class TYPE> -inline ssize_t SortedList<TYPE>::indexOf(const TYPE& item) const { - return SortedListImpl::indexOf(&item); -} - -template<class TYPE> -inline size_t SortedList<TYPE>::orderOf(const TYPE& item) const { - return SortedListImpl::orderOf(&item); -} - -template<class TYPE> -inline ssize_t SortedList<TYPE>::merge(const Vector<TYPE>& vector) { - return SortedListImpl::merge(reinterpret_cast<const VectorImpl&> (vector)); -} - -template<class TYPE> -inline ssize_t SortedList<TYPE>::merge(const SortedList<TYPE>& vector) { - return SortedListImpl::merge(reinterpret_cast<const SortedListImpl&> (vector)); -} - -template<class TYPE> -inline ssize_t SortedList<TYPE>::remove(const TYPE& item) { - return SortedListImpl::remove(&item); -} - -template<class TYPE> -inline ssize_t SortedList<TYPE>::removeItemsAt(size_t index, size_t count) { - return VectorImpl::removeItemsAt(index, count); -} - -template<class TYPE> -void SortedList<TYPE>::do_construct(void* storage, size_t num) const { - construct_type(reinterpret_cast<TYPE*> (storage), num); -} - -template<class TYPE> -void SortedList<TYPE>::do_destroy(void* storage, size_t num) const { - destroy_type(reinterpret_cast<TYPE*> (storage), num); -} - -template<class TYPE> -void SortedList<TYPE>::do_copy(void* dest, const void* from, size_t num) const { - copy_type(reinterpret_cast<TYPE*> (dest), reinterpret_cast<const TYPE*> (from), num); -} - -template<class TYPE> -void SortedList<TYPE>::do_splat(void* dest, const void* item, size_t num) const { - splat_type(reinterpret_cast<TYPE*> (dest), reinterpret_cast<const TYPE*> (item), num); -} - -template<class TYPE> -void SortedList<TYPE>::do_move_forward(void* dest, const void* from, size_t num) const { - move_forward_type(reinterpret_cast<TYPE*> (dest), reinterpret_cast<const TYPE*> (from), num); -} - -template<class TYPE> -void SortedList<TYPE>::do_move_backward(void* dest, const void* from, size_t num) const { - move_backward_type(reinterpret_cast<TYPE*> (dest), reinterpret_cast<const TYPE*> (from), num); -} - -template<class TYPE> -int SortedList<TYPE>::do_compare(const void* lhs, const void* rhs) const { - return compare_type(*reinterpret_cast<const TYPE*> (lhs), *reinterpret_cast<const TYPE*> (rhs)); -} - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_SORTED_LIST_H diff --git a/libs/hwui/utils/SortedListImpl.cpp b/libs/hwui/utils/SortedListImpl.cpp deleted file mode 100644 index 35171d5b1a5b..000000000000 --- a/libs/hwui/utils/SortedListImpl.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2010 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 "SortedListImpl.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Sorted list implementation, not for direct use -/////////////////////////////////////////////////////////////////////////////// - -SortedListImpl::SortedListImpl(size_t itemSize, uint32_t flags): VectorImpl(itemSize, flags) { -} - -SortedListImpl::SortedListImpl(const VectorImpl& rhs): VectorImpl(rhs) { -} - -SortedListImpl::~SortedListImpl() { -} - -SortedListImpl& SortedListImpl::operator =(const SortedListImpl& rhs) { - return static_cast<SortedListImpl&> - (VectorImpl::operator =(static_cast<const VectorImpl&> (rhs))); -} - -ssize_t SortedListImpl::indexOf(const void* item) const { - return _indexOrderOf(item); -} - -size_t SortedListImpl::orderOf(const void* item) const { - size_t o; - _indexOrderOf(item, &o); - return o; -} - -ssize_t SortedListImpl::_indexOrderOf(const void* item, size_t* order) const { - // binary search - ssize_t err = NAME_NOT_FOUND; - ssize_t l = 0; - ssize_t h = size() - 1; - ssize_t mid; - const void* a = arrayImpl(); - const size_t s = itemSize(); - while (l <= h) { - mid = l + (h - l) / 2; - const void* const curr = reinterpret_cast<const char *> (a) + (mid * s); - const int c = do_compare(curr, item); - if (c == 0) { - err = l = mid; - break; - } else if (c < 0) { - l = mid + 1; - } else { - h = mid - 1; - } - } - if (order) { - *order = l; - } - return err; -} - -ssize_t SortedListImpl::add(const void* item) { - size_t order; - ssize_t index = _indexOrderOf(item, &order); - index = VectorImpl::insertAt(item, order, 1); - return index; -} - -ssize_t SortedListImpl::merge(const VectorImpl& vector) { - // naive merge... - if (!vector.isEmpty()) { - const void* buffer = vector.arrayImpl(); - const size_t is = itemSize(); - size_t s = vector.size(); - for (size_t i = 0; i < s; i++) { - ssize_t err = add(reinterpret_cast<const char*> (buffer) + i * is); - if (err < 0) { - return err; - } - } - } - return NO_ERROR; -} - -ssize_t SortedListImpl::merge(const SortedListImpl& vector) { - // we've merging a sorted vector... nice! - ssize_t err = NO_ERROR; - if (!vector.isEmpty()) { - // first take care of the case where the vectors are sorted together - if (do_compare(vector.itemLocation(vector.size() - 1), arrayImpl()) <= 0) { - err = VectorImpl::insertVectorAt(static_cast<const VectorImpl&> (vector), 0); - } else if (do_compare(vector.arrayImpl(), itemLocation(size() - 1)) >= 0) { - err = VectorImpl::appendVector(static_cast<const VectorImpl&> (vector)); - } else { - // this could be made a little better - err = merge(static_cast<const VectorImpl&> (vector)); - } - } - return err; -} - -ssize_t SortedListImpl::remove(const void* item) { - ssize_t i = indexOf(item); - if (i >= 0) { - VectorImpl::removeItemsAt(i, 1); - } - return i; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/utils/SortedListImpl.h b/libs/hwui/utils/SortedListImpl.h deleted file mode 100644 index b1018265566a..000000000000 --- a/libs/hwui/utils/SortedListImpl.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_SORTED_LIST_IMPL_H -#define ANDROID_HWUI_SORTED_LIST_IMPL_H - -#include <utils/VectorImpl.h> - -namespace android { -namespace uirenderer { - -class SortedListImpl: public VectorImpl { -public: - SortedListImpl(size_t itemSize, uint32_t flags); - SortedListImpl(const VectorImpl& rhs); - virtual ~SortedListImpl(); - - SortedListImpl& operator =(const SortedListImpl& rhs); - - ssize_t indexOf(const void* item) const; - size_t orderOf(const void* item) const; - ssize_t add(const void* item); - ssize_t merge(const VectorImpl& vector); - ssize_t merge(const SortedListImpl& vector); - ssize_t remove(const void* item); - -protected: - virtual int do_compare(const void* lhs, const void* rhs) const = 0; - -private: - ssize_t _indexOrderOf(const void* item, size_t* order = nullptr) const; - - // these are made private, because they can't be used on a SortedVector - // (they don't have an implementation either) - ssize_t add(); - void pop(); - void push(); - void push(const void* item); - ssize_t insertVectorAt(const VectorImpl& vector, size_t index); - ssize_t appendVector(const VectorImpl& vector); - ssize_t insertArrayAt(const void* array, size_t index, size_t length); - ssize_t appendArray(const void* array, size_t length); - ssize_t insertAt(size_t where, size_t numItems = 1); - ssize_t insertAt(const void* item, size_t where, size_t numItems = 1); - ssize_t replaceAt(size_t index); - ssize_t replaceAt(const void* item, size_t index); -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_SORTED_LIST_IMPL_H diff --git a/libs/hwui/utils/StringUtils.cpp b/libs/hwui/utils/StringUtils.cpp new file mode 100644 index 000000000000..64a59705028a --- /dev/null +++ b/libs/hwui/utils/StringUtils.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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 "StringUtils.h" + +namespace android { +namespace uirenderer { + +unordered_string_set StringUtils::split(const char* spacedList) { + unordered_string_set set; + const char* current = spacedList; + const char* head = current; + do { + head = strchr(current, ' '); + std::string s(current, head ? head - current : strlen(current)); + if (s.length()) { + set.insert(std::move(s)); + } + current = head + 1; + } while (head); + return set; +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/utils/StringUtils.h b/libs/hwui/utils/StringUtils.h new file mode 100644 index 000000000000..5add95711f2d --- /dev/null +++ b/libs/hwui/utils/StringUtils.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef STRING_UTILS_H +#define STRING_UTILS_H + +#include <string> +#include <unordered_set> +#include <ostream> +#include <iomanip> + +namespace android { +namespace uirenderer { + +class unordered_string_set : public std::unordered_set<std::string> { +public: + bool has(const char* str) { + return find(std::string(str)) != end(); + } +}; + +class StringUtils { +public: + static unordered_string_set split(const char* spacedList); +}; + +struct SizePrinter { + int bytes; + friend std::ostream& operator<<(std::ostream& stream, const SizePrinter& d) { + static const char* SUFFIXES[] = {"B", "KiB", "MiB"}; + size_t suffix = 0; + double temp = d.bytes; + while (temp > 1024 && suffix < 2) { + temp /= 1024.0; + suffix++; + } + stream << std::fixed << std::setprecision(2) << temp << SUFFIXES[suffix]; + return stream; + } +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* GLUTILS_H */ diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp new file mode 100644 index 000000000000..b3195c4bbbfb --- /dev/null +++ b/libs/hwui/utils/TestWindowContext.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2015 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 "TestWindowContext.h" + +#include "AnimationContext.h" +#include "DisplayListCanvas.h" +#include "IContextFactory.h" +#include "RecordingCanvas.h" +#include "RenderNode.h" +#include "SkTypes.h" +#include "gui/BufferQueue.h" +#include "gui/CpuConsumer.h" +#include "gui/IGraphicBufferConsumer.h" +#include "gui/IGraphicBufferProducer.h" +#include "gui/Surface.h" +#include "renderthread/RenderProxy.h" + + +namespace { + +/** + * Helper class for setting up android::uirenderer::renderthread::RenderProxy. + */ +class ContextFactory : public android::uirenderer::IContextFactory { +public: + android::uirenderer::AnimationContext* createAnimationContext + (android::uirenderer::renderthread::TimeLord& clock) override { + return new android::uirenderer::AnimationContext(clock); + } +}; + +} // anonymous namespace + +namespace android { +namespace uirenderer { + +/** + Android strong pointers (android::sp) can't hold forward-declared classes, + so we have to use pointer-to-implementation here if we want to hide the + details from our non-framework users. +*/ + +class TestWindowContext::TestWindowData { + +public: + + TestWindowData(SkISize size) : mSize(size) { + android::BufferQueue::createBufferQueue(&mProducer, &mConsumer); + mCpuConsumer = new android::CpuConsumer(mConsumer, 1); + mCpuConsumer->setName(android::String8("TestWindowContext")); + mCpuConsumer->setDefaultBufferSize(mSize.width(), mSize.height()); + mAndroidSurface = new android::Surface(mProducer); + native_window_set_buffers_dimensions(mAndroidSurface.get(), + mSize.width(), mSize.height()); + native_window_set_buffers_format(mAndroidSurface.get(), + android::PIXEL_FORMAT_RGBA_8888); + native_window_set_usage(mAndroidSurface.get(), + GRALLOC_USAGE_SW_READ_OFTEN | + GRALLOC_USAGE_SW_WRITE_NEVER | + GRALLOC_USAGE_HW_RENDER); + mRootNode.reset(new android::uirenderer::RenderNode()); + mRootNode->incStrong(nullptr); + mRootNode->mutateStagingProperties().setLeftTopRightBottom + (0, 0, mSize.width(), mSize.height()); + mRootNode->mutateStagingProperties().setClipToBounds(false); + mRootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::GENERIC); + ContextFactory factory; + mProxy.reset + (new android::uirenderer::renderthread::RenderProxy(false, + mRootNode.get(), + &factory)); + mProxy->loadSystemProperties(); + mProxy->initialize(mAndroidSurface.get()); + float lightX = mSize.width() / 2.0f; + android::uirenderer::Vector3 lightVector { lightX, -200.0f, 800.0f }; + mProxy->setup(mSize.width(), mSize.height(), 800.0f, + 255 * 0.075f, 255 * 0.15f); + mProxy->setLightCenter(lightVector); +#if HWUI_NEW_OPS + mCanvas.reset(new android::uirenderer::RecordingCanvas(mSize.width(), mSize.height())); +#else + mCanvas.reset(new android::uirenderer::DisplayListCanvas(mSize.width(), mSize.height())); +#endif + } + + SkCanvas* prepareToDraw() { + //mCanvas->reset(mSize.width(), mSize.height()); + mCanvas->clipRect(0, 0, mSize.width(), mSize.height(), + SkRegion::Op::kReplace_Op); + return mCanvas->asSkCanvas(); + } + + void finishDrawing() { + mRootNode->setStagingDisplayList(mCanvas->finishRecording(), nullptr); + mProxy->syncAndDrawFrame(nullptr); + // Surprisingly, calling mProxy->fence() here appears to make no difference to + // the timings we record. + } + + void fence() { + mProxy->fence(); + } + + bool capturePixels(SkBitmap* bmp) { + SkImageInfo destinationConfig = + SkImageInfo::Make(mSize.width(), mSize.height(), + kRGBA_8888_SkColorType, kPremul_SkAlphaType); + bmp->allocPixels(destinationConfig); + sk_memset32((uint32_t*) bmp->getPixels(), SK_ColorRED, + mSize.width() * mSize.height()); + + android::CpuConsumer::LockedBuffer nativeBuffer; + android::status_t retval = mCpuConsumer->lockNextBuffer(&nativeBuffer); + if (retval == android::BAD_VALUE) { + SkDebugf("write_canvas_png() got no buffer; returning transparent"); + // No buffer ready to read - commonly triggered by dm sending us + // a no-op source, or calling code that doesn't do anything on this + // backend. + bmp->eraseColor(SK_ColorTRANSPARENT); + return false; + } else if (retval) { + SkDebugf("Failed to lock buffer to read pixels: %d.", retval); + return false; + } + + // Move the pixels into the destination SkBitmap + + LOG_ALWAYS_FATAL_IF(nativeBuffer.format != android::PIXEL_FORMAT_RGBA_8888, + "Native buffer not RGBA!"); + SkImageInfo nativeConfig = + SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height, + kRGBA_8888_SkColorType, kPremul_SkAlphaType); + + // Android stride is in pixels, Skia stride is in bytes + SkBitmap nativeWrapper; + bool success = + nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, nativeBuffer.stride * 4); + if (!success) { + SkDebugf("Failed to wrap HWUI buffer in a SkBitmap"); + return false; + } + + LOG_ALWAYS_FATAL_IF(bmp->colorType() != kRGBA_8888_SkColorType, + "Destination buffer not RGBA!"); + success = + nativeWrapper.readPixels(destinationConfig, bmp->getPixels(), bmp->rowBytes(), 0, 0); + if (!success) { + SkDebugf("Failed to extract pixels from HWUI buffer"); + return false; + } + + mCpuConsumer->unlockBuffer(nativeBuffer); + + return true; + } + +private: + + std::unique_ptr<android::uirenderer::RenderNode> mRootNode; + std::unique_ptr<android::uirenderer::renderthread::RenderProxy> mProxy; +#if HWUI_NEW_OPS + std::unique_ptr<android::uirenderer::RecordingCanvas> mCanvas; +#else + std::unique_ptr<android::uirenderer::DisplayListCanvas> mCanvas; +#endif + android::sp<android::IGraphicBufferProducer> mProducer; + android::sp<android::IGraphicBufferConsumer> mConsumer; + android::sp<android::CpuConsumer> mCpuConsumer; + android::sp<android::Surface> mAndroidSurface; + SkISize mSize; +}; + + +TestWindowContext::TestWindowContext() : + mData (nullptr) { } + +TestWindowContext::~TestWindowContext() { + delete mData; +} + +void TestWindowContext::initialize(int width, int height) { + mData = new TestWindowData(SkISize::Make(width, height)); +} + +SkCanvas* TestWindowContext::prepareToDraw() { + return mData ? mData->prepareToDraw() : nullptr; +} + +void TestWindowContext::finishDrawing() { + if (mData) { + mData->finishDrawing(); + } +} + +void TestWindowContext::fence() { + if (mData) { + mData->fence(); + } +} + +bool TestWindowContext::capturePixels(SkBitmap* bmp) { + return mData ? mData->capturePixels(bmp) : false; +} + +} // namespace uirenderer +} // namespace android + diff --git a/libs/hwui/utils/TestWindowContext.h b/libs/hwui/utils/TestWindowContext.h new file mode 100644 index 000000000000..48ec95216b14 --- /dev/null +++ b/libs/hwui/utils/TestWindowContext.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef TESTWINDOWCONTEXT_H_ +#define TESTWINDOWCONTEXT_H_ + +#include <cutils/compiler.h> + +class SkBitmap; +class SkCanvas; + +namespace android { + +namespace uirenderer { + +/** + Wraps all libui/libgui classes and types that external tests depend on, + exposing only primitive Skia types. +*/ + +class ANDROID_API TestWindowContext { + +public: + + TestWindowContext(); + ~TestWindowContext(); + + /// We need to know the size of the window. + void initialize(int width, int height); + + /// Returns a canvas to draw into; NULL if not yet initialize()d. + SkCanvas* prepareToDraw(); + + /// Flushes all drawing commands to HWUI; no-op if not yet initialize()d. + void finishDrawing(); + + /// Blocks until HWUI has processed all pending drawing commands; + /// no-op if not yet initialize()d. + void fence(); + + /// Returns false if not yet initialize()d. + bool capturePixels(SkBitmap* bmp); + +private: + /// Hidden implementation. + class TestWindowData; + + TestWindowData* mData; + +}; + +} // namespace uirenderer +} // namespace android + +#endif // TESTWINDOWCONTEXT_H_ + diff --git a/libs/hwui/utils/TimeUtils.h b/libs/hwui/utils/TimeUtils.h new file mode 100644 index 000000000000..8d42d7e55521 --- /dev/null +++ b/libs/hwui/utils/TimeUtils.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef UTILS_TIMEUTILS_H +#define UTILS_TIMEUTILS_H + +#include <utils/Timers.h> + +namespace android { +namespace uirenderer { + +constexpr nsecs_t operator"" _ms (unsigned long long ms) { + return milliseconds_to_nanoseconds(ms); +} + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* UTILS_TIMEUTILS_H */ diff --git a/libs/hwui/utils/TinyHashMap.h b/libs/hwui/utils/TinyHashMap.h deleted file mode 100644 index 4ff9a42f6d5d..000000000000 --- a/libs/hwui/utils/TinyHashMap.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_TINYHASHMAP_H -#define ANDROID_HWUI_TINYHASHMAP_H - -#include <utils/BasicHashtable.h> - -namespace android { -namespace uirenderer { - -/** - * A very simple hash map that doesn't allow duplicate keys, overwriting the older entry. - */ -template <typename TKey, typename TValue> -class TinyHashMap { -public: - typedef key_value_pair_t<TKey, TValue> TEntry; - - /** - * Puts an entry in the hash, removing any existing entry with the same key - */ - void put(TKey key, TValue value) { - hash_t hash = android::hash_type(key); - - ssize_t index = mTable.find(-1, hash, key); - if (index != -1) { - mTable.removeAt(index); - } - - TEntry initEntry(key, value); - mTable.add(hash, initEntry); - } - - /** - * Return true if key is in the map, in which case stores the value in the output ref - */ - bool get(TKey key, TValue& outValue) { - hash_t hash = android::hash_type(key); - ssize_t index = mTable.find(-1, hash, key); - if (index == -1) { - return false; - } - outValue = mTable.entryAt(index).value; - return true; - } - - void clear() { mTable.clear(); } - -private: - BasicHashtable<TKey, TEntry> mTable; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_TINYHASHMAP_H diff --git a/libs/hwui/utils/VectorDrawableUtils.cpp b/libs/hwui/utils/VectorDrawableUtils.cpp new file mode 100644 index 000000000000..ca75c5945b7f --- /dev/null +++ b/libs/hwui/utils/VectorDrawableUtils.cpp @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2015 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 "VectorDrawableUtils.h" + +#include "PathParser.h" + +#include <math.h> +#include <utils/Log.h> + +namespace android { +namespace uirenderer { + +class PathResolver { +public: + float currentX = 0; + float currentY = 0; + float ctrlPointX = 0; + float ctrlPointY = 0; + float currentSegmentStartX = 0; + float currentSegmentStartY = 0; + void addCommand(SkPath* outPath, char previousCmd, + char cmd, const std::vector<float>* points, size_t start, size_t end); +}; + +bool VectorDrawableUtils::canMorph(const PathData& morphFrom, const PathData& morphTo) { + if (morphFrom.verbs.size() != morphTo.verbs.size()) { + return false; + } + + for (unsigned int i = 0; i < morphFrom.verbs.size(); i++) { + if (morphFrom.verbs[i] != morphTo.verbs[i] + || morphFrom.verbSizes[i] != morphTo.verbSizes[i]) { + return false; + } + } + return true; +} + +bool VectorDrawableUtils::interpolatePathData(PathData* outData, const PathData& morphFrom, + const PathData& morphTo, float fraction) { + if (!canMorph(morphFrom, morphTo)) { + return false; + } + interpolatePaths(outData, morphFrom, morphTo, fraction); + return true; +} + + /** + * Convert an array of PathVerb to Path. + */ +void VectorDrawableUtils::verbsToPath(SkPath* outPath, const PathData& data) { + PathResolver resolver; + char previousCommand = 'm'; + size_t start = 0; + outPath->reset(); + for (unsigned int i = 0; i < data.verbs.size(); i++) { + size_t verbSize = data.verbSizes[i]; + resolver.addCommand(outPath, previousCommand, data.verbs[i], &data.points, start, + start + verbSize); + previousCommand = data.verbs[i]; + start += verbSize; + } +} + +/** + * The current PathVerb will be interpolated between the + * <code>nodeFrom</code> and <code>nodeTo</code> according to the + * <code>fraction</code>. + * + * @param nodeFrom The start value as a PathVerb. + * @param nodeTo The end value as a PathVerb + * @param fraction The fraction to interpolate. + */ +void VectorDrawableUtils::interpolatePaths(PathData* outData, + const PathData& from, const PathData& to, float fraction) { + outData->points.resize(from.points.size()); + outData->verbSizes = from.verbSizes; + outData->verbs = from.verbs; + + for (size_t i = 0; i < from.points.size(); i++) { + outData->points[i] = from.points[i] * (1 - fraction) + to.points[i] * fraction; + } +} + +/** + * Converts an arc to cubic Bezier segments and records them in p. + * + * @param p The target for the cubic Bezier segments + * @param cx The x coordinate center of the ellipse + * @param cy The y coordinate center of the ellipse + * @param a The radius of the ellipse in the horizontal direction + * @param b The radius of the ellipse in the vertical direction + * @param e1x E(eta1) x coordinate of the starting point of the arc + * @param e1y E(eta2) y coordinate of the starting point of the arc + * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane + * @param start The start angle of the arc on the ellipse + * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse + */ +static void arcToBezier(SkPath* p, + double cx, + double cy, + double a, + double b, + double e1x, + double e1y, + double theta, + double start, + double sweep) { + // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html + // and http://www.spaceroots.org/documents/ellipse/node22.html + + // Maximum of 45 degrees per cubic Bezier segment + int numSegments = ceil(fabs(sweep * 4 / M_PI)); + + double eta1 = start; + double cosTheta = cos(theta); + double sinTheta = sin(theta); + double cosEta1 = cos(eta1); + double sinEta1 = sin(eta1); + double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1); + double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1); + + double anglePerSegment = sweep / numSegments; + for (int i = 0; i < numSegments; i++) { + double eta2 = eta1 + anglePerSegment; + double sinEta2 = sin(eta2); + double cosEta2 = cos(eta2); + double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2); + double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2); + double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2; + double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2; + double tanDiff2 = tan((eta2 - eta1) / 2); + double alpha = + sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3; + double q1x = e1x + alpha * ep1x; + double q1y = e1y + alpha * ep1y; + double q2x = e2x - alpha * ep2x; + double q2y = e2y - alpha * ep2y; + + p->cubicTo((float) q1x, + (float) q1y, + (float) q2x, + (float) q2y, + (float) e2x, + (float) e2y); + eta1 = eta2; + e1x = e2x; + e1y = e2y; + ep1x = ep2x; + ep1y = ep2y; + } +} + +inline double toRadians(float theta) { return theta * M_PI / 180;} + +static void drawArc(SkPath* p, + float x0, + float y0, + float x1, + float y1, + float a, + float b, + float theta, + bool isMoreThanHalf, + bool isPositiveArc) { + + /* Convert rotation angle from degrees to radians */ + double thetaD = toRadians(theta); + /* Pre-compute rotation matrix entries */ + double cosTheta = cos(thetaD); + double sinTheta = sin(thetaD); + /* Transform (x0, y0) and (x1, y1) into unit space */ + /* using (inverse) rotation, followed by (inverse) scale */ + double x0p = (x0 * cosTheta + y0 * sinTheta) / a; + double y0p = (-x0 * sinTheta + y0 * cosTheta) / b; + double x1p = (x1 * cosTheta + y1 * sinTheta) / a; + double y1p = (-x1 * sinTheta + y1 * cosTheta) / b; + + /* Compute differences and averages */ + double dx = x0p - x1p; + double dy = y0p - y1p; + double xm = (x0p + x1p) / 2; + double ym = (y0p + y1p) / 2; + /* Solve for intersecting unit circles */ + double dsq = dx * dx + dy * dy; + if (dsq == 0.0) { + ALOGW("Points are coincident"); + return; /* Points are coincident */ + } + double disc = 1.0 / dsq - 1.0 / 4.0; + if (disc < 0.0) { + ALOGW("Points are too far apart %f", dsq); + float adjust = (float) (sqrt(dsq) / 1.99999); + drawArc(p, x0, y0, x1, y1, a * adjust, + b * adjust, theta, isMoreThanHalf, isPositiveArc); + return; /* Points are too far apart */ + } + double s = sqrt(disc); + double sdx = s * dx; + double sdy = s * dy; + double cx; + double cy; + if (isMoreThanHalf == isPositiveArc) { + cx = xm - sdy; + cy = ym + sdx; + } else { + cx = xm + sdy; + cy = ym - sdx; + } + + double eta0 = atan2((y0p - cy), (x0p - cx)); + + double eta1 = atan2((y1p - cy), (x1p - cx)); + + double sweep = (eta1 - eta0); + if (isPositiveArc != (sweep >= 0)) { + if (sweep > 0) { + sweep -= 2 * M_PI; + } else { + sweep += 2 * M_PI; + } + } + + cx *= a; + cy *= b; + double tcx = cx; + cx = cx * cosTheta - cy * sinTheta; + cy = tcx * sinTheta + cy * cosTheta; + + arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep); +} + + + +// Use the given verb, and points in the range [start, end) to insert a command into the SkPath. +void PathResolver::addCommand(SkPath* outPath, char previousCmd, + char cmd, const std::vector<float>* points, size_t start, size_t end) { + + int incr = 2; + float reflectiveCtrlPointX; + float reflectiveCtrlPointY; + + switch (cmd) { + case 'z': + case 'Z': + outPath->close(); + // Path is closed here, but we need to move the pen to the + // closed position. So we cache the segment's starting position, + // and restore it here. + currentX = currentSegmentStartX; + currentY = currentSegmentStartY; + ctrlPointX = currentSegmentStartX; + ctrlPointY = currentSegmentStartY; + outPath->moveTo(currentX, currentY); + break; + case 'm': + case 'M': + case 'l': + case 'L': + case 't': + case 'T': + incr = 2; + break; + case 'h': + case 'H': + case 'v': + case 'V': + incr = 1; + break; + case 'c': + case 'C': + incr = 6; + break; + case 's': + case 'S': + case 'q': + case 'Q': + incr = 4; + break; + case 'a': + case 'A': + incr = 7; + break; + } + + for (unsigned int k = start; k < end; k += incr) { + switch (cmd) { + case 'm': // moveto - Start a new sub-path (relative) + currentX += points->at(k + 0); + currentY += points->at(k + 1); + if (k > start) { + // According to the spec, if a moveto is followed by multiple + // pairs of coordinates, the subsequent pairs are treated as + // implicit lineto commands. + outPath->rLineTo(points->at(k + 0), points->at(k + 1)); + } else { + outPath->rMoveTo(points->at(k + 0), points->at(k + 1)); + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + } + break; + case 'M': // moveto - Start a new sub-path + currentX = points->at(k + 0); + currentY = points->at(k + 1); + if (k > start) { + // According to the spec, if a moveto is followed by multiple + // pairs of coordinates, the subsequent pairs are treated as + // implicit lineto commands. + outPath->lineTo(points->at(k + 0), points->at(k + 1)); + } else { + outPath->moveTo(points->at(k + 0), points->at(k + 1)); + currentSegmentStartX = currentX; + currentSegmentStartY = currentY; + } + break; + case 'l': // lineto - Draw a line from the current point (relative) + outPath->rLineTo(points->at(k + 0), points->at(k + 1)); + currentX += points->at(k + 0); + currentY += points->at(k + 1); + break; + case 'L': // lineto - Draw a line from the current point + outPath->lineTo(points->at(k + 0), points->at(k + 1)); + currentX = points->at(k + 0); + currentY = points->at(k + 1); + break; + case 'h': // horizontal lineto - Draws a horizontal line (relative) + outPath->rLineTo(points->at(k + 0), 0); + currentX += points->at(k + 0); + break; + case 'H': // horizontal lineto - Draws a horizontal line + outPath->lineTo(points->at(k + 0), currentY); + currentX = points->at(k + 0); + break; + case 'v': // vertical lineto - Draws a vertical line from the current point (r) + outPath->rLineTo(0, points->at(k + 0)); + currentY += points->at(k + 0); + break; + case 'V': // vertical lineto - Draws a vertical line from the current point + outPath->lineTo(currentX, points->at(k + 0)); + currentY = points->at(k + 0); + break; + case 'c': // curveto - Draws a cubic Bézier curve (relative) + outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3), + points->at(k + 4), points->at(k + 5)); + + ctrlPointX = currentX + points->at(k + 2); + ctrlPointY = currentY + points->at(k + 3); + currentX += points->at(k + 4); + currentY += points->at(k + 5); + + break; + case 'C': // curveto - Draws a cubic Bézier curve + outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3), + points->at(k + 4), points->at(k + 5)); + currentX = points->at(k + 4); + currentY = points->at(k + 5); + ctrlPointX = points->at(k + 2); + ctrlPointY = points->at(k + 3); + break; + case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1), + points->at(k + 2), points->at(k + 3)); + ctrlPointX = currentX + points->at(k + 0); + ctrlPointY = currentY + points->at(k + 1); + currentX += points->at(k + 2); + currentY += points->at(k + 3); + break; + case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'c' || previousCmd == 's' + || previousCmd == 'C' || previousCmd == 'S') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = points->at(k + 0); + ctrlPointY = points->at(k + 1); + currentX = points->at(k + 2); + currentY = points->at(k + 3); + break; + case 'q': // Draws a quadratic Bézier (relative) + outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = currentX + points->at(k + 0); + ctrlPointY = currentY + points->at(k + 1); + currentX += points->at(k + 2); + currentY += points->at(k + 3); + break; + case 'Q': // Draws a quadratic Bézier + outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3)); + ctrlPointX = points->at(k + 0); + ctrlPointY = points->at(k + 1); + currentX = points->at(k + 2); + currentY = points->at(k + 3); + break; + case 't': // Draws a quadratic Bézier curve(reflective control point)(relative) + reflectiveCtrlPointX = 0; + reflectiveCtrlPointY = 0; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = currentX - ctrlPointX; + reflectiveCtrlPointY = currentY - ctrlPointY; + } + outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1)); + ctrlPointX = currentX + reflectiveCtrlPointX; + ctrlPointY = currentY + reflectiveCtrlPointY; + currentX += points->at(k + 0); + currentY += points->at(k + 1); + break; + case 'T': // Draws a quadratic Bézier curve (reflective control point) + reflectiveCtrlPointX = currentX; + reflectiveCtrlPointY = currentY; + if (previousCmd == 'q' || previousCmd == 't' + || previousCmd == 'Q' || previousCmd == 'T') { + reflectiveCtrlPointX = 2 * currentX - ctrlPointX; + reflectiveCtrlPointY = 2 * currentY - ctrlPointY; + } + outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY, + points->at(k + 0), points->at(k + 1)); + ctrlPointX = reflectiveCtrlPointX; + ctrlPointY = reflectiveCtrlPointY; + currentX = points->at(k + 0); + currentY = points->at(k + 1); + break; + case 'a': // Draws an elliptical arc + // (rx ry x-axis-rotation large-arc-flag sweep-flag x y) + drawArc(outPath, + currentX, + currentY, + points->at(k + 5) + currentX, + points->at(k + 6) + currentY, + points->at(k + 0), + points->at(k + 1), + points->at(k + 2), + points->at(k + 3) != 0, + points->at(k + 4) != 0); + currentX += points->at(k + 5); + currentY += points->at(k + 6); + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + case 'A': // Draws an elliptical arc + drawArc(outPath, + currentX, + currentY, + points->at(k + 5), + points->at(k + 6), + points->at(k + 0), + points->at(k + 1), + points->at(k + 2), + points->at(k + 3) != 0, + points->at(k + 4) != 0); + currentX = points->at(k + 5); + currentY = points->at(k + 6); + ctrlPointX = currentX; + ctrlPointY = currentY; + break; + default: + LOG_ALWAYS_FATAL("Unsupported command: %c", cmd); + break; + } + previousCmd = cmd; + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/utils/VectorDrawableUtils.h b/libs/hwui/utils/VectorDrawableUtils.h new file mode 100644 index 000000000000..b5ef5102d219 --- /dev/null +++ b/libs/hwui/utils/VectorDrawableUtils.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_VECTORDRAWABLE_UTILS_H +#define ANDROID_HWUI_VECTORDRAWABLE_UTILS_H + +#include "VectorDrawable.h" + +#include <cutils/compiler.h> +#include "SkPath.h" +#include <vector> + +namespace android { +namespace uirenderer { + +class VectorDrawableUtils { +public: + ANDROID_API static bool canMorph(const PathData& morphFrom, const PathData& morphTo); + ANDROID_API static bool interpolatePathData(PathData* outData, const PathData& morphFrom, + const PathData& morphTo, float fraction); + ANDROID_API static void verbsToPath(SkPath* outPath, const PathData& data); + static void interpolatePaths(PathData* outPathData, const PathData& from, const PathData& to, + float fraction); +}; +} // namespace uirenderer +} // namespace android +#endif /* ANDROID_HWUI_VECTORDRAWABLE_UTILS_H*/ diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 11527378f586..212c6a0d2c3c 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -42,15 +42,14 @@ namespace android { static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds -// Time to wait between animation frames. -static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60; - // Time to spend fading out the spot completely. static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms // Time to spend fading out the pointer completely. static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms +// The number of events to be read at once for DisplayEventReceiver. +static const int EVENT_BUFFER_SIZE = 100; // --- PointerController --- @@ -59,6 +58,13 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mPolicy(policy), mLooper(looper), mSpriteController(spriteController) { mHandler = new WeakMessageHandler(this); + if (mDisplayEventReceiver.initCheck() == NO_ERROR) { + mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK, + Looper::EVENT_INPUT, this, nullptr); + } else { + ALOGE("Failed to initialize DisplayEventReceiver."); + } + AutoMutex _l(mLock); mLocked.animationPending = false; @@ -78,10 +84,22 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mLocked.pointerAlpha = 0.0f; // pointer is initially faded mLocked.pointerSprite = mSpriteController->createSprite(); mLocked.pointerIconChanged = false; + mLocked.requestedPointerShape = mPolicy->getDefaultPointerIconId(); + + mLocked.animationFrameIndex = 0; + mLocked.lastFrameUpdatedTime = 0; mLocked.buttonState = 0; + mLocked.iconDetached = false; + + mPolicy->loadPointerIcon(&mLocked.pointerIcon); loadResources(); + + if (mLocked.pointerIcon.isValid()) { + mLocked.pointerIconChanged = true; + updatePointerLocked(); + } } PointerController::~PointerController() { @@ -167,6 +185,10 @@ void PointerController::setPosition(float x, float y) { } void PointerController::setPositionLocked(float x, float y) { + if (mLocked.iconDetached) { + return; + } + float minX, minY, maxX, maxY; if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) { if (x <= minX) { @@ -200,6 +222,10 @@ void PointerController::fade(Transition transition) { // Remove the inactivity timeout, since we are fading now. removeInactivityTimeoutLocked(); + if (mLocked.iconDetached) { + return; + } + // Start fading. if (transition == TRANSITION_IMMEDIATE) { mLocked.pointerFadeDirection = 0; @@ -217,6 +243,10 @@ void PointerController::unfade(Transition transition) { // Always reset the inactivity timer. resetInactivityTimeoutLocked(); + if (mLocked.iconDetached) { + return; + } + // Start unfading. if (transition == TRANSITION_IMMEDIATE) { mLocked.pointerFadeDirection = 0; @@ -231,6 +261,11 @@ void PointerController::unfade(Transition transition) { void PointerController::setPresentation(Presentation presentation) { AutoMutex _l(mLock); + if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) { + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources); + } + if (mLocked.presentation != presentation) { mLocked.presentation = presentation; mLocked.presentationChanged = true; @@ -310,6 +345,39 @@ void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout } } +void PointerController::reloadPointerResources() { + AutoMutex _l(mLock); + + loadResources(); + + if (mLocked.presentation == PRESENTATION_POINTER) { + mLocked.additionalMouseResources.clear(); + mLocked.animationResources.clear(); + mPolicy->loadPointerIcon(&mLocked.pointerIcon); + mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, + &mLocked.animationResources); + } + + mLocked.presentationChanged = true; + updatePointerLocked(); +} + +void PointerController::detachPointerIcon(bool detached) { + AutoMutex _l(mLock); + + if (mLocked.iconDetached == detached) { + return; + } + + mLocked.iconDetached = detached; + if (detached) { + mLocked.pointerFadeDirection = -1; + } else { + mLocked.pointerFadeDirection = 1; + } + startAnimationLocked(); +} + void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) { AutoMutex _l(mLock); @@ -391,32 +459,80 @@ void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_ updatePointerLocked(); } -void PointerController::setPointerIcon(const SpriteIcon& icon) { +void PointerController::updatePointerShape(int32_t iconId) { + AutoMutex _l(mLock); + if (mLocked.requestedPointerShape != iconId) { + mLocked.requestedPointerShape = iconId; + mLocked.presentationChanged = true; + updatePointerLocked(); + } +} + +void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { AutoMutex _l(mLock); - mLocked.pointerIcon = icon.copy(); - mLocked.pointerIconChanged = true; + const int32_t iconId = mPolicy->getCustomPointerIconId(); + mLocked.additionalMouseResources[iconId] = icon; + mLocked.requestedPointerShape = iconId; + mLocked.presentationChanged = true; updatePointerLocked(); } void PointerController::handleMessage(const Message& message) { switch (message.what) { - case MSG_ANIMATE: - doAnimate(); - break; case MSG_INACTIVITY_TIMEOUT: doInactivityTimeout(); break; } } -void PointerController::doAnimate() { +int PointerController::handleEvent(int /* fd */, int events, void* /* data */) { + if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { + ALOGE("Display event receiver pipe was closed or an error occurred. " + "events=0x%x", events); + return 0; // remove the callback + } + + if (!(events & Looper::EVENT_INPUT)) { + ALOGW("Received spurious callback for unhandled poll event. " + "events=0x%x", events); + return 1; // keep the callback + } + + bool gotVsync = false; + ssize_t n; + nsecs_t timestamp; + DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE]; + while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) { + for (size_t i = 0; i < static_cast<size_t>(n); ++i) { + if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { + timestamp = buf[i].header.timestamp; + gotVsync = true; + } + } + } + if (gotVsync) { + doAnimate(timestamp); + } + return 1; // keep the callback +} + +void PointerController::doAnimate(nsecs_t timestamp) { AutoMutex _l(mLock); - bool keepAnimating = false; mLocked.animationPending = false; - nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime; + + bool keepFading = doFadingAnimationLocked(timestamp); + bool keepBitmapFlipping = doBitmapAnimationLocked(timestamp); + if (keepFading || keepBitmapFlipping) { + startAnimationLocked(); + } +} + +bool PointerController::doFadingAnimationLocked(nsecs_t timestamp) { + bool keepAnimating = false; + nsecs_t frameDelay = timestamp - mLocked.animationTime; // Animate pointer fade. if (mLocked.pointerFadeDirection < 0) { @@ -453,10 +569,32 @@ void PointerController::doAnimate() { } } } + return keepAnimating; +} - if (keepAnimating) { - startAnimationLocked(); +bool PointerController::doBitmapAnimationLocked(nsecs_t timestamp) { + std::map<int32_t, PointerAnimation>::const_iterator iter = mLocked.animationResources.find( + mLocked.requestedPointerShape); + if (iter == mLocked.animationResources.end()) { + return false; } + + if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) { + mSpriteController->openTransaction(); + + int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame; + mLocked.animationFrameIndex += incr; + mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr; + while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) { + mLocked.animationFrameIndex -= iter->second.animationFrames.size(); + } + mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]); + + mSpriteController->closeTransaction(); + } + + // Keep animating. + return true; } void PointerController::doInactivityTimeout() { @@ -467,7 +605,7 @@ void PointerController::startAnimationLocked() { if (!mLocked.animationPending) { mLocked.animationPending = true; mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC); - mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE)); + mDisplayEventReceiver.requestNextVsync(); } } @@ -497,8 +635,29 @@ void PointerController::updatePointerLocked() { } if (mLocked.pointerIconChanged || mLocked.presentationChanged) { - mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER - ? mLocked.pointerIcon : mResources.spotAnchor); + if (mLocked.presentation == PRESENTATION_POINTER) { + if (mLocked.requestedPointerShape == mPolicy->getDefaultPointerIconId()) { + mLocked.pointerSprite->setIcon(mLocked.pointerIcon); + } else { + std::map<int32_t, SpriteIcon>::const_iterator iter = + mLocked.additionalMouseResources.find(mLocked.requestedPointerShape); + if (iter != mLocked.additionalMouseResources.end()) { + std::map<int32_t, PointerAnimation>::const_iterator anim_iter = + mLocked.animationResources.find(mLocked.requestedPointerShape); + if (anim_iter != mLocked.animationResources.end()) { + mLocked.animationFrameIndex = 0; + mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC); + startAnimationLocked(); + } + mLocked.pointerSprite->setIcon(iter->second); + } else { + ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerShape); + mLocked.pointerSprite->setIcon(mLocked.pointerIcon); + } + } + } else { + mLocked.pointerSprite->setIcon(mResources.spotAnchor); + } mLocked.pointerIconChanged = false; mLocked.presentationChanged = false; } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index b9e4ce7e7ed0..c1381f32a37f 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -19,6 +19,9 @@ #include "SpriteController.h" +#include <map> +#include <vector> + #include <ui/DisplayInfo.h> #include <input/Input.h> #include <inputflinger/PointerControllerInterface.h> @@ -26,8 +29,7 @@ #include <utils/RefBase.h> #include <utils/Looper.h> #include <utils/String8.h> - -#include <SkBitmap.h> +#include <gui/DisplayEventReceiver.h> namespace android { @@ -40,6 +42,10 @@ struct PointerResources { SpriteIcon spotAnchor; }; +struct PointerAnimation { + std::vector<SpriteIcon> animationFrames; + nsecs_t durationPerFrame; +}; /* * Pointer controller policy interface. @@ -56,7 +62,12 @@ protected: virtual ~PointerControllerPolicyInterface() { } public: + virtual void loadPointerIcon(SpriteIcon* icon) = 0; virtual void loadPointerResources(PointerResources* outResources) = 0; + virtual void loadAdditionalMouseResources(std::map<int32_t, SpriteIcon>* outResources, + std::map<int32_t, PointerAnimation>* outAnimationResources) = 0; + virtual int32_t getDefaultPointerIconId() = 0; + virtual int32_t getCustomPointerIconId() = 0; }; @@ -65,7 +76,8 @@ public: * * Handles pointer acceleration and animation. */ -class PointerController : public PointerControllerInterface, public MessageHandler { +class PointerController : public PointerControllerInterface, public MessageHandler, + public LooperCallback { protected: virtual ~PointerController(); @@ -93,16 +105,21 @@ public: const uint32_t* spotIdToIndex, BitSet32 spotIdBits); virtual void clearSpots(); + void updatePointerShape(int32_t iconId); + void setCustomPointerIcon(const SpriteIcon& icon); void setDisplayViewport(int32_t width, int32_t height, int32_t orientation); - void setPointerIcon(const SpriteIcon& icon); void setInactivityTimeout(InactivityTimeout inactivityTimeout); + void reloadPointerResources(); + + /* Detach or attach the pointer icon status. When detached, the pointer icon disappears + * and the icon location does not change at all. */ + void detachPointerIcon(bool detached); private: static const size_t MAX_RECYCLED_SPRITES = 12; static const size_t MAX_SPOTS = 12; enum { - MSG_ANIMATE, MSG_INACTIVITY_TIMEOUT, }; @@ -132,12 +149,17 @@ private: sp<SpriteController> mSpriteController; sp<WeakMessageHandler> mHandler; + DisplayEventReceiver mDisplayEventReceiver; + PointerResources mResources; struct Locked { bool animationPending; nsecs_t animationTime; + size_t animationFrameIndex; + nsecs_t lastFrameUpdatedTime; + int32_t displayWidth; int32_t displayHeight; int32_t displayOrientation; @@ -155,8 +177,15 @@ private: SpriteIcon pointerIcon; bool pointerIconChanged; + std::map<int32_t, SpriteIcon> additionalMouseResources; + std::map<int32_t, PointerAnimation> animationResources; + + int32_t requestedPointerShape; + int32_t buttonState; + bool iconDetached; + Vector<Spot*> spots; Vector<sp<Sprite> > recycledSprites; } mLocked; @@ -165,7 +194,10 @@ private: void setPositionLocked(float x, float y); void handleMessage(const Message& message); - void doAnimate(); + int handleEvent(int fd, int events, void* data); + void doAnimate(nsecs_t timestamp); + bool doFadingAnimationLocked(nsecs_t timestamp); + bool doBitmapAnimationLocked(nsecs_t timestamp); void doInactivityTimeout(); void startAnimationLocked(); |