diff options
Diffstat (limited to 'libs')
248 files changed, 21370 insertions, 4543 deletions
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk index f682fb8684e8..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,7 +34,8 @@ deviceSources := \ $(commonSources) \ BackupData.cpp \ BackupHelpers.cpp \ - CursorWindow.cpp + CursorWindow.cpp \ + DisplayEventDispatcher.cpp hostSources := $(commonSources) @@ -65,6 +67,7 @@ LOCAL_SHARED_LIBRARIES := \ libbinder \ liblog \ libcutils \ + libgui \ libutils \ libz diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 623ea896626b..6913f43a87c3 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -176,7 +176,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 +223,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 @@ -233,12 +235,13 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) // 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 +343,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 +613,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; @@ -682,10 +685,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; @@ -831,11 +834,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(); 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..c0c3ab883a42 --- /dev/null +++ b/libs/androidfw/LocaleData.cpp @@ -0,0 +1,201 @@ +/* + * 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. +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 { + out[count++] = ancestor; + 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) { + uint32_t supported_ancestors[MAX_PARENT_DEPTH+1]; + ssize_t request_ancestors_index; + const size_t supported_ancestor_count = findAncestors( + supported_ancestors, &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); + } +} + +} // 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 806eeda3555a..71e9c92ae726 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -1868,7 +1868,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.localeScriptWasProvided ? l.localeScript : emptyScript; + const char *rScript = r.localeScriptWasProvided ? r.localeScript : emptyScript; + int script = memcmp(lScript, rScript, sizeof(l.localeScript)); if (script) { return script; } @@ -2012,10 +2015,10 @@ 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) + + const int score = (localeScriptWasProvided ? 1 : 0) + ((localeVariant[0] != 0) ? 2 : 0); - const int oScore = ((o.localeScript[0] != 0) ? 1 : 0) + + const int oScore = (o.localeScriptWasProvided ? 1 : 0) + ((o.localeVariant[0] != 0) ? 2 : 0); return score - oScore; @@ -2165,6 +2168,63 @@ 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. + 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, 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 +2238,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 +2487,33 @@ 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. + if (localeScript[0] == '\0' || localeScript[1] == '\0') { + if (country[0] != '\0' + && (country[0] != settings.country[0] + || country[1] != settings.country[1])) { + return false; + } + } else { + // But if we could determine the scripts, they should be the same + // for the locales to match. + if (memcmp(localeScript, settings.localeScript, sizeof(localeScript)) != 0) { + return false; + } } } @@ -2587,7 +2642,7 @@ void ResTable_config::appendDirLocale(String8& out) const { return; } - if (!localeScript[0] && !localeVariant[0]) { + if (!localeScriptWasProvided && !localeVariant[0]) { // Legacy format. if (out.size() > 0) { out.append("-"); @@ -2605,7 +2660,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 +2672,7 @@ void ResTable_config::appendDirLocale(String8& out) const { size_t len = unpackLanguage(buf); out.append(buf, len); - if (localeScript[0]) { + if (localeScriptWasProvided) { out.append("+"); out.append(localeScript, sizeof(localeScript)); } @@ -2630,7 +2685,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 +2703,7 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN]) const { charsWritten += unpackLanguage(str); } - if (localeScript[0]) { + if (localeScriptWasProvided) { if (charsWritten) { str[charsWritten++] = '-'; } @@ -2682,11 +2737,16 @@ 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]); + } + config->localeScriptWasProvided = true; + break; } - break; case 5: case 6: case 7: @@ -2704,6 +2764,7 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN]) const { void ResTable_config::setBcp47Locale(const char* in) { locale = 0; + localeScriptWasProvided = false; memset(localeScript, 0, sizeof(localeScript)); memset(localeVariant, 0, sizeof(localeVariant)); @@ -2720,6 +2781,9 @@ void ResTable_config::setBcp47Locale(const char* in) { const size_t size = in + strlen(in) - start; assignLocaleComponent(this, start, size); + if (localeScript[0] == '\0') { + computeScript(); + }; } String8 ResTable_config::toString() const { @@ -3080,13 +3144,16 @@ 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() { @@ -3178,6 +3245,10 @@ struct ResTable::PackageGroup // by having these tables in a per-package scope rather than // per-package-group. DynamicRefTable dynamicRefTable; + + // If the package group comes from a system asset. Used in + // determining non-system locales. + const bool isSystemAsset; }; struct ResTable::bag_set @@ -3532,7 +3603,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 +3624,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 +3639,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 +3664,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 +3719,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 +3822,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++; @@ -5660,11 +5736,18 @@ const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) con return NULL; } -void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMipmap) const -{ +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]; @@ -5701,11 +5784,14 @@ void ResTable::getConfigurations(Vector<ResTable_config>* configs, bool ignoreMi } } -void ResTable::getLocales(Vector<String8>* locales) const +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(); @@ -5931,7 +6017,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 +6065,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 +6098,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 +6310,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 +6397,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 +6430,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/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/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp index 49995942a562..1941563519c8 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->localeScriptWasProvided = true; + } else { + out->computeScript(); + out->localeScriptWasProvided = false; } 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_FALSE(test.localeScriptWasProvided); + 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,397 @@ 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_TRUE(test.localeScriptWasProvided); + 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_FALSE(test.localeScriptWasProvided); + 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_TRUE(test.localeScriptWasProvided); + 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.localeScriptWasProvided = true; + config.getBcp47Locale(out); + EXPECT_EQ(0, strcmp("en-Latn", out)); + + config.localeScriptWasProvided = false; + 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, 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)); } -} // namespace android. +} // namespace android 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/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..8ba6318983fc 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -2,11 +2,308 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +HWUI_NEW_OPS := true + +hwui_src_files := \ + font/CacheTexture.cpp \ + font/Font.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 \ + Canvas.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 \ + 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 \ + 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 + +hwui_shared_libraries := \ + liblog \ + libcutils \ + libutils \ + libEGL \ + libGLESv2 \ + libskia \ + libui \ + libgui \ + libprotobuf-cpp-lite \ + +ifneq (false,$(ANDROID_ENABLE_RENDERSCRIPT)) + hwui_cflags += -DANDROID_ENABLE_RENDERSCRIPT + hwui_shared_libraries += libRS libRScpp + hwui_c_includes += \ + $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,) \ + frameworks/rs/cpp \ + frameworks/rs +endif + + +# ------------------------ +# static library +# ------------------------ + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := libhwui_static +LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) +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 := $(hwui_c_includes) $(call hwui_proto_include) + +include $(BUILD_STATIC_LIBRARY) + +# ------------------------ +# static library null gpu +# ------------------------ + +include $(CLEAR_VARS) + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_MODULE := libhwui_static_null_gpu +LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) +LOCAL_CFLAGS := \ + $(hwui_cflags) \ + -DHWUI_NULL_GPU +LOCAL_SRC_FILES := \ + $(hwui_src_files) \ + tests/common/nullegl.cpp \ + tests/common/nullgles.cpp +LOCAL_C_INCLUDES := $(hwui_c_includes) $(call hwui_proto_include) +LOCAL_EXPORT_C_INCLUDE_DIRS := $(hwui_c_includes) $(call hwui_proto_include) + +include $(BUILD_STATIC_LIBRARY) + +# ------------------------ +# shared library +# ------------------------ + +include $(CLEAR_VARS) + LOCAL_MODULE_CLASS := SHARED_LIBRARIES LOCAL_MODULE := libhwui - -include $(LOCAL_PATH)/Android.common.mk +LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static +LOCAL_SHARED_LIBRARIES := $(hwui_shared_libraries) 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_SHARED_LIBRARIES := $(hwui_shared_libraries) +LOCAL_STATIC_LIBRARIES := libhwui_static_null_gpu +LOCAL_CFLAGS := \ + $(hwui_cflags) \ + -DHWUI_NULL_GPU + +LOCAL_SRC_FILES += \ + $(hwui_test_common_src_files) \ + tests/unit/CanvasStateTests.cpp \ + tests/unit/ClipAreaTests.cpp \ + tests/unit/CrashHandlerInjector.cpp \ + tests/unit/DamageAccumulatorTests.cpp \ + tests/unit/DeviceInfoTests.cpp \ + tests/unit/FatVectorTests.cpp \ + tests/unit/GpuMemoryTrackerTests.cpp \ + tests/unit/LayerUpdateQueueTests.cpp \ + tests/unit/LinearAllocatorTests.cpp \ + tests/unit/VectorDrawableTests.cpp \ + tests/unit/OffscreenBufferPoolTests.cpp \ + tests/unit/StringUtilsTests.cpp + +ifeq (true, $(HWUI_NEW_OPS)) + LOCAL_SRC_FILES += \ + tests/unit/BakedOpStateTests.cpp \ + tests/unit/FrameBuilderTests.cpp \ + tests/unit/LeakCheckTests.cpp \ + tests/unit/RecordingCanvasTests.cpp +endif + +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_SHARED_LIBRARIES := $(hwui_shared_libraries) +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 $(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_SHARED_LIBRARIES := $(hwui_shared_libraries) +LOCAL_CFLAGS := \ + $(hwui_cflags) \ + -DHWUI_NULL_GPU +LOCAL_C_INCLUDES += bionic/benchmarks/ + +LOCAL_WHOLE_STATIC_LIBRARIES := libhwui_static_null_gpu +LOCAL_STATIC_LIBRARIES := libbenchmark libbase + +LOCAL_SRC_FILES += \ + $(hwui_test_common_src_files) \ + tests/microbench/DisplayListCanvasBench.cpp \ + tests/microbench/LinearAllocatorBench.cpp \ + tests/microbench/PathParserBench.cpp \ + tests/microbench/ShadowBench.cpp + +ifeq (true, $(HWUI_NEW_OPS)) + LOCAL_SRC_FILES += \ + tests/microbench/FrameBuilderBench.cpp +endif + +include $(BUILD_EXECUTABLE) diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp index 512e0e24aa93..5ca2a2fa37ab 100644 --- a/libs/hwui/Animator.cpp +++ b/libs/hwui/Animator.cpp @@ -36,8 +36,8 @@ BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue) , mFinalValue(finalValue) , mDeltaValue(0) , mFromValue(0) - , mStagingPlayState(NOT_STARTED) - , mPlayState(NOT_STARTED) + , mStagingPlayState(PlayState::NotStarted) + , mPlayState(PlayState::NotStarted) , mHasStartValue(false) , mStartTime(0) , mDuration(300) @@ -50,7 +50,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!"); } @@ -92,9 +92,9 @@ void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) { if (mStagingPlayState > mPlayState) { mPlayState = mStagingPlayState; // Oh boy, we're starting! Man the battle stations! - if (mPlayState == RUNNING) { + if (mPlayState == PlayState::Running) { transitionToRunning(context); - } else if (mPlayState == FINISHED) { + } else if (mPlayState == PlayState::Finished) { callOnFinishedListener(context); } } @@ -124,10 +124,10 @@ void BaseRenderNodeAnimator::transitionToRunning(AnimationContext& context) { } bool BaseRenderNodeAnimator::animate(AnimationContext& context) { - if (mPlayState < RUNNING) { + if (mPlayState < PlayState::Running) { return false; } - if (mPlayState == FINISHED) { + if (mPlayState == PlayState::Finished) { return true; } @@ -141,18 +141,18 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) { } float fraction = 1.0f; - if (mPlayState == RUNNING && mDuration > 0) { + if (mPlayState == PlayState::Running && mDuration > 0) { fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration; } if (fraction >= 1.0f) { fraction = 1.0f; - mPlayState = FINISHED; + mPlayState = PlayState::Finished; } fraction = mInterpolator->interpolate(fraction); setValue(mTarget, mFromValue + (mDeltaValue * fraction)); - if (mPlayState == FINISHED) { + if (mPlayState == PlayState::Finished) { callOnFinishedListener(context); return true; } @@ -161,8 +161,8 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) { } void BaseRenderNodeAnimator::forceEndNow(AnimationContext& context) { - if (mPlayState < FINISHED) { - mPlayState = FINISHED; + if (mPlayState < PlayState::Finished) { + mPlayState = PlayState::Finished; callOnFinishedListener(context); } } @@ -212,9 +212,9 @@ void RenderPropertyAnimator::onAttached() { } void RenderPropertyAnimator::onStagingPlayStateChanged() { - if (mStagingPlayState == RUNNING) { + if (mStagingPlayState == PlayState::Running) { (mTarget->mutateStagingProperties().*mPropertyAccess->setter)(finalValue()); - } else if (mStagingPlayState == FINISHED) { + } 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 mTarget->setPropertyFieldsDirty(dirtyMask()); diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h index 1b3d8e7f4842..aea95bfc1c0e 100644 --- a/libs/hwui/Animator.h +++ b/libs/hwui/Animator.h @@ -59,8 +59,8 @@ 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() { mStagingPlayState = PlayState::Running; onStagingPlayStateChanged(); } + ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); } void attach(RenderNode* target); virtual void onAttached() {} @@ -68,8 +68,8 @@ 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; } + bool isFinished() { return mPlayState == PlayState::Finished; } float finalValue() { return mFinalValue; } ANDROID_API virtual uint32_t dirtyMask() = 0; @@ -77,6 +77,12 @@ public: void forceEndNow(AnimationContext& context); protected: + enum class PlayState { + NotStarted, + Running, + Finished, + }; + BaseRenderNodeAnimator(float finalValue); virtual ~BaseRenderNodeAnimator(); @@ -88,12 +94,6 @@ protected: virtual void onStagingPlayStateChanged() {} - enum PlayState { - NOT_STARTED, - RUNNING, - FINISHED, - }; - RenderNode* mTarget; float mFinalValue; 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..00381eea4147 --- /dev/null +++ b/libs/hwui/BakedOpDispatcher.cpp @@ -0,0 +1,793 @@ +/* + * 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> + +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]; + Rect opBounds = state.computedState.clippedBounds; + 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& state) { + 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, (const char*) 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(state.roundRectClipState) + .setMeshTexturedUnitQuad(nullptr) + .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height())) + .build(); + renderer.renderGlop(state, 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, + (const char*) 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 = TransformFlags::OffsetByFudgeFactor; + 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, + PathTexture& texture, const RecordedOp& op) { + Rect dest(texture.width(), texture.height()); + dest.translate(texture.left - texture.offset, + texture.top - texture.offset); + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUnitQuad(nullptr) + .setFillPathTexturePaint(texture, *(op.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, *texture, op); + } + } 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::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, *texture, op); + } + } else { + SkPath path; + SkRect rect = getBoundsOfFill(op); + path.addOval(rect); + 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)) { + renderPathTexture(renderer, state, *texture, op); + } +} + +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, *texture, op); + } + } 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, *texture, op); + } + } 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; + renderer.caches().tessellationCache.getShadowBuffers(&state.computedState.transform, + op.localClipRect, op.casterAlpha >= 1.0f, op.casterPath, + &op.shadowMatrixXY, &op.shadowMatrixZ, + op.lightCenter, renderer.getLightInfo().lightRadius, + buffers); + + 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, + reinterpret_cast<const char*>(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) { + OffscreenBuffer* buffer = *op.layerHandle; + + // Note that we don't use op->paint here - it's never set on a LayerOp + 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); + + if (op.destroy) { + renderer.renderState().layerPool().putOrDelete(buffer); + } +} + +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); + } + GL_CHECKPOINT(); + renderer.renderState().layerPool().putOrDelete(*op.layerHandle); + GL_CHECKPOINT(); +} + +} // 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..e65746eea98a --- /dev/null +++ b/libs/hwui/BakedOpRenderer.cpp @@ -0,0 +1,342 @@ +/* + * 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::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) { + LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); + + mRenderTarget.offscreenBuffer = offscreenBuffer; + + // create and bind framebuffer + mRenderTarget.frameBufferId = mRenderState.genFramebuffer(); + mRenderState.bindFramebuffer(mRenderTarget.frameBufferId); + + // attach the texture to the FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + offscreenBuffer->texture.id(), 0); + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "startLayer FAILED"); + 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); + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "glfbrb endlayer failed"); + 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); + LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(), "endLayer FAILED, bound fbo = %u", + mRenderState.getFramebuffer()); + 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(); + } + + mCaches.clearGarbage(); + mCaches.pathCache.trim(); + mCaches.tessellationCache.trim(); + + GL_CHECKPOINT(); + +#if DEBUG_MEMORY_USAGE + mCaches.dumpMemoryUsage(); +#else + if (Properties::debugLevel & kDebugMemory) { + mCaches.dumpMemoryUsage(); + } +#endif +} + +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; +} + +// 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(); + } + } + + // dirty offscreenbuffer + if (dirtyBounds && mRenderTarget.offscreenBuffer) { + // register layer damage to draw-back region + android::Rect dirty(dirtyBounds->left, dirtyBounds->top, + dirtyBounds->right, dirtyBounds->bottom); + mRenderTarget.offscreenBuffer->region.orSelf(dirty); + } +} + +void BakedOpRenderer::renderGlop(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) { + android::Rect dirty(uiDirty.left, uiDirty.top, uiDirty.right, uiDirty.bottom); + mRenderTarget.offscreenBuffer->region.orSelf(dirty); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h new file mode 100644 index 000000000000..10c469875150 --- /dev/null +++ b/libs/hwui/BakedOpRenderer.h @@ -0,0 +1,132 @@ +/* + * 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_RENDERER_H +#define ANDROID_HWUI_BAKED_OP_RENDERER_H + +#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: + /** + * Position agnostic shadow lighting info. Used with all shadow ops in scene. + */ + struct LightInfo { + LightInfo() : LightInfo(0, 0, 0) {} + LightInfo(float lightRadius, uint8_t ambientShadowAlpha, + uint8_t spotShadowAlpha) + : lightRadius(lightRadius) + , ambientShadowAlpha(ambientShadowAlpha) + , spotShadowAlpha(spotShadowAlpha) {} + float lightRadius; + uint8_t ambientShadowAlpha; + uint8_t spotShadowAlpha; + }; + + BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo) + : 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 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); + bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; } + void dirtyRenderTarget(const Rect& dirtyRect); + bool didDraw() const { return mHasDrawn; } +private: + 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 + +#endif // ANDROID_HWUI_BAKED_OP_RENDERER_H diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp new file mode 100644 index 000000000000..87844f9840d7 --- /dev/null +++ b/libs/hwui/BakedOpState.cpp @@ -0,0 +1,84 @@ +/* + * 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 { + +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.mutateClipArea().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 and clipSideFlags + if (clipRect.left > clippedBounds.left) clipSideFlags |= OpClipSideFlags::Left; + if (clipRect.top > clippedBounds.top) clipSideFlags |= OpClipSideFlags::Top; + if (clipRect.right < clippedBounds.right) clipSideFlags |= OpClipSideFlags::Right; + if (clipRect.bottom < clippedBounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom; + clippedBounds.doIntersect(clipRect); + } +} + +ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot) { + transform = *snapshot.transform; + + // Since the op doesn't have known bounds, we conservatively set the mapped bounds + // to the current clipRect, and clipSideFlags to Full. + clipState = snapshot.mutateClipArea().serializeClip(allocator); + LOG_ALWAYS_FATAL_IF(!clipState, "clipState required"); + clippedBounds = clipState->rect; + clipSideFlags = OpClipSideFlags::Full; +} + +ResolvedRenderState::ResolvedRenderState(const ClipRect* viewportRect, const Rect& dstRect) + : transform(Matrix4::identity()) + , clipState(viewportRect) + , clippedBounds(dstRect) + , clipSideFlags(OpClipSideFlags::None) {} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h new file mode 100644 index 000000000000..3db28c982469 --- /dev/null +++ b/libs/hwui/BakedOpState.h @@ -0,0 +1,191 @@ +/* + * 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 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; +}; + +/** + * 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) { + if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; + BakedOpState* bakedState = new (allocator) BakedOpState( + allocator, snapshot, recordedOp, false); + if (bakedState->computedState.clippedBounds.isEmpty()) { + // bounds are empty, so op is rejected + allocator.rewindIfLastAlloc(bakedState); + return nullptr; + } + return bakedState; + } + + enum class StrokeBehavior { + // stroking is forced, regardless of style on paint + Forced, + // stroking is defined by style on paint + StyleDefined, + }; + + static 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 = new (allocator) 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; + } + + static 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 new (allocator) BakedOpState(allocator, snapshot, shadowOpPtr); + } + + static BakedOpState* directConstruct(LinearAllocator& allocator, + const ClipRect* clip, const Rect& dstRect, const RecordedOp& recordedOp) { + return new (allocator) BakedOpState(clip, dstRect, recordedOp); + } + + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + // computed state: + ResolvedRenderState computedState; + + // simple state (straight pointer/value storage): + const float alpha; + const RoundRectClipState* roundRectClipState; + const ProjectionPathMask* projectionPathMask; + const RecordedOp* op; + +private: + BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, + const RecordedOp& recordedOp, bool expandForStroke) + : computedState(allocator, snapshot, recordedOp, expandForStroke) + , alpha(snapshot.alpha) + , roundRectClipState(snapshot.roundRectClipState) + , projectionPathMask(snapshot.projectionPathMask) + , op(&recordedOp) {} + + BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr) + : computedState(allocator, snapshot) + , alpha(snapshot.alpha) + , roundRectClipState(snapshot.roundRectClipState) + , projectionPathMask(snapshot.projectionPathMask) + , op(shadowOpPtr) {} + + BakedOpState(const ClipRect* viewportRect, const Rect& dstRect, const RecordedOp& recordedOp) + : computedState(viewportRect, dstRect) + , alpha(1.0f) + , roundRectClipState(nullptr) + , projectionPathMask(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..330dc2951ec9 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 }; /** @@ -107,7 +101,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 +122,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 +135,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 +154,7 @@ public: TextDropShadowCache dropShadowCache; FboCache fboCache; - GammaFontRenderer* fontRenderer; + GammaFontRenderer fontRenderer; TaskManager tasks; @@ -190,8 +176,6 @@ public: TextureState& textureState() { return *mTextureState; } private: - - void initFont(); void initExtensions(); void initConstraints(); void initStaticProperties(); @@ -206,12 +190,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/Canvas.cpp b/libs/hwui/Canvas.cpp new file mode 100644 index 000000000000..bc88c817ffc8 --- /dev/null +++ b/libs/hwui/Canvas.cpp @@ -0,0 +1,56 @@ +/* + * 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 <SkDrawFilter.h> + +namespace android { + +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); + } + } +} + +} // namespace android diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h index 160d9a8a87dd..0643a54c88c5 100644 --- a/libs/hwui/Canvas.h +++ b/libs/hwui/Canvas.h @@ -19,6 +19,8 @@ #include <cutils/compiler.h> +#include "utils/NinePatch.h" + #include <SkBitmap.h> #include <SkCanvas.h> #include <SkMatrix.h> @@ -62,6 +64,9 @@ public: virtual int width() = 0; virtual int height() = 0; + virtual void setHighContrastText(bool highContrastText) = 0; + virtual bool isHighContrastText() = 0; + // ---------------------------------------------------------------------------- // Canvas state operations // ---------------------------------------------------------------------------- @@ -80,10 +85,6 @@ public: 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 +113,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,20 +142,19 @@ 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; // 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, 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, float hOffset, float vOffset, const SkPaint& paint) = 0; @@ -166,6 +167,9 @@ public: * to be added to each glyph's position to get its absolute position. */ virtual bool drawTextAbsolutePos() const = 0; + +protected: + void drawTextDecorations(float x, float y, float length, const SkPaint& paint); }; }; // namespace android diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp index e22b0d3084ab..cf2726b5f530 100644 --- a/libs/hwui/CanvasState.cpp +++ b/libs/hwui/CanvasState.cpp @@ -28,19 +28,51 @@ 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, + SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + 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, + if (mWidth != viewportWidth || mHeight != viewportHeight) { + mWidth = viewportWidth; + mHeight = viewportHeight; + mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight); + mCanvas.onViewportInitialized(); + } + + freeAllSnapshots(); + mSnapshot = allocSnapshot(&mFirstSnapshot, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); mSnapshot->fbo = mCanvas.getTargetFbo(); @@ -48,18 +80,36 @@ void CanvasState::initializeSaveStack(float clipLeft, float clipTop, mSaveCount = 1; } -void CanvasState::setViewport(int width, int height) { - mWidth = width; - mHeight = height; - mFirstSnapshot->initializeViewport(width, height); - mCanvas.onViewportInitialized(); +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); +} - // 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; +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 +123,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 +135,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 +190,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 +207,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 +274,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 +302,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..160090dcd8cc 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) { @@ -69,9 +60,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 +138,7 @@ Rect RectangleList::calculateBounds() const { if (index == 0) { bounds = tr.transformedBounds(); } else { - bounds.intersect(tr.transformedBounds()); + bounds.doIntersect(tr.transformedBounds()); } } return bounds; @@ -182,12 +172,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 +191,59 @@ 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) { + 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) { + 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) { + onClipUpdated(); SkMatrix skTransform; transform->copyTo(skTransform); SkPath transformed; path.transform(skTransform, &transformed); SkRegion region; regionFromPath(transformed, region); - return clipRegion(region, op); + clipRegion(region, op); } /* @@ -257,41 +254,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 +289,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 +308,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 +320,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 +342,172 @@ 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) { + switch (mMode) { + case ClipMode::Rectangle: + mLastSerialization = allocator.create<ClipRect>(mClipRect); + break; + case ClipMode::RectangleList: + mLastSerialization = allocator.create<ClipRectList>(mRectangleList); + break; + case ClipMode::Region: + mLastSerialization = allocator.create<ClipRegion>(mClipRegion); + break; + } + } + return mLastSerialization; +} + +inline static const Rect& getRect(const ClipBase* scb) { + return reinterpret_cast<const ClipRect*>(scb)->rect; +} + +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; +} + +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 (!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>(getRect(recordedClip)); + recordedClipTransform.mapRect(rectClip->rect); + rectClip->rect.doIntersect(mClipRect); + 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(getRect(recordedClip)); + recordedClipTransform.mapRect(resultClip); + other.setRect(resultClip.toSkIRect()); + } else { + SkPath transformedRect = pathFromTransformedRectangle(getRect(recordedClip), + 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; + } + 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(getRect(recordedClip), 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(); + 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(getRect(clip), &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..479796db042a 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,61 @@ 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; + // Bounds of the clipping area, used to define the scissor, and define which + // portion of the stencil is updated/used + Rect rect; +}; + +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 +141,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 +160,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; } + const ClipBase* serializeClip(LinearAllocator& allocator); + 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 +210,22 @@ private: pathAsRegion.setPath(path, createViewportRegion()); } - enum Mode { - kModeRectangle, - kModeRegion, - kModeRectangleList - }; + ClipMode mMode; + bool mPostViewportClipObserved = 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..c2e14a29f29e 100644 --- a/libs/hwui/DamageAccumulator.cpp +++ b/libs/hwui/DamageAccumulator.cpp @@ -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..e44fc20feaa8 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 { diff --git a/libs/hwui/Debug.h b/libs/hwui/Debug.h index 5808aaca76be..e98fa0440591 100644 --- a/libs/hwui/Debug.h +++ b/libs/hwui/Debug.h @@ -20,9 +20,6 @@ // Turn on to check for OpenGL errors on each frame #define DEBUG_OPENGL 1 -// Turn on to display informations about the GPU -#define DEBUG_EXTENSIONS 0 - // Turn on to enable initialization information #define DEBUG_INIT 0 diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp index 03aecd42d16a..a1825c5bc4c1 100644 --- a/libs/hwui/DeferredDisplayList.cpp +++ b/libs/hwui/DeferredDisplayList.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <SkCanvas.h> #include <utils/Trace.h> @@ -47,6 +44,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 +75,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 +139,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 +224,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()) { @@ -421,7 +427,7 @@ void DeferredDisplayList::addSaveLayer(OpenGLRenderer& renderer, this, op, op->getFlags(), newSaveCount); storeStateOpBarrier(renderer, op); - mSaveStack.push(newSaveCount); + mSaveStack.push_back(newSaveCount); } /** @@ -436,7 +442,7 @@ void DeferredDisplayList::addSave(OpenGLRenderer& renderer, SaveOp* op, int newS // 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 +465,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 +501,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 +516,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 +526,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"); @@ -594,7 +600,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); @@ -605,7 +611,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(); } @@ -618,7 +624,7 @@ void DeferredDisplayList::storeRestoreToCountBarrier(OpenGLRenderer& renderer, S // doesn't have kClip_SaveFlag set DeferredDisplayState* state = createState(); renderer.storeDisplayState(*state, getStateOpDeferFlags()); - mBatches.add(new RestoreToCountBatch(op, state, newSaveCount)); + mBatches.push_back(new RestoreToCountBatch(op, state, newSaveCount)); resetBatchingState(); } @@ -626,7 +632,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++) { @@ -639,7 +645,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); @@ -650,7 +656,7 @@ void DeferredDisplayList::flush(OpenGLRenderer& renderer, Rect& dirty) { // save and restore so that reordering doesn't affect final state renderer.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); - 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); @@ -672,7 +678,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 3bc4904d6921..2d5979f2f1a7 100644 --- a/libs/hwui/DeferredDisplayList.h +++ b/libs/hwui/DeferredDisplayList.h @@ -21,12 +21,13 @@ #include <utils/Errors.h> #include <utils/LinearAllocator.h> -#include <utils/Vector.h> #include "Matrix.h" #include "OpenGLRenderer.h" #include "Rect.h" +#include <vector> + class SkBitmap; namespace android { @@ -48,7 +49,7 @@ typedef const void* mergeid_t; class DeferredDisplayState { public: - /** static void* operator new(size_t size); PURPOSELY OMITTED **/ + static void* operator new(size_t size) = delete; static void* operator new(size_t size, LinearAllocator& allocator) { return allocator.alloc(size); } @@ -60,7 +61,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; @@ -82,8 +82,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(); } @@ -101,7 +101,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. @@ -151,17 +151,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]; 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..6a3c8902dff1 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) { @@ -77,13 +77,13 @@ public: 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 +101,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..59f0d7cc7346 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,32 +21,51 @@ #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) , 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); } @@ -61,9 +78,11 @@ void DisplayListData::cleanupResources() { 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..60cc7bab64dd 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> @@ -43,6 +38,8 @@ #include "Matrix.h" #include "RenderProperties.h" +#include <vector> + class SkBitmap; class SkPaint; class SkPath; @@ -58,12 +55,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. */ @@ -108,15 +112,16 @@ struct ReplayStateStruct : public PlaybackStateStruct { /** * 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; @@ -124,52 +129,61 @@ public: bool reorderChildren; }; - 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<Functor*>& getFunctors() const { return functors; } + + 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<Functor*> functors; + + bool hasDrawOps; // only used if !HWUI_NEW_OPS void cleanupResources(); }; diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index 2dd52788074d..759c12a3f214 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -31,70 +31,62 @@ 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) { + reset(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::reset(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) { addDrawOp(new (alloc()) DrawFunctorOp(functor)); - mDisplayListData->functors.add(functor); + mDisplayList->functors.push_back(functor); } 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)); @@ -176,11 +168,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 +216,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) { @@ -330,13 +317,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 +354,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 +372,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)); @@ -435,28 +423,6 @@ void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count, 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, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, @@ -469,30 +435,35 @@ void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions, 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(text, 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 +503,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 +545,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 +573,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..72fc100ebd1d 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 "Canvas.h" +#include "CanvasState.h" +#include "DisplayList.h" +#include "RenderNode.h" +#include "ResourceCache.h" +#include "SkiaCanvasProxy.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,47 +64,23 @@ 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(); + void reset(int width, int height); + WARN_UNUSED_RESULT DisplayList* finishRecording(); // ---------------------------------------------------------------------------- // 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(); - } + void insertReorderBarrier(bool enableReorder); // ---------------------------------------------------------------------------- // 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, CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom, CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry, @@ -114,16 +92,12 @@ public: // ---------------------------------------------------------------------------- // HWUI Canvas draw operations - special // ---------------------------------------------------------------------------- - void drawLayer(DeferredLayerUpdater* layerHandle, float x, float y); + void drawLayer(DeferredLayerUpdater* layerHandle); void drawRenderNode(RenderNode* renderNode); // TODO: rename for consistency void callDrawGLFunction(Functor* functor); - void setHighContrastText(bool highContrastText) { - mHighContrastText = highContrastText; - } - // ---------------------------------------------------------------------------- // CanvasStateClient interface // ---------------------------------------------------------------------------- @@ -144,6 +118,11 @@ 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 // ---------------------------------------------------------------------------- @@ -165,7 +144,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 +183,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,13 +205,14 @@ 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; // Text virtual void drawText(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, float hOffset, float vOffset, const SkPaint& paint) override; virtual bool drawTextAbsolutePos() const override { return false; } @@ -249,11 +229,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,7 +253,7 @@ 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(count * sizeof(T)); memcpy(dstBuffer, srcBuffer, count * sizeof(T)); return dstBuffer; } @@ -285,7 +268,7 @@ private: // 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 +292,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 +302,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 +312,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); @@ -355,12 +328,12 @@ private: // 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); + 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 +343,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..92217edc2f16 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -139,7 +139,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 +172,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 +209,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 +245,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 +368,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 +468,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 +487,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 +612,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 +627,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 +687,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 +777,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 +798,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 +891,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(); } @@ -1258,12 +1237,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 ? @@ -1299,24 +1278,6 @@ 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, @@ -1328,7 +1289,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 +1308,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 +1319,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; @@ -1417,26 +1378,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 +1411,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 +1442,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 +1452,8 @@ private: * * Note: doesn't include transformation within the RenderNode, or its properties. */ - mat4 mTransformFromCompositingAncestor; - bool mSkipInOrderDraw; + mat4 transformFromCompositingAncestor; + bool skipInOrderDraw; }; /** @@ -1541,23 +1505,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 d96775aa7ff1..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,31 +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"); - mHasUnpackSubImage = hasGlExtension("GL_EXT_unpack_subimage"); - - // 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); @@ -91,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 a4eef0f0bb86..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,10 +37,8 @@ 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 || mHasUnpackSubImage; } inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; } inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; } @@ -52,25 +47,13 @@ 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; diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp index b54d53233a65..cca3cb7a98f1 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" diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index 9a2a879e594e..68bae6dc47f6 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,7 +475,7 @@ void FontRenderer::checkTextureUpdate() { mUploadTexture = false; } -void FontRenderer::issueDrawCommand(Vector<CacheTexture*>& cacheTextures) { +void FontRenderer::issueDrawCommand(std::vector<CacheTexture*>& cacheTextures) { if (!mFunctor) return; bool first = true; @@ -553,7 +558,7 @@ void FontRenderer::setFont(const SkPaint* paint, const SkMatrix& matrix) { } FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) { + int numGlyphs, float radius, const float* positions) { checkInit(); DropShadow image; @@ -572,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, text, numGlyphs, &bounds, positions); uint32_t intRadius = Blur::convertRadiusToInt(radius); uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius; @@ -604,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, text, numGlyphs, penX, penY, Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions); // Unbind any PBO we might have used @@ -649,15 +654,15 @@ void FontRenderer::endPrecaching() { } 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) { + 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, text, numGlyphs, x, y, positions); if (forceFinish) { finishRender(); @@ -667,15 +672,15 @@ bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const c } 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) { + 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, text, numGlyphs, path, hOffset, vOffset); finishRender(); return mDrawn; @@ -724,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 3da20ee255af..99944985cda8 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 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); + 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); + 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 char *text, 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, @@ -162,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..c4c655b75d22 --- /dev/null +++ b/libs/hwui/FrameBuilder.cpp @@ -0,0 +1,741 @@ +/* + * 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 "renderstate/OffscreenBufferPool.h" +#include "utils/FatVector.h" +#include "utils/PaintUtils.h" +#include "utils/TraceUtils.h" + +#include <SkCanvas.h> +#include <SkPathOps.h> +#include <utils/TypeHelpers.h> + +namespace android { +namespace uirenderer { + +FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip, + uint32_t viewportWidth, uint32_t viewportHeight, + const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter) + : mCanvasState(*this) { + ATRACE_NAME("prepare drawing commands"); + + mLayerBuilders.reserve(layers.entries().size()); + mLayerStack.reserve(layers.entries().size()); + + // 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, + lightCenter); + + // 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(); + } + } + + // Defer Fbo0 + for (const sp<RenderNode>& node : nodes) { + if (node->nothingToDraw()) continue; + node->computeOrdering(); + + int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + deferNodePropsAndOps(*node); + mCanvasState.restoreToCount(count); + } +} + +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())); + } + + if (!mCanvasState.quickRejectConservative(0, 0, width, height)) { + // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer) + if (node.getLayer()) { + // HW layer + LayerOp* drawLayerOp = new (mAllocator) 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(*new (mAllocator) BeginLayerOp( + saveLayerBounds, + Matrix4::identity(), + nullptr, // no record-time clip - need only respect defer-time one + &saveLayerPaint)); + deferNodeOps(node); + deferEndLayerOp(*new (mAllocator) 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(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(*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 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; + } + + ShadowOp* shadowOp = new (mAllocator) ShadowOp(casterNodeOp, casterAlpha, casterPath, + mCanvasState.getLocalClipBounds(), + mCanvasState.currentSnapshot()->getRelativeLightCenter()); + BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct( + mAllocator, *mCanvasState.writableSnapshot(), shadowOp); + if (CC_LIKELY(bakedOpState)) { + currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow); + } +} + +void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) { + const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath(); + int count = mCanvasState.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + + // can't be null, since DL=null node rejection happens before deferNodePropsAndOps + const DisplayList& displayList = *(renderNode.getDisplayList()); + + const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]); + const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op); + const RenderProperties& backgroundProps = backgroundOp->renderNode->properties(); + + // Transform renderer to match background we're projecting onto + // (by offsetting canvas by translationX/Y of background rendernode, since only those are set) + mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY()); + + // If the projection receiver has an outline, we mask projected content to it + // (which we know, apriori, are all tessellated paths) + mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline); + + // draw projected nodes + for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) { + RenderNodeOp* childOp = renderNode.mProjectedNodes[i]; + + int restoreTo = mCanvasState.save(SkCanvas::kMatrix_SaveFlag); + mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor); + deferRenderNodeOpImpl(*childOp); + 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 (const DisplayList::Chunk& chunk : displayList.getChunks()) { + FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes; + buildZSortedChildList(&zTranslatedNodes, displayList, chunk); + + defer3dChildren(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(ChildrenSelectMode::Positive, zTranslatedNodes); + } +} + +void FrameBuilder::deferRenderNodeOpImpl(const RenderNodeOp& op) { + if (op.renderNode->nothingToDraw()) return; + int count = mCanvasState.save(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + + // apply state from RecordedOp (clip first, since op's clip is transformed by current matrix) + mCanvasState.writableSnapshot()->mutateClipArea().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. + */ +void 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; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); +} + +/** + * 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 + + // 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::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 = new (mAllocator) OvalOp( + unmappedBounds, + op.localMatrix, + op.localClip, + op.paint); + deferOvalOp(*resolvedOp); +} + +void FrameBuilder::deferFunctorOp(const FunctorOp& op) { + BakedOpState* bakedState = tryBakeOpState(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) { + deferStrokeableOp(op, OpBatchType::Bitmap); +} + +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) { + deferStrokeableOp(op, tessBatchId(op)); +} + +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 = new (mAllocator) 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 = tryBakeOpState(op); + 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); + } +} + +void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) { + BakedOpState* bakedState = tryBakeOpState(op); + if (!bakedState) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint))); +} + +void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) { + BakedOpState* bakedState = tryBakeOpState(op); + 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(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + 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 = new (mAllocator) 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()); + + // 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 = new (mAllocator) CopyToLayerOp(op, layerHandle); + BakedOpState* bakedState = BakedOpState::directConstruct(mAllocator, + &(currentLayer().viewportClip), 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 = new (mAllocator) CopyFromLayerOp(op, layerHandle); + bakedState = BakedOpState::directConstruct(mAllocator, + &(currentLayer().viewportClip), 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().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer); + currentLayer().activeUnclippedSaveLayers.pop_back(); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h new file mode 100644 index 000000000000..bd0185017f52 --- /dev/null +++ b/libs/hwui/FrameBuilder.h @@ -0,0 +1,218 @@ +/* + * 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; + +/** + * Traverses all of the drawing commands from the layers and RenderNodes passed into it, preparing + * them to be rendered. + * + * 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: + FrameBuilder(const LayerUpdateQueue& layers, const SkRect& clip, + uint32_t viewportWidth, uint32_t viewportHeight, + const std::vector< sp<RenderNode> >& nodes, const Vector3& lightCenter); + + 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) { + /** + * 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(); + LayerBuilder& layer = *(mLayerBuilders[i]); + if (layer.renderNode) { + // cached HW layer - can't skip layer if empty + renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect); + GL_CHECKPOINT(); + layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); + GL_CHECKPOINT(); + renderer.endLayer(); + } else if (!layer.empty()) { // save layer - skip entire layer if empty + layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height); + GL_CHECKPOINT(); + layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); + GL_CHECKPOINT(); + renderer.endLayer(); + } + } + + GL_CHECKPOINT(); + const LayerBuilder& fbo0 = *(mLayerBuilders[0]); + renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect); + GL_CHECKPOINT(); + fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); + GL_CHECKPOINT(); + renderer.endFrame(fbo0.repaintRect); + } + + 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: + 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); + } + + // 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(ChildrenSelectMode mode, const V& zTranslatedNodes); + + void deferShadow(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>(); + } + + void 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 + + // List of every deferred layer's render state. Replayed in reverse order to render a frame. + std::vector<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. + */ + std::vector<size_t> mLayerStack; + + CanvasState mCanvasState; + + // contains ResolvedOps and Batches + LinearAllocator mAllocator; +}; + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp index b416615c20e1..b7dd3b7e2f95 100644 --- a/libs/hwui/FrameInfoVisualizer.cpp +++ b/libs/hwui/FrameInfoVisualizer.cpp @@ -16,6 +16,7 @@ #include "FrameInfoVisualizer.h" #include "OpenGLRenderer.h" +#include "utils/Color.h" #include <cutils/compiler.h> #include <array> @@ -27,19 +28,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 +48,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) { @@ -197,9 +198,9 @@ void FrameInfoVisualizer::drawGraph(OpenGLRenderer* canvas) { 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); + paint.setColor(Bar[i].color & BAR_FAST_MASK); canvas->drawRects(mFastRects.get(), mNumFastRects * 4, &paint); - paint.setColor(Bar[i].color | BAR_JANKY_ALPHA); + paint.setColor(Bar[i].color & BAR_JANKY_MASK); canvas->drawRects(mJankyRects.get(), mNumJankyRects * 4, &paint); } } 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/Glop.h b/libs/hwui/Glop.h index fa20b0807a88..e72f39621d57 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 }; }; @@ -135,10 +135,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; diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp index 288fed360162..45fc16cc3136 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()); @@ -463,13 +475,12 @@ GlopBuilder& GlopBuilder::setFillTextureLayer(Layer& layer, float alpha) { // 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; } //////////////////////////////////////////////////////////////////////////////// @@ -615,7 +626,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)); @@ -635,5 +646,42 @@ void GlopBuilder::build() { mOutGlop->transform.meshTransform().mapRect(mOutGlop->bounds); } +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("Glop blend %d %d", glop.blend.src, glop.blend.dst); + ALOGD("Glop bounds " RECT_STRING, RECT_ARGS(glop.bounds)); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h index 549bb21e5f8d..6e5797d1434b 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); @@ -71,9 +72,9 @@ public: GlopBuilder& setFillTextureLayer(Layer& layer, float alpha); 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 +95,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..e899ac71ff36 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 { @@ -110,9 +110,7 @@ void GradientCache::setMaxSize(uint32_t maxSize) { 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,18 +165,16 @@ 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(); + const uint32_t size = info.width * 2 * bytesPerPixel(); while (getSize() + size > mMaxSize) { mCache.removeOldest(); } - generateTexture(colors, positions, texture); + generateTexture(colors, positions, info.width, 2, texture); mSize += size; mCache.put(gradient, texture); @@ -231,10 +227,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 +273,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..b762ca7d5e5c 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 { @@ -144,7 +143,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; @@ -183,7 +183,6 @@ private: 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..c305f65db601 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -243,6 +243,7 @@ void JankTracker::dumpData(const ProfileData* data, int fd) { 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)); diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index 62eeb43a2e2e..114347d94357 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,22 @@ 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; + 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 +229,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 +250,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 +268,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..e00ae66997a5 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() { @@ -241,7 +241,7 @@ public: } inline bool isTextureLayer() const { - return type == kType_Texture; + return type == Type::Texture; } inline SkColorFilter* getColorFilter() const { @@ -263,7 +263,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..7170d4fbeea7 --- /dev/null +++ b/libs/hwui/LayerBuilder.cpp @@ -0,0 +1,365 @@ +/* + * 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: + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + 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: + static void* operator new(size_t size, LinearAllocator& allocator) { + return allocator.alloc(size); + } + + 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; + if (lhs->projectionPathMask != rhs->projectionPathMask) 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) + , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr) + , beginLayerOp(beginLayerOp) + , renderNode(renderNode) + , viewportClip(Rect(width, height)) {} + +// 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::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.alloc(vertCount * sizeof(Vertex)); + + 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 = new (allocator) SimpleRectsOp(bounds, + Matrix4::identity(), nullptr, paint, + verts, vertCount); + BakedOpState* bakedState = BakedOpState::directConstruct(allocator, + &viewportClip, bounds, *op); + deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices); + } +} + +void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator, + BakedOpState* op, batchid_t batchId) { + if (batchId != OpBatchType::CopyToLayer) { + // if first op after one or more unclipped saveLayers, flush the layer clears + flushLayerClears(allocator); + } + + 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 = new (allocator) 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) { + if (batchId != OpBatchType::CopyToLayer) { + // if first op after one or more unclipped saveLayers, flush the layer clears + flushLayerClears(allocator); + } + 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 = new (allocator) 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::dump() const { + ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p", + this, width, height, offscreenBuffer, beginLayerOp, renderNode); + 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..99968e1750c8 --- /dev/null +++ b/libs/hwui/LayerBuilder.h @@ -0,0 +1,138 @@ +/* + * 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() { + mBatches.clear(); + } + + void dump() const; + + const uint32_t width; + const uint32_t height; + const Rect repaintRect; + OffscreenBuffer* offscreenBuffer; + const BeginLayerOp* beginLayerOp; + const RenderNode* renderNode; + const ClipRect viewportClip; + + // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps + std::vector<BakedOpState*> activeUnclippedSaveLayers; +private: + 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..0f219e4792f6 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.genFramebuffer(); 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); @@ -356,7 +357,7 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* && bitmap->width() <= caches.maxTextureSize && bitmap->height() <= caches.maxTextureSize) { - GLuint fbo = caches.fboCache.get(); + GLuint fbo = renderState.getFramebuffer(); 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,7 @@ bool LayerRenderer::copyLayer(RenderState& renderState, Layer* layer, SkBitmap* renderState.bindFramebuffer(fbo); glGenTextures(1, &texture); - if ((error = glGetError()) != GL_NO_ERROR) goto error; + GL_CHECKPOINT(); caches.textureState().activateTexture(0); caches.textureState().bindTexture(texture); @@ -422,23 +422,22 @@ 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; + GL_CHECKPOINT(); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - if ((error = glGetError()) != GL_NO_ERROR) goto error; + GL_CHECKPOINT(); { 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; + GL_CHECKPOINT(); { Rect bounds; @@ -448,24 +447,17 @@ 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; + GL_CHECKPOINT(); } 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); return status; 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..73ebd1304750 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)); diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index ed54a25f3edf..1c25f26c0c25 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -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); diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 5233a2377d7f..db017feff577 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" @@ -40,6 +41,7 @@ #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(); #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; } } @@ -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(); } } @@ -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); @@ -666,7 +614,7 @@ bool OpenGLRenderer::createLayer(float left, float top, float right, float botto 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.genFramebuffer()); 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) { @@ -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,7 +1143,7 @@ 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 @@ -1211,7 +1153,7 @@ bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDef } void OpenGLRenderer::restoreDisplayState(const DeferredDisplayState& state, bool skipClipRestore) { - setMatrix(state.mMatrix); + setGlobalMatrix(state.mMatrix); writableSnapshot()->alpha = state.mAlpha; writableSnapshot()->roundRectClipState = state.mRoundRectClipState; writableSnapshot()->projectionPathMask = state.mProjectionPathMask; @@ -1246,7 +1188,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 +1209,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 +1243,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,7 +1405,7 @@ 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? @@ -1497,10 +1431,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 +1474,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 +1497,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 +1526,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 +1601,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; @@ -1712,7 +1643,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); } @@ -1742,7 +1673,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); } @@ -1836,7 +1767,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; @@ -1919,7 +1850,7 @@ 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); } drawConvexPath(path, p); } @@ -1979,9 +1910,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() @@ -1992,6 +1920,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); @@ -2021,12 +1950,12 @@ 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, + 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"); } @@ -2034,7 +1963,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, text, count, textShadow.radius, positions); // If the drop shadow exceeds the max texture size or couldn't be // allocated, skip drawing if (!texture) return; @@ -2049,69 +1978,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(); @@ -2166,8 +2045,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) { @@ -2228,15 +2108,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, text, count, positions, fontRenderer, alpha, oldX, oldY); } @@ -2261,21 +2140,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, text, 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, text, count, x, y, positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish); } @@ -2286,8 +2170,6 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float dirtyLayerUnchecked(layerBounds, getRegion()); } - drawTextDecorations(totalAdvance, oldX, oldY, paint); - mDirty = true; } @@ -2300,19 +2182,23 @@ void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, // 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, text, count, path, hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) { dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform()); mDirty = true; @@ -2338,7 +2224,7 @@ void OpenGLRenderer::drawPath(const SkPath* path, const SkPaint* paint) { mDirty = true; } -void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { +void OpenGLRenderer::drawLayer(Layer* layer) { if (!layer) { return; } @@ -2354,7 +2240,7 @@ void OpenGLRenderer::drawLayer(Layer* layer, float x, float y) { 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) { @@ -2383,7 +2269,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 @@ -2396,7 +2282,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; @@ -2422,7 +2308,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); } @@ -2431,7 +2317,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; } @@ -2441,61 +2327,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; @@ -2599,12 +2435,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..84bc9b059d33 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); @@ -213,8 +193,6 @@ public: void drawPoints(const float* points, int count, const SkPaint* paint); void drawTextOnPath(const char* text, 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, const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode = DrawOpMode::kImmediate); @@ -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 char* text, 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..98812805259e 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> diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index 3236f6f7c24d..bfabc1d4d94c 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" -#define ATRACE_TAG ATRACE_TAG_VIEW - #include <SkBitmap.h> #include <SkCanvas.h> #include <SkColor.h> @@ -33,6 +30,8 @@ #include "thread/Signal.h" #include "thread/TaskProcessor.h" +#include <cutils/properties.h> + namespace android { namespace uirenderer { @@ -141,10 +140,10 @@ PathCache::PathCache(): 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); + INIT_LOGD(" Setting path cache size to %sMB", property); mMaxSize = MB(atof(property)); } else { - INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_PATH_CACHE_SIZE); + INIT_LOGD(" Using default path cache size of %.2fMB", DEFAULT_PATH_CACHE_SIZE); } mCache.setOnEntryRemovedListener(this); @@ -186,7 +185,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 +209,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 +246,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 +259,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 +277,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 +301,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 +317,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,10 +325,7 @@ 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(); diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index c529915988d9..18f380fe3f7a 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -28,7 +28,8 @@ #include <SkPath.h> #include <utils/LruCache.h> #include <utils/Mutex.h> -#include <utils/Vector.h> + +#include <vector> class SkBitmap; class SkCanvas; @@ -60,13 +61,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) @@ -308,7 +307,7 @@ private: 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..4e9ac9c7f723 --- /dev/null +++ b/libs/hwui/PathParser.cpp @@ -0,0 +1,229 @@ +/* + * 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; +} + +void PathParser::getPathDataFromString(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; + size_t end = 1; + + while (end < strLen) { + end = nextStart(pathStr, strLen, end); + std::vector<float> points; + getFloats(&points, result, pathStr, start, end); + 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) { + data->verbs.push_back(pathStr[start]); + data->verbSizes.push_back(0); + } + return; +} + +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::parseStringForSkPath(SkPath* skPath, ParseResult* result, const char* pathStr, size_t strLen) { + PathData pathData; + getPathDataFromString(&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"; + 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..c4bbb7495a4c --- /dev/null +++ b/libs/hwui/PathParser.h @@ -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. + */ + +#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 parseStringForSkPath(SkPath* outPath, ParseResult* result, + const char* pathStr, size_t strLength); + ANDROID_API static void getPathDataFromString(PathData* outData, ParseResult* result, + const char* pathStr, size_t strLength); + static void dump(const PathData& data); +}; + +}; // 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..6df994c623f2 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> @@ -114,14 +113,10 @@ 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; } 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..083aeb7ed585 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,11 @@ 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::layerPoolSize = DEFAULT_LAYER_CACHE_SIZE; DebugLevel Properties::debugLevel = kDebugDisabled; OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default; @@ -45,6 +53,26 @@ int Properties::overrideSpotShadowStrength = -1; ProfileType Properties::sProfileType = ProfileType::None; bool Properties::sDisableProfileBars = false; +bool Properties::waitForGpuCompletion = 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; @@ -98,13 +126,14 @@ 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); + layerPoolSize = MB(property_get_float(PROPERTY_LAYER_CACHE_SIZE, DEFAULT_LAYER_CACHE_SIZE)); return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 26d8bf754ddb..88f1dbc98926 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 @@ -36,9 +34,6 @@ namespace uirenderer { // 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 +82,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 +140,19 @@ 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_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_SWAP_WITH_DAMAGE "debug.hwui.swap_with_damage" +#define PROPERTY_ENABLE_PARTIAL_UPDATES "debug.hwui.enable_partial_updates" /////////////////////////////////////////////////////////////////////////////// // Runtime configuration properties @@ -204,30 +199,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 +211,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 +219,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 +262,12 @@ 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 layerPoolSize; static DebugLevel debugLevel; static OverdrawColorSet overdrawColorSet; @@ -310,6 +285,9 @@ public: static ProfileType getProfileType(); + // Should be used only by test apps + static bool waitForGpuCompletion; + private: static ProfileType sProfileType; static bool sDisableProfileBars; diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h new file mode 100644 index 000000000000..30d5c293c4bb --- /dev/null +++ b/libs/hwui/RecordedOp.h @@ -0,0 +1,511 @@ +/* + * 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 "font/FontUtil.h" +#include "Matrix.h" +#include "Rect.h" +#include "RenderNode.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; + +/** + * 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) \ + \ + 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(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) } + +/** + * 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 FunctorOp : RecordedOp { + FunctorOp(BASE_PARAMS_PAINTLESS, Functor* functor) + : SUPER_PAINTLESS(FunctorOp) + , 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; +}; + +/** + * 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(const RenderNodeOp& casterOp, float casterAlpha, const SkPath* casterPath, + const Rect& localClipRect, const Vector3& lightCenter) + : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), nullptr, nullptr) + , shadowMatrixXY(casterOp.localMatrix) + , shadowMatrixZ(casterOp.localMatrix) + , casterAlpha(casterAlpha) + , casterPath(casterPath) + , localClipRect(localClipRect) + , lightCenter(lightCenter) { + const RenderNode& node = *casterOp.renderNode; + node.applyViewPropertyTransforms(shadowMatrixXY, false); + node.applyViewPropertyTransforms(shadowMatrixZ, true); + }; + Matrix4 shadowMatrixXY; + Matrix4 shadowMatrixZ; + const float casterAlpha; + const SkPath* casterPath; + const Rect localClipRect; + const Vector3 lightCenter; +}; + +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 { + TextOnPathOp(BASE_PARAMS, const glyph_t* glyphs, int glyphCount, + const SkPath* path, float hOffset, float vOffset) + : SUPER(TextOnPathOp) + , 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) {} + 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. Once drawn, the layer will be destroyed. + 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) + , destroy(true) {} + + 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()) + , destroy(false) {} + + // 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; + + // whether to destroy the layer, once rendered + const bool destroy; +}; + +}; // 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..78855e5d6a0a --- /dev/null +++ b/libs/hwui/RecordingCanvas.cpp @@ -0,0 +1,622 @@ +/* + * 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" + +namespace android { +namespace uirenderer { + +RecordingCanvas::RecordingCanvas(size_t width, size_t height) + : mState(*this) + , mResourceCache(ResourceCache::getInstance()) { + reset(width, height); +} + +RecordingCanvas::~RecordingCanvas() { + LOG_ALWAYS_FATAL_IF(mDisplayList, + "Destroyed a RecordingCanvas during a record!"); +} + +void RecordingCanvas::reset(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; +} + +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(new (alloc()) EndLayerOp()); + } else if (removed.flags & Snapshot::kFlagIsLayer) { + addOp(new (alloc()) EndUnclippedLayerOp()); + } +} + +// ---------------------------------------------------------------------------- +// android/graphics/Canvas state operations +// ---------------------------------------------------------------------------- +// Save (layer) +int RecordingCanvas::save(SkCanvas::SaveFlags 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, SkCanvas::SaveFlags flags) { + // force matrix/clip isolation for layer + flags |= SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag; + bool clippedLayer = flags & SkCanvas::kClipToLayer_SaveFlag; + + 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 |= SkCanvas::kClipToLayer_SaveFlag; + } + + 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(new (alloc()) BeginLayerOp( + unmappedBounds, + *previous.transform, // transform to *draw* with + previousClip, // clip to *draw* with + refPaint(paint))); + } else { + snapshot.flags |= Snapshot::kFlagIsLayer; + + addOp(new (alloc()) 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) { + SkPaint paint; + paint.setColor(color); + paint.setXfermodeMode(mode); + drawPaint(paint); +} + +void RecordingCanvas::drawPaint(const SkPaint& paint) { + addOp(new (alloc()) RectOp( + mState.getRenderTargetClipBounds(), // OK, since we've not passed transform + Matrix4::identity(), + getRecordedClip(), + refPaint(&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(new (alloc()) 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(new (alloc()) 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(new (alloc()) 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.alloc(vertexCount * sizeof(Vertex)); + 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(new (alloc()) 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) { + addOp(new (alloc()) RoundRectOp( + Rect(left, top, right, bottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), rx, ry)); +} + +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(new (alloc()) 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(new (alloc()) 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(new (alloc()) 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) { + addOp(new (alloc()) 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(new (alloc()) PathOp( + Rect(path.getBounds()), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), refPath(&path))); +} + +// Bitmap-based +void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) { + save(SkCanvas::kMatrix_SaveFlag); + 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(SkCanvas::kMatrix_SaveFlag); + 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(SkCanvas::kMatrix_SaveFlag); + translate(dstLeft, dstTop); + drawBitmap(&bitmap, paint); + restore(); + } else { + addOp(new (alloc()) 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(new (alloc()) 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(new (alloc()) PatchOp( + Rect(dstLeft, dstTop, dstRight, dstBottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(paint), refBitmap(bitmap), refPatch(&patch))); +} + +// Text +void RecordingCanvas::drawText(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(new (alloc()) TextOp( + Rect(boundsLeft, boundsTop, boundsRight, boundsBottom), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), glyphs, positions, glyphCount, x, y)); + drawTextDecorations(x, y, totalAdvance, paint); +} + +void RecordingCanvas::drawTextOnPath(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(new (alloc()) TextOnPathOp( + mState.getLocalClipBounds(), // TODO: explicitly define bounds + *(mState.currentSnapshot()->transform), + getRecordedClip(), + refPaint(&paint), glyphs, glyphCount, refPath(&path), hOffset, vOffset)); +} + +void RecordingCanvas::drawBitmap(const SkBitmap* bitmap, const SkPaint* paint) { + addOp(new (alloc()) 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 = new (alloc()) RenderNodeOp( + Rect(stagingProps.getWidth(), stagingProps.getHeight()), + *(mState.currentSnapshot()->transform), + getRecordedClip(), + renderNode); + int opIndex = addOp(op); + 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); + + Layer* layer = layerHandle->backingLayer(); + Matrix4 totalTransform(*(mState.currentSnapshot()->transform)); + totalTransform.multiply(layer->getTransform()); + + addOp(new (alloc()) TextureLayerOp( + Rect(layer->getWidth(), layer->getHeight()), + totalTransform, + getRecordedClip(), + layer)); +} + +void RecordingCanvas::callDrawGLFunction(Functor* functor) { + mDisplayList->functors.push_back(functor); + addOp(new (alloc()) FunctorOp( + mState.getLocalClipBounds(), // TODO: explicitly define bounds + *(mState.currentSnapshot()->transform), + getRecordedClip(), + functor)); +} + +size_t RecordingCanvas::addOp(RecordedOp* op) { + // TODO: validate if "addDrawOp" quickrejection logic is useful before adding + 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); + + 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..8aa750602972 --- /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 "Canvas.h" +#include "CanvasState.h" +#include "DisplayList.h" +#include "ResourceCache.h" +#include "SkiaCanvasProxy.h" +#include "Snapshot.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(); + + void reset(int width, int height); + WARN_UNUSED_RESULT DisplayList* finishRecording(); + +// ---------------------------------------------------------------------------- +// MISC HWUI OPERATIONS - TODO: CATEGORIZE +// ---------------------------------------------------------------------------- + void insertReorderBarrier(bool enableReorder) { + mDeferredBarrierType = enableReorder + ? DeferredBarrierType::OutOfOrder : DeferredBarrierType::InOrder; + } + + void drawLayer(DeferredLayerUpdater* layerHandle); + void drawRenderNode(RenderNode* renderNode); + + // TODO: rename for consistency + void callDrawGLFunction(Functor* functor); + +// ---------------------------------------------------------------------------- +// 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 +// ---------------------------------------------------------------------------- + + 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); + +// ---------------------------------------------------------------------------- +// 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(SkCanvas::SaveFlags 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; + virtual int saveLayerAlpha(float left, float top, float right, float bottom, + int alpha, SkCanvas::SaveFlags 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 */ } + + // 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 void drawText(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) override; + virtual void drawTextOnPath(const uint16_t* glyphs, int glyphCount, const SkPath& path, + float hOffset, float vOffset, const SkPaint& paint) override; + virtual bool drawTextAbsolutePos() const override { return false; } + +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(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 = new (alloc()) SkBitmap(bitmap); + alloc().autoDestroy(localBitmap); + 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; + 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..30c925c8775b 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -125,25 +125,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 +253,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,26 +280,8 @@ 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); - } - -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); + ALOGD("%s[l=%.2f t=%.2f r=%.2f b=%.2f]", label ? label : "Rect", left, top, right, bottom); } - - 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; - } - }; // class Rect }; // namespace uirenderer diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp index d0812c96afd7..11d7a6af3a6a 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 { @@ -100,9 +100,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 +110,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 +140,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..7f59ec1c48b1 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 { @@ -100,14 +101,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 +113,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..ae690fdef4c7 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -14,20 +14,14 @@ * 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 "RecordedOp.h" +#include "BakedOpRenderer.h" +#endif #include "DisplayListOp.h" #include "LayerRenderer.h" #include "OpenGLRenderer.h" @@ -36,46 +30,67 @@ #include "utils/TraceUtils.h" #include "renderthread/CanvasContext.h" +#include "protos/hwui.pb.h" +#include "protos/ProtoHelpers.h" + +#include <SkCanvas.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(); + 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) { + 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(); + } } /** @@ -94,24 +109,100 @@ void RenderNode::output(uint32_t level) { SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); properties().debugOutputProperties(level); - int flags = DisplayListOp::kOpLogFlag_Recurse; - if (mDisplayListData) { + + if (mDisplayList) { +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("op dumping unsupported"); +#else // 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); } +#endif } ALOGD("%*sDone (%p, %s)", (level - 1) * 2, "", this, getName()); } +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; } @@ -137,7 +228,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 +248,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 +287,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 +315,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 +348,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,10 +376,10 @@ 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); @@ -254,12 +388,16 @@ void RenderNode::prepareTreeImpl(TreeInfo& info, bool functorsNeedLayer) { 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 +409,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 +423,7 @@ void RenderNode::pushStagingPropertiesChanges(TreeInfo& info) { } } +#if !HWUI_NEW_OPS void RenderNode::applyLayerPropertiesToLayer(TreeInfo& info) { if (CC_LIKELY(!mLayer)) return; @@ -291,64 +432,66 @@ 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() { + // 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(); + mDisplayList = mStagingDisplayList; + mStagingDisplayList = nullptr; + if (mDisplayList) { + for (size_t i = 0; i < mDisplayList->getFunctors().size(); i++) { + (*mDisplayList->getFunctors()[i])(DrawGlInfo::kModeSync, nullptr); } + } +} + +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(); 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() { + if (mDisplayList) { + for (auto&& child : mDisplayList->getChildren()) { + child->renderNode->decParentRefCount(); } } - 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(); } @@ -357,17 +500,17 @@ void RenderNode::prepareSubTree(TreeInfo& info, bool functorsNeedLayer, DisplayL void RenderNode::destroyHardwareResources() { 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(); } - 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(); } } } @@ -520,46 +663,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 +707,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 +714,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 +777,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 +834,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 +850,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,12 +863,12 @@ 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; } @@ -737,7 +876,7 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, // 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); + renderer.setGlobalMatrix(initialTransform); /** * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters @@ -748,7 +887,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 +904,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++; @@ -784,10 +923,10 @@ void RenderNode::issueOperationsOf3dChildren(ChildrenSelectMode mode, 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++; @@ -808,25 +947,30 @@ void RenderNode::issueOperationsOfProjectedChildren(OpenGLRenderer& renderer, T& // 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 + 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 +989,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. @@ -889,6 +1037,9 @@ void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { 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 +1047,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..8e4a3df271f5 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,51 @@ 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; +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,10 +114,9 @@ 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); void computeOrdering(); @@ -111,13 +125,14 @@ public: ANDROID_API void output(uint32_t level = 1); 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 { @@ -159,11 +174,11 @@ public: return mStagingProperties; } - int getWidth() { + int getWidth() const { return properties().getWidth(); } - int getHeight() { + int getHeight() const { return properties().getHeight(); } @@ -177,38 +192,54 @@ public: 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 + 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,14 +266,20 @@ private: const char* mText; }; + + void syncProperties(); + void syncDisplayList(); + 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(); void damageSelf(TreeInfo& info); void incParentRefCount() { mParentCount++; } @@ -254,31 +291,31 @@ private: 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; }; // class RenderNode diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index 4f6ef4ef9e3d..ce1bd6ab8b03 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "RenderProperties.h" #include <utils/Trace.h> @@ -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) { } 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..96c1a7c18db9 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -26,6 +26,8 @@ #include <SkTArray.h> #include <SkTemplates.h> +#include <memory> + namespace android { // Holds an SkCanvas reference plus additional native data. @@ -55,6 +57,11 @@ public: 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 void restore() override; @@ -67,7 +74,6 @@ public: 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 +101,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,13 +123,14 @@ 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, 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, float hOffset, float vOffset, const SkPaint& paint) override; @@ -134,16 +142,17 @@ private: SkCanvas::SaveFlags saveFlags; }; + bool mHighContrastText = false; + void recordPartialSave(SkCanvas::SaveFlags 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 +191,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 +212,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(); } // ---------------------------------------------------------------------------- @@ -230,11 +237,15 @@ int SkiaCanvas::save(SkCanvas::SaveFlags 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) { @@ -252,8 +263,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 +274,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); } @@ -308,25 +324,27 @@ void SkiaCanvas::recordPartialSave(SkCanvas::SaveFlags flags) { } 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 +492,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 +524,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 +587,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 +609,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,6 +687,12 @@ 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); +} + // ---------------------------------------------------------------------------- // Canvas draw operations: Text // ---------------------------------------------------------------------------- @@ -676,24 +707,9 @@ void SkiaCanvas::drawText(const uint16_t* text, const float* positions, int coun SkPaint paintCopy(paint); paintCopy.setTextAlign(SkPaint::kLeft_Align); - 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"); mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy); -} - -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]; - } - - SkPaint paintCopy(paint); - paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding); - mCanvas->drawPosText(text, count, posPtr, paintCopy); - - delete[] posPtr; + drawTextDecorations(x, y, totalAdvance, paint); } void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp index d96ca2afed00..976f77518337 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; @@ -144,15 +162,15 @@ void SkiaCanvasProxy::willSave() { mCanvas->save(SkCanvas::kMatrixClip_SaveFlag); } -SkCanvas::SaveLayerStrategy SkiaCanvasProxy::willSaveLayer(const SkRect* rectPtr, - const SkPaint* paint, SaveFlags flags) { +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, + (SkCanvas::SaveFlags) SaveLayerFlagsToSaveFlags(saveLayerRec.fSaveLayerFlags)); return SkCanvas::kNoLayer_SaveLayerStrategy; } @@ -165,9 +183,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 +207,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 +219,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 +228,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,7 +275,7 @@ 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); + static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats"); mCanvas->drawText(glyphs.glyphIDs, &pointStorage[0].fX, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0); } @@ -271,7 +288,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,11 +296,12 @@ 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 @@ -293,7 +311,7 @@ void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const S glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds); bounds.offset(x, y); - 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"); mCanvas->drawText(glyphs.glyphIDs, &posArray[0].fX, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, 0); } @@ -301,12 +319,11 @@ void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const S 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, diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h index 0de965094e64..e342d192ea31 100644 --- a/libs/hwui/SkiaCanvasProxy.h +++ b/libs/hwui/SkiaCanvasProxy.h @@ -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..c6d89775390b 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -14,8 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "OpenGLRenderer" - #include "Snapshot.h" #include <SkCanvas.h> @@ -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) @@ -60,7 +58,7 @@ Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags) , mViewportData(s->mViewportData) , mRelativeLightCenter(s->mRelativeLightCenter) { if (saveFlags & SkCanvas::kMatrix_SaveFlag) { - mTransformRoot.load(*s->transform); + mTransformRoot = *s->transform; transform = &mTransformRoot; } else { transform = s->transform; @@ -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,6 +142,7 @@ void Snapshot::resetTransform(float x, float y, float z) { transform = &mTransformRoot; transform->loadTranslate(x, y, z); +#endif } void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const { @@ -150,7 +152,7 @@ void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const { 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 @@ -192,8 +194,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); @@ -243,7 +244,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..dbaa905b0728 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -83,11 +83,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 +116,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 +124,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 +158,16 @@ 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; } /** * Resets the clip to the specified rect. @@ -230,7 +229,7 @@ public: /** * Previous snapshot. */ - sp<Snapshot> previous; + Snapshot* previous; /** * A pointer to the currently active layer. diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp index 5d36a03c43c5..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); } diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp index 17cb3a7fd6fd..0835c29aee97 100644 --- a/libs/hwui/TessellationCache.cpp +++ b/libs/hwui/TessellationCache.cpp @@ -217,7 +217,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 +225,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 +250,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); @@ -312,10 +312,10 @@ TessellationCache::TessellationCache() , 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); + INIT_LOGD(" Setting tessellation cache size to %sMB", property); setMaxSize(MB(atof(property))); } else { - INIT_LOGD(" Using default %s cache size of %.2fMB", name, DEFAULT_VERTEX_CACHE_SIZE); + INIT_LOGD(" Using default tessellation cache size of %.2fMB", DEFAULT_VERTEX_CACHE_SIZE); } mCache.setOnEntryRemovedListener(&mBufferRemovedListener); diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h index 3efeaf64d486..06e567e114e2 100644 --- a/libs/hwui/TessellationCache.h +++ b/libs/hwui/TessellationCache.h @@ -17,17 +17,22 @@ #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 "thread/TaskProcessor.h" #include "utils/Macros.h" #include "utils/Pair.h" +#include <SkPaint.h> + +#include <utils/LruCache.h> +#include <utils/Mutex.h> +#include <utils/StrongPointer.h> + class SkBitmap; class SkCanvas; -class SkPaint; class SkPath; struct SkRect; @@ -186,6 +191,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..62a20fcfa699 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,8 +30,7 @@ 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)); @@ -42,10 +39,10 @@ hash_t ShadowText::hash() const { hash = JenkinsHashMix(hash, android::hash_type(scaleX)); if (text) { hash = JenkinsHashMixShorts( - hash, reinterpret_cast<const uint16_t*>(text), charCount); + hash, reinterpret_cast<const uint16_t*>(text), 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; @@ -78,7 +75,7 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { if (!lhs.text) return -1; if (!rhs.text) return +1; - deltaInt = memcmp(lhs.text, rhs.text, lhs.len); + deltaInt = memcmp(lhs.text, rhs.text, 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; @@ -170,16 +167,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 char* 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,13 +187,10 @@ 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) { @@ -205,15 +199,9 @@ ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, } } - 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); diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h index caf089f6d2a5..c4f3c5d96786 100644 --- a/libs/hwui/TextDropShadowCache.h +++ b/libs/hwui/TextDropShadowCache.h @@ -34,14 +34,14 @@ class Caches; class FontRenderer; struct ShadowText { - ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(nullptr), + ShadowText(): glyphCount(0), radius(0.0f), textSize(0.0f), typeface(nullptr), flags(0), italicStyle(0.0f), scaleX(0), text(nullptr), positions(nullptr) { } // len is the number of bytes in text - ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText, + ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const char* srcText, const float* positions): - len(len), radius(radius), positions(positions) { + glyphCount(glyphCount), radius(radius), positions(positions) { // TODO: Propagate this through the API, we should not cast here text = (const char16_t*) srcText; @@ -73,17 +73,16 @@ struct ShadowText { } void copyTextLocally() { - uint32_t charCount = len / sizeof(char16_t); - str.setTo((const char16_t*) text, charCount); + str.setTo((const char16_t*) text, glyphCount); text = 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; @@ -136,7 +135,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 char* text, int numGlyphs, float radius, const float* positions); /** diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp index 593e91818093..92de89491127 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -14,16 +14,30 @@ * 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) { + case GL_ALPHA: + return 1; + case GL_RGB: + return 3; + case GL_RGBA: + default: + return 4; + } +} + void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force, GLenum renderTarget) { @@ -34,7 +48,7 @@ void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force mWrapT = wrapT; if (bindTexture) { - mCaches.textureState().bindTexture(renderTarget, id); + mCaches.textureState().bindTexture(renderTarget, mId); } glTexParameteri(renderTarget, GL_TEXTURE_WRAP_S, wrapS); @@ -52,7 +66,7 @@ void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool for 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 +76,191 @@ 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::upload(GLint internalformat, uint32_t width, uint32_t height, + GLenum format, GLenum type, const void* pixels) { + GL_CHECKPOINT(); + mCaches.textureState().activateTexture(0); + bool needsAlloc = updateSize(width, height, internalformat); + if (!mId) { + glGenTextures(1, &mId); + needsAlloc = true; + } + 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(); +} + +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(); + + if (!mId) { + glGenTextures(1, &mId); + needsAlloc = 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 (mFirstFilter) { + setFilter(GL_NEAREST); + } + + if (mFirstWrap) { + 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..4e8e6dcf1a77 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,6 +142,19 @@ public: void* isInUse = nullptr; private: + // 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); + + GLuint mId = 0; + uint32_t mWidth = 0; + uint32_t mHeight = 0; + GLint mFormat = 0; + /** * Last wrap modes set on this texture. */ @@ -120,16 +175,16 @@ private: 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..31bfa3a1ada4 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" @@ -144,7 +138,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 +166,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 +180,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 +206,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 +216,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 +249,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..463450c81714 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 { @@ -143,18 +145,6 @@ 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; @@ -165,7 +155,7 @@ private: 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..be25516c587a 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,6 +30,7 @@ class CanvasContext; } class DamageAccumulator; +class LayerUpdateQueue; class OpenGLRenderer; class RenderState; @@ -55,70 +56,51 @@ 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) + TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext) + : mode(mode) + , prepareTextures(mode == MODE_FULL) + , canvasContext(canvasContext) {} - 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) - {} - - 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; 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..3e20608dc4cc --- /dev/null +++ b/libs/hwui/VectorDrawable.cpp @@ -0,0 +1,493 @@ +/* + * 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 "SkImageInfo.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) { + float matrixScale = getMatrixScale(groupStackedMatrix); + if (matrixScale == 0) { + // When either x or y is scaled to 0, we don't need to draw anything. + return; + } + + const SkPath updatedPath = getUpdatedPath(); + 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(); + renderPath.addPath(updatedPath, pathMatrix); + + float minScale = fmin(scaleX, scaleY); + float strokeScale = minScale * matrixScale; + drawPath(outCanvas, renderPath, strokeScale); +} + +void Path::setPathData(const Data& data) { + if (mData == data) { + return; + } + // 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. + mData = data; + mSkPathDirty = true; +} + +void Path::dump() { + ALOGD("Path: %s has %zu points", mName.c_str(), mData.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; +} +Path::Path(const char* pathStr, size_t strLength) { + PathParser::ParseResult result; + PathParser::getPathDataFromString(&mData, &result, pathStr, strLength); + if (!result.failureOccurred) { + VectorDrawableUtils::verbsToPath(&mSkPath, mData); + } +} + +Path::Path(const Data& data) { + mData = data; + // Now we need to construct a path + VectorDrawableUtils::verbsToPath(&mSkPath, data); +} + +Path::Path(const Path& path) : Node(path) { + mData = path.mData; + VectorDrawableUtils::verbsToPath(&mSkPath, mData); +} + +bool Path::canMorph(const Data& morphTo) { + return VectorDrawableUtils::canMorph(mData, morphTo); +} + +bool Path::canMorph(const Path& path) { + return canMorph(path.mData); +} + +const SkPath& Path::getUpdatedPath() { + if (mSkPathDirty) { + mSkPath.reset(); + VectorDrawableUtils::verbsToPath(&mSkPath, mData); + mSkPathDirty = false; + } + return mSkPath; +} + +void Path::setPath(const char* pathStr, size_t strLength) { + PathParser::ParseResult result; + mSkPathDirty = true; + PathParser::getPathDataFromString(&mData, &result, pathStr, strLength); +} + +FullPath::FullPath(const FullPath& path) : Path(path) { + mStrokeWidth = path.mStrokeWidth; + mStrokeColor = path.mStrokeColor; + mStrokeAlpha = path.mStrokeAlpha; + mFillColor = path.mFillColor; + mFillAlpha = path.mFillAlpha; + mTrimPathStart = path.mTrimPathStart; + mTrimPathEnd = path.mTrimPathEnd; + mTrimPathOffset = path.mTrimPathOffset; + mStrokeMiterLimit = path.mStrokeMiterLimit; + mStrokeLineCap = path.mStrokeLineCap; + mStrokeLineJoin = path.mStrokeLineJoin; +} + +const SkPath& FullPath::getUpdatedPath() { + if (!mSkPathDirty && !mTrimDirty) { + return mTrimmedSkPath; + } + Path::getUpdatedPath(); + if (mTrimPathStart != 0.0f || mTrimPathEnd != 1.0f) { + applyTrim(); + return mTrimmedSkPath; + } else { + return mSkPath; + } +} + +void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha, + SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd, + float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin) { + mStrokeWidth = strokeWidth; + mStrokeColor = strokeColor; + mStrokeAlpha = strokeAlpha; + mFillColor = fillColor; + mFillAlpha = fillAlpha; + mStrokeMiterLimit = strokeMiterLimit; + mStrokeLineCap = SkPaint::Cap(strokeLineCap); + mStrokeLineJoin = SkPaint::Join(strokeLineJoin); + + // If any trim property changes, mark trim dirty and update the trim path + setTrimPathStart(trimPathStart); + setTrimPathEnd(trimPathEnd); + setTrimPathOffset(trimPathOffset); +} + +inline SkColor applyAlpha(SkColor color, float alpha) { + int alphaBytes = SkColorGetA(color); + return SkColorSetA(color, alphaBytes * alpha); +} + +void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float strokeScale){ + // Draw path's fill, if fill color isn't transparent. + if (mFillColor != SK_ColorTRANSPARENT) { + mPaint.setStyle(SkPaint::Style::kFill_Style); + mPaint.setAntiAlias(true); + mPaint.setColor(applyAlpha(mFillColor, mFillAlpha)); + outCanvas->drawPath(renderPath, mPaint); + } + // Draw path's stroke, if stroke color isn't transparent + if (mStrokeColor != SK_ColorTRANSPARENT) { + mPaint.setStyle(SkPaint::Style::kStroke_Style); + mPaint.setAntiAlias(true); + mPaint.setStrokeJoin(mStrokeLineJoin); + mPaint.setStrokeCap(mStrokeLineCap); + mPaint.setStrokeMiter(mStrokeMiterLimit); + mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha)); + mPaint.setStrokeWidth(mStrokeWidth * strokeScale); + outCanvas->drawPath(renderPath, mPaint); + } +} + +/** + * Applies trimming to the specified path. + */ +void FullPath::applyTrim() { + if (mTrimPathStart == 0.0f && mTrimPathEnd == 1.0f) { + // No trimming necessary. + return; + } + SkPathMeasure measure(mSkPath, false); + float len = SkScalarToFloat(measure.getLength()); + float start = len * fmod((mTrimPathStart + mTrimPathOffset), 1.0f); + float end = len * fmod((mTrimPathEnd + mTrimPathOffset), 1.0f); + + mTrimmedSkPath.reset(); + if (start > end) { + measure.getSegment(start, len, &mTrimmedSkPath, true); + measure.getSegment(0, end, &mTrimmedSkPath, true); + } else { + measure.getSegment(start, end, &mTrimmedSkPath, true); + } + mTrimDirty = false; +} + +inline int putData(int8_t* outBytes, int startIndex, float value) { + int size = sizeof(float); + memcpy(&outBytes[startIndex], &value, size); + return size; +} + +inline int putData(int8_t* outBytes, int startIndex, int value) { + int size = sizeof(int); + memcpy(&outBytes[startIndex], &value, size); + return size; +} + +struct FullPathProperties { + // TODO: Consider storing full path properties in this struct instead of the fields. + float strokeWidth; + SkColor strokeColor; + float strokeAlpha; + SkColor fillColor; + float fillAlpha; + float trimPathStart; + float trimPathEnd; + float trimPathOffset; + int32_t strokeLineCap; + int32_t strokeLineJoin; + float strokeMiterLimit; +}; + +REQUIRE_COMPATIBLE_LAYOUT(FullPathProperties); + +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::getProperties(int8_t* outProperties, int length) { + int propertyDataSize = sizeof(FullPathProperties); + if (length != propertyDataSize) { + LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided", + propertyDataSize, length); + return false; + } + // TODO: consider replacing the property fields with a FullPathProperties struct. + FullPathProperties properties; + properties.strokeWidth = mStrokeWidth; + properties.strokeColor = mStrokeColor; + properties.strokeAlpha = mStrokeAlpha; + properties.fillColor = mFillColor; + properties.fillAlpha = mFillAlpha; + properties.trimPathStart = mTrimPathStart; + properties.trimPathEnd = mTrimPathEnd; + properties.trimPathOffset = mTrimPathOffset; + properties.strokeLineCap = mStrokeLineCap; + properties.strokeLineJoin = mStrokeLineJoin; + properties.strokeMiterLimit = mStrokeMiterLimit; + + memcpy(outProperties, &properties, length); + return true; +} + +void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, + float strokeScale){ + outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op); +} + +Group::Group(const Group& group) : Node(group) { + mRotate = group.mRotate; + mPivotX = group.mPivotX; + mPivotY = group.mPivotY; + mScaleX = group.mScaleX; + mScaleY = group.mScaleY; + mTranslateX = group.mTranslateX; + mTranslateY = group.mTranslateY; +} + +void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX, + float scaleY) { + // 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; + getLocalMatrix(&stackedMatrix); + 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 (Node* child : mChildren) { + child->draw(outCanvas, stackedMatrix, scaleX, scaleY); + } + // Restore the previous clip information. + outCanvas->restore(); +} + +void Group::dump() { + ALOGD("Group %s has %zu children: ", mName.c_str(), mChildren.size()); + for (size_t i = 0; i < mChildren.size(); i++) { + mChildren[i]->dump(); + } +} + +void Group::updateLocalMatrix(float rotate, float pivotX, float pivotY, + float scaleX, float scaleY, float translateX, float translateY) { + setRotation(rotate); + setPivotX(pivotX); + setPivotY(pivotY); + setScaleX(scaleX); + setScaleY(scaleY); + setTranslateX(translateX); + setTranslateY(translateY); +} + +void Group::getLocalMatrix(SkMatrix* outMatrix) { + 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(-mPivotX, -mPivotY); + outMatrix->postScale(mScaleX, mScaleY); + outMatrix->postRotate(mRotate, 0, 0); + outMatrix->postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY); +} + +void Group::addChild(Node* child) { + mChildren.push_back(child); +} + +bool Group::getProperties(float* outProperties, int length) { + 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; + } + for (int i = 0; i < propertyCount; i++) { + Property currentProperty = static_cast<Property>(i); + switch (currentProperty) { + case Property::Rotate_Property: + outProperties[i] = mRotate; + break; + case Property::PivotX_Property: + outProperties[i] = mPivotX; + break; + case Property::PivotY_Property: + outProperties[i] = mPivotY; + break; + case Property::ScaleX_Property: + outProperties[i] = mScaleX; + break; + case Property::ScaleY_Property: + outProperties[i] = mScaleY; + break; + case Property::TranslateX_Property: + outProperties[i] = mTranslateX; + break; + case Property::TranslateY_Property: + outProperties[i] = mTranslateY; + break; + default: + LOG_ALWAYS_FATAL("Invalid input index: %d", i); + return false; + } + } + return true; +} + +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. + outCanvas->getMatrix(&mCanvasMatrix); + mBounds = bounds; + float canvasScaleX = 1.0f; + float canvasScaleY = 1.0f; + if (mCanvasMatrix.getSkewX() == 0 && mCanvasMatrix.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(mCanvasMatrix.getScaleX()); + canvasScaleY = fabs(mCanvasMatrix.getScaleY()); + } + int scaledWidth = (int) (mBounds.width() * canvasScaleX); + int scaledHeight = (int) (mBounds.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; + } + + int saveCount = outCanvas->save(SkCanvas::SaveFlags::kMatrixClip_SaveFlag); + outCanvas->translate(mBounds.fLeft, mBounds.fTop); + + // Handle RTL mirroring. + if (needsMirroring) { + outCanvas->translate(mBounds.width(), 0); + outCanvas->scale(-1.0f, 1.0f); + } + + // 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); + mBounds.offsetTo(0, 0); + + createCachedBitmapIfNeeded(scaledWidth, scaledHeight); + if (!mAllowCaching) { + updateCachedBitmap(scaledWidth, scaledHeight); + } else { + if (!canReuseCache || mCacheDirty) { + updateCachedBitmap(scaledWidth, scaledHeight); + } + } + drawCachedBitmapWithRootAlpha(outCanvas, colorFilter, mBounds); + + outCanvas->restoreToCount(saveCount); +} + +void Tree::drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter, + const SkRect& originalBounds) { + SkPaint* paint; + if (mRootAlpha == 1.0f && filter == NULL) { + paint = NULL; + } else { + mPaint.setFilterQuality(kLow_SkFilterQuality); + mPaint.setAlpha(mRootAlpha * 255); + mPaint.setColorFilter(filter); + paint = &mPaint; + } + outCanvas->drawBitmap(mCachedBitmap, 0, 0, mCachedBitmap.width(), mCachedBitmap.height(), + originalBounds.fLeft, originalBounds.fTop, originalBounds.fRight, + originalBounds.fBottom, paint); +} + +void Tree::updateCachedBitmap(int width, int height) { + mCachedBitmap.eraseColor(SK_ColorTRANSPARENT); + SkCanvas outCanvas(mCachedBitmap); + float scaleX = width / mViewportWidth; + float scaleY = height / mViewportHeight; + mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY); + mCacheDirty = false; +} + +void Tree::createCachedBitmapIfNeeded(int width, int height) { + if (!canReuseBitmap(width, height)) { + SkImageInfo info = SkImageInfo::Make(width, height, + kN32_SkColorType, kPremul_SkAlphaType); + mCachedBitmap.setInfo(info); + // TODO: Count the bitmap cache against app's java heap + mCachedBitmap.allocPixels(info); + mCacheDirty = true; + } +} + +bool Tree::canReuseBitmap(int width, int height) { + return width == mCachedBitmap.width() && height == mCachedBitmap.height(); +} + +}; // namespace VectorDrawable + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h new file mode 100644 index 000000000000..5ae5f6a3bdba --- /dev/null +++ b/libs/hwui/VectorDrawable.h @@ -0,0 +1,331 @@ +/* + * 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 "Canvas.h" +#include <SkBitmap.h> +#include <SkColor.h> +#include <SkCanvas.h> +#include <SkMatrix.h> +#include <SkPaint.h> +#include <SkPath.h> +#include <SkPathMeasure.h> +#include <SkRect.h> + +#include <cutils/compiler.h> +#include <stddef.h> +#include <vector> +#include <string> + +namespace android { +namespace uirenderer { + +namespace VectorDrawable { +#define VD_SET_PROP_WITH_FLAG(field, value, flag) (VD_SET_PROP(field, value) ? (flag = true, true): false); +#define VD_SET_PROP(field, value) (value != field ? (field = value, true) : false) + +/* 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 + * + */ +class ANDROID_API Node { +public: + Node(const Node& node) { + mName = node.mName; + } + Node() {} + virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, + float scaleX, float scaleY) = 0; + virtual void dump() = 0; + void setName(const char* name) { + mName = name; + } + virtual ~Node(){} +protected: + std::string mName; +}; + +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; + } + }; + Path(const Data& nodes); + Path(const Path& path); + Path(const char* path, size_t strLength); + Path() {} + void dump() override; + bool canMorph(const Data& path); + bool canMorph(const Path& path); + void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix, + float scaleX, float scaleY) override; + void setPath(const char* path, size_t strLength); + void setPathData(const Data& data); + static float getMatrixScale(const SkMatrix& groupStackedMatrix); + +protected: + virtual const SkPath& getUpdatedPath(); + virtual void drawPath(SkCanvas *outCanvas, const SkPath& renderPath, + float strokeScale) = 0; + Data mData; + SkPath mSkPath; + bool mSkPathDirty = true; +}; + +class ANDROID_API FullPath: public Path { +public: + FullPath(const FullPath& path); // for cloning + FullPath(const char* path, size_t strLength) : Path(path, strLength) {} + FullPath() : Path() {} + FullPath(const Data& nodes) : Path(nodes) {} + + void updateProperties(float strokeWidth, SkColor strokeColor, + float strokeAlpha, SkColor fillColor, float fillAlpha, + float trimPathStart, float trimPathEnd, float trimPathOffset, + float strokeMiterLimit, int strokeLineCap, int strokeLineJoin); + float getStrokeWidth() { + return mStrokeWidth; + } + void setStrokeWidth(float strokeWidth) { + mStrokeWidth = strokeWidth; + } + SkColor getStrokeColor() { + return mStrokeColor; + } + void setStrokeColor(SkColor strokeColor) { + mStrokeColor = strokeColor; + } + float getStrokeAlpha() { + return mStrokeAlpha; + } + void setStrokeAlpha(float strokeAlpha) { + mStrokeAlpha = strokeAlpha; + } + SkColor getFillColor() { + return mFillColor; + } + void setFillColor(SkColor fillColor) { + mFillColor = fillColor; + } + float getFillAlpha() { + return mFillAlpha; + } + void setFillAlpha(float fillAlpha) { + mFillAlpha = fillAlpha; + } + float getTrimPathStart() { + return mTrimPathStart; + } + void setTrimPathStart(float trimPathStart) { + VD_SET_PROP_WITH_FLAG(mTrimPathStart, trimPathStart, mTrimDirty); + } + float getTrimPathEnd() { + return mTrimPathEnd; + } + void setTrimPathEnd(float trimPathEnd) { + VD_SET_PROP_WITH_FLAG(mTrimPathEnd, trimPathEnd, mTrimDirty); + } + float getTrimPathOffset() { + return mTrimPathOffset; + } + void setTrimPathOffset(float trimPathOffset) { + VD_SET_PROP_WITH_FLAG(mTrimPathOffset, trimPathOffset, mTrimDirty); + } + bool getProperties(int8_t* outProperties, int length); + +protected: + const SkPath& getUpdatedPath() override; + void drawPath(SkCanvas* outCanvas, const SkPath& renderPath, + float strokeScale) override; + +private: + // Applies trimming to the specified path. + void applyTrim(); + float mStrokeWidth = 0; + SkColor mStrokeColor = SK_ColorTRANSPARENT; + float mStrokeAlpha = 1; + SkColor mFillColor = SK_ColorTRANSPARENT; + float mFillAlpha = 1; + float mTrimPathStart = 0; + float mTrimPathEnd = 1; + float mTrimPathOffset = 0; + bool mTrimDirty = true; + SkPaint::Cap mStrokeLineCap = SkPaint::Cap::kButt_Cap; + SkPaint::Join mStrokeLineJoin = SkPaint::Join::kMiter_Join; + float mStrokeMiterLimit = 4; + SkPath mTrimmedSkPath; + SkPaint mPaint; +}; + +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() {} + ClipPath(const Data& nodes) : Path(nodes) {} + +protected: + void drawPath(SkCanvas* outCanvas, const SkPath& renderPath, + float strokeScale) override; +}; + +class ANDROID_API Group: public Node { +public: + Group(const Group& group); + Group() {} + float getRotation() { + return mRotate; + } + void setRotation(float rotation) { + mRotate = rotation; + } + float getPivotX() { + return mPivotX; + } + void setPivotX(float pivotX) { + mPivotX = pivotX; + } + float getPivotY() { + return mPivotY; + } + void setPivotY(float pivotY) { + mPivotY = pivotY; + } + float getScaleX() { + return mScaleX; + } + void setScaleX(float scaleX) { + mScaleX = scaleX; + } + float getScaleY() { + return mScaleY; + } + void setScaleY(float scaleY) { + mScaleY = scaleY; + } + float getTranslateX() { + return mTranslateX; + } + void setTranslateX(float translateX) { + mTranslateX = translateX; + } + float getTranslateY() { + return mTranslateY; + } + void setTranslateY(float translateY) { + mTranslateY = translateY; + } + virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, + float scaleX, float scaleY) override; + void updateLocalMatrix(float rotate, float pivotX, float pivotY, + float scaleX, float scaleY, float translateX, float translateY); + void getLocalMatrix(SkMatrix* outMatrix); + void addChild(Node* child); + void dump() override; + bool getProperties(float* outProperties, int length); + +private: + enum class Property { + Rotate_Property = 0, + PivotX_Property, + PivotY_Property, + ScaleX_Property, + ScaleY_Property, + TranslateX_Property, + TranslateY_Property, + // Count of the properties, must be at the end. + Count, + }; + float mRotate = 0; + float mPivotX = 0; + float mPivotY = 0; + float mScaleX = 1; + float mScaleY = 1; + float mTranslateX = 0; + float mTranslateY = 0; + std::vector<Node*> mChildren; +}; + +class ANDROID_API Tree { +public: + Tree(Group* rootNode) : mRootNode(rootNode) {} + void draw(Canvas* outCanvas, SkColorFilter* colorFilter, + const SkRect& bounds, bool needsMirroring, bool canReuseCache); + void drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter, + const SkRect& originalBounds); + + void updateCachedBitmap(int width, int height); + void createCachedBitmapIfNeeded(int width, int height); + bool canReuseBitmap(int width, int height); + void setAllowCaching(bool allowCaching) { + mAllowCaching = allowCaching; + } + bool setRootAlpha(float rootAlpha) { + return VD_SET_PROP(mRootAlpha, rootAlpha); + } + + float getRootAlpha() { + return mRootAlpha; + } + void setViewportSize(float viewportWidth, float viewportHeight) { + mViewportWidth = viewportWidth; + mViewportHeight = viewportHeight; + } + +private: + // 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 mCacheDirty = true; + bool mAllowCaching = true; + float mViewportWidth = 0; + float mViewportHeight = 0; + float mRootAlpha = 1.0f; + + Group* mRootNode; + SkRect mBounds; + SkMatrix mCanvasMatrix; + SkPaint mPaint; + SkPathMeasure mPathMeasure; + SkBitmap mCachedBitmap; + +}; + +} // 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/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..5510666eef86 100644 --- a/libs/hwui/font/CacheTexture.h +++ b/libs/hwui/font/CacheTexture.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 762f2bba3340..dc82041e8f89 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> @@ -282,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); } @@ -294,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 char *text, int numGlyphs, int x, int y, const float* positions) { - render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, nullptr, + render(paint, text, 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 char *text, int numGlyphs, + const SkPath* path, float hOffset, float vOffset) { + if (numGlyphs == 0 || text == nullptr) { return; } - text += start; - int glyphsCount = 0; SkFixed prevRsbDelta = 0; @@ -320,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(text, numGlyphs * 2)); float pathOffset = pathLength; if (paint->getTextAlign() == SkPaint::kCenter_Align) { textWidth *= 0.5f; @@ -350,14 +345,14 @@ 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 char* text, 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, text, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions); } void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) { @@ -381,10 +376,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 char* text, 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 || text == nullptr) { return; } @@ -398,7 +393,6 @@ 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) { @@ -473,8 +467,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..59518a1fb8ee 100644 --- a/libs/hwui/font/Font.h +++ b/libs/hwui/font/Font.h @@ -82,10 +82,10 @@ public: ~Font(); - void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, + void render(const SkPaint* paint, const char* text, 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 char* text, int numGlyphs, const SkPath* path, float hOffset, float vOffset); const Font::FontDescription& getDescription() const { @@ -113,11 +113,11 @@ private: void precache(const SkPaint* paint, const char* text, int numGlyphs); - void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len, + void render(const SkPaint* paint, const char *text, 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 char* text, int numGlyphs, Rect *bounds, const float* positions); void invalidateTextureCache(CacheTexture* cacheTexture = nullptr); 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..03cb5ce8ce2e 100644 --- a/libs/hwui/renderstate/MeshState.cpp +++ b/libs/hwui/renderstate/MeshState.cpp @@ -100,6 +100,24 @@ bool MeshState::bindMeshBufferInternal(GLuint buffer) { return false; } +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); +} + +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; + } + glDeleteBuffers(1, &buffer); +} + /////////////////////////////////////////////////////////////////////////////// // Vertices /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h index e80f4d0d6c41..6c0fb78cae17 100644 --- a/libs/hwui/renderstate/MeshState.h +++ b/libs/hwui/renderstate/MeshState.h @@ -75,6 +75,9 @@ public: */ bool unbindMeshBuffer(); + void genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, GLenum usage); + void deleteMeshBuffer(GLuint); + /////////////////////////////////////////////////////////////////////////////// // Vertices /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp new file mode 100644 index 000000000000..98c94dfab1c5 --- /dev/null +++ b/libs/hwui/renderstate/OffscreenBufferPool.cpp @@ -0,0 +1,195 @@ +/* + * 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); + 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::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; + 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..94155efcbb0a --- /dev/null +++ b/libs/hwui/renderstate/OffscreenBufferPool.h @@ -0,0 +1,159 @@ +/* + * 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(); + + // 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 84b696596d40..75dcf16123b5 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::genFramebuffer() { + 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(); + // --------------------------------------------- // ---------- 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,6 +286,8 @@ void RenderState::render(const Glop& glop) { roundedOutRadius); } + GL_CHECKPOINT(); + // -------------------------------- // ---------- Mesh setup ---------- // -------------------------------- @@ -278,7 +311,7 @@ void RenderState::render(const Glop& glop) { texture.texture->setFilter(texture.filter, true, false, texture.target); } - mCaches->textureState().bindTexture(texture.target, texture.texture->id); + mCaches->textureState().bindTexture(texture.target, texture.texture->id()); meshState().enableTexCoordsVertexArray(); meshState().bindTexCoordsVertexPointer(force, vertices.texCoord, vertices.stride); @@ -306,6 +339,7 @@ void RenderState::render(const Glop& glop) { // Shader uniforms SkiaShader::apply(*mCaches, fill.skiaShaderData); + GL_CHECKPOINT(); Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) ? fill.skiaShaderData.bitmapData.bitmapTexture : nullptr; const AutoTexture autoCleanup(texture); @@ -315,6 +349,8 @@ void RenderState::render(const Glop& glop) { // ------------------------------------ blend().setFactors(glop.blend.src, glop.blend.dst); + GL_CHECKPOINT(); + // ------------------------------------ // ---------- Actual drawing ---------- // ------------------------------------ @@ -324,7 +360,7 @@ 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); + GLsizei drawCount = std::min(elementsCount, (GLsizei) kMaxNumberOfQuads * 6); // rebind pointers without forcing, since initial bind handled above meshState().bindPositionVertexPointer(false, vertexData, vertices.stride); @@ -343,6 +379,8 @@ void RenderState::render(const Glop& glop) { glDrawArrays(mesh.primitiveMode, 0, mesh.elementCount); } + GL_CHECKPOINT(); + // ----------------------------------- // ---------- Mesh teardown ---------- // ----------------------------------- @@ -352,6 +390,8 @@ void RenderState::render(const Glop& glop) { if (vertices.attribFlags & VertexAttribFlags::Color) { glDisableVertexAttribArray(colorLocation); } + + GL_CHECKPOINT(); } void RenderState::dump() { diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index 4fd792c1b503..e5d3e79ddfcb 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 genFramebuffer(); + 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 57e5832ff752..6f8d62757437 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -14,27 +14,52 @@ * 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 "renderstate/RenderState.h" #include "renderstate/Stencil.h" +#include "protos/hwui.pb.h" +#include "utils/GLUtils.h" +#include "utils/TimeUtils.h" + +#if HWUI_NEW_OPS +#include "FrameBuilder.h" +#endif -#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,9 +70,10 @@ 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); } @@ -87,19 +113,13 @@ void CanvasContext::setSurface(ANativeWindow* window) { const bool preserveBuffer = (mSwapBehavior != kSwap_discardBuffer); mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); mHaveNewSurface = true; + mSwapHistory.clear(); makeCurrent(); } 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!"); @@ -112,9 +132,11 @@ void CanvasContext::setSwapBehavior(SwapBehavior swapBehavior) { void CanvasContext::initialize(ANativeWindow* window) { setSurface(window); +#if !HWUI_NEW_OPS if (mCanvas) return; mCanvas = new OpenGLRenderer(mRenderThread.renderState()); mCanvas->initProperties(); +#endif } void CanvasContext::updateSurface(ANativeWindow* window) { @@ -128,13 +150,23 @@ bool CanvasContext::pauseSurface(ANativeWindow* window) { // 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 + mLightInfo.lightRadius = 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 + mLightCenter = lightCenter; +#else if (!mCanvas) return; mCanvas->setLightCenter(lightCenter); +#endif } void CanvasContext::setOpaque(bool opaque) { @@ -151,19 +183,12 @@ void CanvasContext::makeCurrent() { } } -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()); - } -} - 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 @@ -176,14 +201,26 @@ 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(); + } mAnimationContext->runRemainingAnimations(info); + GL_CHECKPOINT(); freePrefetechedLayers(); + GL_CHECKPOINT(); if (CC_UNLIKELY(!mNativeWindow.get())) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); @@ -191,13 +228,30 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy 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; + mNativeWindow->query(mNativeWindow.get(), + NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); + info.out.canDrawThisFrame = !runningBehind; + } + } else { + info.out.canDrawThisFrame = true; + } if (!info.out.canDrawThisFrame) { mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame); @@ -222,8 +276,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); @@ -236,56 +292,234 @@ 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 + FrameBuilder frameBuilder(mLayerUpdateQueue, dirty, frame.width(), frame.height(), + mRenderNodes, mLightCenter); + mLayerUpdateQueue.clear(); + BakedOpRenderer renderer(Caches::getInstance(), mRenderThread.renderState(), + mOpaque, mLightInfo); + // TODO: profiler().draw(mCanvas); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); + bool drew = renderer.didDraw(); + +#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(SkCanvas::kClip_SaveFlag); + 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(SkCanvas::kClip_SaveFlag); + 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(SkCanvas::kClip_SaveFlag); + 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(SkCanvas::kClip_SaveFlag); + 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(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + + // 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 + + GL_CHECKPOINT(); // 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 (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; } // 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); + + 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(); @@ -294,8 +528,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(); } @@ -338,9 +572,13 @@ void CanvasContext::buildLayer(RenderNode* node) { // 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; +#if HWUI_NEW_OPS + info.layerUpdateQueue = &mLayerUpdateQueue; +#else info.renderer = mCanvas; +#endif info.runAnimations = false; node->prepareTree(info); SkRect ignore; @@ -349,8 +587,12 @@ void CanvasContext::buildLayer(RenderNode* node) { // purposes when the frame is actually drawn node->setPropertyFieldsDirty(RenderNode::GENERIC); +#if HWUI_NEW_OPS + // TODO: support buildLayer +#else mCanvas->markLayersAsBuildLayers(); mCanvas->flushLayerUpdates(); +#endif node->incStrong(nullptr); mPrefetechedLayers.insert(node); @@ -365,12 +607,14 @@ void CanvasContext::destroyHardwareResources() { stopDrawing(); if (mEglManager.hasEglContext()) { freePrefetechedLayers(); - mRootRenderNode->destroyHardwareResources(); + for (const sp<RenderNode>& node : mRenderNodes) { + node->destroyHardwareResources(); + } 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); } } @@ -380,10 +624,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); } } @@ -429,6 +673,40 @@ 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 +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 1e6f830dbeb2..8e64cbbcc130 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -18,23 +18,29 @@ #define CANVASCONTEXT_H_ #include "DamageAccumulator.h" -#include "IContextFactory.h" #include "FrameInfo.h" #include "FrameInfoVisualizer.h" +#include "IContextFactory.h" +#include "LayerUpdateQueue.h" #include "RenderNode.h" #include "utils/RingBuffer.h" #include "renderthread/RenderTask.h" #include "renderthread/RenderThread.h" +#if HWUI_NEW_OPS +#include "BakedOpDispatcher.h" +#include "BakedOpRenderer.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 <set> #include <string> +#include <vector> namespace android { namespace uirenderer { @@ -77,13 +83,14 @@ public: 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); + void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, + int64_t syncQueued, RenderNode* target); void draw(); void destroy(); - // 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); bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); @@ -112,6 +119,26 @@ 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(); + } + private: friend class RegisterFrameCallbackTask; // TODO: Replace with something better for layer & other GL object @@ -119,25 +146,40 @@ private: friend class android::uirenderer::RenderState; void setSurface(ANativeWindow* window); - void swapBuffers(const SkRect& dirty, EGLint width, EGLint height); void requireSurface(); void freePrefetechedLayers(); + EGLint mLastFrameWidth = 0; + EGLint mLastFrameHeight = 0; + RenderThread& mRenderThread; EglManager& mEglManager; sp<ANativeWindow> mNativeWindow; EGLSurface mEglSurface = EGL_NO_SURFACE; bool mBufferPreserved = false; SwapBehavior mSwapBehavior = kSwap_default; + struct SwapHistory { + SkRect damage; + nsecs_t vsyncTime; + nsecs_t swapTime; + }; + + RingBuffer<SwapHistory, 3> mSwapHistory; bool mOpaque; OpenGLRenderer* mCanvas = nullptr; +#if HWUI_NEW_OPS + BakedOpRenderer::LightInfo mLightInfo; + Vector3 mLightCenter = { 0, 0, 0 }; +#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 @@ -147,6 +189,9 @@ private: FrameInfoVisualizer mProfiler; std::set<RenderNode*> mPrefetechedLayers; + + // Stores the bounds of the main content. + Rect mContentDrawBounds; }; } /* namespace renderthread */ diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index a4ac13bb95a1..e8b9725fe808 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> @@ -40,9 +38,11 @@ DrawFrameTask::DrawFrameTask() 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) { @@ -87,7 +87,7 @@ void DrawFrameTask::run() { bool canUnblockUiThread; bool canDrawThisFrame; { - TreeInfo info(TreeInfo::MODE_FULL, mRenderThread->renderState()); + TreeInfo info(TreeInfo::MODE_FULL, *mContext); canUnblockUiThread = syncFrameState(info); canDrawThisFrame = info.out.canDrawThisFrame; } @@ -117,10 +117,10 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) { 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. diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index ebefcba9f6a5..cae251a980df 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 { @@ -48,7 +48,7 @@ enum SyncResult { /* * 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,7 +57,7 @@ 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); @@ -78,6 +78,7 @@ private: RenderThread* mRenderThread; CanvasContext* mContext; + RenderNode* mTargetNode = nullptr; /********************************************* * Single frame data diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index eb332d59fee3..466fef9def09 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()); @@ -238,64 +277,68 @@ 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::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_BAD_SURFACE on %p, halting rendering...", + frame.mSurface); return false; } LOG_ALWAYS_FATAL("Encountered EGL error %d %s during rendering", @@ -312,18 +355,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..62b5b99a6e30 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,9 @@ 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); + 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 +86,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 +100,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 e65cb11ec12f..db2a2c8b0e55 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -74,7 +74,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 +91,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); @@ -272,16 +272,15 @@ 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; @@ -457,6 +456,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; } @@ -469,7 +473,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; @@ -498,6 +503,71 @@ 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); +} + void RenderProxy::post(RenderTask* task) { mRenderThread.queue(task); } @@ -516,12 +586,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 8a1f5bf4fa49..0f91b2afe170 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -27,7 +27,6 @@ #include <utils/Mutex.h> #include <utils/Timers.h> #include <utils/StrongPointer.h> -#include <utils/Vector.h> #include "../Caches.h" #include "../IContextFactory.h" @@ -39,7 +38,7 @@ namespace uirenderer { class DeferredLayerUpdater; class RenderNode; -class DisplayListData; +class DisplayList; class Layer; class Rect; @@ -106,6 +105,13 @@ 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); + 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..5ed7aa464026 --- /dev/null +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -0,0 +1,160 @@ +/* + * 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 "DeferredLayerUpdater.h" +#include "LayerRenderer.h" + +#include <unistd.h> +#include <signal.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, + std::function<void(Matrix4*)> transformSetupCallback) { + bool isOpaque = true; + bool forceFilter = true; + GLenum renderTarget = GL_TEXTURE_EXTERNAL_OES; + + Layer* layer = LayerRenderer::createTextureLayer(renderThread.renderState()); + LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, forceFilter, + renderTarget, Matrix4::identity().data); + transformSetupCallback(&(layer->getTransform())); + + sp<DeferredLayerUpdater> layerUpdater = new DeferredLayerUpdater(layer); + return layerUpdater; +} + +void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text, + const SkPaint& paint, float x, float y) { + // drawing text requires GlyphID TextEncoding (which JNI layer would have done) + LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding, + "must use glyph encoding"); + SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry); + SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I()); + + float totalAdvance = 0; + std::vector<glyph_t> glyphs; + std::vector<float> positions; + Rect bounds; + 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 + glyphs.push_back(glyph); + positions.push_back(totalAdvance); + positions.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; + } + + // apply alignment via x parameter (which JNI layer would have done) + if (paint.getTextAlign() == SkPaint::kCenter_Align) { + x -= totalAdvance / 2; + } else if (paint.getTextAlign() == SkPaint::kRight_Align) { + x -= totalAdvance; + } + + bounds.translate(x, y); + + // Force left alignment, since alignment offset is already baked in + SkPaint alignPaintCopy(paint); + alignPaintCopy.setTextAlign(SkPaint::kLeft_Align); + canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), alignPaintCopy, x, y, + bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance); +} + +void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text, + const SkPaint& paint, const SkPath& path) { + // drawing text requires GlyphID TextEncoding (which JNI layer would have done) + LOG_ALWAYS_FATAL_IF(paint.getTextEncoding() != SkPaint::kGlyphID_TextEncoding, + "must use glyph encoding"); + SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry); + SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I()); + + std::vector<glyph_t> glyphs; + while (*text != '\0') { + SkUnichar unichar = SkUTF8_NextUnichar(&text); + glyphs.push_back(autoCache.getCache()->unicharToGlyph(unichar)); + } + canvas->drawTextOnPath(glyphs.data(), glyphs.size(), path, 0, 0, paint); +} + +static void defaultCrashHandler() { + fprintf(stderr, "RenderThread crashed!"); +} + +static std::function<void()> gCrashHandler = defaultCrashHandler; +static sighandler_t gPreviousSignalHandler; + +static void signalHandler(int sig) { + gCrashHandler(); + if (gPreviousSignalHandler) { + gPreviousSignalHandler(sig); + } +} + +void TestUtils::setRenderThreadCrashHandler(std::function<void()> crashHandler) { + gCrashHandler = crashHandler; +} + +void TestUtils::TestTask::run() { + gPreviousSignalHandler = signal(SIGABRT, signalHandler); + + // RenderState only valid once RenderThread is running, so queried here + RenderState& renderState = renderthread::RenderThread::getInstance().renderState(); + + renderState.onGLContextCreated(); + rtCallback(renderthread::RenderThread::getInstance()); + renderState.onGLContextDestroyed(); + + // Restore the previous signal handler + signal(SIGABRT, gPreviousSignalHandler); +} + +} /* 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..ae0814241055 --- /dev/null +++ b/libs/hwui/tests/common/TestUtils.h @@ -0,0 +1,231 @@ +/* + * 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, + std::function<void(Matrix4*)> transformSetupCallback); + + 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()); + } + 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()); + } + + /** + * 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 std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) { + TestUtils::syncHierarchyPropertiesAndDisplayList(node); + std::vector<sp<RenderNode>> vec; + vec.emplace_back(node); + return vec; + } + + typedef std::function<void(renderthread::RenderThread& thread)> RtCallback; + + static void setRenderThreadCrashHandler(std::function<void()> crashHandler); + + 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 drawTextToCanvas(TestCanvas* canvas, const char* text, + const SkPaint& paint, float x, float y); + + static void drawTextToCanvas(TestCanvas* canvas, const char* text, + const SkPaint& paint, const SkPath& path); + +private: + static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { + node->syncProperties(); + node->syncDisplayList(); + 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/nullegl.cpp b/libs/hwui/tests/common/nullegl.cpp index b6cc2f247627..b6cc2f247627 100644 --- a/libs/hwui/tests/nullegl.cpp +++ b/libs/hwui/tests/common/nullegl.cpp diff --git a/libs/hwui/tests/nullgles.cpp b/libs/hwui/tests/common/nullgles.cpp index 8ca7598a91a0..f8e8c98c20ba 100644 --- a/libs/hwui/tests/nullgles.cpp +++ b/libs/hwui/tests/common/nullgles.cpp @@ -261,8 +261,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/tests/common/scenes/ClippingAnimation.cpp b/libs/hwui/tests/common/scenes/ClippingAnimation.cpp new file mode 100644 index 000000000000..db6402ce136f --- /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(SkCanvas::kMatrixClip_SaveFlag); + { + 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(SkCanvas::kMatrixClip_SaveFlag); + { + 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/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..43e247e68bc0 --- /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()); + } +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::drawTextToCanvas(&canvas, buf, textPaint, cardHeight, dp(25)); + textPaint.setTextSize(dp(15)); + TestUtils::drawTextToCanvas(&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..c89985009fbd --- /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, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawColor(Color::Green_700, SkXfermode::kSrcOver_Mode); + canvas.clipRect(50, 50, 350, 350, SkRegion::kIntersect_Op); + canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawColor(Color::Blue_500, SkXfermode::kSrcOver_Mode); + canvas.restore(); + canvas.restore(); + + // single unclipped saveLayer + canvas.save(SkCanvas::kMatrixClip_SaveFlag); + canvas.translate(0, 400); + canvas.saveLayerAlpha(100, 100, 300, 300, 128, SkCanvas::SaveFlags(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..0cba34479a3c --- /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(SkCanvas::kMatrixClip_SaveFlag); + 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(SkCanvas::kMatrixClip_SaveFlag); + for (auto op : ops) { + int innerCount = canvas.save(SkCanvas::kMatrixClip_SaveFlag); + 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..1823db2940aa --- /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::drawTextToCanvas(&canvas, "Test string", paint, 400, i * 100); + } + + SkPath path; + path.addOval(SkRect::MakeLTRB(100, 100, 300, 300)); + + paint.setColor(Color::Blue_500); + TestUtils::drawTextToCanvas(&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..a843e9265ef1 --- /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(); + } + + 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(); + } + proxy->fence(); + nsecs_t done = systemTime(CLOCK_MONOTONIC); + if (opts.reportFrametimeWeight) { + avgMs.add((done - vsync) / 1000000.0); + if (i % 10 == 9) { + printf("Average frametime %.3fms\n", avgMs.average()); + } + } + } + + proxy->dumpProfileInfo(STDOUT_FILENO, 0); +} 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..2e59eb450c9f --- /dev/null +++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp @@ -0,0 +1,190 @@ +/* + * 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" +#include "tests/microbench/MicroBench.h" + +using namespace android; +using namespace android::uirenderer; + +#if HWUI_NEW_OPS +typedef RecordingCanvas TestCanvas; +#else +typedef DisplayListCanvas TestCanvas; +#endif + +BENCHMARK_NO_ARG(BM_DisplayList_alloc); +void BM_DisplayList_alloc::Run(int iters) { + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + auto displayList = new DisplayList(); + MicroBench::DoNotOptimize(displayList); + delete displayList; + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_DisplayList_alloc_theoretical); +void BM_DisplayList_alloc_theoretical::Run(int iters) { + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + auto displayList = new char[sizeof(DisplayList)]; + MicroBench::DoNotOptimize(displayList); + delete[] displayList; + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_empty); +void BM_DisplayListCanvas_record_empty::Run(int iters) { + TestCanvas canvas(100, 100); + delete canvas.finishRecording(); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + canvas.reset(100, 100); + MicroBench::DoNotOptimize(&canvas); + delete canvas.finishRecording(); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_saverestore); +void BM_DisplayListCanvas_record_saverestore::Run(int iters) { + TestCanvas canvas(100, 100); + delete canvas.finishRecording(); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + canvas.reset(100, 100); + canvas.save(SkCanvas::kMatrixClip_SaveFlag); + canvas.save(SkCanvas::kMatrixClip_SaveFlag); + MicroBench::DoNotOptimize(&canvas); + canvas.restore(); + canvas.restore(); + delete canvas.finishRecording(); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_translate); +void BM_DisplayListCanvas_record_translate::Run(int iters) { + TestCanvas canvas(100, 100); + delete canvas.finishRecording(); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + canvas.reset(100, 100); + canvas.scale(10, 10); + MicroBench::DoNotOptimize(&canvas); + delete canvas.finishRecording(); + } + StopBenchmarkTiming(); +} + +/** + * 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. + */ +BENCHMARK_NO_ARG(BM_DisplayListCanvas_record_simpleBitmapView); +void BM_DisplayListCanvas_record_simpleBitmapView::Run(int iters) { + TestCanvas canvas(100, 100); + delete canvas.finishRecording(); + + SkPaint rectPaint; + SkBitmap iconBitmap = TestUtils::createSkBitmap(80, 80); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + canvas.reset(100, 100); + { + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.drawRect(0, 0, 100, 100, rectPaint); + canvas.restore(); + } + { + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.translate(10, 10); + canvas.drawBitmap(iconBitmap, 0, 0, nullptr); + canvas.restore(); + } + MicroBench::DoNotOptimize(&canvas); + delete canvas.finishRecording(); + } + StopBenchmarkTiming(); +} + +class NullClient: public CanvasStateClient { + void onViewportInitialized() override {} + void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} + GLuint getTargetFbo() const override { return 0; } +}; + +BENCHMARK_NO_ARG(BM_CanvasState_saverestore); +void BM_CanvasState_saverestore::Run(int iters) { + NullClient client; + CanvasState state(client); + state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + state.save(SkCanvas::kMatrixClip_SaveFlag); + state.save(SkCanvas::kMatrixClip_SaveFlag); + MicroBench::DoNotOptimize(&state); + state.restore(); + state.restore(); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_CanvasState_init); +void BM_CanvasState_init::Run(int iters) { + NullClient client; + CanvasState state(client); + state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); + MicroBench::DoNotOptimize(&state); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_CanvasState_translate); +void BM_CanvasState_translate::Run(int iters) { + NullClient client; + CanvasState state(client); + state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; ++i) { + state.translate(5, 5, 0); + MicroBench::DoNotOptimize(&state); + state.translate(-5, -5, 0); + } + StopBenchmarkTiming(); +} diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp new file mode 100644 index 000000000000..67c95e2e921f --- /dev/null +++ b/libs/hwui/tests/microbench/FrameBuilderBench.cpp @@ -0,0 +1,160 @@ +/* + * 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 "tests/microbench/MicroBench.h" + +#include <vector> + +using namespace android; +using namespace android::uirenderer; +using namespace android::uirenderer::renderthread; +using namespace android::uirenderer::test; + +const LayerUpdateQueue sEmptyLayerUpdateQueue; +const Vector3 sLightCenter = {100, 100, 100}; + +static std::vector<sp<RenderNode>> createTestNodeList() { + 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(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + 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); + std::vector<sp<RenderNode>> vec; + vec.emplace_back(node); + return vec; +} + +BENCHMARK_NO_ARG(BM_FrameBuilder_defer); +void BM_FrameBuilder_defer::Run(int iters) { + auto nodes = createTestNodeList(); + StartBenchmarkTiming(); + for (int i = 0; i < iters; i++) { + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + nodes, sLightCenter); + MicroBench::DoNotOptimize(&frameBuilder); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_FrameBuilder_deferAndRender); +void BM_FrameBuilder_deferAndRender::Run(int iters) { + TestUtils::runOnRenderThread([this, iters](RenderThread& thread) { + auto nodes = createTestNodeList(); + BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128 }; + + RenderState& renderState = thread.renderState(); + Caches& caches = Caches::getInstance(); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; i++) { + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + nodes, sLightCenter); + + BakedOpRenderer renderer(caches, renderState, true, lightInfo); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); + MicroBench::DoNotOptimize(&renderer); + } + StopBenchmarkTiming(); + }); +} + +static std::vector<sp<RenderNode>> getSyncedSceneNodes(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); + std::vector<sp<RenderNode>> nodes; + nodes.emplace_back(rootNode); + return nodes; +} + +static void benchDeferScene(testing::Benchmark& benchmark, int iters, const char* sceneName) { + auto nodes = getSyncedSceneNodes(sceneName); + benchmark.StartBenchmarkTiming(); + for (int i = 0; i < iters; i++) { + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, + SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h, + nodes, sLightCenter); + MicroBench::DoNotOptimize(&frameBuilder); + } + benchmark.StopBenchmarkTiming(); +} + +static void benchDeferAndRenderScene(testing::Benchmark& benchmark, + int iters, const char* sceneName) { + TestUtils::runOnRenderThread([&benchmark, iters, sceneName](RenderThread& thread) { + auto nodes = getSyncedSceneNodes(sceneName); + BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128 }; // TODO! + + RenderState& renderState = thread.renderState(); + Caches& caches = Caches::getInstance(); + + benchmark.StartBenchmarkTiming(); + for (int i = 0; i < iters; i++) { + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, + SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, gDisplay.h, + nodes, sLightCenter); + + BakedOpRenderer renderer(caches, renderState, true, lightInfo); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); + MicroBench::DoNotOptimize(&renderer); + } + benchmark.StopBenchmarkTiming(); + }); +} + +BENCHMARK_NO_ARG(BM_FrameBuilder_listview_defer); +void BM_FrameBuilder_listview_defer::Run(int iters) { + benchDeferScene(*this, iters, "listview"); +} + +BENCHMARK_NO_ARG(BM_FrameBuilder_listview_deferAndRender); +void BM_FrameBuilder_listview_deferAndRender::Run(int iters) { + benchDeferAndRenderScene(*this, iters, "listview"); +} + diff --git a/libs/hwui/tests/microbench/LinearAllocatorBench.cpp b/libs/hwui/tests/microbench/LinearAllocatorBench.cpp new file mode 100644 index 000000000000..28513e438fe1 --- /dev/null +++ b/libs/hwui/tests/microbench/LinearAllocatorBench.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 <benchmark/Benchmark.h> + +#include "tests/microbench/MicroBench.h" +#include "utils/LinearAllocator.h" + +#include <vector> + +using namespace android; +using namespace android::uirenderer; + +BENCHMARK_NO_ARG(BM_LinearStdAllocator_vectorBaseline); +void BM_LinearStdAllocator_vectorBaseline::Run(int iters) { + StartBenchmarkTiming(); + for (int i = 0; i < iters; i++) { + std::vector<char> v; + for (int j = 0; j < 200; j++) { + v.push_back(j); + } + MicroBench::DoNotOptimize(&v); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_LinearStdAllocator_vector); +void BM_LinearStdAllocator_vector::Run(int iters) { + StartBenchmarkTiming(); + for (int i = 0; i < iters; i++) { + LinearAllocator la; + LinearStdAllocator<void*> stdAllocator(la); + std::vector<char, LinearStdAllocator<char> > v(stdAllocator); + for (int j = 0; j < 200; j++) { + v.push_back(j); + } + MicroBench::DoNotOptimize(&v); + } + StopBenchmarkTiming(); +} diff --git a/libs/hwui/tests/microbench/MicroBench.h b/libs/hwui/tests/microbench/MicroBench.h new file mode 100644 index 000000000000..f05e92cd86d1 --- /dev/null +++ b/libs/hwui/tests/microbench/MicroBench.h @@ -0,0 +1,35 @@ +/* + * 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 MICROBENCH_MICROBENCH_H +#define MICROBENCH_MICROBENCH_H + +namespace android { +namespace uirenderer { + +#define NO_INLINE __attribute__ ((noinline)) + +class MicroBench { +public: + template <class Tp> + static inline void DoNotOptimize(Tp const& value) { + asm volatile("" : "+rm" (const_cast<Tp&>(value))); + } +}; + +} /* namespace uirenderer */ +} /* namespace android */ + +#endif /* MICROBENCH_MICROBENCH_H */ diff --git a/libs/hwui/tests/microbench/PathParserBench.cpp b/libs/hwui/tests/microbench/PathParserBench.cpp new file mode 100644 index 000000000000..bd742c6ededf --- /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"; + +BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForSkPath); +void BM_PathParser_parseStringPathForSkPath::Run(int iter) { + SkPath skPath; + size_t length = strlen(sPathString); + PathParser::ParseResult result; + StartBenchmarkTiming(); + for (int i = 0; i < iter; i++) { + PathParser::parseStringForSkPath(&skPath, &result, sPathString, length); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForPathData); +void BM_PathParser_parseStringPathForPathData::Run(int iter) { + size_t length = strlen(sPathString); + PathData outData; + PathParser::ParseResult result; + StartBenchmarkTiming(); + for (int i = 0; i < iter; i++) { + PathParser::getPathDataFromString(&outData, &result, sPathString, length); + } + StopBenchmarkTiming(); +} diff --git a/libs/hwui/tests/microbench/ShadowBench.cpp b/libs/hwui/tests/microbench/ShadowBench.cpp new file mode 100644 index 000000000000..98ec4d9b8f9d --- /dev/null +++ b/libs/hwui/tests/microbench/ShadowBench.cpp @@ -0,0 +1,116 @@ +/* + * 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 "tests/microbench/MicroBench.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); +} + +BENCHMARK_NO_ARG(BM_TessellateShadows_roundrect_opaque); +void BM_TessellateShadows_roundrect_opaque::Run(int iters) { + ShadowTestData shadowData; + createShadowTestData(&shadowData); + SkPath path; + path.addRoundRect(SkRect::MakeWH(100, 100), 5, 5); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; i++) { + VertexBuffer ambient; + VertexBuffer spot; + tessellateShadows(shadowData, true, path, &ambient, &spot); + MicroBench::DoNotOptimize(&ambient); + MicroBench::DoNotOptimize(&spot); + } + StopBenchmarkTiming(); +} + +BENCHMARK_NO_ARG(BM_TessellateShadows_roundrect_translucent); +void BM_TessellateShadows_roundrect_translucent::Run(int iters) { + ShadowTestData shadowData; + createShadowTestData(&shadowData); + SkPath path; + path.reset(); + path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5); + + StartBenchmarkTiming(); + for (int i = 0; i < iters; i++) { + std::unique_ptr<VertexBuffer> ambient(new VertexBuffer); + std::unique_ptr<VertexBuffer> spot(new VertexBuffer); + tessellateShadows(shadowData, false, path, ambient.get(), spot.get()); + MicroBench::DoNotOptimize(ambient.get()); + MicroBench::DoNotOptimize(spot.get()); + } + StopBenchmarkTiming(); +} 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/tests/scripts/prep_volantis.sh b/libs/hwui/tests/scripts/prep_volantis.sh new file mode 100755 index 000000000000..09d4869523ba --- /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/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..4df2687394fa --- /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 "utils/LinearAllocator.h" + +#include <gtest/gtest.h> +#include <SkPath.h> +#include <SkRegion.h> +#include <SkCanvas.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(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + { + // 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(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + { + // 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(SkCanvas::kClip_SaveFlag | SkCanvas::kMatrix_SaveFlag); + { + // 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(SkCanvas::kClip_SaveFlag); + { + 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(SkCanvas::kMatrix_SaveFlag); + { + 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(SkCanvas::kMatrix_SaveFlag); // 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(SkCanvas::kClip_SaveFlag); // 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..4cae737ab295 --- /dev/null +++ b/libs/hwui/tests/unit/ClipAreaTests.cpp @@ -0,0 +1,240 @@ +/* + * 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()); + 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); + auto clipRect = reinterpret_cast<const ClipRect*>(serializedClip); + ASSERT_EQ(Rect(200, 200), clipRect->rect); + EXPECT_EQ(serializedClip, area.serializeClip(allocator)) + << "Requery of clip on unmodified ClipArea must return same pointer."; + } + + // rect list + Matrix4 rotate; + rotate.loadRotate(2.0f); + area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op); + { + auto serializedClip = area.serializeClip(allocator); + ASSERT_NE(nullptr, serializedClip); + ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode); + auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip); + ASSERT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount()); + 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); + auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip); + ASSERT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds()) + << "Clip region should be 200x200"; + EXPECT_EQ(serializedClip, area.serializeClip(allocator)) + << "Requery of clip on unmodified ClipArea must return same pointer."; + } +} + +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), + reinterpret_cast<const ClipRect*>(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))); + + 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()); + } +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/tests/unit/CrashHandlerInjector.cpp b/libs/hwui/tests/unit/CrashHandlerInjector.cpp new file mode 100644 index 000000000000..b1c678d1c6fa --- /dev/null +++ b/libs/hwui/tests/unit/CrashHandlerInjector.cpp @@ -0,0 +1,41 @@ +/* + * 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 <cstdio> + +using namespace android::uirenderer; + +static void gunitCrashHandler() { + auto testinfo = ::testing::UnitTest::GetInstance()->current_test_info(); + printf("[ FAILED ] %s.%s\n", testinfo->test_case_name(), + testinfo->name()); + printf("[ FATAL! ] RenderThread crashed, aborting tests!\n"); + fflush(stdout); +} + +static void hookError() { + TestUtils::setRenderThreadCrashHandler(gunitCrashHandler); +} + +class HookErrorInit { +public: + HookErrorInit() { hookError(); } +}; + +static HookErrorInit sInit; 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/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp new file mode 100644 index 000000000000..b51bd2ff99cf --- /dev/null +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -0,0 +1,1388 @@ +/* + * 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 LayerUpdateQueue sEmptyLayerUpdateQueue; +const Vector3 sLightCenter = {100, 100, 100}; + +/** + * 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() << "Layer creation not expected in this test"; + return nullptr; + } + 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 {}; + +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(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + SimpleTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end +} + +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(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + SimpleStrokeTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + +TEST(FrameBuilder, simpleRejection) { + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); // intersection should be empty + canvas.drawRect(0, 0, 400, 400, SkPaint()); + canvas.restore(); + }); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + + FailRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +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(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + 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(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + SimpleBatchingTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2 * LOOPS, renderer.getIndex()) + << "Expect number of ops = 2 * loop count"; +} + +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(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + TestUtils::createSyncedNodeList(node), sLightCenter); + ClippedMergingTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + +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::drawTextToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped + TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped + }); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400, + TestUtils::createSyncedNodeList(node), sLightCenter); + TextMergingTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops"; +} + +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::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1)); + } + }); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000, + TestUtils::createSyncedNodeList(node), sLightCenter); + TextStrikethroughTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2 * LOOPS, renderer.getIndex()) + << "Expect number of ops = 2 * loop count"; +} + +RENDERTHREAD_TEST(FrameBuilder, textureLayer) { + class TextureLayerTestRenderer : 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, + [](Matrix4* transform) { + transform->loadTranslate(5, 5, 0); + }); + + auto node = TestUtils::createNode(0, 0, 200, 200, + [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { + canvas.save(SkCanvas::kMatrixClip_SaveFlag); + canvas.clipRect(50, 50, 150, 150, SkRegion::kIntersect_Op); + canvas.drawLayer(layerUpdater.get()); + canvas.restore(); + }); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + TextureLayerTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()); +} + +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(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.translate(40, 40); + canvas.drawRenderNode(child.get()); + canvas.restore(); + }); + + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(parent), sLightCenter); + RenderNodeTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +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); + }); + + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, + SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver + 200, 200, TestUtils::createSyncedNodeList(node), sLightCenter); + ClippedTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +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()); + } + }; + + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawRect(10, 10, 190, 190, SkPaint()); + canvas.restore(); + }); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + SaveLayerSimpleTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + +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(); } + } + }; + + auto node = TestUtils::createNode(0, 0, 800, 800, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 800, 800, 128, SkCanvas::kClipToLayer_SaveFlag); + { + canvas.drawRect(0, 0, 800, 800, SkPaint()); + canvas.saveLayerAlpha(0, 0, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag); + { + canvas.drawRect(0, 0, 400, 400, SkPaint()); + } + canvas.restore(); + } + canvas.restore(); + }); + + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800, + TestUtils::createSyncedNodeList(node), sLightCenter); + SaveLayerNestedTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(10, renderer.getIndex()); +} + +TEST(FrameBuilder, saveLayer_contentRejection) { + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.clipRect(200, 200, 400, 400, SkRegion::kIntersect_Op); + canvas.saveLayerAlpha(200, 200, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag); + + // 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(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + + FailRenderer renderer; + // should see no ops, even within the layer, since the layer should be rejected + frameBuilder.replayBakedOps<TestDispatcher>(renderer); +} + +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, (SkCanvas::SaveFlags)(0)); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.restore(); + }); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + SaveLayerUnclippedSimpleTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, renderer.getIndex()); +} + +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(SkCanvas::kMatrixClip_SaveFlag); + canvas.scale(2, 2); + canvas.saveLayerAlpha(0, 0, 5, 5, 128, SkCanvas::kMatrixClip_SaveFlag); + canvas.saveLayerAlpha(95, 0, 100, 5, 128, SkCanvas::kMatrixClip_SaveFlag); + canvas.saveLayerAlpha(0, 95, 5, 100, 128, SkCanvas::kMatrixClip_SaveFlag); + canvas.saveLayerAlpha(95, 95, 100, 100, 128, SkCanvas::kMatrixClip_SaveFlag); + canvas.drawRect(0, 0, 100, 100, SkPaint()); + canvas.restoreToCount(restoreTo); + }); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + SaveLayerUnclippedMergedClearsTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(10, renderer.getIndex()) + << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect."; +} + +/* saveLayerUnclipped { saveLayer { saveLayerUnclipped { rect } } } will play back as: + * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer + * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe + */ +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++); + } + void endFrame(const Rect& repaintRect) override { + EXPECT_EQ(11, mIndex++); + } + }; + + auto node = TestUtils::createNode(0, 0, 600, 600, // 500x500 triggers clipping + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SkCanvas::SaveFlags)0); // unclipped + canvas.saveLayerAlpha(100, 100, 400, 400, 128, SkCanvas::kClipToLayer_SaveFlag); // clipped + canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SkCanvas::SaveFlags)0); // unclipped + canvas.drawRect(200, 200, 300, 300, SkPaint()); + canvas.restore(); + canvas.restore(); + canvas.restore(); + }); + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600, + TestUtils::createSyncedNodeList(node), sLightCenter); + SaveLayerUnclippedComplexTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(12, 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 syncedNodeList = TestUtils::createSyncedNodeList(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(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + syncedNodeList, sLightCenter); + 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++); + } + }; + + 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, SkCanvas::kClipToLayer_SaveFlag); + canvas.drawRenderNode(childPtr); + canvas.restore(); + }); + OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200); + *(parent->getLayerHandle()) = &parentLayer; + + auto syncedList = TestUtils::createSyncedNodeList(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(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + syncedList, sLightCenter); + HwLayerComplexTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(13, renderer.getIndex()); + + // clean up layer pointers, so we can safely destruct RenderNodes + *(child->getLayerHandle()) = nullptr; + *(parent->getLayerHandle()) = 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 +} +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(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + TestUtils::createSyncedNodeList(parent), sLightCenter); + ZReorderTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(10, renderer.getIndex()); +}; + +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(); + break; + case 1: + EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds); + EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); + expectedMatrix.loadTranslate(50, 50, 0); // TODO: should scroll be respected here? + break; + case 2: + EXPECT_EQ(Rect(100, 50), op.unmappedBounds); + EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); + expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0); + break; + default: + ADD_FAILURE(); + } + EXPECT_MATRIX_APPROX_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) { + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally) + canvas.drawRenderNode(receiverBackground.get()); + canvas.drawRenderNode(child.get()); + canvas.restore(); + }); + + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100, + TestUtils::createSyncedNodeList(parent), sLightCenter); + ProjectionReorderTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(3, 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); + }); +} + +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.casterPath->isRect(nullptr)); + EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowMatrixXY); + + Matrix4 expectedZ; + expectedZ.loadTranslate(0, 0, 5); + EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowMatrixZ); + } + 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(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(parent), sLightCenter); + ShadowTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2, renderer.getIndex()); +} + +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.lightCenter.x); + EXPECT_FLOAT_EQ(40, op.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++); + } + }; + + 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, SkCanvas::kClipToLayer_SaveFlag); + canvas.insertReorderBarrier(true); + canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); + canvas.insertReorderBarrier(false); + canvas.restoreToCount(count); + }); + + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(parent), (Vector3) { 100, 100, 100 }); + ShadowSaveLayerTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(5, 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.lightCenter.x); + EXPECT_FLOAT_EQ(40, op.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++); + } + }; + + auto parent = TestUtils::createNode(50, 60, 150, 160, + [](RenderProperties& props, RecordingCanvas& canvas) { + props.mutateLayerProperties().setType(LayerType::RenderLayer); + canvas.insertReorderBarrier(true); + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + 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 syncedList = TestUtils::createSyncedNodeList(parent); + LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid + layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100)); + FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + syncedList, (Vector3) { 100, 100, 100 }); + ShadowHwLayerTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(5, renderer.getIndex()); + + // clean up layer pointer, so we can safely destruct RenderNode + *layerHandle = nullptr; +} + +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(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(parent), sLightCenter); + ShadowLayeringTestRenderer renderer; + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(4, 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(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + PropertyTestRenderer renderer(opValidateCallback); + frameBuilder.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op"; +} + +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"; + }); +} + +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"; + }); +} + +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); + }); +} + +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); + }); +} + +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++); + } + 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 nodes = TestUtils::createSyncedNodeList(node); // sync before querying height + + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter); + 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(4, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior."; +} + +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"; +} + +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); +} + +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); +} + +} // namespace uirenderer +} // namespace android 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/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..41e44fc88a6b --- /dev/null +++ b/libs/hwui/tests/unit/LeakCheckTests.cpp @@ -0,0 +1,47 @@ +/* + * 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 LayerUpdateQueue sEmptyLayerUpdateQueue; +const Vector3 sLightCenter = {100, 100, 100}; + +RENDERTHREAD_TEST(LeakCheck, saveLayerUnclipped_simple) { + auto node = TestUtils::createNode(0, 0, 200, 200, + [](RenderProperties& props, RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SkCanvas::SaveFlags)(0)); + canvas.drawRect(0, 0, 200, 200, SkPaint()); + canvas.restore(); + }); + BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128}; + RenderState& renderState = renderThread.renderState(); + Caches& caches = Caches::getInstance(); + + FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, + TestUtils::createSyncedNodeList(node), sLightCenter); + BakedOpRenderer renderer(caches, renderState, true, lightInfo); + frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); +} diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/tests/unit/LinearAllocatorTests.cpp index b3959d169e1d..5c442901045e 100644 --- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp +++ b/libs/hwui/tests/unit/LinearAllocatorTests.cpp @@ -17,6 +17,8 @@ #include <gtest/gtest.h> #include <utils/LinearAllocator.h> +#include <tests/common/TestUtils.h> + using namespace android; using namespace android::uirenderer; @@ -25,28 +27,7 @@ struct SimplePair { 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) { +TEST(LinearAllocator, create) { LinearAllocator la; EXPECT_EQ(0u, la.usedSize()); la.alloc(64); @@ -54,7 +35,7 @@ TEST(LinearAllocator, alloc) { // so the usedSize isn't strictly defined EXPECT_LE(64u, la.usedSize()); EXPECT_GT(80u, la.usedSize()); - auto pair = la.alloc<SimplePair>(); + auto pair = la.create<SimplePair>(); EXPECT_LE(64u + sizeof(SimplePair), la.usedSize()); EXPECT_GT(80u + sizeof(SimplePair), la.usedSize()); EXPECT_EQ(1, pair->one); @@ -62,31 +43,31 @@ TEST(LinearAllocator, alloc) { } TEST(LinearAllocator, dtor) { - bool destroyed[10]; + int destroyed[10] = { 0 }; { LinearAllocator la; for (int i = 0; i < 5; i++) { - la.alloc<SignalingDtor>()->setSignal(destroyed + i); - la.alloc<SimplePair>(); + la.create<TestUtils::SignalingDtor>()->setSignal(destroyed + i); + la.create<SimplePair>(); } la.alloc(100); for (int i = 0; i < 5; i++) { - auto sd = new (la) SignalingDtor(destroyed + 5 + i); + auto sd = new (la) TestUtils::SignalingDtor(destroyed + 5 + i); la.autoDestroy(sd); new (la) SimplePair(); } la.alloc(100); for (int i = 0; i < 10; i++) { - EXPECT_FALSE(destroyed[i]); + EXPECT_EQ(0, destroyed[i]); } } for (int i = 0; i < 10; i++) { - EXPECT_TRUE(destroyed[i]); + EXPECT_EQ(1, destroyed[i]); } } TEST(LinearAllocator, rewind) { - bool destroyed; + int destroyed = 0; { LinearAllocator la; auto addr = la.alloc(100); @@ -94,15 +75,42 @@ TEST(LinearAllocator, rewind) { la.rewindIfLastAlloc(addr, 100); EXPECT_GT(16u, la.usedSize()); size_t emptySize = la.usedSize(); - auto sigdtor = la.alloc<SignalingDtor>(); + auto sigdtor = la.create<TestUtils::SignalingDtor>(); sigdtor->setSignal(&destroyed); - EXPECT_FALSE(destroyed); + EXPECT_EQ(0, destroyed); EXPECT_LE(emptySize, la.usedSize()); la.rewindIfLastAlloc(sigdtor); - EXPECT_TRUE(destroyed); + EXPECT_EQ(1, destroyed); EXPECT_EQ(emptySize, la.usedSize()); - destroyed = false; } // Checking for a double-destroy case - EXPECT_EQ(destroyed, false); + 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]); + } diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp new file mode 100644 index 000000000000..2fd879562265 --- /dev/null +++ b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp @@ -0,0 +1,134 @@ +/* + * 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)); +} + +TEST(OffscreenBuffer, construct) { + TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) { + OffscreenBuffer layer(thread.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()); + }); +} + +TEST(OffscreenBuffer, getTextureCoordinates) { + TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) { + OffscreenBuffer layerAligned(thread.renderState(), Caches::getInstance(), 256u, 256u); + EXPECT_EQ(Rect(0, 1, 1, 0), + layerAligned.getTextureCoordinates()); + + OffscreenBuffer layerUnaligned(thread.renderState(), Caches::getInstance(), 200u, 225u); + EXPECT_EQ(Rect(0, 225.0f / 256.0f, 200.0f / 256.0f, 0), + layerUnaligned.getTextureCoordinates()); + }); +} + +TEST(OffscreenBufferPool, construct) { + TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) { + 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"; + }); +} + +TEST(OffscreenBufferPool, getPutClear) { + TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) { + OffscreenBufferPool pool; + + auto layer = pool.get(thread.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(thread.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()); + }); +} + +TEST(OffscreenBufferPool, resize) { + TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) { + OffscreenBufferPool pool; + + auto layer = pool.get(thread.renderState(), 64u, 64u); + + // resize in place + ASSERT_EQ(layer, pool.resize(layer, 60u, 55u)); + 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(thread.renderState(), 128u, 128u); + pool.putOrDelete(layer2); + ASSERT_EQ(1u, pool.getCount()); + ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u)); + 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); + }); +} + +TEST(OffscreenBufferPool, putAndDestroy) { + TestUtils::runOnRenderThread([] (renderthread::RenderThread& thread) { + 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(thread.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) + }); +} diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp new file mode 100644 index 000000000000..ff098c8bb3d5 --- /dev/null +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -0,0 +1,514 @@ +/* + * 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 <RecordedOp.h> +#include <RecordingCanvas.h> +#include <tests/common/TestUtils.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); + } + } +} + +TEST(RecordingCanvas, emptyPlayback) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.restore(); + }); + playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); +} + +TEST(RecordingCanvas, clipRect) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { + canvas.save(SkCanvas::kMatrixClip_SaveFlag); + 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, 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, drawText) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + TestUtils::drawTextToCanvas(&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, drawText_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::drawTextToCanvas(&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, drawText_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::drawTextToCanvas(&canvas, "test text", paint, 25, 25); + paint.setTextAlign(SkPaint::kCenter_Align); + TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); + paint.setTextAlign(SkPaint::kRight_Align); + TestUtils::drawTextToCanvas(&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, 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(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + { + // a background! + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.drawRect(0, 0, 100, 200, paint); + canvas.restore(); + } + { + // an image! + canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + 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); +} + +TEST(RecordingCanvas, saveLayer_simple) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kClipToLayer_SaveFlag); + 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, SkCanvas::kClipToLayer_SaveFlag); + 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, (SkCanvas::SaveFlags)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(SkCanvas::kMatrixClip_SaveFlag); + canvas.clipRect(10, 20, 190, 180, SkRegion::kIntersect_Op); + canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SkCanvas::SaveFlags)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, SkCanvas::kClipToLayer_SaveFlag); + 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(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + canvas.translate(100, 100); + canvas.rotate(45); + canvas.translate(-50, -50); + + canvas.saveLayerAlpha(0, 0, 100, 100, 128, SkCanvas::kClipToLayer_SaveFlag); + 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(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); + 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, SkCanvas::kClipToLayer_SaveFlag); + 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_RECT_APPROX_EQ(Rect(58.57864, 58.57864, 341.42136, 341.42136), + (reinterpret_cast<const ClipRect*>(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_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, 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, refPaint) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextAlign(SkPaint::kLeft_Align); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [&paint](RecordingCanvas& canvas) { + paint.setColor(SK_ColorBLUE); + // first three should use same paint + canvas.drawRect(0, 0, 200, 10, paint); + SkPaint paintCopy(paint); + canvas.drawRect(0, 10, 200, 20, paintCopy); + TestUtils::drawTextToCanvas(&canvas, "helloworld", paint, 50, 25); + + // 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(4u, ops.size()); + + // first three are the same + EXPECT_NE(nullptr, ops[0]->paint); + EXPECT_NE(&paint, ops[0]->paint); + EXPECT_EQ(ops[0]->paint, ops[1]->paint); + EXPECT_EQ(ops[0]->paint, ops[2]->paint); + + // last is different, but still copied / non-null + EXPECT_NE(nullptr, ops[3]->paint); + EXPECT_NE(ops[0]->paint, ops[3]->paint); + EXPECT_NE(&paint, ops[3]->paint); +} + +} // namespace uirenderer +} // namespace android 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/VectorDrawableTests.cpp b/libs/hwui/tests/unit/VectorDrawableTests.cpp new file mode 100644 index 000000000000..720854779c98 --- /dev/null +++ b/libs/hwui/tests/unit/VectorDrawableTests.cpp @@ -0,0 +1,393 @@ +/* + * 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}, + {"L.M.F.A.O", false}, + {"m 1 1", true}, + {"z", true}, + {"1-2e34567", false} +}; + + +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::getPathDataFromString(&pathData, &result, testData.pathString, length); + EXPECT_EQ(testData.pathData, pathData); + } + + for (StringPath stringPath : sStringPaths) { + PathParser::ParseResult result; + PathData pathData; + SkPath skPath; + PathParser::getPathDataFromString(&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, parseStringForSkPath) { + 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::parseStringForSkPath(&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::parseStringForSkPath(&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); + } +} +}; // 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/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp index e9dde294b2aa..a07845ecf659 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,15 +89,14 @@ 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); } @@ -111,14 +110,13 @@ bool TaskManager::WorkerThread::addTask(TaskWrapper task) { 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..d0eb3049ae37 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 { @@ -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/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.h b/libs/hwui/utils/GLUtils.h index 702046148ad8..85a10f94dc4c 100644 --- a/libs/hwui/utils/GLUtils.h +++ b/libs/hwui/utils/GLUtils.h @@ -16,9 +16,20 @@ #ifndef GLUTILS_H #define GLUTILS_H +#include "Debug.h" + +#include <cutils/log.h> + namespace android { namespace uirenderer { +#if DEBUG_OPENGL +#define GL_CHECKPOINT() LOG_ALWAYS_FATAL_IF(GLUtils::dumpGLErrors(),\ + "GL errors! %s:%d", __FILE__, __LINE__) +#else +#define GL_CHECKPOINT() +#endif + class GLUtils { public: /** diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp index 59b12cf66a89..e6a4c03156b4 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,18 +65,18 @@ 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)) @@ -114,7 +114,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 +134,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 +156,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; @@ -237,7 +238,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..dcbc0dda951a 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 { @@ -54,12 +56,12 @@ public: void* alloc(size_t 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; + template<class T, typename... Params> + T* create(Params... params) { + T* ret = new (*this) T(params...); autoDestroy(ret); return ret; } @@ -134,6 +136,54 @@ 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(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 diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h index 5ca9083aab87..ccf2287400dc 100644 --- a/libs/hwui/utils/Macros.h +++ b/libs/hwui/utils/Macros.h @@ -35,4 +35,7 @@ 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..db537130e12e 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: @@ -73,6 +81,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..05a3d5931e5d --- /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 > 1000 && 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..dcc49469b7f2 --- /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()); + mProxy->syncAndDrawFrame(); + // 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 + + SK_ALWAYSBREAK(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; + } + + SK_ALWAYSBREAK(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/unit_tests/main.cpp b/libs/hwui/utils/TimeUtils.h index c9b96360b36b..8d42d7e55521 100644 --- a/libs/hwui/unit_tests/main.cpp +++ b/libs/hwui/utils/TimeUtils.h @@ -13,10 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#ifndef UTILS_TIMEUTILS_H +#define UTILS_TIMEUTILS_H -#include <gtest/gtest.h> +#include <utils/Timers.h> -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); +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/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(); |