diff options
Diffstat (limited to 'libs')
299 files changed, 8491 insertions, 34552 deletions
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index 0c32fdf5baf5..98af3eb05391 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -59,8 +59,6 @@ cc_library { "ZipFileRO.cpp", "ZipUtils.cpp", ], - // Allow implicit fallthroughs in Locale.cpp and ResourceTypes.cpp until they are fixed. - cflags: ["-Wno-implicit-fallthrough"], export_include_dirs: ["include"], export_shared_lib_headers: ["libz"], target: { @@ -142,6 +140,7 @@ cc_test { "tests/Config_test.cpp", "tests/ConfigDescription_test.cpp", "tests/ConfigLocale_test.cpp", + "tests/DynamicRefTable_test.cpp", "tests/Idmap_test.cpp", "tests/LoadedArsc_test.cpp", "tests/Locale_test.cpp", diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index fc625bbaf72d..843c1461e21b 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -74,6 +74,7 @@ const char* AssetManager::RESOURCES_FILENAME = "resources.arsc"; const char* AssetManager::IDMAP_BIN = "/system/bin/idmap"; const char* AssetManager::OVERLAY_DIR = "/vendor/overlay"; const char* AssetManager::PRODUCT_OVERLAY_DIR = "/product/overlay"; +const char* AssetManager::PRODUCT_SERVICES_OVERLAY_DIR = "/product_services/overlay"; const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme"; const char* AssetManager::TARGET_PACKAGE_NAME = "android"; const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk"; diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 04cc5bb30ade..9e6948878b1d 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -21,6 +21,7 @@ #include <algorithm> #include <iterator> #include <set> +#include <map> #include "android-base/logging.h" #include "android-base/stringprintf.h" @@ -161,6 +162,13 @@ void AssetManager2::DumpToLog() const { LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list; + + for (size_t i = 0; i < 256; i++) { + if (package_group.dynamic_ref_table.mLookupTable[i] != 0) { + LOG(INFO) << base::StringPrintf(" e[0x%02x] -> 0x%02x", (uint8_t) i, + package_group.dynamic_ref_table.mLookupTable[i]); + } + } } } @@ -869,6 +877,17 @@ void AssetManager2::InvalidateCaches(uint32_t diff) { } } +uint8_t AssetManager2::GetAssignedPackageId(const LoadedPackage* package) { + for (auto& package_group : package_groups_) { + for (auto& package2 : package_group.packages_) { + if (package2.loaded_package_ == package) { + return package_group.dynamic_ref_table.mAssignedPackageId; + } + } + } + return 0; +} + std::unique_ptr<Theme> AssetManager2::NewTheme() { return std::unique_ptr<Theme>(new Theme(this)); } @@ -1054,44 +1073,231 @@ void Theme::Clear() { } } -bool Theme::SetTo(const Theme& o) { +void Theme::SetTo(const Theme& o) { if (this == &o) { - return true; + return; } type_spec_flags_ = o.type_spec_flags_; - const bool copy_only_system = asset_manager_ != o.asset_manager_; + if (asset_manager_ == o.asset_manager_) { + // The theme comes from the same asset manager so all theme data can be copied exactly + for (size_t p = 0; p < packages_.size(); p++) { + const Package *package = o.packages_[p].get(); + if (package == nullptr) { + // The other theme doesn't have this package, clear ours. + packages_[p].reset(); + continue; + } - for (size_t p = 0; p < packages_.size(); p++) { - const Package* package = o.packages_[p].get(); - if (package == nullptr || (copy_only_system && p != 0x01)) { - // The other theme doesn't have this package, clear ours. - packages_[p].reset(); - continue; + if (packages_[p] == nullptr) { + // The other theme has this package, but we don't. Make one. + packages_[p].reset(new Package()); + } + + for (size_t t = 0; t < package->types.size(); t++) { + const ThemeType *type = package->types[t].get(); + if (type == nullptr) { + // The other theme doesn't have this type, clear ours. + packages_[p]->types[t].reset(); + continue; + } + + // Create a new type and update it to theirs. + const size_t type_alloc_size = sizeof(ThemeType) + (type->entry_count * sizeof(ThemeEntry)); + void *copied_data = malloc(type_alloc_size); + memcpy(copied_data, type, type_alloc_size); + packages_[p]->types[t].reset(reinterpret_cast<ThemeType *>(copied_data)); + } } + } else { + std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies; + typedef std::map<int, int> SourceToDestinationRuntimePackageMap; + std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map; + + // Determine which ApkAssets are loaded in both theme AssetManagers + std::vector<const ApkAssets*> src_assets = o.asset_manager_->GetApkAssets(); + for (size_t i = 0; i < src_assets.size(); i++) { + const ApkAssets* src_asset = src_assets[i]; + + std::vector<const ApkAssets*> dest_assets = asset_manager_->GetApkAssets(); + for (size_t j = 0; j < dest_assets.size(); j++) { + const ApkAssets* dest_asset = dest_assets[j]; + + // Map the runtime package of the source apk asset to the destination apk asset + if (src_asset->GetPath() == dest_asset->GetPath()) { + const std::vector<std::unique_ptr<const LoadedPackage>>& src_packages = + src_asset->GetLoadedArsc()->GetPackages(); + const std::vector<std::unique_ptr<const LoadedPackage>>& dest_packages = + dest_asset->GetLoadedArsc()->GetPackages(); + + SourceToDestinationRuntimePackageMap package_map; + + // The source and destination package should have the same number of packages loaded in + // the same order. + const size_t N = src_packages.size(); + CHECK(N == dest_packages.size()) + << " LoadedArsc " << src_asset->GetPath() << " differs number of packages."; + for (size_t p = 0; p < N; p++) { + auto& src_package = src_packages[p]; + auto& dest_package = dest_packages[p]; + CHECK(src_package->GetPackageName() == dest_package->GetPackageName()) + << " Package " << src_package->GetPackageName() << " differs in load order."; + + int src_package_id = o.asset_manager_->GetAssignedPackageId(src_package.get()); + int dest_package_id = asset_manager_->GetAssignedPackageId(dest_package.get()); + package_map[src_package_id] = dest_package_id; + } - if (packages_[p] == nullptr) { - // The other theme has this package, but we don't. Make one. - packages_[p].reset(new Package()); + src_to_dest_asset_cookies.insert(std::pair<ApkAssetsCookie, ApkAssetsCookie>(i, j)); + src_asset_cookie_id_map.insert( + std::pair<ApkAssetsCookie, SourceToDestinationRuntimePackageMap>(i, package_map)); + break; + } + } + } + + // Reset the data in the destination theme + for (size_t p = 0; p < packages_.size(); p++) { + if (packages_[p] != nullptr) { + packages_[p].reset(); + } } - for (size_t t = 0; t < package->types.size(); t++) { - const ThemeType* type = package->types[t].get(); + for (size_t p = 0; p < packages_.size(); p++) { + const Package *package = o.packages_[p].get(); + if (package == nullptr) { + continue; + } + + for (size_t t = 0; t < package->types.size(); t++) { + const ThemeType *type = package->types[t].get(); + if (type == nullptr) { + continue; + } + + for (size_t e = 0; e < type->entry_count; e++) { + const ThemeEntry &entry = type->entries[e]; + if (entry.value.dataType == Res_value::TYPE_NULL && + entry.value.data != Res_value::DATA_NULL_EMPTY) { + continue; + } + + // The package id of the attribute needs to be rewritten to the package id of the value in + // the destination + int attribute_dest_package_id = p; + if (attribute_dest_package_id != 0x01) { + // Find the cookie of the attribute resource id + FindEntryResult attribute_entry_result; + ApkAssetsCookie attribute_cookie = + o.asset_manager_->FindEntry(make_resid(p, t, e), 0 /* density_override */ , false, + &attribute_entry_result); + + // Determine the package id of the attribute in the destination AssetManager + auto attribute_package_map = src_asset_cookie_id_map.find(attribute_cookie); + if (attribute_package_map == src_asset_cookie_id_map.end()) { + continue; + } + auto attribute_dest_package = attribute_package_map->second.find( + attribute_dest_package_id); + if (attribute_dest_package == attribute_package_map->second.end()) { + continue; + } + attribute_dest_package_id = attribute_dest_package->second; + } + + // If the attribute value represents an attribute or reference, the package id of the + // value needs to be rewritten to the package id of the value in the destination + uint32_t attribue_data = entry.value.data; + if (entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE + || entry.value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE + || entry.value.dataType == Res_value::TYPE_ATTRIBUTE + || entry.value.dataType == Res_value::TYPE_REFERENCE) { + + // Determine the package id of the reference in the destination AssetManager + auto value_package_map = src_asset_cookie_id_map.find(entry.cookie); + if (value_package_map == src_asset_cookie_id_map.end()) { + continue; + } + + auto value_dest_package = value_package_map->second.find( + get_package_id(entry.value.data)); + if (value_dest_package == value_package_map->second.end()) { + continue; + } + + attribue_data = fix_package_id(entry.value.data, value_dest_package->second); + } + + // Lazily instantiate the destination package + std::unique_ptr<Package>& dest_package = packages_[attribute_dest_package_id]; + if (dest_package == nullptr) { + dest_package.reset(new Package()); + } + + // Lazily instantiate and resize the destination type + util::unique_cptr<ThemeType>& dest_type = dest_package->types[t]; + if (dest_type == nullptr || dest_type->entry_count < type->entry_count) { + const size_t type_alloc_size = sizeof(ThemeType) + + (type->entry_count * sizeof(ThemeEntry)); + void* dest_data = malloc(type_alloc_size); + memset(dest_data, 0, type->entry_count * sizeof(ThemeEntry)); + + // Copy the existing destination type values if the type is resized + if (dest_type != nullptr) { + memcpy(dest_data, type, sizeof(ThemeType) + + (dest_type->entry_count * sizeof(ThemeEntry))); + } + + dest_type.reset(reinterpret_cast<ThemeType *>(dest_data)); + dest_type->entry_count = type->entry_count; + } + + // Find the cookie of the value in the destination + auto value_dest_cookie = src_to_dest_asset_cookies.find(entry.cookie); + if (value_dest_cookie == src_to_dest_asset_cookies.end()) { + continue; + } + + dest_type->entries[e].cookie = value_dest_cookie->second; + dest_type->entries[e].value.dataType = entry.value.dataType; + dest_type->entries[e].value.data = attribue_data; + dest_type->entries[e].type_spec_flags = entry.type_spec_flags; + } + } + } + } +} + +void Theme::Dump() const { + base::ScopedLogSeverity _log(base::INFO); + LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_); + + for (int p = 0; p < packages_.size(); p++) { + auto& package = packages_[p]; + if (package == nullptr) { + continue; + } + + for (int t = 0; t < package->types.size(); t++) { + auto& type = package->types[t]; if (type == nullptr) { - // The other theme doesn't have this type, clear ours. - packages_[p]->types[t].reset(); continue; } - // Create a new type and update it to theirs. - const size_t type_alloc_size = sizeof(ThemeType) + (type->entry_count * sizeof(ThemeEntry)); - void* copied_data = malloc(type_alloc_size); - memcpy(copied_data, type, type_alloc_size); - packages_[p]->types[t].reset(reinterpret_cast<ThemeType*>(copied_data)); + for (int e = 0; e < type->entry_count; e++) { + auto& entry = type->entries[e]; + if (entry.value.dataType == Res_value::TYPE_NULL && + entry.value.data != Res_value::DATA_NULL_EMPTY) { + continue; + } + + LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)", + make_resid(p, t, e), entry.value.data, + entry.value.dataType, entry.cookie); + } } } - return true; } } // namespace android diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp index 2870066ccbba..3eedda88fdce 100644 --- a/libs/androidfw/Locale.cpp +++ b/libs/androidfw/Locale.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "android-base/macros.h" #include "androidfw/Locale.h" #include "androidfw/Util.h" @@ -162,6 +163,7 @@ bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char s set_script(subtags[1].c_str()); break; } + FALLTHROUGH_INTENDED; case 5: case 6: case 7: diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 861dc0f3879c..76db18de6122 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -29,6 +29,7 @@ #include <memory> #include <type_traits> +#include <android-base/macros.h> #include <androidfw/ByteBucketArray.h> #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> @@ -3073,6 +3074,7 @@ struct LocaleParserState { } break; } + FALLTHROUGH_INTENDED; case 5: case 6: case 7: @@ -3205,20 +3207,6 @@ String8 ResTable_config::toString() const { break; } } - if ((colorMode&MASK_HDR) != 0) { - if (res.size() > 0) res.append("-"); - switch (colorMode&MASK_HDR) { - case ResTable_config::HDR_NO: - res.append("lowdr"); - break; - case ResTable_config::HDR_YES: - res.append("highdr"); - break; - default: - res.appendFormat("hdr=%d", dtohs(colorMode&MASK_HDR)); - break; - } - } if ((colorMode&MASK_WIDE_COLOR_GAMUT) != 0) { if (res.size() > 0) res.append("-"); switch (colorMode&MASK_WIDE_COLOR_GAMUT) { @@ -3233,6 +3221,20 @@ String8 ResTable_config::toString() const { break; } } + if ((colorMode&MASK_HDR) != 0) { + if (res.size() > 0) res.append("-"); + switch (colorMode&MASK_HDR) { + case ResTable_config::HDR_NO: + res.append("lowdr"); + break; + case ResTable_config::HDR_YES: + res.append("highdr"); + break; + default: + res.appendFormat("hdr=%d", dtohs(colorMode&MASK_HDR)); + break; + } + } if (orientation != ORIENTATION_ANY) { if (res.size() > 0) res.append("-"); switch (orientation) { @@ -6960,6 +6962,11 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { uint32_t res = *resId; size_t packageId = Res_GETPACKAGE(res) + 1; + if (!Res_VALIDID(res)) { + // Cannot look up a null or invalid id, so no lookup needs to be done. + return NO_ERROR; + } + if (packageId == APP_PACKAGE_ID && !mAppAsLib) { // No lookup needs to be done, app package IDs are absolute. return NO_ERROR; @@ -6995,24 +7002,26 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { status_t DynamicRefTable::lookupResourceValue(Res_value* value) const { uint8_t resolvedType = Res_value::TYPE_REFERENCE; switch (value->dataType) { - case Res_value::TYPE_ATTRIBUTE: - resolvedType = Res_value::TYPE_ATTRIBUTE; - // fallthrough - case Res_value::TYPE_REFERENCE: - if (!mAppAsLib) { - return NO_ERROR; - } + case Res_value::TYPE_ATTRIBUTE: + resolvedType = Res_value::TYPE_ATTRIBUTE; + FALLTHROUGH_INTENDED; + case Res_value::TYPE_REFERENCE: + // Only resolve non-dynamic references and attributes if the package is loaded as a + // library or if a shared library is attempting to retrieve its own resource + if (!(mAppAsLib || (Res_GETPACKAGE(value->data) + 1) == 0)) { + return NO_ERROR; + } // If the package is loaded as shared library, the resource reference // also need to be fixed. break; - case Res_value::TYPE_DYNAMIC_ATTRIBUTE: - resolvedType = Res_value::TYPE_ATTRIBUTE; - // fallthrough - case Res_value::TYPE_DYNAMIC_REFERENCE: - break; - default: - return NO_ERROR; + case Res_value::TYPE_DYNAMIC_ATTRIBUTE: + resolvedType = Res_value::TYPE_ATTRIBUTE; + FALLTHROUGH_INTENDED; + case Res_value::TYPE_DYNAMIC_REFERENCE: + break; + default: + return NO_ERROR; } status_t err = lookupResourceId(&value->data); diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index 08da7319de85..cdb87bcb8e11 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -61,6 +61,7 @@ public: static const char* IDMAP_BIN; static const char* OVERLAY_DIR; static const char* PRODUCT_OVERLAY_DIR; + static const char* PRODUCT_SERVICES_OVERLAY_DIR; /* * If OVERLAY_THEME_DIR_PROPERTY is set, search for runtime resource overlay * APKs in OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index 2f0ee01639fe..5312b062473a 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -74,6 +74,8 @@ struct FindEntryResult; // AssetManager2 is the main entry point for accessing assets and resources. // AssetManager2 provides caching of resources retrieved via the underlying ApkAssets. class AssetManager2 { + friend Theme; + public: struct ResourceName { const char* package = nullptr; @@ -285,6 +287,9 @@ class AssetManager2 { // been seen while traversing bag parents. const ResolvedBag* GetBag(uint32_t resid, std::vector<uint32_t>& child_resids); + // Retrieve the assigned package id of the package if loaded into this AssetManager + uint8_t GetAssignedPackageId(const LoadedPackage* package); + // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must // have a longer lifetime. std::vector<const ApkAssets*> apk_assets_; @@ -355,11 +360,14 @@ class Theme { bool ApplyStyle(uint32_t resid, bool force = false); // Sets this Theme to be a copy of `o` if `o` has the same AssetManager as this Theme. - // Returns false if the AssetManagers of the Themes were not compatible. - bool SetTo(const Theme& o); + // If `o` does not have the same AssetManager as this theme, only attributes from ApkAssets loaded + // into both AssetManagers will be copied to this theme. + void SetTo(const Theme& o); void Clear(); + void Dump() const; + inline const AssetManager2* GetAssetManager() const { return asset_manager_; } diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index a02851502c9b..59abad45edbb 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1709,13 +1709,13 @@ public: struct resource_name { - const char16_t* package; + const char16_t* package = NULL; size_t packageLen; - const char16_t* type; - const char* type8; + const char16_t* type = NULL; + const char* type8 = NULL; size_t typeLen; - const char16_t* name; - const char* name8; + const char16_t* name = NULL; + const char* name8 = NULL; size_t nameLen; }; diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h index 99b424568a1f..a33865f4d34f 100644 --- a/libs/androidfw/include/androidfw/StringPiece.h +++ b/libs/androidfw/include/androidfw/StringPiece.h @@ -32,6 +32,14 @@ namespace android { // WARNING: When creating from std::basic_string<>, moving the original // std::basic_string<> will invalidate the data held in a BasicStringPiece<>. // BasicStringPiece<> should only be used transitively. +// +// NOTE: When creating an std::pair<StringPiece, T> using std::make_pair(), +// passing an std::string will first copy the string, then create a StringPiece +// on the copy, which is then immediately destroyed. +// Instead, create a StringPiece explicitly: +// +// std::string my_string = "foo"; +// std::make_pair<StringPiece, T>(StringPiece(my_string), ...); template <typename TChar> class BasicStringPiece { public: diff --git a/libs/androidfw/tests/DynamicRefTable_test.cpp b/libs/androidfw/tests/DynamicRefTable_test.cpp new file mode 100644 index 000000000000..5acc46a3c0d9 --- /dev/null +++ b/libs/androidfw/tests/DynamicRefTable_test.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018 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 "utils/String8.h" + +#include "gtest/gtest.h" +namespace android { + +TEST(DynamicRefTableTest, LookupSharedLibSelfReferences) { + // Shared library + DynamicRefTable shared_table(0x02, /* appAsLib */ false); + shared_table.addMapping(0x00, 0x02); + Res_value value; + value.dataType = Res_value::TYPE_REFERENCE; + value.data = 0x00010000; + ASSERT_EQ(shared_table.lookupResourceValue(&value), NO_ERROR); + EXPECT_EQ(value.data, 0x02010000); + + // App loaded as a shared library + DynamicRefTable shared_app_table(0x02, /* appAsLib */ true); + shared_app_table.addMapping(0x7f, 0x02); + Res_value value2; + value2.dataType = Res_value::TYPE_REFERENCE; + value2.data = 0x7f010000; + ASSERT_EQ(shared_app_table.lookupResourceValue(&value2), NO_ERROR); + EXPECT_EQ(value2.data, 0x02010000); +}; + +TEST(DynamicRefTableTest, LookupSharedLibSelfAttributes) { + // Shared library + DynamicRefTable shared_table(0x03, /* appAsLib */ false); + shared_table.addMapping(0x00, 0x03); + Res_value value; + value.dataType = Res_value::TYPE_ATTRIBUTE; + value.data = 0x00010000; + ASSERT_EQ(shared_table.lookupResourceValue(&value), NO_ERROR); + EXPECT_EQ(value.data, 0x03010000); + + // App loaded as a shared library + DynamicRefTable shared_app_table(0x04, /* appAsLib */ true); + shared_app_table.addMapping(0x7f, 0x04); + Res_value value2; + value2.dataType = Res_value::TYPE_ATTRIBUTE; + value2.data = 0x7f010000; + ASSERT_EQ(shared_app_table.lookupResourceValue(&value2), NO_ERROR); + EXPECT_EQ(value2.data, 0x04010000); +}; + +TEST(DynamicRefTableTest, LookupDynamicReferences) { + // Shared library + DynamicRefTable shared_table(0x2, /* appAsLib */ false); + shared_table.addMapping(0x00, 0x02); + shared_table.addMapping(0x03, 0x05); + Res_value value; + value.dataType = Res_value::TYPE_DYNAMIC_REFERENCE; + value.data = 0x03010000; + ASSERT_EQ(shared_table.lookupResourceValue(&value), NO_ERROR); + EXPECT_EQ(value.data, 0x05010000); + + // Regular application + DynamicRefTable app_table(0x7f, /* appAsLib */ false); + app_table.addMapping(0x03, 0x05); + Res_value value3; + value3.dataType = Res_value::TYPE_DYNAMIC_REFERENCE; + value3.data = 0x03010000; + ASSERT_EQ(app_table.lookupResourceValue(&value3), NO_ERROR); + EXPECT_EQ(value3.data, 0x05010000); +}; + +TEST(DynamicRefTableTest, LookupDynamicAttributes) { +// App loaded as a shared library + DynamicRefTable shared_app_table(0x2, /* appAsLib */ true); + shared_app_table.addMapping(0x03, 0x05); + shared_app_table.addMapping(0x7f, 0x2); + Res_value value2; + value2.dataType = Res_value::TYPE_DYNAMIC_ATTRIBUTE; + value2.data = 0x03010000; + ASSERT_EQ(shared_app_table.lookupResourceValue(&value2), NO_ERROR); + EXPECT_EQ(value2.data, 0x05010000); +} + +TEST(DynamicRefTableTest, DoNotLookupNonDynamicReferences) { + // Regular application + DynamicRefTable app_table(0x7f, /* appAsLib */ false); + Res_value value; + value.dataType = Res_value::TYPE_REFERENCE; + value.data = 0x03010000; + ASSERT_EQ(app_table.lookupResourceValue(&value), NO_ERROR); + EXPECT_EQ(value.data, 0x03010000); +}; + +TEST(DynamicRefTableTest, DoNotLookupNonDynamicAttributes) { + // App with custom package id + DynamicRefTable custom_app_table(0x8f, /* appAsLib */ false); + Res_value value2; + value2.dataType = Res_value::TYPE_ATTRIBUTE; + value2.data = 0x03010000; + ASSERT_EQ(custom_app_table.lookupResourceValue(&value2), NO_ERROR); + EXPECT_EQ(value2.data, 0x03010000); +}; + +} // namespace android
\ No newline at end of file diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index 9eb4a13f34d1..26d28965d459 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -94,15 +94,15 @@ TEST_F(IdmapTest, OverlaidResourceHasSameName) { target_table_.add(overlay_data_.data(), overlay_data_.size(), data_, data_size_)); ResTable::resource_name res_name; - ASSERT_TRUE(target_table_.getResourceName(R::array::integerArray1, false, &res_name)); + ASSERT_TRUE(target_table_.getResourceName(R::array::integerArray1, true, &res_name)); ASSERT_TRUE(res_name.package != NULL); ASSERT_TRUE(res_name.type != NULL); - ASSERT_TRUE(res_name.name != NULL); + ASSERT_TRUE(res_name.name8 != NULL); EXPECT_EQ(String16("com.android.basic"), String16(res_name.package, res_name.packageLen)); EXPECT_EQ(String16("array"), String16(res_name.type, res_name.typeLen)); - EXPECT_EQ(String16("integerArray1"), String16(res_name.name, res_name.nameLen)); + EXPECT_EQ(String8("integerArray1"), String8(res_name.name8, res_name.nameLen)); } constexpr const uint32_t kNonOverlaidResourceId = 0x7fff0000u; diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index 55d53edf6a2b..2c39ceead123 100644 --- a/libs/androidfw/tests/Theme_test.cpp +++ b/libs/androidfw/tests/Theme_test.cpp @@ -21,12 +21,14 @@ #include "TestHelpers.h" #include "androidfw/ResourceUtils.h" #include "data/lib_one/R.h" +#include "data/lib_two/R.h" #include "data/libclient/R.h" #include "data/styles/R.h" #include "data/system/R.h" namespace app = com::android::app; namespace lib_one = com::android::lib_one; +namespace lib_two = com::android::lib_two; namespace libclient = com::android::libclient; namespace android { @@ -263,7 +265,7 @@ TEST_F(ThemeTest, CopyThemeSameAssetManager) { ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleThree)); // Copy the theme to theme_one. - ASSERT_TRUE(theme_one->SetTo(*theme_two)); + theme_one->SetTo(*theme_two); // Clear theme_two to make sure we test that there WAS a copy. theme_two->Clear(); @@ -279,12 +281,14 @@ TEST_F(ThemeTest, CopyThemeSameAssetManager) { EXPECT_EQ(static_cast<uint32_t>(ResTable_typeSpec::SPEC_PUBLIC), flags); } -TEST_F(ThemeTest, OnlyCopySystemThemeWhenAssetManagersDiffer) { +TEST_F(ThemeTest, OnlyCopySameAssetsThemeWhenAssetManagersDiffer) { AssetManager2 assetmanager_one; - assetmanager_one.SetApkAssets({system_assets_.get(), style_assets_.get()}); + assetmanager_one.SetApkAssets({system_assets_.get(), lib_one_assets_.get(), style_assets_.get(), + libclient_assets_.get()}); AssetManager2 assetmanager_two; - assetmanager_two.SetApkAssets({system_assets_.get(), style_assets_.get()}); + assetmanager_two.SetApkAssets({system_assets_.get(), lib_two_assets_.get(), lib_one_assets_.get(), + style_assets_.get()}); auto theme_one = assetmanager_one.NewTheme(); ASSERT_TRUE(theme_one->ApplyStyle(app::R::style::StyleOne)); @@ -292,17 +296,34 @@ TEST_F(ThemeTest, OnlyCopySystemThemeWhenAssetManagersDiffer) { auto theme_two = assetmanager_two.NewTheme(); ASSERT_TRUE(theme_two->ApplyStyle(R::style::Theme_One)); ASSERT_TRUE(theme_two->ApplyStyle(app::R::style::StyleTwo)); + ASSERT_TRUE(theme_two->ApplyStyle(fix_package_id(lib_one::R::style::Theme, 0x03), + false /*force*/)); + ASSERT_TRUE(theme_two->ApplyStyle(fix_package_id(lib_two::R::style::Theme, 0x02), + false /*force*/)); - EXPECT_TRUE(theme_one->SetTo(*theme_two)); + theme_one->SetTo(*theme_two); Res_value value; uint32_t flags; - // No app resources. - EXPECT_EQ(kInvalidCookie, theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags)); + // System resources (present in destination asset manager) + EXPECT_EQ(0, theme_one->GetAttribute(R::attr::foreground, &value, &flags)); + + // The cookie of the style asset is 3 in the source and 2 in the destination. + // Check that the cookie has been rewritten to the destination values + EXPECT_EQ(2, theme_one->GetAttribute(app::R::attr::attr_one, &value, &flags)); + + // The cookie of the lib_one asset is 2 in the source and 1 in the destination. + // The package id of the lib_one package is 0x03 in the source and 0x02 in the destination + // Check that the cookie and packages have been rewritten to the destination values + EXPECT_EQ(1, theme_one->GetAttribute(fix_package_id(lib_one::R::attr::attr1, 0x02), &value, + &flags)); + EXPECT_EQ(1, theme_one->GetAttribute(fix_package_id(lib_one::R::attr::attr2, 0x02), &value, + &flags)); - // Only system. - EXPECT_NE(kInvalidCookie, theme_one->GetAttribute(R::attr::foreground, &value, &flags)); + // attr2 references an attribute in lib_one. Check that the resolution of the attribute value is + // correct after the value of attr2 had its package id rewritten to the destination package id + EXPECT_EQ(700, value.data); } } // namespace android diff --git a/libs/androidfw/tests/data/lib_two/R.h b/libs/androidfw/tests/data/lib_two/R.h index c04a9d3b4de0..92b9cc10e7a8 100644 --- a/libs/androidfw/tests/data/lib_two/R.h +++ b/libs/androidfw/tests/data/lib_two/R.h @@ -24,12 +24,24 @@ namespace android { namespace lib_two { struct R { + struct attr { + enum : uint32_t { + attr3 = 0x02010000, // default + }; + }; + struct string { enum : uint32_t { LibraryString = 0x02020000, // default foo = 0x02020001, // default }; }; + + struct style { + enum : uint32_t { + Theme = 0x02030000, // default + }; + }; }; } // namespace lib_two diff --git a/libs/androidfw/tests/data/lib_two/lib_two.apk b/libs/androidfw/tests/data/lib_two/lib_two.apk Binary files differindex ad44f9c21e31..486c23000276 100644 --- a/libs/androidfw/tests/data/lib_two/lib_two.apk +++ b/libs/androidfw/tests/data/lib_two/lib_two.apk diff --git a/libs/androidfw/tests/data/lib_two/res/values/values.xml b/libs/androidfw/tests/data/lib_two/res/values/values.xml index f4eea2610cab..340d14c34c5d 100644 --- a/libs/androidfw/tests/data/lib_two/res/values/values.xml +++ b/libs/androidfw/tests/data/lib_two/res/values/values.xml @@ -15,9 +15,17 @@ --> <resources> + <public type="attr" name="attr3" id="0x00010000" /> + <attr name="attr3" format="integer" /> + <public type="string" name="LibraryString" id="0x00020000" /> <string name="LibraryString">Hi from library two</string> <public type="string" name="foo" id="0x00020001" /> <string name="foo">Foo from lib_two</string> + + <public type="style" name="Theme" id="0x02030000" /> + <style name="Theme"> + <item name="com.android.lib_two:attr3">800</item> + </style> </resources> diff --git a/libs/hwui/AmbientShadow.cpp b/libs/hwui/AmbientShadow.cpp deleted file mode 100644 index aa96698c1e53..000000000000 --- a/libs/hwui/AmbientShadow.cpp +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Extra vertices for the corner for smoother corner. - * Only for outer vertices. - * Note that we use such extra memory to avoid an extra loop. - */ -// For half circle, we could add EXTRA_VERTEX_PER_PI vertices. -// Set to 1 if we don't want to have any. -#define EXTRA_CORNER_VERTEX_PER_PI 12 - -// For the whole polygon, the sum of all the deltas b/t normals is 2 * M_PI, -// therefore, the maximum number of extra vertices will be twice bigger. -#define MAX_EXTRA_CORNER_VERTEX_NUMBER (2 * EXTRA_CORNER_VERTEX_PER_PI) - -// For each RADIANS_DIVISOR, we would allocate one more vertex b/t the normals. -#define CORNER_RADIANS_DIVISOR (M_PI / EXTRA_CORNER_VERTEX_PER_PI) - -/** - * Extra vertices for the Edge for interpolation artifacts. - * Same value for both inner and outer vertices. - */ -#define EXTRA_EDGE_VERTEX_PER_PI 50 - -#define MAX_EXTRA_EDGE_VERTEX_NUMBER (2 * EXTRA_EDGE_VERTEX_PER_PI) - -#define EDGE_RADIANS_DIVISOR (M_PI / EXTRA_EDGE_VERTEX_PER_PI) - -/** - * Other constants: - */ -#define OUTER_ALPHA (0.0f) - -// Once the alpha difference is greater than this threshold, we will allocate extra -// edge vertices. -// If this is set to negative value, then all the edge will be tessellated. -#define ALPHA_THRESHOLD (0.1f / 255.0f) - -#include "AmbientShadow.h" - -#include "ShadowTessellator.h" -#include "Vertex.h" -#include "VertexBuffer.h" - -#include <utils/Log.h> -#include <algorithm> - -namespace android { -namespace uirenderer { - -/** - * Local utility functions. - */ -inline Vector2 getNormalFromVertices(const Vector3* vertices, int current, int next) { - // Convert from Vector3 to Vector2 first. - Vector2 currentVertex = {vertices[current].x, vertices[current].y}; - Vector2 nextVertex = {vertices[next].x, vertices[next].y}; - - return ShadowTessellator::calculateNormal(currentVertex, nextVertex); -} - -// 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 + std::max(factoredZ, 0.0f)); -} - -inline int getEdgeExtraAndUpdateSpike(Vector2* currentSpike, const Vector3& secondVertex, - const Vector3& centroid) { - Vector2 secondSpike = {secondVertex.x - centroid.x, secondVertex.y - centroid.y}; - secondSpike.normalize(); - - int result = ShadowTessellator::getExtraVertexNumber(secondSpike, *currentSpike, - EDGE_RADIANS_DIVISOR); - *currentSpike = secondSpike; - return result; -} - -// Given the caster's vertex count, compute all the buffers size depending on -// whether or not the caster is opaque. -inline void computeBufferSize(int* totalVertexCount, int* totalIndexCount, int* totalUmbraCount, - int casterVertexCount, bool isCasterOpaque) { - // Compute the size of the vertex buffer. - int outerVertexCount = - casterVertexCount * 2 + MAX_EXTRA_CORNER_VERTEX_NUMBER + MAX_EXTRA_EDGE_VERTEX_NUMBER; - int innerVertexCount = casterVertexCount + MAX_EXTRA_EDGE_VERTEX_NUMBER; - *totalVertexCount = outerVertexCount + innerVertexCount; - - // Compute the size of the index buffer. - *totalIndexCount = 2 * outerVertexCount + 2; - - // Compute the size of the umber buffer. - // For translucent object, keep track of the umbra(inner) vertex in order to draw - // inside. We only need to store the index information. - *totalUmbraCount = 0; - if (!isCasterOpaque) { - // Add the centroid if occluder is translucent. - (*totalVertexCount)++; - *totalIndexCount += 2 * innerVertexCount + 1; - *totalUmbraCount = innerVertexCount; - } -} - -inline bool needsExtraForEdge(float firstAlpha, float secondAlpha) { - return fabsf(firstAlpha - secondAlpha) > ALPHA_THRESHOLD; -} - -/** - * Calculate the shadows as a triangle strips while alpha value as the - * shadow values. - * - * @param isCasterOpaque Whether the caster is opaque. - * @param vertices The shadow caster's polygon, which is represented in a Vector3 - * array. - * @param vertexCount The length of caster's polygon in terms of number of - * vertices. - * @param centroid3d The centroid of the shadow caster. - * @param heightFactor The factor showing the higher the object, the lighter the - * shadow. - * @param geomFactor The factor scaling the geometry expansion along the normal. - * - * @param shadowVertexBuffer Return an floating point array of (x, y, a) - * triangle strips mode. - * - * An simple illustration: - * For now let's mark the outer vertex as Pi, the inner as Vi, the centroid as C. - * - * First project the occluder to the Z=0 surface. - * Then we got all the inner vertices. And we compute the normal for each edge. - * According to the normal, we generate outer vertices. E.g: We generate P1 / P4 - * as extra corner vertices to make the corner looks round and smoother. - * - * Due to the fact that the alpha is not linear interpolated along the inner - * edge, when the alpha is different, we may add extra vertices such as P2.1, P2.2, - * V0.1, V0.2 to avoid the visual artifacts. - * - * (P3) - * (P2) (P2.1) (P2.2) | ' (P4) - * (P1)' | | | | ' - * ' | | | | ' - * (P0) ------------------------------------------------(P5) - * | (V0) (V0.1) (V0.2) |(V1) - * | | - * | | - * | (C) | - * | | - * | | - * | | - * | | - * (V3)-----------------------------------(V2) - */ -void AmbientShadow::createAmbientShadow(bool isCasterOpaque, const Vector3* casterVertices, - int casterVertexCount, const Vector3& centroid3d, - float heightFactor, float geomFactor, - VertexBuffer& shadowVertexBuffer) { - shadowVertexBuffer.setMeshFeatureFlags(VertexBuffer::kAlpha | VertexBuffer::kIndices); - - // In order to computer the outer vertices in one loop, we need pre-compute - // the normal by the vertex (n - 1) to vertex 0, and the spike and alpha value - // for vertex 0. - Vector2 previousNormal = getNormalFromVertices(casterVertices, casterVertexCount - 1, 0); - Vector2 currentSpike = {casterVertices[0].x - centroid3d.x, casterVertices[0].y - centroid3d.y}; - currentSpike.normalize(); - float currentAlpha = getAlphaFromFactoredZ(casterVertices[0].z * heightFactor); - - // Preparing all the output data. - int totalVertexCount, totalIndexCount, totalUmbraCount; - computeBufferSize(&totalVertexCount, &totalIndexCount, &totalUmbraCount, casterVertexCount, - isCasterOpaque); - AlphaVertex* shadowVertices = shadowVertexBuffer.alloc<AlphaVertex>(totalVertexCount); - int vertexBufferIndex = 0; - uint16_t* indexBuffer = shadowVertexBuffer.allocIndices<uint16_t>(totalIndexCount); - int indexBufferIndex = 0; - uint16_t umbraVertices[totalUmbraCount]; - int umbraIndex = 0; - - for (int i = 0; i < casterVertexCount; i++) { - // Corner: first figure out the extra vertices we need for the corner. - const Vector3& innerVertex = casterVertices[i]; - Vector2 currentNormal = - getNormalFromVertices(casterVertices, i, (i + 1) % casterVertexCount); - - int extraVerticesNumber = ShadowTessellator::getExtraVertexNumber( - currentNormal, previousNormal, CORNER_RADIANS_DIVISOR); - - float expansionDist = innerVertex.z * heightFactor * geomFactor; - const int cornerSlicesNumber = extraVerticesNumber + 1; // Minimal as 1. -#if DEBUG_SHADOW - ALOGD("cornerSlicesNumber is %d", cornerSlicesNumber); -#endif - - // Corner: fill the corner Vertex Buffer(VB) and Index Buffer(IB). - // We fill the inner vertex first, such that we can fill the index buffer - // inside the loop. - int currentInnerVertexIndex = vertexBufferIndex; - if (!isCasterOpaque) { - umbraVertices[umbraIndex++] = vertexBufferIndex; - } - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], casterVertices[i].x, - casterVertices[i].y, currentAlpha); - - const Vector3& innerStart = casterVertices[i]; - - // outerStart is the first outer vertex for this inner vertex. - // outerLast is the last outer vertex for this inner vertex. - Vector2 outerStart = {0, 0}; - Vector2 outerLast = {0, 0}; - // This will create vertices from [0, cornerSlicesNumber] inclusively, - // which means minimally 2 vertices even without the extra ones. - for (int j = 0; j <= cornerSlicesNumber; j++) { - Vector2 averageNormal = previousNormal * (cornerSlicesNumber - j) + currentNormal * j; - averageNormal /= cornerSlicesNumber; - averageNormal.normalize(); - Vector2 outerVertex; - outerVertex.x = innerVertex.x + averageNormal.x * expansionDist; - outerVertex.y = innerVertex.y + averageNormal.y * expansionDist; - - indexBuffer[indexBufferIndex++] = vertexBufferIndex; - indexBuffer[indexBufferIndex++] = currentInnerVertexIndex; - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], outerVertex.x, outerVertex.y, - OUTER_ALPHA); - - if (j == 0) { - outerStart = outerVertex; - } else if (j == cornerSlicesNumber) { - outerLast = outerVertex; - } - } - previousNormal = currentNormal; - - // Edge: first figure out the extra vertices needed for the edge. - const Vector3& innerNext = casterVertices[(i + 1) % casterVertexCount]; - float nextAlpha = getAlphaFromFactoredZ(innerNext.z * heightFactor); - if (needsExtraForEdge(currentAlpha, nextAlpha)) { - // TODO: See if we can / should cache this outer vertex across the loop. - Vector2 outerNext; - float expansionDist = innerNext.z * heightFactor * geomFactor; - outerNext.x = innerNext.x + currentNormal.x * expansionDist; - outerNext.y = innerNext.y + currentNormal.y * expansionDist; - - // Compute the angle and see how many extra points we need. - int extraVerticesNumber = - getEdgeExtraAndUpdateSpike(¤tSpike, innerNext, centroid3d); -#if DEBUG_SHADOW - ALOGD("extraVerticesNumber %d for edge %d", extraVerticesNumber, i); -#endif - // Edge: fill the edge's VB and IB. - // This will create vertices pair from [1, extraVerticesNumber - 1]. - // If there is no extra vertices created here, the edge will be drawn - // as just 2 triangles. - for (int k = 1; k < extraVerticesNumber; k++) { - int startWeight = extraVerticesNumber - k; - Vector2 currentOuter = - (outerLast * startWeight + outerNext * k) / extraVerticesNumber; - indexBuffer[indexBufferIndex++] = vertexBufferIndex; - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentOuter.x, - currentOuter.y, OUTER_ALPHA); - - if (!isCasterOpaque) { - umbraVertices[umbraIndex++] = vertexBufferIndex; - } - Vector3 currentInner = - (innerStart * startWeight + innerNext * k) / extraVerticesNumber; - indexBuffer[indexBufferIndex++] = vertexBufferIndex; - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], currentInner.x, - currentInner.y, - getAlphaFromFactoredZ(currentInner.z * heightFactor)); - } - } - currentAlpha = nextAlpha; - } - - indexBuffer[indexBufferIndex++] = 1; - indexBuffer[indexBufferIndex++] = 0; - - if (!isCasterOpaque) { - // Add the centroid as the last one in the vertex buffer. - float centroidOpacity = getAlphaFromFactoredZ(centroid3d.z * heightFactor); - int centroidIndex = vertexBufferIndex; - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid3d.x, centroid3d.y, - centroidOpacity); - - for (int i = 0; i < umbraIndex; i++) { - // Note that umbraVertices[0] is always 0. - // So the start and the end of the umbra are using the "0". - // And penumbra ended with 0, so a degenerated triangle is formed b/t - // the umbra and penumbra. - indexBuffer[indexBufferIndex++] = umbraVertices[i]; - indexBuffer[indexBufferIndex++] = centroidIndex; - } - indexBuffer[indexBufferIndex++] = 0; - } - - // At the end, update the real index and vertex buffer size. - shadowVertexBuffer.updateVertexCount(vertexBufferIndex); - shadowVertexBuffer.updateIndexCount(indexBufferIndex); - shadowVertexBuffer.computeBounds<AlphaVertex>(); - - ShadowTessellator::checkOverflow(vertexBufferIndex, totalVertexCount, "Ambient Vertex Buffer"); - ShadowTessellator::checkOverflow(indexBufferIndex, totalIndexCount, "Ambient Index Buffer"); - ShadowTessellator::checkOverflow(umbraIndex, totalUmbraCount, "Ambient Umbra Buffer"); - -#if DEBUG_SHADOW - for (int i = 0; i < vertexBufferIndex; i++) { - ALOGD("vertexBuffer i %d, (%f, %f %f)", i, shadowVertices[i].x, shadowVertices[i].y, - shadowVertices[i].alpha); - } - for (int i = 0; i < indexBufferIndex; i++) { - ALOGD("indexBuffer i %d, indexBuffer[i] %d", i, indexBuffer[i]); - } -#endif -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/AmbientShadow.h b/libs/hwui/AmbientShadow.h deleted file mode 100644 index cb1d915f2540..000000000000 --- a/libs/hwui/AmbientShadow.h +++ /dev/null @@ -1,42 +0,0 @@ - -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_AMBIENT_SHADOW_H -#define ANDROID_HWUI_AMBIENT_SHADOW_H - -#include "Debug.h" -#include "Vector.h" - -namespace android { -namespace uirenderer { - -class VertexBuffer; - -/** - * AmbientShadow is used to calculate the ambient shadow value around a polygon. - */ -class AmbientShadow { -public: - static void createAmbientShadow(bool isCasterOpaque, const Vector3* poly, int polyLength, - const Vector3& centroid3d, float heightFactor, float geomFactor, - VertexBuffer& shadowVertexBuffer); -}; // AmbientShadow - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_AMBIENT_SHADOW_H diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index fb8274426b16..17d2db71ab58 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -61,6 +61,7 @@ cc_defaults { shared_libs: [ "liblog", "libcutils", + "libstatslog", "libutils", "libEGL", "libGLESv2", @@ -72,7 +73,7 @@ cc_defaults { "libft2", "libminikin", "libandroidfw", - "libRScpp", + "libcrypto", ], static_libs: [ "libEGL_blobCache", @@ -81,7 +82,6 @@ cc_defaults { cc_defaults { name: "hwui_bugreport_font_cache_usage", - srcs: ["font/FontCacheHistoryTracker.cpp"], cflags: ["-DBUGREPORT_FONT_CACHE_USAGE"], } @@ -158,8 +158,6 @@ cc_defaults { "hwui/AnimatedImageDrawable.cpp", "hwui/AnimatedImageThread.cpp", "hwui/Bitmap.cpp", - "font/CacheTexture.cpp", - "font/Font.cpp", "hwui/Canvas.cpp", "hwui/MinikinSkia.cpp", "hwui/MinikinUtils.cpp", @@ -173,23 +171,15 @@ cc_defaults { "pipeline/skia/SkiaDisplayList.cpp", "pipeline/skia/SkiaMemoryTracer.cpp", "pipeline/skia/SkiaOpenGLPipeline.cpp", - "pipeline/skia/SkiaOpenGLReadback.cpp", "pipeline/skia/SkiaPipeline.cpp", "pipeline/skia/SkiaProfileRenderer.cpp", "pipeline/skia/SkiaRecordingCanvas.cpp", "pipeline/skia/SkiaVulkanPipeline.cpp", "pipeline/skia/VectorDrawableAtlas.cpp", - "renderstate/Blend.cpp", - "renderstate/MeshState.cpp", - "renderstate/OffscreenBufferPool.cpp", - "renderstate/PixelBufferState.cpp", + "pipeline/skia/VkInteropFunctorDrawable.cpp", "renderstate/RenderState.cpp", - "renderstate/Scissor.cpp", - "renderstate/Stencil.cpp", - "renderstate/TextureState.cpp", "renderthread/CacheManager.cpp", "renderthread/CanvasContext.cpp", - "renderthread/OpenGLPipeline.cpp", "renderthread/DrawFrameTask.cpp", "renderthread/EglManager.cpp", "renderthread/VulkanManager.cpp", @@ -199,89 +189,56 @@ cc_defaults { "renderthread/TimeLord.cpp", "renderthread/Frame.cpp", "service/GraphicsStatsService.cpp", + "surfacetexture/EGLConsumer.cpp", + "surfacetexture/ImageConsumer.cpp", + "surfacetexture/SurfaceTexture.cpp", "thread/TaskManager.cpp", "utils/Blur.cpp", "utils/Color.cpp", "utils/GLUtils.cpp", "utils/LinearAllocator.cpp", "utils/StringUtils.cpp", - "utils/TestWindowContext.cpp", "utils/VectorDrawableUtils.cpp", - "AmbientShadow.cpp", "AnimationContext.cpp", "Animator.cpp", "AnimatorManager.cpp", - "BakedOpDispatcher.cpp", - "BakedOpRenderer.cpp", - "BakedOpState.cpp", - "Caches.cpp", - "CanvasState.cpp", - "ClipArea.cpp", + "CanvasTransform.cpp", "DamageAccumulator.cpp", "DeferredLayerUpdater.cpp", "DeviceInfo.cpp", - "DisplayList.cpp", - "Extensions.cpp", - "FboCache.cpp", - "FontRenderer.cpp", - "FrameBuilder.cpp", "FrameInfo.cpp", "FrameInfoVisualizer.cpp", - "GammaFontRenderer.cpp", - "GlLayer.cpp", - "GlopBuilder.cpp", "GpuMemoryTracker.cpp", - "GradientCache.cpp", - "Image.cpp", + "HardwareBitmapUploader.cpp", "Interpolator.cpp", "JankTracker.cpp", "Layer.cpp", - "LayerBuilder.cpp", "LayerUpdateQueue.cpp", "Matrix.cpp", - "OpDumper.cpp", - "OpenGLReadback.cpp", - "Patch.cpp", - "PatchCache.cpp", - "PathCache.cpp", "PathParser.cpp", - "PathTessellator.cpp", - "PixelBuffer.cpp", "ProfileData.cpp", "ProfileDataContainer.cpp", - "ProfileRenderer.cpp", - "Program.cpp", - "ProgramCache.cpp", "Properties.cpp", "PropertyValuesAnimatorSet.cpp", "PropertyValuesHolder.cpp", + "Readback.cpp", "RecordingCanvas.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", + "TreeInfo.cpp", "VectorDrawable.cpp", - "VkLayer.cpp", "protos/graphicsstats.proto", - "protos/hwui.proto", ], + // Allow implicit fallthroughs in HardwareBitmapUploader.cpp until they are fixed. + cflags: ["-Wno-implicit-fallthrough"], + proto: { export_proto_headers: true, }, export_include_dirs: ["."], - export_shared_lib_headers: ["libRScpp"], } cc_library { @@ -292,7 +249,7 @@ cc_library { // Enables fine-grained GLES error checking // If enabled, every GLES call is wrapped & error checked // Has moderate overhead - "hwui_enable_opengl_validation", + //"hwui_enable_opengl_validation", ], } @@ -348,33 +305,18 @@ cc_test { srcs: [ "tests/unit/main.cpp", - "tests/unit/BakedOpDispatcherTests.cpp", - "tests/unit/BakedOpRendererTests.cpp", - "tests/unit/BakedOpStateTests.cpp", "tests/unit/CacheManagerTests.cpp", "tests/unit/CanvasContextTests.cpp", - "tests/unit/CanvasStateTests.cpp", - "tests/unit/ClipAreaTests.cpp", "tests/unit/DamageAccumulatorTests.cpp", "tests/unit/DeferredLayerUpdaterTests.cpp", - "tests/unit/DeviceInfoTests.cpp", "tests/unit/FatVectorTests.cpp", - "tests/unit/FontRendererTests.cpp", - "tests/unit/FrameBuilderTests.cpp", - "tests/unit/GlopBuilderTests.cpp", "tests/unit/GpuMemoryTrackerTests.cpp", - "tests/unit/GradientCacheTests.cpp", "tests/unit/GraphicsStatsServiceTests.cpp", "tests/unit/LayerUpdateQueueTests.cpp", - "tests/unit/LeakCheckTests.cpp", "tests/unit/LinearAllocatorTests.cpp", "tests/unit/MatrixTests.cpp", - "tests/unit/MeshStateTests.cpp", - "tests/unit/OffscreenBufferPoolTests.cpp", - "tests/unit/OpDumperTests.cpp", "tests/unit/PathInterpolatorTests.cpp", "tests/unit/RenderNodeDrawableTests.cpp", - "tests/unit/RecordingCanvasTests.cpp", "tests/unit/RenderNodeTests.cpp", "tests/unit/RenderPropertiesTests.cpp", "tests/unit/ShaderCacheTests.cpp", @@ -383,11 +325,8 @@ cc_test { "tests/unit/SkiaPipelineTests.cpp", "tests/unit/SkiaRenderPropertiesTests.cpp", "tests/unit/SkiaCanvasTests.cpp", - "tests/unit/SnapshotTests.cpp", "tests/unit/StringUtilsTests.cpp", "tests/unit/TestUtilsTests.cpp", - "tests/unit/TextDropShadowCacheTests.cpp", - "tests/unit/TextureCacheTests.cpp", "tests/unit/ThreadBaseTests.cpp", "tests/unit/TypefaceTests.cpp", "tests/unit/VectorDrawableTests.cpp", @@ -436,12 +375,9 @@ cc_benchmark { srcs: [ "tests/microbench/main.cpp", "tests/microbench/DisplayListCanvasBench.cpp", - "tests/microbench/FontBench.cpp", - "tests/microbench/FrameBuilderBench.cpp", "tests/microbench/LinearAllocatorBench.cpp", "tests/microbench/PathParserBench.cpp", "tests/microbench/RenderNodeBench.cpp", - "tests/microbench/ShadowBench.cpp", "tests/microbench/TaskManagerBench.cpp", ], } diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h index 42f4cf8828bd..ed7b6eb1cf4a 100644 --- a/libs/hwui/Animator.h +++ b/libs/hwui/Animator.h @@ -16,6 +16,8 @@ #ifndef ANIMATOR_H #define ANIMATOR_H +#include "CanvasProperty.h" + #include <cutils/compiler.h> #include <utils/RefBase.h> #include <utils/StrongPointer.h> @@ -31,8 +33,6 @@ namespace uirenderer { class AnimationContext; class BaseRenderNodeAnimator; -class CanvasPropertyPrimitive; -class CanvasPropertyPaint; class Interpolator; class RenderNode; class RenderProperties; diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp deleted file mode 100644 index f78cb418c5cd..000000000000 --- a/libs/hwui/BakedOpDispatcher.cpp +++ /dev/null @@ -1,879 +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 "BakedOpDispatcher.h" - -#include "BakedOpRenderer.h" -#include "Caches.h" -#include "DeferredLayerUpdater.h" -#include "Glop.h" -#include "GlopBuilder.h" -#include "Patch.h" -#include "PathTessellator.h" -#include "VertexBuffer.h" -#include "renderstate/OffscreenBufferPool.h" -#include "renderstate/RenderState.h" -#include "utils/GLUtils.h" - -#include <SkPaintDefaults.h> -#include <SkPathOps.h> -#include <math.h> -#include <algorithm> - -namespace android { -namespace uirenderer { - -static void storeTexturedRect(TextureVertex* vertices, const Rect& bounds) { - vertices[0] = {bounds.left, bounds.top, 0, 0}; - vertices[1] = {bounds.right, bounds.top, 1, 0}; - vertices[2] = {bounds.left, bounds.bottom, 0, 1}; - vertices[3] = {bounds.right, bounds.bottom, 1, 1}; -} - -void BakedOpDispatcher::onMergedBitmapOps(BakedOpRenderer& renderer, - const MergedBakedOpList& opList) { - const BakedOpState& firstState = *(opList.states[0]); - Bitmap* bitmap = (static_cast<const BitmapOp*>(opList.states[0]->op))->bitmap; - - Texture* texture = renderer.caches().textureCache.get(bitmap); - if (!texture) return; - const AutoTexture autoCleanup(texture); - - TextureVertex vertices[opList.count * 4]; - for (size_t i = 0; i < opList.count; i++) { - const BakedOpState& state = *(opList.states[i]); - TextureVertex* rectVerts = &vertices[i * 4]; - - // calculate unclipped bounds, since they'll determine texture coordinates - Rect opBounds = state.op->unmappedBounds; - state.computedState.transform.mapRect(opBounds); - if (CC_LIKELY(state.computedState.transform.isPureTranslate())) { - // pure translate, so snap (same behavior as onBitmapOp) - opBounds.snapToPixelBoundaries(); - } - storeTexturedRect(rectVerts, opBounds); - 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]); - - // 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( - 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( - 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 = 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, const TextOp& op, - const BakedOpState& textOpState) { - if (CC_LIKELY(!PaintUtils::hasTextShadow(op.paint))) return; - - FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer(); - fontRenderer.setFont(op.paint, SkMatrix::I()); - renderer.caches().textureState().activateTexture(0); - - PaintUtils::TextShadow textShadow; - if (!PaintUtils::getTextShadow(op.paint, &textShadow)) { - LOG_ALWAYS_FATAL("failed to query shadow attributes"); - } - - renderer.caches().dropShadowCache.setFontRenderer(fontRenderer); - ShadowTexture* texture = renderer.caches().dropShadowCache.get( - op.paint, op.glyphs, op.glyphCount, textShadow.radius, op.positions); - // If the drop shadow exceeds the max texture size or couldn't be - // allocated, skip drawing - if (!texture) return; - const AutoTexture autoCleanup(texture); - - const float sx = op.x - texture->left + textShadow.dx; - const float sy = op.y - texture->top + textShadow.dy; - - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(textOpState.roundRectClipState) - .setMeshTexturedUnitQuad(nullptr) - .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, textOpState.alpha) - .setTransform(textOpState.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width(), sy + texture->height())) - .build(); - - // Compute damage bounds and clip (since may differ from those in textOpState). - // Bounds should be same as text op, but with dx/dy offset and radius outset - // applied in local space. - auto& transform = textOpState.computedState.transform; - Rect shadowBounds = op.unmappedBounds; // STROKE - const bool expandForStroke = op.paint->getStyle() != SkPaint::kFill_Style; - if (expandForStroke) { - shadowBounds.outset(op.paint->getStrokeWidth() * 0.5f); - } - shadowBounds.translate(textShadow.dx, textShadow.dy); - shadowBounds.outset(textShadow.radius, textShadow.radius); - transform.mapRect(shadowBounds); - if (CC_UNLIKELY(expandForStroke && - (!transform.isPureTranslate() || op.paint->getStrokeWidth() < 1.0f))) { - shadowBounds.outset(0.5f); - } - - auto clipState = textOpState.computedState.clipState; - if (clipState->mode != ClipMode::Rectangle || !clipState->rect.contains(shadowBounds)) { - // need clip, so pass it and clip bounds - shadowBounds.doIntersect(clipState->rect); - } else { - // don't need clip, ignore - clipState = nullptr; - } - - renderer.renderGlop(&shadowBounds, clipState, glop); -} - -enum class TextRenderType { Defer, Flush }; - -static void renderText(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state, - const ClipBase* renderClip, TextRenderType renderType) { - FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer(); - 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; - SkBlendMode mode = PaintUtils::getBlendModeDirect(op.paint); - TextDrawFunctor functor(&renderer, &state, renderClip, x, y, pureTranslate, alpha, mode, - op.paint); - - bool forceFinish = (renderType == TextRenderType::Flush); - bool mustDirtyRenderTarget = renderer.offscreenRenderTarget(); - const Rect* localOpClip = pureTranslate ? &state.computedState.clipRect() : nullptr; - fontRenderer.renderPosText(op.paint, localOpClip, op.glyphs, op.glyphCount, x, y, op.positions, - mustDirtyRenderTarget ? &layerBounds : nullptr, &functor, - forceFinish); - - if (mustDirtyRenderTarget) { - if (!pureTranslate) { - transform.mapRect(layerBounds); - } - renderer.dirtyRenderTarget(layerBounds); - } -} - -void BakedOpDispatcher::onMergedTextOps(BakedOpRenderer& renderer, - const MergedBakedOpList& opList) { - for (size_t i = 0; i < opList.count; i++) { - const BakedOpState& state = *(opList.states[i]); - const TextOp& op = *(static_cast<const TextOp*>(state.op)); - renderTextShadow(renderer, op, state); - } - - 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; - renderText(renderer, op, state, clip, renderType); - } -} - -namespace VertexBufferRenderFlags { -enum { - Offset = 0x1, - ShadowInterp = 0x2, -}; -} - -static void renderVertexBuffer(BakedOpRenderer& renderer, const BakedOpState& state, - const VertexBuffer& vertexBuffer, float translateX, float translateY, - const SkPaint& paint, int vertexBufferRenderFlags) { - if (CC_LIKELY(vertexBuffer.getVertexCount())) { - bool shadowInterp = vertexBufferRenderFlags & VertexBufferRenderFlags::ShadowInterp; - const int transformFlags = vertexBufferRenderFlags & VertexBufferRenderFlags::Offset - ? TransformFlags::OffsetByFudgeFactor - : 0; - - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshVertexBuffer(vertexBuffer) - .setFillPaint(paint, state.alpha, shadowInterp) - .setTransform(state.computedState.transform, transformFlags) - .setModelViewOffsetRect(translateX, translateY, vertexBuffer.getBounds()) - .build(); - renderer.renderGlop(state, glop); - } -} - -static void renderConvexPath(BakedOpRenderer& renderer, const BakedOpState& state, - const SkPath& path, const SkPaint& paint) { - VertexBuffer vertexBuffer; - // TODO: try clipping large paths to viewport - PathTessellator::tessellatePath(path, &paint, state.computedState.transform, vertexBuffer); - renderVertexBuffer(renderer, state, vertexBuffer, 0.0f, 0.0f, paint, 0); -} - -static void renderPathTexture(BakedOpRenderer& renderer, const BakedOpState& state, float xOffset, - float yOffset, PathTexture& texture, const SkPaint& paint) { - Rect dest(texture.width(), texture.height()); - dest.translate(xOffset + texture.left - texture.offset, yOffset + texture.top - texture.offset); - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshTexturedUnitQuad(nullptr) - .setFillPathTexturePaint(texture, paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRect(dest) - .build(); - renderer.renderGlop(state, glop); -} - -SkRect getBoundsOfFill(const RecordedOp& op) { - SkRect bounds = op.unmappedBounds.toSkRect(); - if (op.paint->getStyle() == SkPaint::kStrokeAndFill_Style) { - float outsetDistance = op.paint->getStrokeWidth() / 2; - bounds.outset(outsetDistance, outsetDistance); - } - return bounds; -} - -void BakedOpDispatcher::onArcOp(BakedOpRenderer& renderer, const ArcOp& op, - const BakedOpState& state) { - // TODO: support fills (accounting for concavity if useCenter && sweepAngle > 180) - if (op.paint->getStyle() != SkPaint::kStroke_Style || op.paint->getPathEffect() != nullptr || - op.useCenter) { - PathTexture* texture = renderer.caches().pathCache.getArc( - op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.startAngle, - op.sweepAngle, op.useCenter, op.paint); - const AutoTexture holder(texture); - if (CC_LIKELY(holder.texture)) { - renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, - *texture, *(op.paint)); - } - } else { - SkRect rect = getBoundsOfFill(op); - SkPath path; - if (op.useCenter) { - path.moveTo(rect.centerX(), rect.centerY()); - } - path.arcTo(rect, op.startAngle, op.sweepAngle, !op.useCenter); - if (op.useCenter) { - path.close(); - } - renderConvexPath(renderer, state, path, *(op.paint)); - } -} - -void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op, - const BakedOpState& state) { - Texture* texture = renderer.getTexture(op.bitmap); - if (!texture) return; - const AutoTexture autoCleanup(texture); - - const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType) - ? TextureFillFlags::IsAlphaMaskTexture - : TextureFillFlags::None; - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshTexturedUnitQuad(texture->uvMapper) - .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRectSnap(Rect(texture->width(), texture->height())) - .build(); - renderer.renderGlop(state, glop); -} - -void BakedOpDispatcher::onBitmapMeshOp(BakedOpRenderer& renderer, const BitmapMeshOp& op, - const BakedOpState& state) { - Texture* texture = renderer.caches().textureCache.get(op.bitmap); - if (!texture) { - return; - } - const AutoTexture autoCleanup(texture); - - 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(); - } - - 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; - - 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]); - } - } - - /* - * TODO: handle alpha_8 textures correctly by applying paint color, but *not* - * shader in that case to mimic the behavior in SkiaCanvas::drawBitmapMesh. - */ - const int textureFillFlags = TextureFillFlags::None; - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshColoredTexturedMesh(mesh.get(), elementCount) - .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewOffsetRect(0, 0, op.unmappedBounds) - .build(); - renderer.renderGlop(state, glop); -} - -void BakedOpDispatcher::onBitmapRectOp(BakedOpRenderer& renderer, const BitmapRectOp& op, - const BakedOpState& state) { - Texture* texture = renderer.getTexture(op.bitmap); - if (!texture) return; - const AutoTexture autoCleanup(texture); - - Rect uv(std::max(0.0f, op.src.left / texture->width()), - std::max(0.0f, op.src.top / texture->height()), - std::min(1.0f, op.src.right / texture->width()), - std::min(1.0f, op.src.bottom / texture->height())); - - const int textureFillFlags = (op.bitmap->colorType() == kAlpha_8_SkColorType) - ? TextureFillFlags::IsAlphaMaskTexture - : TextureFillFlags::None; - const bool tryToSnap = MathUtils::areEqual(op.src.getWidth(), op.unmappedBounds.getWidth()) && - MathUtils::areEqual(op.src.getHeight(), op.unmappedBounds.getHeight()); - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshTexturedUvQuad(texture->uvMapper, uv) - .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRectOptionalSnap(tryToSnap, op.unmappedBounds) - .build(); - renderer.renderGlop(state, glop); -} - -void BakedOpDispatcher::onColorOp(BakedOpRenderer& renderer, const ColorOp& op, - const BakedOpState& state) { - SkPaint paint; - paint.setColor(op.color); - paint.setBlendMode(op.mode); - - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshUnitQuad() - .setFillPaint(paint, state.alpha) - .setTransform(Matrix4::identity(), TransformFlags::None) - .setModelViewMapUnitToRect(state.computedState.clipState->rect) - .build(); - renderer.renderGlop(state, glop); -} - -void BakedOpDispatcher::onFunctorOp(BakedOpRenderer& renderer, const FunctorOp& op, - const BakedOpState& state) { - renderer.renderFunctor(op, state); -} - -void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, - const BakedOpState& state) { - VertexBuffer buffer; - PathTessellator::tessellateLines(op.points, op.floatCount, op.paint, - state.computedState.transform, buffer); - int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset; - renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags); -} - -void BakedOpDispatcher::onOvalOp(BakedOpRenderer& renderer, const OvalOp& op, - const BakedOpState& state) { - if (op.paint->getPathEffect() != nullptr) { - PathTexture* texture = renderer.caches().pathCache.getOval( - op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint); - const AutoTexture holder(texture); - if (CC_LIKELY(holder.texture)) { - renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, - *texture, *(op.paint)); - } - } else { - SkPath path; - SkRect rect = getBoundsOfFill(op); - path.addOval(rect); - - if (state.computedState.localProjectionPathMask != nullptr) { - // Mask the ripple path by the local space projection mask in local space. - // Note that this can create CCW paths. - Op(path, *state.computedState.localProjectionPathMask, kIntersect_SkPathOp, &path); - } - renderConvexPath(renderer, state, path, *(op.paint)); - } -} - -void BakedOpDispatcher::onPatchOp(BakedOpRenderer& renderer, const PatchOp& op, - const BakedOpState& state) { - // 9 patches are built for stretching - always filter - int textureFillFlags = TextureFillFlags::ForceFilter; - if (op.bitmap->colorType() == kAlpha_8_SkColorType) { - textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture; - } - - // TODO: avoid redoing the below work each frame: - const Patch* mesh = renderer.caches().patchCache.get(op.bitmap->width(), op.bitmap->height(), - op.unmappedBounds.getWidth(), - op.unmappedBounds.getHeight(), op.patch); - - Texture* texture = renderer.caches().textureCache.get(op.bitmap); - if (CC_LIKELY(texture)) { - const AutoTexture autoCleanup(texture); - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshPatchQuads(*mesh) - .setFillTexturePaint(*texture, textureFillFlags, op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewOffsetRectSnap( - op.unmappedBounds.left, op.unmappedBounds.top, - Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight())) - .build(); - renderer.renderGlop(state, glop); - } -} - -void BakedOpDispatcher::onPathOp(BakedOpRenderer& renderer, const PathOp& op, - const BakedOpState& state) { - PathTexture* texture = renderer.caches().pathCache.get(op.path, op.paint); - const AutoTexture holder(texture); - if (CC_LIKELY(holder.texture)) { - // Unlike other callers to renderPathTexture, no offsets are used because PathOp doesn't - // have any translate built in, other than what's in the SkPath itself - renderPathTexture(renderer, state, 0, 0, *texture, *(op.paint)); - } -} - -void BakedOpDispatcher::onPointsOp(BakedOpRenderer& renderer, const PointsOp& op, - const BakedOpState& state) { - VertexBuffer buffer; - PathTessellator::tessellatePoints(op.points, op.floatCount, op.paint, - state.computedState.transform, buffer); - int displayFlags = op.paint->isAntiAlias() ? 0 : VertexBufferRenderFlags::Offset; - renderVertexBuffer(renderer, state, buffer, 0, 0, *(op.paint), displayFlags); -} - -// See SkPaintDefaults.h -#define SkPaintDefaults_MiterLimit SkIntToScalar(4) - -void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, - const BakedOpState& state) { - if (op.paint->getStyle() != SkPaint::kFill_Style) { - // only fill + default miter is supported by drawConvexPath, since others must handle joins - static_assert(SkPaintDefaults_MiterLimit == 4.0f, "Miter limit has changed"); - if (CC_UNLIKELY(op.paint->getPathEffect() != nullptr || - op.paint->getStrokeJoin() != SkPaint::kMiter_Join || - op.paint->getStrokeMiter() != SkPaintDefaults_MiterLimit)) { - PathTexture* texture = renderer.caches().pathCache.getRect( - op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.paint); - const AutoTexture holder(texture); - if (CC_LIKELY(holder.texture)) { - renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, - *texture, *(op.paint)); - } - } else { - SkPath path; - path.addRect(getBoundsOfFill(op)); - renderConvexPath(renderer, state, path, *(op.paint)); - } - } else { - if (op.paint->isAntiAlias() && !state.computedState.transform.isSimple()) { - SkPath path; - path.addRect(op.unmappedBounds.toSkRect()); - renderConvexPath(renderer, state, path, *(op.paint)); - } else { - // render simple unit quad, no tessellation required - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshUnitQuad() - .setFillPaint(*op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRect(op.unmappedBounds) - .build(); - renderer.renderGlop(state, glop); - } - } -} - -void BakedOpDispatcher::onRoundRectOp(BakedOpRenderer& renderer, const RoundRectOp& op, - const BakedOpState& state) { - if (op.paint->getPathEffect() != nullptr) { - PathTexture* texture = renderer.caches().pathCache.getRoundRect( - op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight(), op.rx, op.ry, - op.paint); - const AutoTexture holder(texture); - if (CC_LIKELY(holder.texture)) { - renderPathTexture(renderer, state, op.unmappedBounds.left, op.unmappedBounds.top, - *texture, *(op.paint)); - } - } else { - const VertexBuffer* buffer = renderer.caches().tessellationCache.getRoundRect( - state.computedState.transform, *(op.paint), op.unmappedBounds.getWidth(), - op.unmappedBounds.getHeight(), op.rx, op.ry); - renderVertexBuffer(renderer, state, *buffer, op.unmappedBounds.left, op.unmappedBounds.top, - *(op.paint), 0); - } -} - -static void renderShadow(BakedOpRenderer& renderer, const BakedOpState& state, float casterAlpha, - const VertexBuffer* ambientShadowVertexBuffer, - const VertexBuffer* spotShadowVertexBuffer) { - SkPaint paint; - paint.setAntiAlias(true); // want to use AlphaVertex - - // The caller has made sure casterAlpha > 0. - uint8_t ambientShadowAlpha = renderer.getLightInfo().ambientShadowAlpha; - if (CC_UNLIKELY(Properties::overrideAmbientShadowStrength >= 0)) { - ambientShadowAlpha = Properties::overrideAmbientShadowStrength; - } - if (ambientShadowVertexBuffer && ambientShadowAlpha > 0) { - paint.setAlpha((uint8_t)(casterAlpha * ambientShadowAlpha)); - renderVertexBuffer(renderer, state, *ambientShadowVertexBuffer, 0, 0, paint, - VertexBufferRenderFlags::ShadowInterp); - } - - uint8_t spotShadowAlpha = renderer.getLightInfo().spotShadowAlpha; - if (CC_UNLIKELY(Properties::overrideSpotShadowStrength >= 0)) { - spotShadowAlpha = Properties::overrideSpotShadowStrength; - } - if (spotShadowVertexBuffer && spotShadowAlpha > 0) { - paint.setAlpha((uint8_t)(casterAlpha * spotShadowAlpha)); - renderVertexBuffer(renderer, state, *spotShadowVertexBuffer, 0, 0, paint, - VertexBufferRenderFlags::ShadowInterp); - } -} - -void BakedOpDispatcher::onShadowOp(BakedOpRenderer& renderer, const ShadowOp& op, - const BakedOpState& state) { - TessellationCache::vertexBuffer_pair_t buffers = op.shadowTask->getResult(); - renderShadow(renderer, state, op.casterAlpha, buffers.first, buffers.second); -} - -void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleRectsOp& op, - const BakedOpState& state) { - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshIndexedQuads(&op.vertices[0], op.vertexCount / 4) - .setFillPaint(*op.paint, state.alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewOffsetRect(0, 0, op.unmappedBounds) - .build(); - renderer.renderGlop(state, glop); -} - -void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, - const BakedOpState& state) { - renderTextShadow(renderer, op, state); - renderText(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; - SkBlendMode mode = PaintUtils::getBlendModeDirect(op.paint); - TextDrawFunctor functor(&renderer, &state, renderTargetClip, 0.0f, 0.0f, false, alpha, mode, - op.paint); - - bool mustDirtyRenderTarget = renderer.offscreenRenderTarget(); - const Rect localSpaceClip = state.computedState.computeLocalSpaceClip(); - if (fontRenderer.renderTextOnPath(op.paint, &localSpaceClip, op.glyphs, op.glyphCount, op.path, - op.hOffset, op.vOffset, - mustDirtyRenderTarget ? &layerBounds : nullptr, &functor)) { - if (mustDirtyRenderTarget) { - // manually dirty render target, since TextDrawFunctor won't - state.computedState.transform.mapRect(layerBounds); - renderer.dirtyRenderTarget(layerBounds); - } - } -} - -void BakedOpDispatcher::onTextureLayerOp(BakedOpRenderer& renderer, const TextureLayerOp& op, - const BakedOpState& state) { - GlLayer* layer = static_cast<GlLayer*>(op.layerHandle->backingLayer()); - if (!layer) { - return; - } - const bool tryToSnap = layer->getForceFilter(); - float alpha = (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(*(layer), alpha) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRectOptionalSnap(tryToSnap, - Rect(layer->getWidth(), layer->getHeight())) - .build(); - renderer.renderGlop(state, glop); -} - -void renderRectForLayer(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state, - int color, SkBlendMode mode, SkColorFilter* colorFilter) { - SkPaint paint; - paint.setColor(color); - paint.setBlendMode(mode); - paint.setColorFilter(sk_ref_sp(colorFilter)); - RectOp rectOp(op.unmappedBounds, op.localMatrix, op.localClip, &paint); - BakedOpDispatcher::onRectOp(renderer, rectOp, state); -} - -void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, - const BakedOpState& state) { - // Note that we don't use op->paint in this function - it's never set on a LayerOp - OffscreenBuffer* buffer = *op.layerHandle; - - if (CC_UNLIKELY(!buffer)) return; - - 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 (!buffer->hasRenderedSinceRepaint) { - buffer->hasRenderedSinceRepaint = true; - if (CC_UNLIKELY(Properties::debugLayersUpdates)) { - // render debug layer highlight - renderRectForLayer(renderer, op, state, 0x7f00ff00, SkBlendMode::kSrcOver, nullptr); - } else if (CC_UNLIKELY(Properties::debugOverdraw)) { - // render transparent to increment overdraw for repaint area - renderRectForLayer(renderer, op, state, SK_ColorTRANSPARENT, SkBlendMode::kSrcOver, - nullptr); - } - } -} - -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.setBlendMode(SkBlendMode::kDstIn); - layerPaint.setColorFilter(sk_ref_sp(op.paint->getColorFilter())); - RectOp rectOp(state.computedState.clippedBounds, Matrix4::identity(), nullptr, - &layerPaint); - BakedOpDispatcher::onRectOp(renderer, rectOp, state); - } - - OffscreenBuffer& layer = **(op.layerHandle); - auto mode = PaintUtils::getBlendModeDirect(op.paint); - Glop glop; - GlopBuilder(renderer.renderState(), renderer.caches(), &glop) - .setRoundRectClipState(state.roundRectClipState) - .setMeshTexturedUvQuad(nullptr, layer.getTextureCoordinates()) - .setFillLayer(layer.texture, nullptr, 1.0f, mode, Blend::ModeOrderSwap::Swap) - .setTransform(state.computedState.transform, TransformFlags::None) - .setModelViewMapUnitToRect(state.computedState.clippedBounds) - .build(); - renderer.renderGlop(state, glop); - } - renderer.renderState().layerPool().putOrDelete(*op.layerHandle); -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/BakedOpDispatcher.h b/libs/hwui/BakedOpDispatcher.h deleted file mode 100644 index e3708685afc9..000000000000 --- a/libs/hwui/BakedOpDispatcher.h +++ /dev/null @@ -1,52 +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. - */ - -#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 deleted file mode 100644 index 6b64b94d291e..000000000000 --- a/libs/hwui/BakedOpRenderer.cpp +++ /dev/null @@ -1,383 +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 "BakedOpRenderer.h" - -#include "Caches.h" -#include "Glop.h" -#include "GlopBuilder.h" -#include "VertexBuffer.h" -#include "renderstate/OffscreenBufferPool.h" -#include "renderstate/RenderState.h" -#include "utils/GLUtils.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, mWideColorGamut); - startRepaintLayer(buffer, Rect(width, height)); - return buffer; -} - -void BakedOpRenderer::recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) { - mRenderState.layerPool().putOrDelete(offscreenBuffer); -} - -void BakedOpRenderer::startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) { - LOG_ALWAYS_FATAL_IF(mRenderTarget.offscreenBuffer, "already has layer..."); - - // subtract repaintRect from region, since it will be regenerated - if (repaintRect.contains(0, 0, offscreenBuffer->viewportWidth, - offscreenBuffer->viewportHeight)) { - // repaint full layer, so throw away entire region - offscreenBuffer->region.clear(); - } else { - offscreenBuffer->region.subtractSelf(android::Rect(repaintRect.left, repaintRect.top, - repaintRect.right, repaintRect.bottom)); - } - - mRenderTarget.offscreenBuffer = offscreenBuffer; - mRenderTarget.offscreenBuffer->hasRenderedSinceRepaint = false; - - // create and bind framebuffer - mRenderTarget.frameBufferId = mRenderState.createFramebuffer(); - mRenderState.bindFramebuffer(mRenderTarget.frameBufferId); - - // attach the texture to the FBO - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - offscreenBuffer->texture.id(), 0); - GL_CHECKPOINT(LOW); - - int status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - LOG_ALWAYS_FATAL_IF(status != GL_FRAMEBUFFER_COMPLETE, - "framebuffer incomplete, status %d, textureId %d, size %dx%d", status, - offscreenBuffer->texture.id(), offscreenBuffer->texture.width(), - offscreenBuffer->texture.height()); - - // Change the viewport & ortho projection - setViewport(offscreenBuffer->viewportWidth, offscreenBuffer->viewportHeight); - - clearColorBuffer(repaintRect); -} - -void BakedOpRenderer::endLayer() { - if (mRenderTarget.stencil) { - // if stencil was used for clipping, detach it and return it to pool - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0); - GL_CHECKPOINT(MODERATE); - mCaches.renderBufferCache.put(mRenderTarget.stencil); - mRenderTarget.stencil = nullptr; - } - mRenderTarget.lastStencilClip = nullptr; - - mRenderTarget.offscreenBuffer->updateMeshFromRegion(); - mRenderTarget.offscreenBuffer = nullptr; // It's in drawLayerOp's hands now. - - // Detach the texture from the FBO - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - GL_CHECKPOINT(LOW); - mRenderState.deleteFramebuffer(mRenderTarget.frameBufferId); - mRenderTarget.frameBufferId = 0; -} - -OffscreenBuffer* BakedOpRenderer::copyToLayer(const Rect& area) { - const uint32_t width = area.getWidth(); - const uint32_t height = area.getHeight(); - OffscreenBuffer* buffer = - mRenderState.layerPool().get(mRenderState, width, height, mWideColorGamut); - if (!area.isEmpty() && width != 0 && height != 0) { - mCaches.textureState().activateTexture(0); - mCaches.textureState().bindTexture(buffer->texture.id()); - - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, area.left, - mRenderTarget.viewportHeight - area.bottom, width, height); - } - return buffer; -} - -void BakedOpRenderer::startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) { - LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "primary framebufferId must be 0"); - mRenderState.bindFramebuffer(0); - setViewport(width, height); - - if (!mOpaque) { - clearColorBuffer(repaintRect); - } - - mRenderState.debugOverdraw(true, true); -} - -void BakedOpRenderer::endFrame(const Rect& repaintRect) { - if (CC_UNLIKELY(Properties::debugOverdraw)) { - ClipRect overdrawClip(repaintRect); - Rect viewportRect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight); - // overdraw visualization - for (int i = 1; i <= 4; i++) { - if (i < 4) { - // nth level of overdraw tests for n+1 draws per pixel - mRenderState.stencil().enableDebugTest(i + 1, false); - } else { - // 4th level tests for 4 or higher draws per pixel - mRenderState.stencil().enableDebugTest(4, true); - } - - SkPaint paint; - paint.setColor(mCaches.getOverdrawColor(i)); - Glop glop; - GlopBuilder(mRenderState, mCaches, &glop) - .setRoundRectClipState(nullptr) - .setMeshUnitQuad() - .setFillPaint(paint, 1.0f) - .setTransform(Matrix4::identity(), TransformFlags::None) - .setModelViewMapUnitToRect(viewportRect) - .build(); - renderGlop(nullptr, &overdrawClip, glop); - } - mRenderState.stencil().disable(); - } - - // Note: we leave FBO 0 renderable here, for post-frame-content decoration -} - -void BakedOpRenderer::setViewport(uint32_t width, uint32_t height) { - mRenderTarget.viewportWidth = width; - mRenderTarget.viewportHeight = height; - mRenderTarget.orthoMatrix.loadOrtho(width, height); - - mRenderState.setViewport(width, height); - mRenderState.blend().syncEnabled(); -} - -void BakedOpRenderer::clearColorBuffer(const Rect& rect) { - if (rect.contains(Rect(mRenderTarget.viewportWidth, mRenderTarget.viewportHeight))) { - // Full viewport is being cleared - disable scissor - mRenderState.scissor().setEnabled(false); - } else { - // Requested rect is subset of viewport - scissor to it to avoid over-clearing - mRenderState.scissor().setEnabled(true); - mRenderState.scissor().set(rect.left, mRenderTarget.viewportHeight - rect.bottom, - rect.getWidth(), rect.getHeight()); - } - glClear(GL_COLOR_BUFFER_BIT); - if (!mRenderTarget.frameBufferId) mHasDrawn = true; -} - -Texture* BakedOpRenderer::getTexture(Bitmap* bitmap) { - return mCaches.textureCache.get(bitmap); -} - -void BakedOpRenderer::drawRects(const float* rects, int count, const SkPaint* paint) { - std::vector<Vertex> vertices; - vertices.reserve(count); - Vertex* vertex = vertices.data(); - - for (int index = 0; index < count; index += 4) { - float l = rects[index + 0]; - float t = rects[index + 1]; - float r = rects[index + 2]; - float b = rects[index + 3]; - - Vertex::set(vertex++, l, t); - Vertex::set(vertex++, r, t); - Vertex::set(vertex++, l, b); - Vertex::set(vertex++, r, b); - } - - LOG_ALWAYS_FATAL_IF(mRenderTarget.frameBufferId != 0, "decoration only supported for FBO 0"); - // TODO: Currently assume full FBO damage, due to FrameInfoVisualizer::unionDirty. - // Should should scissor/set mHasDrawn safely. - mRenderState.scissor().setEnabled(false); - Glop glop; - GlopBuilder(mRenderState, mCaches, &glop) - .setRoundRectClipState(nullptr) - .setMeshIndexedQuads(vertices.data(), count / 4) - .setFillPaint(*paint, 1.0f) - .setTransform(Matrix4::identity(), TransformFlags::None) - .setModelViewIdentityEmptyBounds() - .build(); - mRenderState.render(glop, mRenderTarget.orthoMatrix, false); - mHasDrawn = true; -} - -// 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, false); - mRenderState.stencil().enableTest(incrementThreshold); -} - -void BakedOpRenderer::setupStencilRectList(const ClipBase* clip) { - LOG_ALWAYS_FATAL_IF(clip->mode != ClipMode::RectangleList, - "can't rectlist clip without rectlist"); - auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList; - int quadCount = rectList.getTransformedRectanglesCount(); - std::vector<Vertex> rectangleVertices; - rectangleVertices.reserve(quadCount * 4); - for (int i = 0; i < quadCount; i++) { - const TransformedRectangle& tr(rectList.getTransformedRectangle(i)); - const Matrix4& transform = tr.getTransform(); - Rect bounds = tr.getBounds(); - if (transform.rectToRect()) { - // If rectToRect, can simply map bounds before storing verts - transform.mapRect(bounds); - bounds.doIntersect(clip->rect); - if (bounds.isEmpty()) { - continue; // will be outside of scissor, skip - } - } - - rectangleVertices.push_back(Vertex{bounds.left, bounds.top}); - rectangleVertices.push_back(Vertex{bounds.right, bounds.top}); - rectangleVertices.push_back(Vertex{bounds.left, bounds.bottom}); - rectangleVertices.push_back(Vertex{bounds.right, bounds.bottom}); - - if (!transform.rectToRect()) { - // If not rectToRect, must map each point individually - for (auto cur = rectangleVertices.end() - 4; cur < rectangleVertices.end(); cur++) { - transform.mapPoint(cur->x, cur->y); - } - } - } - setupStencilQuads(rectangleVertices, rectList.getTransformedRectanglesCount()); -} - -void BakedOpRenderer::setupStencilRegion(const ClipBase* clip) { - LOG_ALWAYS_FATAL_IF(clip->mode != ClipMode::Region, "can't region clip without region"); - auto&& region = reinterpret_cast<const ClipRegion*>(clip)->region; - - std::vector<Vertex> regionVertices; - SkRegion::Cliperator it(region, clip->rect.toSkIRect()); - while (!it.done()) { - const SkIRect& r = it.rect(); - regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fTop}); - regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fTop}); - regionVertices.push_back(Vertex{(float)r.fLeft, (float)r.fBottom}); - regionVertices.push_back(Vertex{(float)r.fRight, (float)r.fBottom}); - it.next(); - } - setupStencilQuads(regionVertices, 0); -} - -void BakedOpRenderer::prepareRender(const Rect* dirtyBounds, const ClipBase* clip) { - // Prepare scissor (done before stencil, to simplify filling stencil) - mRenderState.scissor().setEnabled(clip != nullptr); - if (clip) { - mRenderState.scissor().set(mRenderTarget.viewportHeight, clip->rect); - } - - // If stencil may be used for clipping, enable it, fill it, or disable it as appropriate - if (CC_LIKELY(!Properties::debugOverdraw)) { - // only modify stencil mode and content when it's not used for overdraw visualization - if (CC_UNLIKELY(clip && clip->mode != ClipMode::Rectangle)) { - // NOTE: this pointer check is only safe for non-rect clips, - // since rect clips may be created on the stack - if (mRenderTarget.lastStencilClip != clip) { - // Stencil needed, but current stencil isn't up to date - mRenderTarget.lastStencilClip = clip; - - if (mRenderTarget.frameBufferId != 0 && !mRenderTarget.stencil) { - OffscreenBuffer* layer = mRenderTarget.offscreenBuffer; - mRenderTarget.stencil = mCaches.renderBufferCache.get( - Stencil::getLayerStencilFormat(), layer->texture.width(), - layer->texture.height()); - // stencil is bound + allocated - associate it with current FBO - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, - GL_RENDERBUFFER, mRenderTarget.stencil->getName()); - } - - if (clip->mode == ClipMode::RectangleList) { - setupStencilRectList(clip); - } else { - setupStencilRegion(clip); - } - } else { - // stencil is up to date - just need to ensure it's enabled (since an unclipped - // or scissor-only clipped op may have been drawn, disabling the stencil) - int incrementThreshold = 0; - if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) { - auto&& rectList = reinterpret_cast<const ClipRectList*>(clip)->rectList; - incrementThreshold = rectList.getTransformedRectanglesCount(); - } - mRenderState.stencil().enableTest(incrementThreshold); - } - } else { - // either scissor or no clip, so disable stencil test - mRenderState.stencil().disable(); - } - } - - if (dirtyBounds) { - // dirty offscreenbuffer if present - dirtyRenderTarget(*dirtyBounds); - } -} - -void BakedOpRenderer::renderGlopImpl(const Rect* dirtyBounds, const ClipBase* clip, - const Glop& glop) { - prepareRender(dirtyBounds, clip); - // Disable blending if this is the first draw to the main framebuffer, in case app has defined - // transparency where it doesn't make sense - as first draw in opaque window. Note that we only - // apply this improvement when the blend mode is SRC_OVER - other modes (e.g. CLEAR) can be - // valid draws that affect other content (e.g. draw CLEAR, then draw DST_OVER) - bool overrideDisableBlending = !mHasDrawn && mOpaque && !mRenderTarget.frameBufferId && - glop.blend.src == GL_ONE && - glop.blend.dst == GL_ONE_MINUS_SRC_ALPHA; - mRenderState.render(glop, mRenderTarget.orthoMatrix, overrideDisableBlending); - 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); - if (!mRenderTarget.frameBufferId) mHasDrawn = true; -} - -void BakedOpRenderer::dirtyRenderTarget(const Rect& uiDirty) { - if (mRenderTarget.offscreenBuffer) { - mRenderTarget.offscreenBuffer->dirty(uiDirty); - } -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h deleted file mode 100644 index 7c0590d7ab48..000000000000 --- a/libs/hwui/BakedOpRenderer.h +++ /dev/null @@ -1,149 +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. - */ - -#pragma once - -#include "BakedOpState.h" -#include "Matrix.h" -#include "utils/Macros.h" - -namespace android { -namespace uirenderer { - -class Caches; -struct Glop; -class Layer; -class RenderState; -struct ClipBase; - -/** - * Main rendering manager for a collection of work - one frame + any contained FBOs. - * - * Manages frame and FBO lifecycle, binding the GL framebuffer as appropriate. This is the only - * place where FBOs are bound, created, and destroyed. - * - * All rendering operations will be sent by the Dispatcher, a collection of static methods, - * which has intentionally limited access to the renderer functionality. - */ -class BakedOpRenderer { -public: - typedef void (*GlopReceiver)(BakedOpRenderer&, const Rect*, const ClipBase*, const Glop&); - /** - * Position agnostic shadow lighting info. Used with all shadow ops in scene. - */ - struct LightInfo { - LightInfo() : LightInfo(0, 0) {} - LightInfo(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) - : ambientShadowAlpha(ambientShadowAlpha), spotShadowAlpha(spotShadowAlpha) {} - uint8_t ambientShadowAlpha; - uint8_t spotShadowAlpha; - }; - - BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, bool wideColorGamut, - const LightInfo& lightInfo) - : mGlopReceiver(DefaultGlopReceiver) - , mRenderState(renderState) - , mCaches(caches) - , mOpaque(opaque) - , mWideColorGamut(wideColorGamut) - , mLightInfo(lightInfo) {} - - RenderState& renderState() { return mRenderState; } - Caches& caches() { return mCaches; } - - void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect); - void endFrame(const Rect& repaintRect); - WARN_UNUSED_RESULT OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height); - void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer); - void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect); - void endLayer(); - WARN_UNUSED_RESULT OffscreenBuffer* copyToLayer(const Rect& area); - - Texture* getTexture(Bitmap* bitmap); - const LightInfo& getLightInfo() const { return mLightInfo; } - - void renderGlop(const BakedOpState& state, const Glop& glop) { - renderGlop(&state.computedState.clippedBounds, state.computedState.getClipIfNeeded(), glop); - } - void renderFunctor(const FunctorOp& op, const BakedOpState& state); - - void renderGlop(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop) { - mGlopReceiver(*this, dirtyBounds, clip, glop); - } - bool offscreenRenderTarget() { return mRenderTarget.offscreenBuffer != nullptr; } - void dirtyRenderTarget(const Rect& dirtyRect); - bool didDraw() const { return mHasDrawn; } - - uint32_t getViewportWidth() const { return mRenderTarget.viewportWidth; } - uint32_t getViewportHeight() const { return mRenderTarget.viewportHeight; } - - // simple draw methods, to be used for end frame decoration - void drawRect(float left, float top, float right, float bottom, const SkPaint* paint) { - float ltrb[4] = {left, top, right, bottom}; - drawRects(ltrb, 4, paint); - } - void drawRects(const float* rects, int count, const SkPaint* paint); - -protected: - GlopReceiver mGlopReceiver; - -private: - static void DefaultGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds, - const ClipBase* clip, const Glop& glop) { - renderer.renderGlopImpl(dirtyBounds, clip, glop); - } - void renderGlopImpl(const Rect* dirtyBounds, const ClipBase* clip, const Glop& glop); - void setViewport(uint32_t width, uint32_t height); - void clearColorBuffer(const Rect& clearRect); - void prepareRender(const Rect* dirtyBounds, const ClipBase* clip); - void setupStencilRectList(const ClipBase* clip); - void setupStencilRegion(const ClipBase* clip); - void setupStencilQuads(std::vector<Vertex>& quadVertices, int incrementThreshold); - - RenderState& mRenderState; - Caches& mCaches; - bool mOpaque; - bool mWideColorGamut; - bool mHasDrawn = false; - - // render target state - setup by start/end layer/frame - // only valid to use in between start/end pairs. - struct { - // If not drawing to a layer: fbo = 0, offscreenBuffer = null, - // Otherwise these refer to currently painting layer's state - GLuint frameBufferId = 0; - OffscreenBuffer* offscreenBuffer = nullptr; - - // Used when drawing to a layer and using stencil clipping. otherwise null. - RenderBuffer* stencil = nullptr; - - // value representing the ClipRectList* or ClipRegion* currently stored in - // the stencil of the current render target - const ClipBase* lastStencilClip = nullptr; - - // Size of renderable region in current render target - for layers, may not match actual - // bounds of FBO texture. offscreenBuffer->texture has this information. - uint32_t viewportWidth = 0; - uint32_t viewportHeight = 0; - - Matrix4 orthoMatrix; - } mRenderTarget; - - const LightInfo mLightInfo; -}; - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp deleted file mode 100644 index 63edf77279e3..000000000000 --- a/libs/hwui/BakedOpState.cpp +++ /dev/null @@ -1,173 +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 "BakedOpState.h" - -#include "ClipArea.h" - -namespace android { -namespace uirenderer { - -static int computeClipSideFlags(const Rect& clip, const Rect& bounds) { - int clipSideFlags = 0; - if (clip.left > bounds.left) clipSideFlags |= OpClipSideFlags::Left; - if (clip.top > bounds.top) clipSideFlags |= OpClipSideFlags::Top; - if (clip.right < bounds.right) clipSideFlags |= OpClipSideFlags::Right; - if (clip.bottom < bounds.bottom) clipSideFlags |= OpClipSideFlags::Bottom; - return clipSideFlags; -} - -ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp, bool expandForStroke, - bool expandForPathTexture) { - // 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); - } else if (CC_UNLIKELY(expandForPathTexture)) { - clippedBounds.outset(1); - } - transform.mapRect(clippedBounds); - if (CC_UNLIKELY(expandForStroke && - (!transform.isPureTranslate() || recordedOp.paint->getStrokeWidth() < 1.0f))) { - // account for hairline stroke when stroke may be < 1 scaled pixel - // Non translate || strokeWidth < 1 is conservative, but will cover all cases - clippedBounds.outset(0.5f); - } - - // resolvedClipRect = intersect(parentMatrix * localClip, parentClip) - clipState = snapshot.serializeIntersectedClip(allocator, recordedOp.localClip, - *(snapshot.transform)); - LOG_ALWAYS_FATAL_IF(!clipState, "must clip!"); - - const Rect& clipRect = clipState->rect; - if (CC_UNLIKELY(clipRect.isEmpty() || !clippedBounds.intersects(clipRect))) { - // Rejected based on either empty clip, or bounds not intersecting with clip - - // Note: we could rewind the clipState object in situations where the clipRect is empty, - // but *only* if the caching logic within ClipArea was aware of the rewind. - clipState = nullptr; - clippedBounds.setEmpty(); - } else { - // Not rejected! compute true clippedBounds, clipSideFlags, and path mask - clipSideFlags = computeClipSideFlags(clipRect, clippedBounds); - clippedBounds.doIntersect(clipRect); - - if (CC_UNLIKELY(snapshot.projectionPathMask)) { - // map projection path mask from render target space into op space, - // so intersection with op geometry is possible - Matrix4 inverseTransform; - inverseTransform.loadInverse(transform); - SkMatrix skInverseTransform; - inverseTransform.copyTo(skInverseTransform); - - auto localMask = allocator.create<SkPath>(); - snapshot.projectionPathMask->transform(skInverseTransform, localMask); - localProjectionPathMask = localMask; - } - } -} - -ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, - const Matrix4& localTransform, const ClipBase* localClip) { - transform.loadMultiply(*snapshot.transform, localTransform); - clipState = snapshot.serializeIntersectedClip(allocator, localClip, *(snapshot.transform)); - clippedBounds = clipState->rect; - clipSideFlags = OpClipSideFlags::Full; - localProjectionPathMask = nullptr; -} - -ResolvedRenderState::ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot) - : transform(*snapshot.transform) - , clipState(snapshot.mutateClipArea().serializeClip(allocator)) - , clippedBounds(clipState->rect) - , clipSideFlags(OpClipSideFlags::Full) - , localProjectionPathMask(nullptr) {} - -ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect) - : transform(Matrix4::identity()) - , clipState(clipRect) - , clippedBounds(dstRect) - , clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect)) - , localProjectionPathMask(nullptr) { - clippedBounds.doIntersect(clipRect->rect); -} - -BakedOpState* BakedOpState::tryConstruct(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp) { - if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - BakedOpState* bakedState = - allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp, false, false); - if (bakedState->computedState.clippedBounds.isEmpty()) { - // bounds are empty, so op is rejected - allocator.rewindIfLastAlloc(bakedState); - return nullptr; - } - return bakedState; -} - -BakedOpState* BakedOpState::tryConstructUnbounded(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp) { - if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - return allocator.create_trivial<BakedOpState>(allocator, snapshot, recordedOp); -} - -BakedOpState* BakedOpState::tryStrokeableOpConstruct(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp, - StrokeBehavior strokeBehavior, - bool expandForPathTexture) { - if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - bool expandForStroke = - (strokeBehavior == StrokeBehavior::Forced || - (recordedOp.paint && recordedOp.paint->getStyle() != SkPaint::kFill_Style)); - - BakedOpState* bakedState = allocator.create_trivial<BakedOpState>( - allocator, snapshot, recordedOp, expandForStroke, expandForPathTexture); - if (bakedState->computedState.clippedBounds.isEmpty()) { - // bounds are empty, so op is rejected - // NOTE: this won't succeed if a clip was allocated - allocator.rewindIfLastAlloc(bakedState); - return nullptr; - } - return bakedState; -} - -BakedOpState* BakedOpState::tryShadowOpConstruct(LinearAllocator& allocator, Snapshot& snapshot, - const ShadowOp* shadowOpPtr) { - if (CC_UNLIKELY(snapshot.getRenderTargetClip().isEmpty())) return nullptr; - - // clip isn't empty, so construct the op - return allocator.create_trivial<BakedOpState>(allocator, snapshot, shadowOpPtr); -} - -BakedOpState* BakedOpState::directConstruct(LinearAllocator& allocator, const ClipRect* clip, - const Rect& dstRect, const RecordedOp& recordedOp) { - return allocator.create_trivial<BakedOpState>(clip, dstRect, recordedOp); -} - -void BakedOpState::setupOpacity(const SkPaint* paint) { - computedState.opaqueOverClippedBounds = computedState.transform.isSimple() && - computedState.clipState->mode == ClipMode::Rectangle && - MathUtils::areEqual(alpha, 1.0f) && - !roundRectClipState && PaintUtils::isOpaquePaint(paint); -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h deleted file mode 100644 index c74475516cdc..000000000000 --- a/libs/hwui/BakedOpState.h +++ /dev/null @@ -1,171 +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. - */ - -#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, - bool expandForPathTexture); - - // Constructor for unbounded ops *with* transform/clip - ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot, - const Matrix4& localTransform, const ClipBase* localClip); - - // Constructor for unbounded ops without transform/clip (namely shadows) - ResolvedRenderState(LinearAllocator& allocator, Snapshot& snapshot); - - // Constructor for primitive ops provided clip, and no transform - ResolvedRenderState(const ClipRect* viewportRect, const Rect& dstRect); - - Rect computeLocalSpaceClip() const { - Matrix4 inverse; - inverse.loadInverse(transform); - - Rect outClip(clipRect()); - inverse.mapRect(outClip); - return outClip; - } - - const Rect& clipRect() const { return clipState->rect; } - - bool requiresClip() const { - return clipSideFlags != OpClipSideFlags::None || - CC_UNLIKELY(clipState->mode != ClipMode::Rectangle); - } - - // returns the clip if it's needed to draw the operation, otherwise nullptr - const ClipBase* getClipIfNeeded() const { return requiresClip() ? clipState : nullptr; } - - Matrix4 transform; - const ClipBase* clipState = nullptr; - Rect clippedBounds; - int clipSideFlags = 0; - const SkPath* localProjectionPathMask = nullptr; - bool opaqueOverClippedBounds = false; -}; - -/** - * Self-contained op wrapper, containing all resolved state required to draw the op. - * - * Stashed pointers within all point to longer lived objects, with no ownership implied. - */ -class BakedOpState { -public: - static BakedOpState* tryConstruct(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp); - - static BakedOpState* tryConstructUnbounded(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp); - - enum class StrokeBehavior { - // stroking is forced, regardless of style on paint (such as for lines) - Forced, - // stroking is defined by style on paint - StyleDefined, - }; - - static BakedOpState* tryStrokeableOpConstruct(LinearAllocator& allocator, Snapshot& snapshot, - const RecordedOp& recordedOp, - StrokeBehavior strokeBehavior, - bool expandForPathTexture); - - static BakedOpState* tryShadowOpConstruct(LinearAllocator& allocator, Snapshot& snapshot, - const ShadowOp* shadowOpPtr); - - static BakedOpState* directConstruct(LinearAllocator& allocator, const ClipRect* clip, - const Rect& dstRect, const RecordedOp& recordedOp); - - // Set opaqueOverClippedBounds. If this method isn't called, the op is assumed translucent. - void setupOpacity(const SkPaint* paint); - - // computed state: - ResolvedRenderState computedState; - - // simple state (straight pointer/value storage): - const float alpha; - const RoundRectClipState* roundRectClipState; - const RecordedOp* op; - -private: - friend class LinearAllocator; - - BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const RecordedOp& recordedOp, - bool expandForStroke, bool expandForPathTexture) - : computedState(allocator, snapshot, recordedOp, expandForStroke, expandForPathTexture) - , alpha(snapshot.alpha) - , roundRectClipState(snapshot.roundRectClipState) - , op(&recordedOp) {} - - // TODO: fix this brittleness - BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const RecordedOp& recordedOp) - : computedState(allocator, snapshot, recordedOp.localMatrix, recordedOp.localClip) - , alpha(snapshot.alpha) - , roundRectClipState(snapshot.roundRectClipState) - , op(&recordedOp) {} - - BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr) - : computedState(allocator, snapshot) - , alpha(snapshot.alpha) - , roundRectClipState(snapshot.roundRectClipState) - , op(shadowOpPtr) {} - - BakedOpState(const ClipRect* clipRect, const Rect& dstRect, const RecordedOp& recordedOp) - : computedState(clipRect, dstRect) - , alpha(1.0f) - , roundRectClipState(nullptr) - , op(&recordedOp) {} -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_BAKED_OP_STATE_H diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp deleted file mode 100644 index 3c774a3313d2..000000000000 --- a/libs/hwui/Caches.cpp +++ /dev/null @@ -1,274 +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 "Caches.h" - -#include "GammaFontRenderer.h" -#include "GlLayer.h" -#include "Properties.h" -#include "ShadowTessellator.h" -#include "renderstate/RenderState.h" -#ifdef BUGREPORT_FONT_CACHE_USAGE -#include "font/FontCacheHistoryTracker.h" -#endif -#include "utils/GLUtils.h" - -#include <cutils/properties.h> -#include <utils/Log.h> -#include <utils/String8.h> - -namespace android { -namespace uirenderer { - -Caches* Caches::sInstance = nullptr; - -/////////////////////////////////////////////////////////////////////////////// -// Macros -/////////////////////////////////////////////////////////////////////////////// - -#if DEBUG_CACHE_FLUSH -#define FLUSH_LOGD(...) ALOGD(__VA_ARGS__) -#else -#define FLUSH_LOGD(...) -#endif - -/////////////////////////////////////////////////////////////////////////////// -// Constructors/destructor -/////////////////////////////////////////////////////////////////////////////// - -Caches::Caches(RenderState& renderState) - : gradientCache(extensions()) - , patchCache(renderState) - , programCache(extensions()) - , mRenderState(&renderState) - , mInitialized(false) { - INIT_LOGD("Creating OpenGL renderer caches"); - init(); - initConstraints(); - initStaticProperties(); - initExtensions(); -} - -bool Caches::init() { - if (mInitialized) return false; - - ATRACE_NAME("Caches::init"); - - mRegionMesh = nullptr; - mProgram = nullptr; - - mInitialized = true; - - mPixelBufferState = new PixelBufferState(); - mTextureState = new TextureState(); - mTextureState->constructTexture(*this); - - return true; -} - -void Caches::initExtensions() { - if (extensions().hasDebugMarker()) { - eventMark = glInsertEventMarkerEXT; - - startMark = glPushGroupMarkerEXT; - endMark = glPopGroupMarkerEXT; - } else { - eventMark = eventMarkNull; - startMark = startMarkNull; - endMark = endMarkNull; - } -} - -void Caches::initConstraints() { - maxTextureSize = DeviceInfo::get()->maxTextureSize(); -} - -void Caches::initStaticProperties() { - // OpenGL ES 3.0+ specific features - gpuPixelBuffersEnabled = extensions().hasPixelBufferObjects() && - property_get_bool(PROPERTY_ENABLE_GPU_PIXEL_BUFFERS, true); -} - -void Caches::terminate() { - if (!mInitialized) return; - mRegionMesh.reset(nullptr); - - fboCache.clear(); - - programCache.clear(); - mProgram = nullptr; - - patchCache.clear(); - - clearGarbage(); - - delete mPixelBufferState; - mPixelBufferState = nullptr; - delete mTextureState; - mTextureState = nullptr; - mInitialized = false; -} - -void Caches::setProgram(const ProgramDescription& description) { - setProgram(programCache.get(description)); -} - -void Caches::setProgram(Program* program) { - if (!program || !program->isInUse()) { - if (mProgram) { - mProgram->remove(); - } - if (program) { - program->use(); - } - mProgram = program; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Debug -/////////////////////////////////////////////////////////////////////////////// - -uint32_t Caches::getOverdrawColor(uint32_t amount) const { - static uint32_t sOverdrawColors[2][4] = {{0x2f0000ff, 0x2f00ff00, 0x3fff0000, 0x7fff0000}, - {0x2f0000ff, 0x4fffff00, 0x5fff8ad8, 0x7fff0000}}; - if (amount < 1) amount = 1; - if (amount > 4) amount = 4; - - int overdrawColorIndex = static_cast<int>(Properties::overdrawColorSet); - return sOverdrawColors[overdrawColorIndex][amount - 1]; -} - -void Caches::dumpMemoryUsage() { - String8 stringLog; - dumpMemoryUsage(stringLog); - ALOGD("%s", stringLog.string()); -} - -void Caches::dumpMemoryUsage(String8& log) { - uint32_t total = 0; - log.appendFormat("Current memory usage / total memory usage (bytes):\n"); - log.appendFormat(" TextureCache %8d / %8d\n", textureCache.getSize(), - textureCache.getMaxSize()); - if (mRenderState) { - int memused = 0; - for (std::set<Layer*>::iterator it = mRenderState->mActiveLayers.begin(); - it != mRenderState->mActiveLayers.end(); it++) { - const Layer* layer = *it; - LOG_ALWAYS_FATAL_IF(layer->getApi() != Layer::Api::OpenGL); - const GlLayer* glLayer = static_cast<const GlLayer*>(layer); - log.appendFormat(" GlLayer size %dx%d; texid=%u refs=%d\n", layer->getWidth(), - layer->getHeight(), glLayer->getTextureId(), layer->getStrongCount()); - memused += layer->getWidth() * layer->getHeight() * 4; - } - log.appendFormat(" Layers total %8d (numLayers = %zu)\n", memused, - mRenderState->mActiveLayers.size()); - total += memused; - } - log.appendFormat(" RenderBufferCache %8d / %8d\n", renderBufferCache.getSize(), - renderBufferCache.getMaxSize()); - log.appendFormat(" GradientCache %8d / %8d\n", gradientCache.getSize(), - gradientCache.getMaxSize()); - log.appendFormat(" PathCache %8d / %8d\n", pathCache.getSize(), - pathCache.getMaxSize()); - log.appendFormat(" TessellationCache %8d / %8d\n", tessellationCache.getSize(), - tessellationCache.getMaxSize()); - log.appendFormat(" TextDropShadowCache %8d / %8d\n", dropShadowCache.getSize(), - dropShadowCache.getMaxSize()); - log.appendFormat(" PatchCache %8d / %8d\n", patchCache.getSize(), - patchCache.getMaxSize()); - - fontRenderer.dumpMemoryUsage(log); - - log.appendFormat("Other:\n"); - log.appendFormat(" FboCache %8d / %8d\n", fboCache.getSize(), - fboCache.getMaxSize()); - - total += textureCache.getSize(); - total += renderBufferCache.getSize(); - total += gradientCache.getSize(); - total += pathCache.getSize(); - total += tessellationCache.getSize(); - total += dropShadowCache.getSize(); - total += patchCache.getSize(); - total += fontRenderer.getSize(); - - log.appendFormat("Total memory usage:\n"); - log.appendFormat(" %d bytes, %.2f MB\n", total, total / 1024.0f / 1024.0f); - -#ifdef BUGREPORT_FONT_CACHE_USAGE - fontRenderer.getFontRenderer().historyTracker().dump(log); -#endif -} - -/////////////////////////////////////////////////////////////////////////////// -// Memory management -/////////////////////////////////////////////////////////////////////////////// - -void Caches::clearGarbage() { - pathCache.clearGarbage(); - patchCache.clearGarbage(); -} - -void Caches::flush(FlushMode mode) { - FLUSH_LOGD("Flushing caches (mode %d)", mode); - - switch (mode) { - case FlushMode::Full: - textureCache.clear(); - patchCache.clear(); - dropShadowCache.clear(); - gradientCache.clear(); - fontRenderer.clear(); - fboCache.clear(); - // fall through - case FlushMode::Moderate: - fontRenderer.flush(); - textureCache.flush(); - pathCache.clear(); - tessellationCache.clear(); - // fall through - case FlushMode::Layers: - renderBufferCache.clear(); - break; - } - - clearGarbage(); - glFinish(); - // Errors during cleanup should be considered non-fatal, dump them and - // and move on. TODO: All errors or just errors like bad surface? - GLUtils::dumpGLErrors(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Regions -/////////////////////////////////////////////////////////////////////////////// - -TextureVertex* Caches::getRegionMesh() { - // Create the mesh, 2 triangles and 4 vertices per rectangle in the region - if (!mRegionMesh) { - mRegionMesh.reset(new TextureVertex[kMaxNumberOfQuads * 4]); - } - - return mRegionMesh.get(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Temporary Properties -/////////////////////////////////////////////////////////////////////////////// - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h deleted file mode 100644 index 97328324df04..000000000000 --- a/libs/hwui/Caches.h +++ /dev/null @@ -1,201 +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. - */ - -#pragma once - -#include "DeviceInfo.h" -#include "Extensions.h" -#include "FboCache.h" -#include "GammaFontRenderer.h" -#include "GradientCache.h" -#include "PatchCache.h" -#include "PathCache.h" -#include "ProgramCache.h" -#include "RenderBufferCache.h" -#include "ResourceCache.h" -#include "TessellationCache.h" -#include "TextDropShadowCache.h" -#include "TextureCache.h" -#include "renderstate/PixelBufferState.h" -#include "renderstate/TextureState.h" -#include "thread/TaskManager.h" -#include "thread/TaskProcessor.h" - -#include <memory> -#include <vector> - -#include <GLES3/gl3.h> - -#include <utils/KeyedVector.h> - -#include <cutils/compiler.h> - -#include <SkPath.h> - -#include <vector> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Caches -/////////////////////////////////////////////////////////////////////////////// - -class RenderNode; -class RenderState; - -class ANDROID_API Caches { -public: - static Caches& createInstance(RenderState& renderState) { - LOG_ALWAYS_FATAL_IF(sInstance, "double create of Caches attempted"); - sInstance = new Caches(renderState); - return *sInstance; - } - - static Caches& getInstance() { - LOG_ALWAYS_FATAL_IF(!sInstance, "instance not yet created"); - return *sInstance; - } - - static bool hasInstance() { return sInstance != nullptr; } - -private: - explicit Caches(RenderState& renderState); - static Caches* sInstance; - -public: - enum class FlushMode { Layers = 0, Moderate, Full }; - - /** - * Initialize caches. - */ - bool init(); - - bool isInitialized() { return mInitialized; } - - /** - * Flush the cache. - * - * @param mode Indicates how much of the cache should be flushed - */ - void flush(FlushMode mode); - - /** - * Destroys all resources associated with this cache. This should - * be called after a flush(FlushMode::Full). - */ - void terminate(); - - /** - * Returns a non-premultiplied ARGB color for the specified - * amount of overdraw (1 for 1x, 2 for 2x, etc.) - */ - uint32_t getOverdrawColor(uint32_t amount) const; - - /** - * Call this on each frame to ensure that garbage is deleted from - * GPU memory. - */ - void clearGarbage(); - - /** - * Can be used to delete a layer from a non EGL thread. - */ - void deleteLayerDeferred(Layer* layer); - - /** - * Returns the mesh used to draw regions. Calling this method will - * bind a VBO of type GL_ELEMENT_ARRAY_BUFFER that contains the - * indices for the region mesh. - */ - TextureVertex* getRegionMesh(); - - /** - * Returns the GL RGBA internal format to use for the current device - * If the device supports linear blending and needSRGB is true, - * this function returns GL_SRGB8_ALPHA8, otherwise it returns GL_RGBA - */ - constexpr GLint rgbaInternalFormat(bool needSRGB = true) const { - return extensions().hasLinearBlending() && needSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA; - } - - /** - * Displays the memory usage of each cache and the total sum. - */ - void dumpMemoryUsage(); - void dumpMemoryUsage(String8& log); - - // Misc - GLint maxTextureSize; - -public: - TextureCache textureCache; - RenderBufferCache renderBufferCache; - GradientCache gradientCache; - PatchCache patchCache; - PathCache pathCache; - ProgramCache programCache; - TessellationCache tessellationCache; - TextDropShadowCache dropShadowCache; - FboCache fboCache; - - GammaFontRenderer fontRenderer; - - TaskManager tasks; - - bool gpuPixelBuffersEnabled; - - // Debug methods - PFNGLINSERTEVENTMARKEREXTPROC eventMark; - PFNGLPUSHGROUPMARKEREXTPROC startMark; - PFNGLPOPGROUPMARKEREXTPROC endMark; - - void setProgram(const ProgramDescription& description); - void setProgram(Program* program); - - const Extensions& extensions() const { return DeviceInfo::get()->extensions(); } - Program& program() { return *mProgram; } - PixelBufferState& pixelBufferState() { return *mPixelBufferState; } - TextureState& textureState() { return *mTextureState; } - -private: - void initExtensions(); - void initConstraints(); - void initStaticProperties(); - - static void eventMarkNull(GLsizei length, const GLchar* marker) {} - static void startMarkNull(GLsizei length, const GLchar* marker) {} - static void endMarkNull() {} - - RenderState* mRenderState; - - // Used to render layers - std::unique_ptr<TextureVertex[]> mRegionMesh; - - mutable Mutex mGarbageLock; - std::vector<Layer*> mLayerGarbage; - - bool mInitialized; - - // TODO: move below to RenderState - PixelBufferState* mPixelBufferState = nullptr; - TextureState* mTextureState = nullptr; - Program* mProgram = nullptr; // note: object owned by ProgramCache - -}; // class Caches - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp deleted file mode 100644 index d18c4abde7f2..000000000000 --- a/libs/hwui/CanvasState.cpp +++ /dev/null @@ -1,284 +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 "CanvasState.h" -#include "hwui/Canvas.h" -#include "utils/MathUtils.h" - -namespace android { -namespace uirenderer { - -CanvasState::CanvasState(CanvasStateClient& renderer) - : mWidth(-1), mHeight(-1), mSaveCount(1), mCanvas(renderer), mSnapshot(&mFirstSnapshot) {} - -CanvasState::~CanvasState() { - // First call freeSnapshot on all but mFirstSnapshot - // to invoke all the dtors - freeAllSnapshots(); - - // Now actually release the memory - while (mSnapshotPool) { - void* temp = mSnapshotPool; - mSnapshotPool = mSnapshotPool->previous; - free(temp); - } -} - -void CanvasState::initializeRecordingSaveStack(int viewportWidth, int viewportHeight) { - if (mWidth != viewportWidth || mHeight != viewportHeight) { - mWidth = viewportWidth; - mHeight = viewportHeight; - mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight); - mCanvas.onViewportInitialized(); - } - - freeAllSnapshots(); - mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip); - mSnapshot->setRelativeLightCenter(Vector3()); - mSaveCount = 1; -} - -void CanvasState::initializeSaveStack(int viewportWidth, int viewportHeight, float clipLeft, - float clipTop, float clipRight, float clipBottom, - const Vector3& lightCenter) { - if (mWidth != viewportWidth || mHeight != viewportHeight) { - mWidth = viewportWidth; - mHeight = viewportHeight; - mFirstSnapshot.initializeViewport(viewportWidth, viewportHeight); - mCanvas.onViewportInitialized(); - } - - freeAllSnapshots(); - mSnapshot = allocSnapshot(&mFirstSnapshot, SaveFlags::MatrixClip); - mSnapshot->setClip(clipLeft, clipTop, clipRight, clipBottom); - mSnapshot->fbo = mCanvas.getTargetFbo(); - mSnapshot->setRelativeLightCenter(lightCenter); - mSaveCount = 1; -} - -Snapshot* CanvasState::allocSnapshot(Snapshot* previous, int savecount) { - void* memory; - if (mSnapshotPool) { - memory = mSnapshotPool; - mSnapshotPool = mSnapshotPool->previous; - mSnapshotPoolCount--; - } else { - memory = malloc(sizeof(Snapshot)); - } - return new (memory) Snapshot(previous, savecount); -} - -void CanvasState::freeSnapshot(Snapshot* snapshot) { - snapshot->~Snapshot(); - // Arbitrary number, just don't let this grown unbounded - if (mSnapshotPoolCount > 10) { - free((void*)snapshot); - } else { - snapshot->previous = mSnapshotPool; - mSnapshotPool = snapshot; - mSnapshotPoolCount++; - } -} - -void CanvasState::freeAllSnapshots() { - while (mSnapshot != &mFirstSnapshot) { - Snapshot* temp = mSnapshot; - mSnapshot = mSnapshot->previous; - freeSnapshot(temp); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Save (layer) -/////////////////////////////////////////////////////////////////////////////// - -/** - * Guaranteed to save without side-effects - * - * This approach, here and in restoreSnapshot(), allows subclasses to directly manipulate the save - * stack, and ensures restoreToCount() doesn't call back into subclass overrides. - */ -int CanvasState::saveSnapshot(int flags) { - mSnapshot = allocSnapshot(mSnapshot, flags); - return mSaveCount++; -} - -int CanvasState::save(int flags) { - return saveSnapshot(flags); -} - -/** - * Guaranteed to restore without side-effects. - */ -void CanvasState::restoreSnapshot() { - Snapshot* toRemove = mSnapshot; - Snapshot* toRestore = mSnapshot->previous; - - mSaveCount--; - mSnapshot = toRestore; - - // subclass handles restore implementation - mCanvas.onSnapshotRestored(*toRemove, *toRestore); - - freeSnapshot(toRemove); -} - -void CanvasState::restore() { - if (mSaveCount > 1) { - restoreSnapshot(); - } -} - -void CanvasState::restoreToCount(int saveCount) { - if (saveCount < 1) saveCount = 1; - - while (mSaveCount > saveCount) { - restoreSnapshot(); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Matrix -/////////////////////////////////////////////////////////////////////////////// - -void CanvasState::getMatrix(SkMatrix* matrix) const { - mSnapshot->transform->copyTo(*matrix); -} - -void CanvasState::translate(float dx, float dy, float dz) { - mSnapshot->transform->translate(dx, dy, dz); -} - -void CanvasState::rotate(float degrees) { - mSnapshot->transform->rotate(degrees, 0.0f, 0.0f, 1.0f); -} - -void CanvasState::scale(float sx, float sy) { - mSnapshot->transform->scale(sx, sy, 1.0f); -} - -void CanvasState::skew(float sx, float sy) { - mSnapshot->transform->skew(sx, sy); -} - -void CanvasState::setMatrix(const SkMatrix& matrix) { - mSnapshot->transform->load(matrix); -} - -void CanvasState::setMatrix(const Matrix4& matrix) { - *(mSnapshot->transform) = matrix; -} - -void CanvasState::concatMatrix(const SkMatrix& matrix) { - mat4 transform(matrix); - mSnapshot->transform->multiply(transform); -} - -void CanvasState::concatMatrix(const Matrix4& matrix) { - mSnapshot->transform->multiply(matrix); -} - -/////////////////////////////////////////////////////////////////////////////// -// Clip -/////////////////////////////////////////////////////////////////////////////// - -bool CanvasState::clipRect(float left, float top, float right, float bottom, SkClipOp op) { - mSnapshot->clip(Rect(left, top, right, bottom), op); - return !mSnapshot->clipIsEmpty(); -} - -bool CanvasState::clipPath(const SkPath* path, SkClipOp op) { - mSnapshot->clipPath(*path, op); - return !mSnapshot->clipIsEmpty(); -} - -void CanvasState::setClippingOutline(LinearAllocator& allocator, const Outline* outline) { - Rect bounds; - float radius; - if (!outline->getAsRoundRect(&bounds, &radius)) return; // only RR supported - - bool outlineIsRounded = MathUtils::isPositive(radius); - if (!outlineIsRounded || currentTransform()->isSimple()) { - // TODO: consider storing this rect separately, so that this can't be replaced with clip ops - clipRect(bounds.left, bounds.top, bounds.right, bounds.bottom, SkClipOp::kIntersect); - } - if (outlineIsRounded) { - setClippingRoundRect(allocator, bounds, radius, false); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Quick Rejection -/////////////////////////////////////////////////////////////////////////////// - -/** - * Calculates whether content drawn within the passed bounds would be outside of, or intersect with - * the clipRect. Does not modify the scissor. - * - * @param clipRequired if not null, will be set to true if element intersects clip - * (and wasn't rejected) - * - * @param snapOut if set, the geometry will be treated as having an AA ramp. - * See Rect::snapGeometryToPixelBoundaries() - */ -bool CanvasState::calculateQuickRejectForScissor(float left, float top, float right, float bottom, - bool* clipRequired, bool* roundRectClipRequired, - bool snapOut) const { - if (bottom <= top || right <= left) { - return true; - } - - Rect r(left, top, right, bottom); - currentTransform()->mapRect(r); - r.snapGeometryToPixelBoundaries(snapOut); - - Rect clipRect(currentRenderTargetClip()); - clipRect.snapToPixelBoundaries(); - - if (!clipRect.intersects(r)) return true; - - // clip is required if geometry intersects clip rect - if (clipRequired) { - *clipRequired = !clipRect.contains(r); - } - - // round rect clip is required if RR clip exists, and geometry intersects its corners - if (roundRectClipRequired) { - *roundRectClipRequired = mSnapshot->roundRectClipState != nullptr && - mSnapshot->roundRectClipState->areaRequiresRoundRectClip(r); - } - return false; -} - -bool CanvasState::quickRejectConservative(float left, float top, float right, float bottom) const { - if (bottom <= top || right <= left) { - return true; - } - - Rect r(left, top, right, bottom); - currentTransform()->mapRect(r); - r.roundOut(); // rounded out to be conservative - - Rect clipRect(currentRenderTargetClip()); - clipRect.snapToPixelBoundaries(); - - if (!clipRect.intersects(r)) return true; - - return false; -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h deleted file mode 100644 index 9ac35ff47dab..000000000000 --- a/libs/hwui/CanvasState.h +++ /dev/null @@ -1,195 +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. - */ - -#pragma once - -#include "Snapshot.h" - -#include <SkClipOp.h> -#include <SkMatrix.h> -#include <SkPath.h> -#include <SkRegion.h> - -namespace android { -namespace uirenderer { - -/** - * Abstract base class for any class containing CanvasState. - * Defines three mandatory callbacks. - */ -class CanvasStateClient { -public: - CanvasStateClient() {} - virtual ~CanvasStateClient() {} - - /** - * Callback allowing embedder to take actions in the middle of a - * setViewport() call. - */ - virtual void onViewportInitialized() = 0; - - /** - * Callback allowing embedder to take actions in the middle of a - * restore() call. May be called several times sequentially. - */ - virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) = 0; - - /** - * Allows subclasses to control what value is stored in snapshot's - * fbo field in * initializeSaveStack. - */ - virtual GLuint getTargetFbo() const = 0; - -}; // class CanvasStateClient - -/** - * Implements Canvas state methods on behalf of Renderers. - * - * Manages the Snapshot stack, implementing matrix, save/restore, and clipping methods in the - * Renderer interface. Drawing and recording classes that include a CanvasState will have - * different use cases: - * - * Drawing code maintaining canvas state (e.g. FrameBuilder) can query attributes (such as - * transform) or hook into changes (e.g. save/restore) with minimal surface area for manipulating - * the stack itself. - * - * Recording code maintaining canvas state (e.g. RecordingCanvas) can both record and pass - * through state operations to CanvasState, so that not only will querying operations work - * (getClip/Matrix), but so that quickRejection can also be used. - */ - -class CanvasState { -public: - explicit CanvasState(CanvasStateClient& renderer); - ~CanvasState(); - - /** - * Initializes the first snapshot, computing the projection matrix, - * and stores the dimensions of the render target. - */ - void initializeRecordingSaveStack(int viewportWidth, int viewportHeight); - - /** - * 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()); } - - // Save (layer) - int getSaveCount() const { return mSaveCount; } - int save(int flags); - void restore(); - void restoreToCount(int saveCount); - - // Save/Restore without side-effects - int saveSnapshot(int flags); - void restoreSnapshot(); - - // Matrix - void getMatrix(SkMatrix* outMatrix) const; - void translate(float dx, float dy, float dz = 0.0f); - void rotate(float degrees); - void scale(float sx, float sy); - void skew(float sx, float sy); - - void setMatrix(const SkMatrix& matrix); - void setMatrix(const Matrix4& matrix); // internal only convenience method - void concatMatrix(const SkMatrix& matrix); - void concatMatrix(const Matrix4& matrix); // internal only convenience method - - // Clip - const Rect& getLocalClipBounds() const { return mSnapshot->getLocalClip(); } - const Rect& getRenderTargetClipBounds() const { return mSnapshot->getRenderTargetClip(); } - - bool quickRejectConservative(float left, float top, float right, float bottom) const; - - bool clipRect(float left, float top, float right, float bottom, SkClipOp op); - bool clipPath(const SkPath* path, SkClipOp op); - - /** - * Sets a "clipping outline", which is independent from the regular clip. - * Currently only supports rectangles or rounded rectangles; passing in a - * more complicated outline fails silently. Replaces any previous clipping - * outline. - */ - void setClippingOutline(LinearAllocator& allocator, const Outline* outline); - void setClippingRoundRect(LinearAllocator& allocator, const Rect& rect, float radius, - bool highPriority = true) { - mSnapshot->setClippingRoundRect(allocator, rect, radius, highPriority); - } - void setProjectionPathMask(const SkPath* path) { mSnapshot->setProjectionPathMask(path); } - - /** - * Returns true if drawing in the rectangle (left, top, right, bottom) - * will be clipped out. Is conservative: might return false when subpixel- - * perfect tests would return true. - */ - bool calculateQuickRejectForScissor(float left, float top, float right, float bottom, - bool* clipRequired, bool* roundRectClipRequired, - bool snapOut) const; - - void scaleAlpha(float alpha) { mSnapshot->alpha *= alpha; } - - inline const mat4* currentTransform() const { return currentSnapshot()->transform; } - inline const Rect& currentRenderTargetClip() const { - return currentSnapshot()->getRenderTargetClip(); - } - inline int currentFlags() const { return currentSnapshot()->flags; } - const Vector3& currentLightCenter() const { - return currentSnapshot()->getRelativeLightCenter(); - } - int getViewportWidth() const { return currentSnapshot()->getViewportWidth(); } - int getViewportHeight() const { return currentSnapshot()->getViewportHeight(); } - int getWidth() const { return mWidth; } - int getHeight() const { return mHeight; } - bool clipIsSimple() const { return currentSnapshot()->clipIsSimple(); } - - inline const Snapshot* currentSnapshot() const { return mSnapshot; } - inline Snapshot* writableSnapshot() { return mSnapshot; } - inline const Snapshot* firstSnapshot() const { return &mFirstSnapshot; } - -private: - Snapshot* allocSnapshot(Snapshot* previous, int savecount); - void freeSnapshot(Snapshot* snapshot); - void freeAllSnapshots(); - - /// Dimensions of the drawing surface - int mWidth, mHeight; - - /// Number of saved states - int mSaveCount; - - /// Base state - Snapshot mFirstSnapshot; - - /// Host providing callbacks - CanvasStateClient& mCanvas; - - /// Current state - Snapshot* mSnapshot; - - // Pool of allocated snapshots to re-use - // NOTE: The dtors have already been invoked! - Snapshot* mSnapshotPool = nullptr; - int mSnapshotPoolCount = 0; - -}; // class CanvasState - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp new file mode 100644 index 000000000000..06e937ab66f4 --- /dev/null +++ b/libs/hwui/CanvasTransform.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 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 "CanvasTransform.h" +#include "Properties.h" +#include "utils/Color.h" + +#include <SkColorFilter.h> +#include <SkGradientShader.h> +#include <SkPaint.h> +#include <SkShader.h> +#include <ui/ColorSpace.h> + +#include <algorithm> +#include <cmath> + +#include <log/log.h> +#include <SkHighContrastFilter.h> + +namespace android::uirenderer { + +static SkColor makeLight(SkColor color) { + Lab lab = sRGBToLab(color); + float invertedL = std::min(110 - lab.L, 100.0f); + if (invertedL > lab.L) { + lab.L = invertedL; + return LabToSRGB(lab, SkColorGetA(color)); + } else { + return color; + } +} + +static SkColor makeDark(SkColor color) { + Lab lab = sRGBToLab(color); + float invertedL = std::min(110 - lab.L, 100.0f); + if (invertedL < lab.L) { + lab.L = invertedL; + return LabToSRGB(lab, SkColorGetA(color)); + } else { + return color; + } +} + +static SkColor transformColor(ColorTransform transform, SkColor color) { + switch (transform) { + case ColorTransform::Light: + return makeLight(color); + case ColorTransform::Dark: + return makeDark(color); + default: + return color; + } +} + +static void applyColorTransform(ColorTransform transform, SkPaint& paint) { + if (transform == ColorTransform::None) return; + + SkColor newColor = transformColor(transform, paint.getColor()); + paint.setColor(newColor); + + if (paint.getShader()) { + SkShader::GradientInfo info; + std::array<SkColor, 10> _colorStorage; + std::array<SkScalar, _colorStorage.size()> _offsetStorage; + info.fColorCount = _colorStorage.size(); + info.fColors = _colorStorage.data(); + info.fColorOffsets = _offsetStorage.data(); + SkShader::GradientType type = paint.getShader()->asAGradient(&info); + + if (info.fColorCount <= 10) { + switch (type) { + case SkShader::kLinear_GradientType: + for (int i = 0; i < info.fColorCount; i++) { + info.fColors[i] = transformColor(transform, info.fColors[i]); + } + paint.setShader(SkGradientShader::MakeLinear(info.fPoint, info.fColors, + info.fColorOffsets, info.fColorCount, + info.fTileMode, info.fGradientFlags, nullptr)); + break; + default:break; + } + + } + } + + if (paint.getColorFilter()) { + SkBlendMode mode; + SkColor color; + // TODO: LRU this or something to avoid spamming new color mode filters + if (paint.getColorFilter()->asColorMode(&color, &mode)) { + color = transformColor(transform, color); + paint.setColorFilter(SkColorFilter::MakeModeFilter(color, mode)); + } + } +} + +static BitmapPalette paletteForColorHSV(SkColor color) { + float hsv[3]; + SkColorToHSV(color, hsv); + return hsv[2] >= .5f ? BitmapPalette::Light : BitmapPalette::Dark; +} + +static BitmapPalette filterPalette(const SkPaint* paint, BitmapPalette palette) { + if (palette == BitmapPalette::Unknown || !paint || !paint->getColorFilter()) { + return palette; + } + + SkColor color = palette == BitmapPalette::Light ? SK_ColorWHITE : SK_ColorBLACK; + color = paint->getColorFilter()->filterColor(color); + return paletteForColorHSV(color); +} + +bool transformPaint(ColorTransform transform, SkPaint* paint) { + // TODO + applyColorTransform(transform, *paint); + return true; +} + +bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette) { + palette = filterPalette(paint, palette); + bool shouldInvert = false; + if (palette == BitmapPalette::Light && transform == ColorTransform::Dark) { + shouldInvert = true; + } + if (palette == BitmapPalette::Dark && transform == ColorTransform::Light) { + shouldInvert = true; + } + if (shouldInvert) { + SkHighContrastConfig config; + config.fInvertStyle = SkHighContrastConfig::InvertStyle::kInvertLightness; + paint->setColorFilter(SkHighContrastFilter::Make(config)->makeComposed(paint->refColorFilter())); + } + return shouldInvert; +} + +}; // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h new file mode 100644 index 000000000000..e723d645e05e --- /dev/null +++ b/libs/hwui/CanvasTransform.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 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 "hwui/Bitmap.h" + +#include <SkCanvas.h> +#include <SkPaintFilterCanvas.h> + +#include <memory> + +namespace android::uirenderer { + +enum class UsageHint { + Unknown = 0, + Background = 1, + Foreground = 2, +}; + +enum class ColorTransform { + None, + Light, + Dark, +}; + +// True if the paint was modified, false otherwise +bool transformPaint(ColorTransform transform, SkPaint* paint); + +bool transformPaint(ColorTransform transform, SkPaint* paint, BitmapPalette palette); + +} // namespace android::uirenderer;
\ No newline at end of file diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp deleted file mode 100644 index 27d93cfa0391..000000000000 --- a/libs/hwui/ClipArea.cpp +++ /dev/null @@ -1,534 +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 "ClipArea.h" - -#include "utils/LinearAllocator.h" - -#include <SkPath.h> -#include <limits> -#include <type_traits> - -namespace android { -namespace uirenderer { - -static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) { - Vertex v = {x, y}; - transform.mapPoint(v.x, v.y); - transformedBounds.expandToCover(v.x, v.y); -} - -Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) { - const float kMinFloat = std::numeric_limits<float>::lowest(); - const float kMaxFloat = std::numeric_limits<float>::max(); - Rect transformedBounds = {kMaxFloat, kMaxFloat, kMinFloat, kMinFloat}; - handlePoint(transformedBounds, transform, r.left, r.top); - handlePoint(transformedBounds, transform, r.right, r.top); - handlePoint(transformedBounds, transform, r.left, r.bottom); - handlePoint(transformedBounds, transform, r.right, r.bottom); - return transformedBounds; -} - -void ClipBase::dump() const { - ALOGD("mode %d" RECT_STRING, mode, RECT_ARGS(rect)); -} - -/* - * TransformedRectangle - */ - -TransformedRectangle::TransformedRectangle() {} - -TransformedRectangle::TransformedRectangle(const Rect& bounds, const Matrix4& transform) - : mBounds(bounds), mTransform(transform) {} - -bool TransformedRectangle::canSimplyIntersectWith(const TransformedRectangle& other) const { - return mTransform == other.mTransform; -} - -void TransformedRectangle::intersectWith(const TransformedRectangle& other) { - mBounds.doIntersect(other.mBounds); -} - -bool TransformedRectangle::isEmpty() const { - return mBounds.isEmpty(); -} - -/* - * RectangleList - */ - -RectangleList::RectangleList() : mTransformedRectanglesCount(0) {} - -bool RectangleList::isEmpty() const { - if (mTransformedRectanglesCount < 1) { - return true; - } - - for (int i = 0; i < mTransformedRectanglesCount; i++) { - if (mTransformedRectangles[i].isEmpty()) { - return true; - } - } - return false; -} - -int RectangleList::getTransformedRectanglesCount() const { - return mTransformedRectanglesCount; -} - -const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const { - return mTransformedRectangles[i]; -} - -void RectangleList::setEmpty() { - mTransformedRectanglesCount = 0; -} - -void RectangleList::set(const Rect& bounds, const Matrix4& transform) { - mTransformedRectanglesCount = 1; - mTransformedRectangles[0] = TransformedRectangle(bounds, transform); -} - -bool RectangleList::intersectWith(const Rect& bounds, const Matrix4& transform) { - TransformedRectangle newRectangle(bounds, transform); - - // Try to find a rectangle with a compatible transformation - int index = 0; - for (; index < mTransformedRectanglesCount; index++) { - TransformedRectangle& tr(mTransformedRectangles[index]); - if (tr.canSimplyIntersectWith(newRectangle)) { - tr.intersectWith(newRectangle); - return true; - } - } - - // Add it to the list if there is room - if (index < kMaxTransformedRectangles) { - mTransformedRectangles[index] = newRectangle; - mTransformedRectanglesCount += 1; - return true; - } - - // This rectangle list is full - return false; -} - -Rect RectangleList::calculateBounds() const { - Rect bounds; - for (int index = 0; index < mTransformedRectanglesCount; index++) { - const TransformedRectangle& tr(mTransformedRectangles[index]); - if (index == 0) { - bounds = tr.transformedBounds(); - } else { - bounds.doIntersect(tr.transformedBounds()); - } - } - return bounds; -} - -static SkPath pathFromTransformedRectangle(const Rect& bounds, const Matrix4& transform) { - SkPath rectPath; - SkPath rectPathTransformed; - rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom); - SkMatrix skTransform; - transform.copyTo(skTransform); - rectPath.transform(skTransform, &rectPathTransformed); - return rectPathTransformed; -} - -SkRegion RectangleList::convertToRegion(const SkRegion& clip) const { - SkRegion rectangleListAsRegion; - for (int index = 0; index < mTransformedRectanglesCount; index++) { - const TransformedRectangle& tr(mTransformedRectangles[index]); - SkPath rectPathTransformed = - pathFromTransformedRectangle(tr.getBounds(), tr.getTransform()); - if (index == 0) { - rectangleListAsRegion.setPath(rectPathTransformed, clip); - } else { - SkRegion rectRegion; - rectRegion.setPath(rectPathTransformed, clip); - rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op); - } - } - return rectangleListAsRegion; -} - -void RectangleList::transform(const Matrix4& transform) { - for (int index = 0; index < mTransformedRectanglesCount; index++) { - mTransformedRectangles[index].transform(transform); - } -} - -/* - * ClipArea - */ - -ClipArea::ClipArea() : mMode(ClipMode::Rectangle) {} - -/* - * Interface - */ - -void ClipArea::setViewportDimensions(int width, int height) { - mPostViewportClipObserved = false; - mViewportBounds.set(0, 0, width, height); - mClipRect = mViewportBounds; -} - -void ClipArea::setEmpty() { - onClipUpdated(); - mMode = ClipMode::Rectangle; - mClipRect.setEmpty(); - mClipRegion.setEmpty(); - mRectangleList.setEmpty(); -} - -void ClipArea::setClip(float left, float top, float right, float bottom) { - onClipUpdated(); - mMode = ClipMode::Rectangle; - mClipRect.set(left, top, right, bottom); - mClipRegion.setEmpty(); -} - -void ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op) { - if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; - if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; - onClipUpdated(); - switch (mMode) { - case ClipMode::Rectangle: - rectangleModeClipRectWithTransform(r, transform, op); - break; - case ClipMode::RectangleList: - rectangleListModeClipRectWithTransform(r, transform, op); - break; - case ClipMode::Region: - regionModeClipRectWithTransform(r, transform, op); - break; - } -} - -void ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) { - if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; - if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; - onClipUpdated(); - enterRegionMode(); - mClipRegion.op(region, op); - onClipRegionUpdated(); -} - -void ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform, SkRegion::Op op) { - if (op == SkRegion::kReplace_Op) mReplaceOpObserved = true; - if (!mPostViewportClipObserved && op == SkRegion::kIntersect_Op) op = SkRegion::kReplace_Op; - onClipUpdated(); - SkMatrix skTransform; - transform->copyTo(skTransform); - SkPath transformed; - path.transform(skTransform, &transformed); - SkRegion region; - regionFromPath(transformed, region); - enterRegionMode(); - mClipRegion.op(region, op); - onClipRegionUpdated(); -} - -/* - * Rectangle mode - */ - -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 = ClipMode::Rectangle; -} - -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; - } else if (op != SkRegion::kIntersect_Op) { - enterRegionMode(); - regionModeClipRectWithTransform(r, transform, op); - return; - } - - if (transform->rectToRect()) { - Rect transformed(r); - transform->mapRect(transformed); - mClipRect.doIntersect(transformed); - return; - } - - enterRectangleListMode(); - rectangleListModeClipRectWithTransform(r, transform, op); -} - -/* - * RectangleList mode implementation - */ - -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 == ClipMode::Rectangle); - mMode = ClipMode::RectangleList; - mRectangleList.set(mClipRect, Matrix4::identity()); -} - -void ClipArea::rectangleListModeClipRectWithTransform(const Rect& r, const mat4* transform, - SkRegion::Op op) { - if (op != SkRegion::kIntersect_Op || !mRectangleList.intersectWith(r, *transform)) { - enterRegionMode(); - regionModeClipRectWithTransform(r, transform, op); - } -} - -/* - * Region mode implementation - */ - -void ClipArea::enterRegionMode() { - ClipMode oldMode = mMode; - mMode = ClipMode::Region; - if (oldMode != ClipMode::Region) { - if (oldMode == ClipMode::Rectangle) { - mClipRegion.setRect(mClipRect.toSkIRect()); - } else { - mClipRegion = mRectangleList.convertToRegion(createViewportRegion()); - onClipRegionUpdated(); - } - } -} - -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(); -} - -void ClipArea::onClipRegionUpdated() { - if (!mClipRegion.isEmpty()) { - mClipRect.set(mClipRegion.getBounds()); - - if (mClipRegion.isRect()) { - mClipRegion.setEmpty(); - enterRectangleMode(); - } - } else { - mClipRect.setEmpty(); - } -} - -/** - * Clip serialization - */ - -const ClipBase* ClipArea::serializeClip(LinearAllocator& allocator) { - if (!mPostViewportClipObserved) { - // Only initial clip-to-viewport observed, so no serialization of clip necessary - return nullptr; - } - - static_assert(std::is_trivially_destructible<Rect>::value, - "expect Rect to be trivially destructible"); - static_assert(std::is_trivially_destructible<RectangleList>::value, - "expect RectangleList to be trivially destructible"); - - if (mLastSerialization == nullptr) { - ClipBase* serialization = nullptr; - switch (mMode) { - case ClipMode::Rectangle: - serialization = allocator.create<ClipRect>(mClipRect); - break; - case ClipMode::RectangleList: - serialization = allocator.create<ClipRectList>(mRectangleList); - serialization->rect = mRectangleList.calculateBounds(); - break; - case ClipMode::Region: - serialization = allocator.create<ClipRegion>(mClipRegion); - serialization->rect.set(mClipRegion.getBounds()); - break; - } - serialization->intersectWithRoot = mReplaceOpObserved; - // TODO: this is only done for draw time, should eventually avoid for record time - serialization->rect.snapToPixelBoundaries(); - mLastSerialization = serialization; - } - return mLastSerialization; -} - -inline static const RectangleList& getRectList(const ClipBase* scb) { - return reinterpret_cast<const ClipRectList*>(scb)->rectList; -} - -inline static const SkRegion& getRegion(const ClipBase* scb) { - return reinterpret_cast<const ClipRegion*>(scb)->region; -} - -// Conservative check for too many rectangles to fit in rectangle list. -// For simplicity, doesn't account for rect merging -static bool cannotFitInRectangleList(const ClipArea& clipArea, const ClipBase* scb) { - int currentRectCount = clipArea.isRectangleList() - ? clipArea.getRectangleList().getTransformedRectanglesCount() - : 1; - int recordedRectCount = (scb->mode == ClipMode::RectangleList) - ? getRectList(scb).getTransformedRectanglesCount() - : 1; - return currentRectCount + recordedRectCount > RectangleList::kMaxTransformedRectangles; -} - -static const ClipRect sEmptyClipRect(Rect(0, 0)); - -const ClipBase* ClipArea::serializeIntersectedClip(LinearAllocator& allocator, - const ClipBase* recordedClip, - const Matrix4& recordedClipTransform) { - // if no recordedClip passed, just serialize current state - if (!recordedClip) return serializeClip(allocator); - - // if either is empty, clip is empty - if (CC_UNLIKELY(recordedClip->rect.isEmpty()) || mClipRect.isEmpty()) return &sEmptyClipRect; - - if (!mLastResolutionResult || recordedClip != mLastResolutionClip || - recordedClipTransform != mLastResolutionTransform) { - mLastResolutionClip = recordedClip; - mLastResolutionTransform = recordedClipTransform; - - if (CC_LIKELY(mMode == ClipMode::Rectangle && recordedClip->mode == ClipMode::Rectangle && - recordedClipTransform.rectToRect())) { - // common case - result is a single rectangle - auto rectClip = allocator.create<ClipRect>(recordedClip->rect); - recordedClipTransform.mapRect(rectClip->rect); - rectClip->rect.doIntersect(mClipRect); - rectClip->rect.snapToPixelBoundaries(); - mLastResolutionResult = rectClip; - } else if (CC_UNLIKELY(mMode == ClipMode::Region || - recordedClip->mode == ClipMode::Region || - cannotFitInRectangleList(*this, recordedClip))) { - // region case - SkRegion other; - switch (recordedClip->mode) { - case ClipMode::Rectangle: - if (CC_LIKELY(recordedClipTransform.rectToRect())) { - // simple transform, skip creating SkPath - Rect resultClip(recordedClip->rect); - recordedClipTransform.mapRect(resultClip); - other.setRect(resultClip.toSkIRect()); - } else { - SkPath transformedRect = pathFromTransformedRectangle( - recordedClip->rect, recordedClipTransform); - other.setPath(transformedRect, createViewportRegion()); - } - break; - case ClipMode::RectangleList: { - RectangleList transformedList(getRectList(recordedClip)); - transformedList.transform(recordedClipTransform); - other = transformedList.convertToRegion(createViewportRegion()); - break; - } - case ClipMode::Region: - other = getRegion(recordedClip); - applyTransformToRegion(recordedClipTransform, &other); - } - - ClipRegion* regionClip = allocator.create<ClipRegion>(); - switch (mMode) { - case ClipMode::Rectangle: - regionClip->region.op(mClipRect.toSkIRect(), other, SkRegion::kIntersect_Op); - break; - case ClipMode::RectangleList: - regionClip->region.op(mRectangleList.convertToRegion(createViewportRegion()), - other, SkRegion::kIntersect_Op); - break; - case ClipMode::Region: - regionClip->region.op(mClipRegion, other, SkRegion::kIntersect_Op); - break; - } - // Don't need to snap, since region's in int bounds - regionClip->rect.set(regionClip->region.getBounds()); - mLastResolutionResult = regionClip; - } else { - auto rectListClip = allocator.create<ClipRectList>(mRectangleList); - auto&& rectList = rectListClip->rectList; - if (mMode == ClipMode::Rectangle) { - rectList.set(mClipRect, Matrix4::identity()); - } - - if (recordedClip->mode == ClipMode::Rectangle) { - rectList.intersectWith(recordedClip->rect, recordedClipTransform); - } else { - const RectangleList& other = getRectList(recordedClip); - for (int i = 0; i < other.getTransformedRectanglesCount(); i++) { - auto&& tr = other.getTransformedRectangle(i); - Matrix4 totalTransform(recordedClipTransform); - totalTransform.multiply(tr.getTransform()); - rectList.intersectWith(tr.getBounds(), totalTransform); - } - } - rectListClip->rect = rectList.calculateBounds(); - rectListClip->rect.snapToPixelBoundaries(); - mLastResolutionResult = rectListClip; - } - } - return mLastResolutionResult; -} - -void ClipArea::applyClip(const ClipBase* clip, const Matrix4& transform) { - if (!clip) return; // nothing to do - - if (CC_LIKELY(clip->mode == ClipMode::Rectangle)) { - clipRectWithTransform(clip->rect, &transform, SkRegion::kIntersect_Op); - } else if (CC_LIKELY(clip->mode == ClipMode::RectangleList)) { - auto&& rectList = getRectList(clip); - for (int i = 0; i < rectList.getTransformedRectanglesCount(); i++) { - auto&& tr = rectList.getTransformedRectangle(i); - Matrix4 totalTransform(transform); - totalTransform.multiply(tr.getTransform()); - clipRectWithTransform(tr.getBounds(), &totalTransform, SkRegion::kIntersect_Op); - } - } else { - SkRegion region(getRegion(clip)); - applyTransformToRegion(transform, ®ion); - clipRegion(region, SkRegion::kIntersect_Op); - } -} - -void ClipArea::applyTransformToRegion(const Matrix4& transform, SkRegion* region) { - if (transform.rectToRect() && !transform.isPureTranslate()) { - // handle matrices with scale manually by mapping each rect - SkRegion other; - SkRegion::Iterator it(*region); - while (!it.done()) { - Rect rect(it.rect()); - transform.mapRect(rect); - rect.snapGeometryToPixelBoundaries(true); - other.op(rect.left, rect.top, rect.right, rect.bottom, SkRegion::kUnion_Op); - it.next(); - } - region->swap(other); - } else { - // TODO: handle non-translate transforms properly! - region->translate(transform.getTranslateX(), transform.getTranslateY()); - } -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h deleted file mode 100644 index a7a11801cfe2..000000000000 --- a/libs/hwui/ClipArea.h +++ /dev/null @@ -1,212 +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. - */ -#ifndef CLIPAREA_H -#define CLIPAREA_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 { -public: - TransformedRectangle(); - TransformedRectangle(const Rect& bounds, const Matrix4& transform); - - bool canSimplyIntersectWith(const TransformedRectangle& other) const; - void intersectWith(const TransformedRectangle& other); - - bool isEmpty() const; - - const Rect& getBounds() const { return mBounds; } - - Rect transformedBounds() const { - Rect transformedBounds(transformAndCalculateBounds(mBounds, mTransform)); - return transformedBounds; - } - - const Matrix4& getTransform() const { return mTransform; } - - void transform(const Matrix4& transform) { - Matrix4 t; - t.loadMultiply(transform, mTransform); - mTransform = t; - } - -private: - Rect mBounds; - Matrix4 mTransform; -}; - -class RectangleList { -public: - RectangleList(); - - bool isEmpty() const; - int getTransformedRectanglesCount() const; - const TransformedRectangle& getTransformedRectangle(int i) const; - - 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; - - 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 { - explicit ClipBase(ClipMode mode) : mode(mode) {} - explicit ClipBase(const Rect& rect) : mode(ClipMode::Rectangle), rect(rect) {} - const ClipMode mode; - bool intersectWithRoot = false; - // Bounds of the clipping area, used to define the scissor, and define which - // portion of the stencil is updated/used - Rect rect; - - void dump() const; -}; - -struct ClipRect : ClipBase { - explicit ClipRect(const Rect& rect) : ClipBase(rect) {} -}; - -struct ClipRectList : ClipBase { - explicit ClipRectList(const RectangleList& rectList) - : ClipBase(ClipMode::RectangleList), rectList(rectList) {} - RectangleList rectList; -}; - -struct ClipRegion : ClipBase { - explicit ClipRegion(const SkRegion& region) : ClipBase(ClipMode::Region), region(region) {} - ClipRegion() : ClipBase(ClipMode::Region) {} - SkRegion region; -}; - -class ClipArea { -public: - ClipArea(); - - void setViewportDimensions(int width, int height); - - bool isEmpty() const { return mClipRect.isEmpty(); } - - void setEmpty(); - void setClip(float left, float top, float right, float bottom); - void clipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); - void clipPathWithTransform(const SkPath& path, const mat4* transform, SkRegion::Op op); - - const Rect& getClipRect() const { return mClipRect; } - - const SkRegion& getClipRegion() const { return mClipRegion; } - - const RectangleList& getRectangleList() const { return mRectangleList; } - - bool isRegion() const { return ClipMode::Region == mMode; } - - bool isSimple() const { return mMode == ClipMode::Rectangle; } - - bool isRectangleList() const { return mMode == ClipMode::RectangleList; } - - WARN_UNUSED_RESULT const ClipBase* serializeClip(LinearAllocator& allocator); - WARN_UNUSED_RESULT const ClipBase* serializeIntersectedClip( - LinearAllocator& allocator, const ClipBase* recordedClip, - const Matrix4& recordedClipTransform); - void applyClip(const ClipBase* recordedClip, const Matrix4& recordedClipTransform); - - static void applyTransformToRegion(const Matrix4& transform, SkRegion* region); - -private: - void enterRectangleMode(); - void rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); - - void enterRectangleListMode(); - void rectangleListModeClipRectWithTransform(const Rect& r, const mat4* transform, - SkRegion::Op op); - - void enterRegionModeFromRectangleMode(); - void enterRegionModeFromRectangleListMode(); - void enterRegionMode(); - void regionModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op); - - void clipRegion(const SkRegion& region, SkRegion::Op op); - void ensureClipRegion(); - void onClipRegionUpdated(); - - // Called by every state modifying public method. - void onClipUpdated() { - mPostViewportClipObserved = true; - mLastSerialization = nullptr; - mLastResolutionResult = nullptr; - } - - SkRegion createViewportRegion() { return SkRegion(mViewportBounds.toSkIRect()); } - - void regionFromPath(const SkPath& path, SkRegion& pathAsRegion) { - // TODO: this should not mask every path to the viewport - this makes it impossible to use - // paths to clip to larger areas (which is valid e.g. with SkRegion::kReplace_Op) - pathAsRegion.setPath(path, createViewportRegion()); - } - - ClipMode mMode; - bool mPostViewportClipObserved = false; - bool mReplaceOpObserved = false; - - /** - * If mLastSerialization is non-null, it represents an already serialized copy - * of the current clip state. If null, it has not been computed. - */ - const ClipBase* mLastSerialization = nullptr; - - /** - * This pair of pointers is a single entry cache of most recently seen - */ - const ClipBase* mLastResolutionResult = nullptr; - const ClipBase* mLastResolutionClip = nullptr; - Matrix4 mLastResolutionTransform; - - Rect mViewportBounds; - Rect mClipRect; - SkRegion mClipRegion; - RectangleList mRectangleList; -}; - -} /* namespace uirenderer */ -} /* namespace android */ - -#endif /* CLIPAREA_H_ */ diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp index c060740dc9a4..b772e5b87f2a 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -15,33 +15,30 @@ */ #include "DeferredLayerUpdater.h" -#include "GlLayer.h" -#include "VkLayer.h" #include "renderstate/RenderState.h" -#include "renderthread/EglManager.h" -#include "renderthread/RenderTask.h" #include "utils/PaintUtils.h" namespace android { namespace uirenderer { -DeferredLayerUpdater::DeferredLayerUpdater(RenderState& renderState, CreateLayerFn createLayerFn, - Layer::Api layerApi) +DeferredLayerUpdater::DeferredLayerUpdater(RenderState& renderState) : mRenderState(renderState) , mBlend(false) , mSurfaceTexture(nullptr) , mTransform(nullptr) , mGLContextAttached(false) , mUpdateTexImage(false) - , mLayer(nullptr) - , mLayerApi(layerApi) - , mCreateLayerFn(createLayerFn) { - renderState.registerDeferredLayerUpdater(this); + , mLayer(nullptr) { + renderState.registerContextCallback(this); } DeferredLayerUpdater::~DeferredLayerUpdater() { setTransform(nullptr); - mRenderState.unregisterDeferredLayerUpdater(this); + mRenderState.removeContextCallback(this); + destroyLayer(); +} + +void DeferredLayerUpdater::onContextDestroyed() { destroyLayer(); } @@ -50,16 +47,13 @@ void DeferredLayerUpdater::destroyLayer() { return; } - if (mSurfaceTexture.get() && mLayerApi == Layer::Api::OpenGL && mGLContextAttached) { - status_t err = mSurfaceTexture->detachFromContext(); + if (mSurfaceTexture.get() && mGLContextAttached) { + mSurfaceTexture->detachFromView(); mGLContextAttached = false; - if (err != 0) { - // TODO: Elevate to fatal exception - ALOGE("Failed to detach SurfaceTexture from context %d", err); - } } mLayer->postDecStrong(); + mLayer = nullptr; } @@ -75,100 +69,53 @@ void DeferredLayerUpdater::setPaint(const SkPaint* paint) { void DeferredLayerUpdater::apply() { if (!mLayer) { - mLayer = mCreateLayerFn(mRenderState, mWidth, mHeight, mColorFilter, mAlpha, mMode, mBlend); + mLayer = new Layer(mRenderState, mColorFilter, mAlpha, mMode); } mLayer->setColorFilter(mColorFilter); mLayer->setAlpha(mAlpha, mMode); if (mSurfaceTexture.get()) { - if (mLayer->getApi() == Layer::Api::Vulkan) { - if (mUpdateTexImage) { - mUpdateTexImage = false; - doUpdateVkTexImage(); - } - } else { - LOG_ALWAYS_FATAL_IF(mLayer->getApi() != Layer::Api::OpenGL, - "apply surfaceTexture with non GL backend %x, GL %x, VK %x", - mLayer->getApi(), Layer::Api::OpenGL, Layer::Api::Vulkan); - if (!mGLContextAttached) { - mGLContextAttached = true; - mUpdateTexImage = true; - mSurfaceTexture->attachToContext(static_cast<GlLayer*>(mLayer)->getTextureId()); - } - if (mUpdateTexImage) { - mUpdateTexImage = false; - doUpdateTexImage(); + if (!mGLContextAttached) { + mGLContextAttached = true; + mUpdateTexImage = true; + mSurfaceTexture->attachToView(); + } + if (mUpdateTexImage) { + mUpdateTexImage = false; + sk_sp<SkImage> layerImage; + SkMatrix textureTransform; + android_dataspace dataSpace; + bool queueEmpty = true; + // If the SurfaceTexture queue is in synchronous mode, need to discard all + // but latest frame. Since we can't tell which mode it is in, + // do this unconditionally. + do { + layerImage = mSurfaceTexture->dequeueImage(textureTransform, dataSpace, &queueEmpty, + mRenderState); + } while (layerImage.get() && (!queueEmpty)); + if (layerImage.get()) { + // force filtration if buffer size != layer size + bool forceFilter = mWidth != layerImage->width() || mHeight != layerImage->height(); + updateLayer(forceFilter, textureTransform, dataSpace, layerImage); } - GLenum renderTarget = mSurfaceTexture->getCurrentTextureTarget(); - static_cast<GlLayer*>(mLayer)->setRenderTarget(renderTarget); } + if (mTransform) { - mLayer->getTransform().load(*mTransform); + mLayer->getTransform() = *mTransform; setTransform(nullptr); } } } -void DeferredLayerUpdater::doUpdateTexImage() { - LOG_ALWAYS_FATAL_IF(mLayer->getApi() != Layer::Api::OpenGL, - "doUpdateTexImage non GL backend %x, GL %x, VK %x", mLayer->getApi(), - Layer::Api::OpenGL, Layer::Api::Vulkan); - if (mSurfaceTexture->updateTexImage() == NO_ERROR) { - float transform[16]; - - int64_t frameNumber = mSurfaceTexture->getFrameNumber(); - // If the GLConsumer queue is in synchronous mode, need to discard all - // but latest frame, using the frame number to tell when we no longer - // have newer frames to target. Since we can't tell which mode it is in, - // do this unconditionally. - int dropCounter = 0; - while (mSurfaceTexture->updateTexImage() == NO_ERROR) { - int64_t newFrameNumber = mSurfaceTexture->getFrameNumber(); - if (newFrameNumber == frameNumber) break; - frameNumber = newFrameNumber; - dropCounter++; - } - - bool forceFilter = false; - sp<GraphicBuffer> buffer = mSurfaceTexture->getCurrentBuffer(); - if (buffer != nullptr) { - mLayer->setBufferSize(buffer->getWidth(), buffer->getHeight()); - // force filtration if buffer size != layer size - forceFilter = mWidth != static_cast<int>(mLayer->getBufferWidth()) || - mHeight != static_cast<int>(mLayer->getBufferHeight()); - } - -#if DEBUG_RENDERER - if (dropCounter > 0) { - RENDERER_LOGD("Dropped %d frames on texture layer update", dropCounter); - } -#endif - mSurfaceTexture->getTransformMatrix(transform); - - updateLayer(forceFilter, transform, mSurfaceTexture->getCurrentDataSpace()); - } -} - -void DeferredLayerUpdater::doUpdateVkTexImage() { - LOG_ALWAYS_FATAL_IF(mLayer->getApi() != Layer::Api::Vulkan, - "updateLayer non Vulkan backend %x, GL %x, VK %x", mLayer->getApi(), - Layer::Api::OpenGL, Layer::Api::Vulkan); - - static const mat4 identityMatrix; - updateLayer(false, identityMatrix.data, HAL_DATASPACE_UNKNOWN); - - VkLayer* vkLayer = static_cast<VkLayer*>(mLayer); - vkLayer->updateTexture(); -} - -void DeferredLayerUpdater::updateLayer(bool forceFilter, const float* textureTransform, - android_dataspace dataspace) { +void DeferredLayerUpdater::updateLayer(bool forceFilter, const SkMatrix& textureTransform, + android_dataspace dataspace, const sk_sp<SkImage>& layerImage) { mLayer->setBlend(mBlend); mLayer->setForceFilter(forceFilter); mLayer->setSize(mWidth, mHeight); - mLayer->getTexTransform().load(textureTransform); + mLayer->getTexTransform() = textureTransform; mLayer->setDataSpace(dataspace); + mLayer->setImage(layerImage); } void DeferredLayerUpdater::detachSurfaceTexture() { diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h index fe3ee7a2b4c6..b2c5131dd613 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -17,18 +17,20 @@ #pragma once #include <SkColorFilter.h> +#include <SkImage.h> #include <SkMatrix.h> #include <cutils/compiler.h> -#include <gui/GLConsumer.h> +#include <map> #include <system/graphics.h> #include <utils/StrongPointer.h> #include <GLES2/gl2.h> #include <GLES2/gl2ext.h> +#include "renderstate/RenderState.h" +#include "surfacetexture/SurfaceTexture.h" #include "Layer.h" #include "Rect.h" -#include "renderthread/RenderThread.h" namespace android { namespace uirenderer { @@ -37,16 +39,11 @@ class RenderState; // Container to hold the properties a layer should be set to at the start // of a render pass -class DeferredLayerUpdater : public VirtualLightRefBase { +class DeferredLayerUpdater : public VirtualLightRefBase, public IGpuContextCallback { public: // Note that DeferredLayerUpdater assumes it is taking ownership of the layer // and will not call incrementRef on it as a result. - typedef std::function<Layer*(RenderState& renderState, uint32_t layerWidth, - uint32_t layerHeight, sk_sp<SkColorFilter> colorFilter, int alpha, - SkBlendMode mode, bool blend)> - CreateLayerFn; - ANDROID_API explicit DeferredLayerUpdater(RenderState& renderState, CreateLayerFn createLayerFn, - Layer::Api layerApi); + ANDROID_API explicit DeferredLayerUpdater(RenderState& renderState); ANDROID_API ~DeferredLayerUpdater(); @@ -70,13 +67,13 @@ public: return false; } - ANDROID_API void setSurfaceTexture(const sp<GLConsumer>& texture) { - if (texture.get() != mSurfaceTexture.get()) { - mSurfaceTexture = texture; + ANDROID_API void setSurfaceTexture(const sp<SurfaceTexture>& consumer) { + if (consumer.get() != mSurfaceTexture.get()) { + mSurfaceTexture = consumer; - GLenum target = texture->getCurrentTextureTarget(); + GLenum target = consumer->getCurrentTextureTarget(); LOG_ALWAYS_FATAL_IF(target != GL_TEXTURE_2D && target != GL_TEXTURE_EXTERNAL_OES, - "set unsupported GLConsumer with target %x", target); + "set unsupported SurfaceTexture with target %x", target); } } @@ -97,11 +94,13 @@ public: void detachSurfaceTexture(); - void updateLayer(bool forceFilter, const float* textureTransform, android_dataspace dataspace); + void updateLayer(bool forceFilter, const SkMatrix& textureTransform, + android_dataspace dataspace, const sk_sp<SkImage>& layerImage); void destroyLayer(); - Layer::Api getBackingLayerApi() { return mLayerApi; } +protected: + void onContextDestroyed() override; private: RenderState& mRenderState; @@ -113,17 +112,12 @@ private: sk_sp<SkColorFilter> mColorFilter; int mAlpha = 255; SkBlendMode mMode = SkBlendMode::kSrcOver; - sp<GLConsumer> mSurfaceTexture; + sp<SurfaceTexture> mSurfaceTexture; SkMatrix* mTransform; bool mGLContextAttached; bool mUpdateTexImage; Layer* mLayer; - Layer::Api mLayerApi; - CreateLayerFn mCreateLayerFn; - - void doUpdateTexImage(); - void doUpdateVkTexImage(); }; } /* namespace uirenderer */ diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp index 40cc73a82846..0b9d82b105a3 100644 --- a/libs/hwui/DeviceInfo.cpp +++ b/libs/hwui/DeviceInfo.cpp @@ -26,12 +26,10 @@ #include <log/log.h> -#include <GLES2/gl2.h> - namespace android { namespace uirenderer { -static constexpr android::DisplayInfo sDummyDisplay { +static constexpr android::DisplayInfo sDummyDisplay{ 1080, // w 1920, // h 320.0, // xdpi @@ -42,37 +40,16 @@ static constexpr android::DisplayInfo sDummyDisplay { false, // secure? 0, // appVsyncOffset 0, // presentationDeadline + 1080, // viewportW + 1920, // viewportH }; -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::initialize(int maxTextureSize) { - std::call_once(sInitializedFlag, [maxTextureSize]() { - sDeviceInfo = new DeviceInfo(); - sDeviceInfo->mDisplayInfo = DeviceInfo::queryDisplayInfo(); - sDeviceInfo->mMaxTextureSize = maxTextureSize; - }); -} - -void DeviceInfo::load() { - mDisplayInfo = queryDisplayInfo(); - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize); + static DeviceInfo sDeviceInfo; + return &sDeviceInfo; } -DisplayInfo DeviceInfo::queryDisplayInfo() { +DisplayInfo QueryDisplayInfo() { if (Properties::isolatedProcess) { return sDummyDisplay; } @@ -84,5 +61,23 @@ DisplayInfo DeviceInfo::queryDisplayInfo() { return displayInfo; } +DeviceInfo::DeviceInfo() { +#if HWUI_NULL_GPU + mMaxTextureSize = NULL_GPU_MAX_TEXTURE_SIZE; +#else + mMaxTextureSize = -1; +#endif + mDisplayInfo = QueryDisplayInfo(); +} + +int DeviceInfo::maxTextureSize() const { + LOG_ALWAYS_FATAL_IF(mMaxTextureSize < 0, "MaxTextureSize has not been initialized yet."); + return mMaxTextureSize; +} + +void DeviceInfo::setMaxTextureSize(int maxTextureSize) { + const_cast<DeviceInfo*>(DeviceInfo::get())->mMaxTextureSize = maxTextureSize; +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index 297b2664414b..595621573e6e 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -18,46 +18,34 @@ #include <ui/DisplayInfo.h> -#include "Extensions.h" #include "utils/Macros.h" namespace android { namespace uirenderer { +namespace renderthread { + class RenderThread; +} + 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(); - static void initialize(int maxTextureSize); - - int maxTextureSize() const { return mMaxTextureSize; } + // this value is only valid after the GPU has been initialized and there is a valid graphics + // context or if you are using the HWUI_NULL_GPU + int maxTextureSize() const; const DisplayInfo& displayInfo() const { return mDisplayInfo; } - const Extensions& extensions() const { return mExtensions; } - - static uint32_t multiplyByResolution(uint32_t in) { - auto di = DeviceInfo::get()->displayInfo(); - return di.w * di.h * in; - } - - static DisplayInfo queryDisplayInfo(); private: - DeviceInfo() {} - ~DeviceInfo() {} + friend class renderthread::RenderThread; + static void setMaxTextureSize(int maxTextureSize); - void load(); + DeviceInfo(); int mMaxTextureSize; DisplayInfo mDisplayInfo; - Extensions mExtensions; }; } /* namespace uirenderer */ diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp deleted file mode 100644 index aa87aea8b374..000000000000 --- a/libs/hwui/DisplayList.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <SkCanvas.h> -#include <algorithm> - -#include <utils/Trace.h> - -#include "DamageAccumulator.h" -#include "Debug.h" -#include "DisplayList.h" -#include "OpDumper.h" -#include "RecordedOp.h" -#include "RenderNode.h" -#include "VectorDrawable.h" -#include "renderthread/CanvasContext.h" - -namespace android { -namespace uirenderer { - -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) - , vectorDrawables(stdAllocator) {} - -DisplayList::~DisplayList() { - cleanupResources(); -} - -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[i]); - } - - resourceCache.unlock(); - } - - for (size_t i = 0; i < pathResources.size(); i++) { - const SkPath* path = pathResources[i]; - if (path->unique() && Caches::hasInstance()) { - Caches::getInstance().pathCache.removeDeferred(path); - } - delete path; - } - - for (auto& iter : functors) { - if (iter.listener) { - iter.listener->onGlFunctorReleased(iter.functor); - } - } - - patchResources.clear(); - pathResources.clear(); - paints.clear(); - regions.clear(); -} - -size_t DisplayList::addChild(NodeOpType* op) { - referenceHolders.push_back(op->renderNode); - size_t index = children.size(); - children.push_back(op); - return index; -} - -void DisplayList::syncContents() { - for (auto& iter : functors) { - (*iter.functor)(DrawGlInfo::kModeSync, nullptr); - } - for (auto& vectorDrawable : vectorDrawables) { - vectorDrawable->syncProperties(); - } -} - -void DisplayList::updateChildren(std::function<void(RenderNode*)> updateFn) { - for (auto&& child : children) { - updateFn(child->renderNode); - } -} - -bool DisplayList::prepareListAndChildren( - TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer, - std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) { - info.prepareTextures = info.canvasContext.pinImages(bitmapResources); - - for (auto&& op : children) { - RenderNode* childNode = op->renderNode; - info.damageAccumulator->pushTransform(&op->localMatrix); - bool childFunctorsNeedLayer = - functorsNeedLayer; // TODO! || op->mRecordedWithPotentialStencilClip; - childFn(childNode, observer, info, childFunctorsNeedLayer); - info.damageAccumulator->popTransform(); - } - - bool isDirty = false; - for (auto& vectorDrawable : vectorDrawables) { - // If any vector drawable in the display list needs update, damage the node. - if (vectorDrawable->isDirty()) { - isDirty = true; - } - vectorDrawable->setPropertyChangeWillBeConsumed(true); - } - return isDirty; -} - -void DisplayList::output(std::ostream& output, uint32_t level) { - for (auto&& op : getOps()) { - OpDumper::dump(*op, output, level + 1); - if (op->opId == RecordedOpId::RenderNodeOp) { - auto rnOp = reinterpret_cast<const RenderNodeOp*>(op); - rnOp->renderNode->output(output, level + 1); - } else { - output << std::endl; - } - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h index 0cfc3b701aff..a952cc23e1ef 100644 --- a/libs/hwui/DisplayList.h +++ b/libs/hwui/DisplayList.h @@ -16,148 +16,20 @@ #pragma once -#include <SkCamera.h> -#include <SkDrawable.h> -#include <SkMatrix.h> - -#include <private/hwui/DrawGlInfo.h> - -#include <utils/KeyedVector.h> -#include <utils/LinearAllocator.h> -#include <utils/RefBase.h> -#include <utils/SortedVector.h> -#include <utils/String8.h> - -#include <cutils/compiler.h> - -#include <androidfw/ResourceTypes.h> - -#include "CanvasProperty.h" -#include "Debug.h" -#include "GlFunctorLifecycleListener.h" -#include "Matrix.h" -#include "RenderProperties.h" -#include "TreeInfo.h" -#include "hwui/Bitmap.h" - -#include <vector> - -class SkBitmap; -class SkPaint; -class SkPath; -class SkRegion; +#include "pipeline/skia/SkiaDisplayList.h" namespace android { namespace uirenderer { -class Rect; -class Layer; - -struct RecordedOp; -struct RenderNodeOp; - -typedef RecordedOp BaseOpType; -typedef RenderNodeOp NodeOpType; - namespace VectorDrawable { class Tree; }; typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; -struct FunctorContainer { - Functor* functor; - GlFunctorLifecycleListener* listener; -}; - /** * Data structure that holds the list of commands used in display list stream */ -class DisplayList { - friend class RecordingCanvas; - -public: - struct Chunk { - // range of included ops in DisplayList::ops() - size_t beginOpIndex; - size_t endOpIndex; - - // range of included children in DisplayList::children() - size_t beginChildIndex; - size_t endChildIndex; - - // whether children with non-zero Z in the chunk should be reordered - bool reorderChildren; - - // clip at the beginning of a reorder section, applied to reordered children - const ClipBase* reorderClip; - }; - - DisplayList(); - virtual ~DisplayList(); - - // index of DisplayListOp restore, after which projected descendants should be drawn - int projectionReceiveIndex; - - const LsaVector<Chunk>& getChunks() const { return chunks; } - const LsaVector<BaseOpType*>& getOps() const { return ops; } - - const LsaVector<NodeOpType*>& getChildren() const { return children; } - - const LsaVector<sk_sp<Bitmap>>& getBitmapResources() const { return bitmapResources; } - - size_t addChild(NodeOpType* childOp); - - void ref(VirtualLightRefBase* prop) { referenceHolders.push_back(prop); } - - size_t getUsedSize() { return allocator.usedSize(); } - - virtual bool isEmpty() const { return ops.empty(); } - virtual bool hasFunctor() const { return !functors.empty(); } - virtual bool hasVectorDrawables() const { return !vectorDrawables.empty(); } - virtual bool isSkiaDL() const { return false; } - virtual bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) { - return false; - } - - virtual void syncContents(); - virtual void updateChildren(std::function<void(RenderNode*)> updateFn); - virtual bool prepareListAndChildren( - TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer, - std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn); - - virtual void output(std::ostream& output, uint32_t level); - -protected: - // allocator into which all ops and LsaVector arrays allocated - LinearAllocator allocator; - LinearStdAllocator<void*> stdAllocator; - -private: - LsaVector<Chunk> chunks; - LsaVector<BaseOpType*> ops; - - // list of Ops referring to RenderNode children for quick, non-drawing traversal - LsaVector<NodeOpType*> children; - - // Resources - Skia objects + 9 patches referred to by this DisplayList - LsaVector<sk_sp<Bitmap>> bitmapResources; - LsaVector<const SkPath*> pathResources; - LsaVector<const Res_png_9patch*> patchResources; - LsaVector<std::unique_ptr<const SkPaint>> paints; - LsaVector<std::unique_ptr<const SkRegion>> regions; - LsaVector<sp<VirtualLightRefBase>> referenceHolders; - - // List of functors - LsaVector<FunctorContainer> functors; - - // List of VectorDrawables that need to be notified of pushStaging. Note that this list gets - // nothing - // but a callback during sync DisplayList, unlike the list of functors defined above, which - // gets special treatment exclusive for webview. - LsaVector<VectorDrawableRoot*> vectorDrawables; - - void cleanupResources(); -}; +using DisplayList = skiapipeline::SkiaDisplayList; }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in new file mode 100644 index 000000000000..04cf611c8322 --- /dev/null +++ b/libs/hwui/DisplayListOps.in @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 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. + */ + +X(Flush) +X(Save) +X(Restore) +X(SaveLayer) +X(Concat) +X(SetMatrix) +X(Translate) +X(ClipPath) +X(ClipRect) +X(ClipRRect) +X(ClipRegion) +X(DrawPaint) +X(DrawPath) +X(DrawRect) +X(DrawRegion) +X(DrawOval) +X(DrawArc) +X(DrawRRect) +X(DrawDRRect) +X(DrawAnnotation) +X(DrawDrawable) +X(DrawPicture) +X(DrawImage) +X(DrawImageNine) +X(DrawImageRect) +X(DrawImageLattice) +X(DrawText) +X(DrawPosText) +X(DrawPosTextH) +X(DrawTextRSXform) +X(DrawTextBlob) +X(DrawPatch) +X(DrawPoints) +X(DrawVertices) +X(DrawAtlas) +X(DrawShadowRec) +X(DrawVectorDrawable)
\ No newline at end of file diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp deleted file mode 100644 index 530e82e65a28..000000000000 --- a/libs/hwui/Extensions.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Extensions.h" - -#include "Debug.h" -#include "Properties.h" -#include "utils/StringUtils.h" - -#include <cutils/compiler.h> - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -#include <utils/Log.h> - -namespace android { -namespace uirenderer { - -Extensions::Extensions() { - if (Properties::isSkiaEnabled()) { - return; - } - const char* version = (const char*)glGetString(GL_VERSION); - - // Section 6.1.5 of the OpenGL ES specification indicates the GL version - // string strictly follows this format: - // - // OpenGL<space>ES<space><version number><space><vendor-specific information> - // - // In addition section 6.1.5 describes the version number thusly: - // - // "The version number is either of the form major number.minor number or - // major number.minor number.release number, where the numbers all have one - // or more digits. The release number and vendor specific information are - // optional." - - if (sscanf(version, "OpenGL ES %d.%d", &mVersionMajor, &mVersionMinor) != 2) { - // If we cannot parse the version number, assume OpenGL ES 2.0 - mVersionMajor = 2; - mVersionMinor = 0; - } - - 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"); - mHasRenderableFloatTexture = extensions.has("GL_OES_texture_half_float"); - - mHasSRGB = mVersionMajor >= 3 || extensions.has("GL_EXT_sRGB"); - mHasSRGBWriteControl = extensions.has("GL_EXT_sRGB_write_control"); - -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - // If linear blending is enabled, the device must have (ES3.0 or EXT_sRGB) - // and EXT_sRGB_write_control - LOG_ALWAYS_FATAL_IF(!mHasSRGB, "Linear blending requires ES 3.0 or EXT_sRGB"); - LOG_ALWAYS_FATAL_IF(!mHasSRGBWriteControl, "Linear blending requires EXT_sRGB_write_control"); - - mHasLinearBlending = true; -#else - mHasLinearBlending = false; -#endif -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h deleted file mode 100644 index 214ee0bbeefd..000000000000 --- a/libs/hwui/Extensions.h +++ /dev/null @@ -1,71 +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_EXTENSIONS_H -#define ANDROID_HWUI_EXTENSIONS_H - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Classes -/////////////////////////////////////////////////////////////////////////////// - -class Extensions { -public: - Extensions(); - - inline bool hasNPot() const { return mHasNPot; } - inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; } - inline bool hasDiscardFramebuffer() const { return mHasDiscardFramebuffer; } - inline bool hasDebugMarker() const { return mHasDebugMarker; } - inline bool has1BitStencil() const { return mHas1BitStencil; } - inline bool has4BitStencil() const { return mHas4BitStencil; } - inline bool hasUnpackRowLength() const { return mVersionMajor >= 3 || mHasUnpackSubImage; } - inline bool hasPixelBufferObjects() const { return mVersionMajor >= 3; } - inline bool hasOcclusionQueries() const { return mVersionMajor >= 3; } - inline bool hasFloatTextures() const { return mVersionMajor >= 3; } - inline bool hasRenderableFloatTextures() const { - return (mVersionMajor >= 3 && mVersionMinor >= 2) || mHasRenderableFloatTexture; - } - inline bool hasSRGB() const { return mHasSRGB; } - inline bool hasSRGBWriteControl() const { return hasSRGB() && mHasSRGBWriteControl; } - inline bool hasLinearBlending() const { return hasSRGB() && mHasLinearBlending; } - - inline int getMajorGlVersion() const { return mVersionMajor; } - inline int getMinorGlVersion() const { return mVersionMinor; } - -private: - bool mHasNPot; - bool mHasFramebufferFetch; - bool mHasDiscardFramebuffer; - bool mHasDebugMarker; - bool mHas1BitStencil; - bool mHas4BitStencil; - bool mHasUnpackSubImage; - bool mHasSRGB; - bool mHasSRGBWriteControl; - bool mHasLinearBlending; - bool mHasRenderableFloatTexture; - - int mVersionMajor; - int mVersionMinor; -}; // class Extensions - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_EXTENSIONS_H diff --git a/libs/hwui/FboCache.cpp b/libs/hwui/FboCache.cpp deleted file mode 100644 index 88302cc52c2b..000000000000 --- a/libs/hwui/FboCache.cpp +++ /dev/null @@ -1,82 +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 <stdlib.h> - -#include "Debug.h" -#include "FboCache.h" -#include "Properties.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Constructors/destructor -/////////////////////////////////////////////////////////////////////////////// - -FboCache::FboCache() : mMaxSize(0) {} - -FboCache::~FboCache() { - clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Size management -/////////////////////////////////////////////////////////////////////////////// - -uint32_t FboCache::getSize() { - return mCache.size(); -} - -uint32_t FboCache::getMaxSize() { - return mMaxSize; -} - -/////////////////////////////////////////////////////////////////////////////// -// Caching -/////////////////////////////////////////////////////////////////////////////// - -void FboCache::clear() { - for (size_t i = 0; i < mCache.size(); i++) { - const GLuint fbo = mCache.itemAt(i); - glDeleteFramebuffers(1, &fbo); - } - mCache.clear(); -} - -GLuint FboCache::get() { - GLuint fbo; - if (mCache.size() > 0) { - fbo = mCache.itemAt(mCache.size() - 1); - mCache.removeAt(mCache.size() - 1); - } else { - glGenFramebuffers(1, &fbo); - } - return fbo; -} - -bool FboCache::put(GLuint fbo) { - if (mCache.size() < mMaxSize) { - mCache.add(fbo); - return true; - } - - glDeleteFramebuffers(1, &fbo); - return false; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/FboCache.h b/libs/hwui/FboCache.h deleted file mode 100644 index 5e8bb0c7a7a7..000000000000 --- a/libs/hwui/FboCache.h +++ /dev/null @@ -1,79 +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_FBO_CACHE_H -#define ANDROID_HWUI_FBO_CACHE_H - -#include <GLES2/gl2.h> - -#include <utils/SortedVector.h> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Cache -/////////////////////////////////////////////////////////////////////////////// - -class FboCache { -public: - FboCache(); - ~FboCache(); - - /** - * Returns an FBO from the cache. If no FBO is available, a new one - * is created. If creating a new FBO fails, 0 is returned. - * - * When an FBO is obtained from the cache, it is removed and the - * total number of FBOs available in the cache decreases. - * - * @return The name of the FBO, or 0 if no FBO can be obtained. - */ - GLuint get(); - - /** - * Adds the specified FBO to the cache. - * - * @param fbo The FBO to add to the cache. - * - * @return True if the FBO was added, false otherwise. - */ - bool put(GLuint fbo); - - /** - * Clears the cache. This causes all FBOs to be deleted. - */ - void clear(); - - /** - * Returns the current size of the cache. - */ - uint32_t getSize(); - - /** - * Returns the maximum number of FBOs that the cache can hold. - */ - uint32_t getMaxSize(); - -private: - SortedVector<GLuint> mCache; - uint32_t mMaxSize; -}; // class FboCache - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_FBO_CACHE_H diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h deleted file mode 100644 index b424f97a5004..000000000000 --- a/libs/hwui/FloatColor.h +++ /dev/null @@ -1,71 +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. - */ -#ifndef FLOATCOLOR_H -#define FLOATCOLOR_H - -#include "utils/Color.h" -#include "utils/Macros.h" -#include "utils/MathUtils.h" - -#include <stdint.h> - -namespace android { -namespace uirenderer { - -struct FloatColor { - // "color" is a gamma-encoded sRGB color - // After calling this method, the color is stored as a pre-multiplied linear color - // if linear blending is enabled. Otherwise, the color is stored as a pre-multiplied - // gamma-encoded sRGB color - void set(uint32_t color) { - a = ((color >> 24) & 0xff) / 255.0f; - r = a * EOCF(((color >> 16) & 0xff) / 255.0f); - g = a * EOCF(((color >> 8) & 0xff) / 255.0f); - b = a * EOCF(((color)&0xff) / 255.0f); - } - - // "color" is a gamma-encoded sRGB color - // After calling this method, the color is stored as a un-premultiplied linear color - // if linear blending is enabled. Otherwise, the color is stored as a un-premultiplied - // gamma-encoded sRGB color - void setUnPreMultiplied(uint32_t color) { - a = ((color >> 24) & 0xff) / 255.0f; - r = EOCF(((color >> 16) & 0xff) / 255.0f); - g = EOCF(((color >> 8) & 0xff) / 255.0f); - b = EOCF(((color)&0xff) / 255.0f); - } - - bool isNotBlack() { return a < 1.0f || r > 0.0f || g > 0.0f || b > 0.0f; } - - bool operator==(const FloatColor& other) const { - return MathUtils::areEqual(r, other.r) && MathUtils::areEqual(g, other.g) && - MathUtils::areEqual(b, other.b) && MathUtils::areEqual(a, other.a); - } - - bool operator!=(const FloatColor& other) const { return !(*this == other); } - - float r; - float g; - float b; - float a; -}; - -REQUIRE_COMPATIBLE_LAYOUT(FloatColor); - -} /* namespace uirenderer */ -} /* namespace android */ - -#endif /* FLOATCOLOR_H */ diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp deleted file mode 100644 index bbcedb15335d..000000000000 --- a/libs/hwui/FontRenderer.cpp +++ /dev/null @@ -1,802 +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 "FontRenderer.h" - -#include "BakedOpDispatcher.h" -#include "BakedOpRenderer.h" -#include "BakedOpState.h" -#include "Caches.h" -#include "Debug.h" -#include "Extensions.h" -#include "Glop.h" -#include "GlopBuilder.h" -#include "PixelBuffer.h" -#include "Rect.h" -#include "font/Font.h" -#include "renderstate/RenderState.h" -#include "utils/Blur.h" -#include "utils/Timing.h" - -#include <RenderScript.h> -#include <SkGlyph.h> -#include <SkUtils.h> -#include <utils/Log.h> -#include <algorithm> - -namespace android { -namespace uirenderer { - -// blur inputs smaller than this constant will bypass renderscript -#define RS_MIN_INPUT_CUTOFF 10000 - -/////////////////////////////////////////////////////////////////////////////// -// TextSetupFunctor -/////////////////////////////////////////////////////////////////////////////// - -void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { - int textureFillFlags = TextureFillFlags::None; - if (texture.getFormat() == GL_ALPHA) { - textureFillFlags |= TextureFillFlags::IsAlphaMaskTexture; - } - if (linearFiltering) { - textureFillFlags |= TextureFillFlags::ForceFilter; - } - int transformFlags = - pureTranslate ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None; -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - bool gammaCorrection = true; -#else - bool gammaCorrection = false; -#endif - Glop glop; - GlopBuilder(renderer->renderState(), renderer->caches(), &glop) - .setRoundRectClipState(bakedState->roundRectClipState) - .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount()) - .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha) - .setGammaCorrection(gammaCorrection) - .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); -} - -/////////////////////////////////////////////////////////////////////////////// -// FontRenderer -/////////////////////////////////////////////////////////////////////////////// - -static bool sLogFontRendererCreate = true; - -FontRenderer::FontRenderer(const uint8_t* gammaTable) - : mGammaTable(gammaTable) - , mCurrentFont(nullptr) - , mActiveFonts(LruCache<Font::FontDescription, Font*>::kUnlimitedCapacity) - , mCurrentCacheTexture(nullptr) - , mUploadTexture(false) - , mFunctor(nullptr) - , mClip(nullptr) - , mBounds(nullptr) - , mDrawn(false) - , mInitialized(false) - , mLinearFiltering(false) { - if (sLogFontRendererCreate) { - INIT_LOGD("Creating FontRenderer"); - } - - auto deviceInfo = DeviceInfo::get(); - auto displayInfo = deviceInfo->displayInfo(); - int maxTextureSize = deviceInfo->maxTextureSize(); - - // Adjust cache size based on Pixel's desnsity. - constexpr float PIXEL_DENSITY = 2.6; - const float densityRatio = displayInfo.density / PIXEL_DENSITY; - - // TODO: Most devices are hardcoded with this configuration, does it need to be dynamic? - mSmallCacheWidth = - OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); - mSmallCacheHeight = - OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); - mLargeCacheWidth = - OffscreenBuffer::computeIdealDimension(std::min(2048, maxTextureSize) * densityRatio); - mLargeCacheHeight = - OffscreenBuffer::computeIdealDimension(std::min(1024, maxTextureSize) * densityRatio); - - if (sLogFontRendererCreate) { - INIT_LOGD(" Text cache sizes, in pixels: %i x %i, %i x %i, %i x %i, %i x %i", - mSmallCacheWidth, mSmallCacheHeight, mLargeCacheWidth, mLargeCacheHeight >> 1, - mLargeCacheWidth, mLargeCacheHeight >> 1, mLargeCacheWidth, mLargeCacheHeight); - } - - sLogFontRendererCreate = false; -} - -void clearCacheTextures(std::vector<CacheTexture*>& cacheTextures) { - for (uint32_t i = 0; i < cacheTextures.size(); i++) { - delete cacheTextures[i]; - } - cacheTextures.clear(); -} - -FontRenderer::~FontRenderer() { - clearCacheTextures(mACacheTextures); - clearCacheTextures(mRGBACacheTextures); - - LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); - while (it.next()) { - delete it.value(); - } - mActiveFonts.clear(); -} - -void FontRenderer::flushAllAndInvalidate() { - issueDrawCommand(); - - LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); - while (it.next()) { - it.value()->invalidateTextureCache(); - } - - for (uint32_t i = 0; i < mACacheTextures.size(); i++) { - mACacheTextures[i]->init(); - -#ifdef BUGREPORT_FONT_CACHE_USAGE - mHistoryTracker.glyphsCleared(mACacheTextures[i]); -#endif - } - - for (uint32_t i = 0; i < mRGBACacheTextures.size(); i++) { - mRGBACacheTextures[i]->init(); -#ifdef BUGREPORT_FONT_CACHE_USAGE - mHistoryTracker.glyphsCleared(mRGBACacheTextures[i]); -#endif - } - - mDrawn = false; -} - -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]; - if (cacheTexture->getPixelBuffer()) { - cacheTexture->init(); -#ifdef BUGREPORT_FONT_CACHE_USAGE - mHistoryTracker.glyphsCleared(cacheTexture); -#endif - LruCache<Font::FontDescription, Font*>::Iterator it(mActiveFonts); - while (it.next()) { - it.value()->invalidateTextureCache(cacheTexture); - } - cacheTexture->releasePixelBuffer(); - } - } -} - -void FontRenderer::flushLargeCaches() { - flushLargeCaches(mACacheTextures); - flushLargeCaches(mRGBACacheTextures); -} - -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)) { - return cacheTextures[i]; - } - } - // Could not fit glyph into current cache textures - return nullptr; -} - -void FontRenderer::cacheBitmap(const SkGlyph& glyph, CachedGlyphInfo* cachedGlyph, - uint32_t* retOriginX, uint32_t* retOriginY, bool precaching) { - checkInit(); - - // If the glyph bitmap is empty let's assum the glyph is valid - // so we can avoid doing extra work later on - if (glyph.fWidth == 0 || glyph.fHeight == 0) { - cachedGlyph->mIsValid = true; - cachedGlyph->mCacheTexture = nullptr; - return; - } - - cachedGlyph->mIsValid = false; - - // choose an appropriate cache texture list for this glyph format - SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat); - std::vector<CacheTexture*>* cacheTextures = nullptr; - switch (format) { - case SkMask::kA8_Format: - case SkMask::kBW_Format: - cacheTextures = &mACacheTextures; - break; - case SkMask::kARGB32_Format: - cacheTextures = &mRGBACacheTextures; - break; - default: -#if DEBUG_FONT_RENDERER - ALOGD("getCacheTexturesForFormat: unknown SkMask format %x", format); -#endif - return; - } - - // If the glyph is too tall, don't cache it - if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > - (*cacheTextures)[cacheTextures->size() - 1]->getHeight()) { - ALOGE("Font size too large to fit in cache. width, height = %i, %i", (int)glyph.fWidth, - (int)glyph.fHeight); - return; - } - - // Now copy the bitmap into the cache texture - uint32_t startX = 0; - uint32_t startY = 0; - - CacheTexture* cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY); - - if (!cacheTexture) { - if (!precaching) { - // If the new glyph didn't fit and we are not just trying to precache it, - // clear out the cache and try again - flushAllAndInvalidate(); - cacheTexture = cacheBitmapInTexture(*cacheTextures, glyph, &startX, &startY); - } - - if (!cacheTexture) { - // either the glyph didn't fit or we're precaching and will cache it when we draw - return; - } - } - - cachedGlyph->mCacheTexture = cacheTexture; - - *retOriginX = startX; - *retOriginY = startY; - - uint32_t endX = startX + glyph.fWidth; - uint32_t endY = startY + glyph.fHeight; - - uint32_t cacheWidth = cacheTexture->getWidth(); - - if (!cacheTexture->getPixelBuffer()) { - Caches::getInstance().textureState().activateTexture(0); - // Large-glyph texture memory is allocated only as needed - cacheTexture->allocatePixelBuffer(); - } - if (!cacheTexture->mesh()) { - cacheTexture->allocateMesh(); - } - - uint8_t* cacheBuffer = cacheTexture->getPixelBuffer()->map(); - uint8_t* bitmapBuffer = (uint8_t*)glyph.fImage; - int srcStride = glyph.rowBytes(); - - // Copy the glyph image, taking the mask format into account - switch (format) { - case SkMask::kA8_Format: { - uint32_t row = - (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE; - // write leading border line - memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); - // write glyph data - if (mGammaTable) { - for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { - row = cacheY * cacheWidth; - cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; - for (uint32_t cacheX = startX, bX = 0; cacheX < endX; cacheX++, bX++) { - uint8_t tempCol = bitmapBuffer[bY + bX]; - cacheBuffer[row + cacheX] = mGammaTable[tempCol]; - } - cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; - } - } else { - for (uint32_t cacheY = startY, bY = 0; cacheY < endY; cacheY++, bY += srcStride) { - row = cacheY * cacheWidth; - memcpy(&cacheBuffer[row + startX], &bitmapBuffer[bY], glyph.fWidth); - cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; - cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; - } - } - // write trailing border line - row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; - memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); - break; - } - case SkMask::kARGB32_Format: { - // prep data lengths - const size_t formatSize = PixelBuffer::formatSize(GL_RGBA); - const size_t borderSize = formatSize * TEXTURE_BORDER_SIZE; - size_t rowSize = formatSize * glyph.fWidth; - // prep advances - size_t dstStride = formatSize * cacheWidth; - // prep indices - // - we actually start one row early, and then increment before first copy - uint8_t* src = &bitmapBuffer[0 - srcStride]; - uint8_t* dst = &cacheBuffer[cacheTexture->getOffset(startX, startY - 1)]; - uint8_t* dstEnd = &cacheBuffer[cacheTexture->getOffset(startX, endY - 1)]; - uint8_t* dstL = dst - borderSize; - uint8_t* dstR = dst + rowSize; - // write leading border line - memset(dstL, 0, rowSize + 2 * borderSize); - // write glyph data - while (dst < dstEnd) { - memset(dstL += dstStride, 0, borderSize); // leading border column - memcpy(dst += dstStride, src += srcStride, rowSize); // glyph data - memset(dstR += dstStride, 0, borderSize); // trailing border column - } - // write trailing border line - memset(dstL += dstStride, 0, rowSize + 2 * borderSize); - break; - } - case SkMask::kBW_Format: { - uint32_t cacheX = 0, cacheY = 0; - uint32_t row = - (startY - TEXTURE_BORDER_SIZE) * cacheWidth + startX - TEXTURE_BORDER_SIZE; - static const uint8_t COLORS[2] = {0, 255}; - // write leading border line - memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); - // write glyph data - for (cacheY = startY; cacheY < endY; cacheY++) { - cacheX = startX; - int rowBytes = srcStride; - uint8_t* buffer = bitmapBuffer; - - row = cacheY * cacheWidth; - cacheBuffer[row + startX - TEXTURE_BORDER_SIZE] = 0; - while (--rowBytes >= 0) { - uint8_t b = *buffer++; - for (int8_t mask = 7; mask >= 0 && cacheX < endX; mask--) { - cacheBuffer[cacheY * cacheWidth + cacheX++] = COLORS[(b >> mask) & 0x1]; - } - } - cacheBuffer[row + endX + TEXTURE_BORDER_SIZE - 1] = 0; - - bitmapBuffer += srcStride; - } - // write trailing border line - row = (endY + TEXTURE_BORDER_SIZE - 1) * cacheWidth + startX - TEXTURE_BORDER_SIZE; - memset(&cacheBuffer[row], 0, glyph.fWidth + 2 * TEXTURE_BORDER_SIZE); - break; - } - default: - ALOGW("Unknown glyph format: 0x%x", format); - break; - } - - cachedGlyph->mIsValid = true; - -#ifdef BUGREPORT_FONT_CACHE_USAGE - mHistoryTracker.glyphUploaded(cacheTexture, startX, startY, glyph.fWidth, glyph.fHeight); -#endif -} - -CacheTexture* FontRenderer::createCacheTexture(int width, int height, GLenum format, - bool allocate) { - CacheTexture* cacheTexture = new CacheTexture(width, height, format, kMaxNumberOfQuads); - - if (allocate) { - Caches::getInstance().textureState().activateTexture(0); - cacheTexture->allocatePixelBuffer(); - cacheTexture->allocateMesh(); - } - - return cacheTexture; -} - -void FontRenderer::initTextTexture() { - clearCacheTextures(mACacheTextures); - clearCacheTextures(mRGBACacheTextures); - - mUploadTexture = false; - mACacheTextures.push_back( - createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, GL_ALPHA, true)); - mACacheTextures.push_back( - createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL_ALPHA, false)); - mACacheTextures.push_back( - createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL_ALPHA, false)); - mACacheTextures.push_back( - createCacheTexture(mLargeCacheWidth, mLargeCacheHeight, GL_ALPHA, false)); - mRGBACacheTextures.push_back( - createCacheTexture(mSmallCacheWidth, mSmallCacheHeight, GL_RGBA, false)); - mRGBACacheTextures.push_back( - createCacheTexture(mLargeCacheWidth, mLargeCacheHeight >> 1, GL_RGBA, false)); - mCurrentCacheTexture = mACacheTextures[0]; -} - -// We don't want to allocate anything unless we actually draw text -void FontRenderer::checkInit() { - if (mInitialized) { - return; - } - - initTextTexture(); - - mInitialized = true; -} - -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]; - if (cacheTexture->isDirty() && cacheTexture->getPixelBuffer()) { - if (cacheTexture->getTextureId() != lastTextureId) { - lastTextureId = cacheTexture->getTextureId(); - caches.textureState().activateTexture(0); - caches.textureState().bindTexture(lastTextureId); - } - - if (cacheTexture->upload()) { - resetPixelStore = true; - } - } - } -} - -void FontRenderer::checkTextureUpdate() { - if (!mUploadTexture) { - return; - } - - Caches& caches = Caches::getInstance(); - GLuint lastTextureId = 0; - - bool resetPixelStore = false; - - // Iterate over all the cache textures and see which ones need to be updated - checkTextureUpdateForCache(caches, mACacheTextures, resetPixelStore, lastTextureId); - checkTextureUpdateForCache(caches, mRGBACacheTextures, resetPixelStore, lastTextureId); - - // Unbind any PBO we might have used to update textures - caches.pixelBufferState().unbind(); - - // Reset to default unpack row length to avoid affecting texture - // uploads in other parts of the renderer - if (resetPixelStore) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - } - - mUploadTexture = false; -} - -void FontRenderer::issueDrawCommand(std::vector<CacheTexture*>& cacheTextures) { - if (!mFunctor) return; - - bool first = true; - for (uint32_t i = 0; i < cacheTextures.size(); i++) { - CacheTexture* texture = cacheTextures[i]; - if (texture->canDraw()) { - if (first) { - checkTextureUpdate(); - first = false; - mDrawn = true; - } - - mFunctor->draw(*texture, mLinearFiltering); - - texture->resetMesh(); - } - } -} - -void FontRenderer::issueDrawCommand() { - issueDrawCommand(mACacheTextures); - issueDrawCommand(mRGBACacheTextures); -} - -void FontRenderer::appendMeshQuadNoClip(float x1, float y1, float u1, float v1, float x2, float y2, - float u2, float v2, float x3, float y3, float u3, float v3, - float x4, float y4, float u4, float v4, - CacheTexture* texture) { - if (texture != mCurrentCacheTexture) { - // Now use the new texture id - mCurrentCacheTexture = texture; - } - - mCurrentCacheTexture->addQuad(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4); -} - -void FontRenderer::appendMeshQuad(float x1, float y1, float u1, float v1, float x2, float y2, - float u2, float v2, float x3, float y3, float u3, float v3, - float x4, float y4, float u4, float v4, CacheTexture* texture) { - if (mClip && (x1 > mClip->right || y1 < mClip->top || x2 < mClip->left || y4 > mClip->bottom)) { - return; - } - - appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); - - if (mBounds) { - mBounds->left = std::min(mBounds->left, x1); - mBounds->top = std::min(mBounds->top, y3); - mBounds->right = std::max(mBounds->right, x3); - mBounds->bottom = std::max(mBounds->bottom, y1); - } - - if (mCurrentCacheTexture->endOfMesh()) { - issueDrawCommand(); - } -} - -void FontRenderer::appendRotatedMeshQuad(float x1, float y1, float u1, float v1, float x2, float y2, - float u2, float v2, float x3, float y3, float u3, float v3, - float x4, float y4, float u4, float v4, - CacheTexture* texture) { - appendMeshQuadNoClip(x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, x4, y4, u4, v4, texture); - - if (mBounds) { - mBounds->left = std::min(mBounds->left, std::min(x1, std::min(x2, std::min(x3, x4)))); - mBounds->top = std::min(mBounds->top, std::min(y1, std::min(y2, std::min(y3, y4)))); - mBounds->right = std::max(mBounds->right, std::max(x1, std::max(x2, std::max(x3, x4)))); - mBounds->bottom = std::max(mBounds->bottom, std::max(y1, std::max(y2, std::max(y3, y4)))); - } - - if (mCurrentCacheTexture->endOfMesh()) { - issueDrawCommand(); - } -} - -void FontRenderer::setFont(const SkPaint* paint, const SkMatrix& matrix) { - mCurrentFont = Font::create(this, paint, matrix); -} - -FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const glyph_t* glyphs, - int numGlyphs, float radius, - const float* positions) { - checkInit(); - - DropShadow image; - image.width = 0; - image.height = 0; - image.image = nullptr; - image.penX = 0; - image.penY = 0; - - if (!mCurrentFont) { - return image; - } - - mDrawn = false; - mClip = nullptr; - mBounds = nullptr; - - Rect bounds; - mCurrentFont->measure(paint, glyphs, numGlyphs, &bounds, positions); - - uint32_t intRadius = Blur::convertRadiusToInt(radius); - uint32_t paddedWidth = (uint32_t)(bounds.right - bounds.left) + 2 * intRadius; - uint32_t paddedHeight = (uint32_t)(bounds.top - bounds.bottom) + 2 * intRadius; - - uint32_t maxSize = Caches::getInstance().maxTextureSize; - if (paddedWidth > maxSize || paddedHeight > maxSize) { - return image; - } - - // Align buffers for renderscript usage - if (paddedWidth & (RS_CPU_ALLOCATION_ALIGNMENT - 1)) { - paddedWidth += RS_CPU_ALLOCATION_ALIGNMENT - paddedWidth % RS_CPU_ALLOCATION_ALIGNMENT; - } - int size = paddedWidth * paddedHeight; - uint8_t* dataBuffer = (uint8_t*)memalign(RS_CPU_ALLOCATION_ALIGNMENT, size); - - memset(dataBuffer, 0, size); - - int penX = intRadius - bounds.left; - int penY = intRadius - bounds.bottom; - - if ((bounds.right > bounds.left) && (bounds.top > bounds.bottom)) { - // 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, glyphs, numGlyphs, penX, penY, Font::BITMAP, dataBuffer, - paddedWidth, paddedHeight, nullptr, positions); - - // Unbind any PBO we might have used - Caches::getInstance().pixelBufferState().unbind(); - - blurImage(&dataBuffer, paddedWidth, paddedHeight, radius); - } - - image.width = paddedWidth; - image.height = paddedHeight; - image.image = dataBuffer; - image.penX = penX; - image.penY = penY; - - return image; -} - -void FontRenderer::initRender(const Rect* clip, Rect* bounds, TextDrawFunctor* functor) { - checkInit(); - - mDrawn = false; - mBounds = bounds; - mFunctor = functor; - mClip = clip; -} - -void FontRenderer::finishRender() { - mBounds = nullptr; - mClip = nullptr; - - issueDrawCommand(); -} - -void FontRenderer::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, - const SkMatrix& matrix) { - Font* font = Font::create(this, paint, matrix); - font->precache(paint, glyphs, numGlyphs); -} - -void FontRenderer::endPrecaching() { - checkTextureUpdate(); -} - -bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, - int numGlyphs, int x, int y, const float* positions, Rect* bounds, - TextDrawFunctor* functor, bool forceFinish) { - if (!mCurrentFont) { - ALOGE("No font set"); - return false; - } - - initRender(clip, bounds, functor); - mCurrentFont->render(paint, glyphs, numGlyphs, x, y, positions); - - if (forceFinish) { - finishRender(); - } - - return mDrawn; -} - -bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, - int numGlyphs, const SkPath* path, float hOffset, float vOffset, - Rect* bounds, TextDrawFunctor* functor) { - if (!mCurrentFont) { - ALOGE("No font set"); - return false; - } - - initRender(clip, bounds, functor); - mCurrentFont->render(paint, glyphs, numGlyphs, path, hOffset, vOffset); - finishRender(); - - return mDrawn; -} - -void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, float radius) { - uint32_t intRadius = Blur::convertRadiusToInt(radius); - if (width * height * intRadius >= RS_MIN_INPUT_CUTOFF && radius <= 25.0f) { - uint8_t* outImage = (uint8_t*)memalign(RS_CPU_ALLOCATION_ALIGNMENT, width * height); - - if (mRs == nullptr) { - mRs = new RSC::RS(); - // a null path is OK because there are no custom kernels used - // hence nothing gets cached by RS - if (!mRs->init("", RSC::RS_INIT_LOW_LATENCY | RSC::RS_INIT_SYNCHRONOUS)) { - mRs.clear(); - ALOGE("blur RS failed to init"); - } else { - mRsElement = RSC::Element::A_8(mRs); - mRsScript = RSC::ScriptIntrinsicBlur::create(mRs, mRsElement); - } - } - if (mRs != nullptr) { - RSC::sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0); - RSC::sp<RSC::Allocation> ain = RSC::Allocation::createTyped( - mRs, t, RS_ALLOCATION_MIPMAP_NONE, - RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, *image); - RSC::sp<RSC::Allocation> aout = RSC::Allocation::createTyped( - mRs, t, RS_ALLOCATION_MIPMAP_NONE, - RS_ALLOCATION_USAGE_SCRIPT | RS_ALLOCATION_USAGE_SHARED, outImage); - - mRsScript->setRadius(radius); - mRsScript->setInput(ain); - mRsScript->forEach(aout); - - // replace the original image's pointer, avoiding a copy back to the original buffer - free(*image); - *image = outImage; - - return; - } - } - - std::unique_ptr<float[]> gaussian(new float[2 * intRadius + 1]); - 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 std::vector<CacheTexture*>& cacheTextures) { - uint32_t size = 0; - for (uint32_t i = 0; i < cacheTextures.size(); i++) { - CacheTexture* cacheTexture = cacheTextures[i]; - if (cacheTexture && cacheTexture->getPixelBuffer()) { - size += cacheTexture->getPixelBuffer()->getSize(); - } - } - return size; -} - -static uint32_t calculateFreeCacheSize(const std::vector<CacheTexture*>& cacheTextures) { - uint32_t size = 0; - for (uint32_t i = 0; i < cacheTextures.size(); i++) { - CacheTexture* cacheTexture = cacheTextures[i]; - if (cacheTexture && cacheTexture->getPixelBuffer()) { - size += cacheTexture->calculateFreeMemory(); - } - } - return size; -} - -const std::vector<CacheTexture*>& FontRenderer::cacheTexturesForFormat(GLenum format) const { - switch (format) { - case GL_ALPHA: { - return mACacheTextures; - } - case GL_RGBA: { - return mRGBACacheTextures; - } - default: { - LOG_ALWAYS_FATAL("Unsupported format: %d", format); - // Impossible to hit this, but the compiler doesn't know that - return *(new std::vector<CacheTexture*>()); - } - } -} - -static void dumpTextures(String8& log, const char* tag, - const std::vector<CacheTexture*>& cacheTextures) { - for (uint32_t i = 0; i < cacheTextures.size(); i++) { - CacheTexture* cacheTexture = cacheTextures[i]; - if (cacheTexture && cacheTexture->getPixelBuffer()) { - uint32_t free = cacheTexture->calculateFreeMemory(); - uint32_t total = cacheTexture->getPixelBuffer()->getSize(); - log.appendFormat(" %-4s texture %d %8d / %8d\n", tag, i, total - free, total); - } - } -} - -void FontRenderer::dumpMemoryUsage(String8& log) const { - const uint32_t sizeA8 = getCacheSize(GL_ALPHA); - const uint32_t usedA8 = sizeA8 - getFreeCacheSize(GL_ALPHA); - const uint32_t sizeRGBA = getCacheSize(GL_RGBA); - const uint32_t usedRGBA = sizeRGBA - getFreeCacheSize(GL_RGBA); - log.appendFormat(" FontRenderer A8 %8d / %8d\n", usedA8, sizeA8); - dumpTextures(log, "A8", cacheTexturesForFormat(GL_ALPHA)); - log.appendFormat(" FontRenderer RGBA %8d / %8d\n", usedRGBA, sizeRGBA); - dumpTextures(log, "RGBA", cacheTexturesForFormat(GL_RGBA)); - log.appendFormat(" FontRenderer total %8d / %8d\n", usedA8 + usedRGBA, sizeA8 + sizeRGBA); -} - -uint32_t FontRenderer::getCacheSize(GLenum format) const { - return calculateCacheSize(cacheTexturesForFormat(format)); -} - -uint32_t FontRenderer::getFreeCacheSize(GLenum format) const { - return calculateFreeCacheSize(cacheTexturesForFormat(format)); -} - -uint32_t FontRenderer::getSize() const { - return getCacheSize(GL_ALPHA) + getCacheSize(GL_RGBA); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h deleted file mode 100644 index 6b9dec4719cb..000000000000 --- a/libs/hwui/FontRenderer.h +++ /dev/null @@ -1,208 +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. - */ - -#pragma once - -#include "font/CacheTexture.h" -#include "font/CachedGlyphInfo.h" -#include "font/Font.h" -#include "font/FontUtil.h" -#ifdef BUGREPORT_FONT_CACHE_USAGE -#include "font/FontCacheHistoryTracker.h" -#endif - -#include <utils/LruCache.h> -#include <utils/String8.h> -#include <utils/StrongPointer.h> - -#include <SkPaint.h> - -#include <GLES2/gl2.h> - -#include <vector> - -#include "RenderScript.h" -namespace RSC { -class Element; -class RS; -class ScriptIntrinsicBlur; -class sp; -} - -namespace android { -namespace uirenderer { - -class BakedOpState; -class BakedOpRenderer; -struct ClipBase; - -class TextDrawFunctor { -public: - TextDrawFunctor(BakedOpRenderer* renderer, const BakedOpState* bakedState, const ClipBase* clip, - float x, float y, bool pureTranslate, int alpha, SkBlendMode mode, - const SkPaint* paint) - : renderer(renderer) - , bakedState(bakedState) - , clip(clip) - , x(x) - , y(y) - , pureTranslate(pureTranslate) - , alpha(alpha) - , mode(mode) - , paint(paint) {} - - void draw(CacheTexture& texture, bool linearFiltering); - - BakedOpRenderer* renderer; - const BakedOpState* bakedState; - const ClipBase* clip; - float x; - float y; - bool pureTranslate; - int alpha; - SkBlendMode mode; - const SkPaint* paint; -}; - -class FontRenderer { -public: - explicit FontRenderer(const uint8_t* gammaTable); - ~FontRenderer(); - - void flushLargeCaches(std::vector<CacheTexture*>& cacheTextures); - void flushLargeCaches(); - - void setFont(const SkPaint* paint, const SkMatrix& matrix); - - void precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, - const SkMatrix& matrix); - void endPrecaching(); - - bool renderPosText(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, int numGlyphs, - int x, int y, const float* positions, Rect* outBounds, - TextDrawFunctor* functor, bool forceFinish = true); - - bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const glyph_t* glyphs, - int numGlyphs, const SkPath* path, float hOffset, float vOffset, - Rect* outBounds, TextDrawFunctor* functor); - - struct DropShadow { - uint32_t width; - uint32_t height; - uint8_t* image; - int32_t penX; - int32_t penY; - }; - - // 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 glyph_t* glyphs, int numGlyphs, - float radius, const float* positions); - - void setTextureFiltering(bool linearFiltering) { mLinearFiltering = linearFiltering; } - - uint32_t getSize() const; - void dumpMemoryUsage(String8& log) const; - -#ifdef BUGREPORT_FONT_CACHE_USAGE - FontCacheHistoryTracker& historyTracker() { return mHistoryTracker; } -#endif - -private: - friend class Font; - - const uint8_t* mGammaTable; - - void allocateTextureMemory(CacheTexture* cacheTexture); - void deallocateTextureMemory(CacheTexture* cacheTexture); - void initTextTexture(); - 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(std::vector<CacheTexture*>& cacheTextures, - const SkGlyph& glyph, uint32_t* startX, uint32_t* startY); - - void flushAllAndInvalidate(); - - void checkInit(); - void initRender(const Rect* clip, Rect* bounds, TextDrawFunctor* functor); - void finishRender(); - - 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, float x3, float y3, float u3, float v3, float x4, float y4, - float u4, float v4, CacheTexture* texture); - void appendMeshQuad(float x1, float y1, float u1, float v1, float x2, float y2, float u2, - float v2, float x3, float y3, float u3, float v3, float x4, float y4, - float u4, float v4, CacheTexture* texture); - void appendRotatedMeshQuad(float x1, float y1, float u1, float v1, float x2, float y2, float u2, - float v2, float x3, float y3, float u3, float v3, float x4, float y4, - float u4, float v4, CacheTexture* texture); - - void checkTextureUpdate(); - - void setTextureDirty() { mUploadTexture = true; } - - const std::vector<CacheTexture*>& cacheTexturesForFormat(GLenum format) const; - uint32_t getCacheSize(GLenum format) const; - uint32_t getFreeCacheSize(GLenum format) const; - - uint32_t mSmallCacheWidth; - uint32_t mSmallCacheHeight; - uint32_t mLargeCacheWidth; - uint32_t mLargeCacheHeight; - - std::vector<CacheTexture*> mACacheTextures; - std::vector<CacheTexture*> mRGBACacheTextures; - - Font* mCurrentFont; - LruCache<Font::FontDescription, Font*> mActiveFonts; - - CacheTexture* mCurrentCacheTexture; - - bool mUploadTexture; - - TextDrawFunctor* mFunctor; - const Rect* mClip; - Rect* mBounds; - bool mDrawn; - - bool mInitialized; - - bool mLinearFiltering; - -#ifdef BUGREPORT_FONT_CACHE_USAGE - FontCacheHistoryTracker mHistoryTracker; -#endif - - // RS constructs - RSC::sp<RSC::RS> mRs; - RSC::sp<const RSC::Element> mRsElement; - RSC::sp<RSC::ScriptIntrinsicBlur> mRsScript; - - static void computeGaussianWeights(float* weights, int32_t radius); - static void horizontalBlur(float* weights, int32_t radius, const uint8_t* source, uint8_t* dest, - int32_t width, int32_t height); - static void verticalBlur(float* weights, int32_t radius, const uint8_t* source, uint8_t* dest, - int32_t width, int32_t height); - - // the input image handle may have its pointer replaced (to avoid copies) - void blurImage(uint8_t** image, int32_t width, int32_t height, float radius); -}; - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp deleted file mode 100644 index ced37ede0746..000000000000 --- a/libs/hwui/FrameBuilder.cpp +++ /dev/null @@ -1,976 +0,0 @@ -/* - * 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 "DeferredLayerUpdater.h" -#include "LayerUpdateQueue.h" -#include "RenderNode.h" -#include "VectorDrawable.h" -#include "hwui/Canvas.h" -#include "renderstate/OffscreenBufferPool.h" -#include "utils/FatVector.h" -#include "utils/PaintUtils.h" -#include "utils/TraceUtils.h" - -#include <SkPathOps.h> -#include <utils/TypeHelpers.h> - -namespace android { -namespace uirenderer { - -FrameBuilder::FrameBuilder(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, - const LightGeometry& lightGeometry, Caches& caches) - : mStdAllocator(mAllocator) - , mLayerBuilders(mStdAllocator) - , mLayerStack(mStdAllocator) - , mCanvasState(*this) - , mCaches(caches) - , mLightRadius(lightGeometry.radius) - , mDrawFbo0(true) { - // Prepare to defer Fbo0 - auto fbo0 = mAllocator.create<LayerBuilder>(viewportWidth, viewportHeight, Rect(clip)); - mLayerBuilders.push_back(fbo0); - mLayerStack.push_back(0); - mCanvasState.initializeSaveStack(viewportWidth, viewportHeight, clip.fLeft, clip.fTop, - clip.fRight, clip.fBottom, lightGeometry.center); -} - -FrameBuilder::FrameBuilder(const LayerUpdateQueue& layers, const LightGeometry& lightGeometry, - Caches& caches) - : mStdAllocator(mAllocator) - , mLayerBuilders(mStdAllocator) - , mLayerStack(mStdAllocator) - , mCanvasState(*this) - , mCaches(caches) - , mLightRadius(lightGeometry.radius) - , mDrawFbo0(false) { - // TODO: remove, with each layer on its own save stack - - // Prepare to defer Fbo0 (which will be empty) - auto fbo0 = mAllocator.create<LayerBuilder>(1, 1, Rect(1, 1)); - mLayerBuilders.push_back(fbo0); - mLayerStack.push_back(0); - mCanvasState.initializeSaveStack(1, 1, 0, 0, 1, 1, lightGeometry.center); - - deferLayers(layers); -} - -void FrameBuilder::deferLayers(const LayerUpdateQueue& layers) { - // Render all layers to be updated, in order. Defer in reverse order, so that they'll be - // updated in the order they're passed in (mLayerBuilders are issued to Renderer in reverse) - for (int i = layers.entries().size() - 1; i >= 0; i--) { - RenderNode* layerNode = layers.entries()[i].renderNode.get(); - // 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 - OffscreenBuffer* layer = layerNode->getLayer(); - if (CC_LIKELY(layer)) { - ATRACE_FORMAT("Optimize HW Layer DisplayList %s %ux%u", layerNode->getName(), - layerNode->getWidth(), layerNode->getHeight()); - - Rect layerDamage = layers.entries()[i].damage; - // TODO: ensure layer damage can't be larger than layer - layerDamage.doIntersect(0, 0, layer->viewportWidth, layer->viewportHeight); - layerNode->computeOrdering(); - - // map current light center into RenderNode's coordinate space - Vector3 lightCenter = mCanvasState.currentSnapshot()->getRelativeLightCenter(); - layer->inverseTransformInWindow.mapPoint3d(lightCenter); - - saveForLayer(layerNode->getWidth(), layerNode->getHeight(), 0, 0, layerDamage, - lightCenter, nullptr, layerNode); - - if (layerNode->getDisplayList()) { - deferNodeOps(*layerNode); - } - restoreForLayer(); - } - } -} - -void FrameBuilder::deferRenderNode(RenderNode& renderNode) { - renderNode.computeOrdering(); - - mCanvasState.save(SaveFlags::MatrixClip); - deferNodePropsAndOps(renderNode); - mCanvasState.restore(); -} - -void FrameBuilder::deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode) { - renderNode.computeOrdering(); - - mCanvasState.save(SaveFlags::MatrixClip); - mCanvasState.translate(tx, ty); - mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, - SkClipOp::kIntersect); - deferNodePropsAndOps(renderNode); - mCanvasState.restore(); -} - -static Rect nodeBounds(RenderNode& node) { - auto& props = node.properties(); - return Rect(props.getLeft(), props.getTop(), props.getRight(), props.getBottom()); -} - -void FrameBuilder::deferRenderNodeScene(const std::vector<sp<RenderNode> >& nodes, - const Rect& contentDrawBounds) { - if (nodes.size() < 1) return; - if (nodes.size() == 1) { - if (!nodes[0]->nothingToDraw()) { - deferRenderNode(*nodes[0]); - } - return; - } - // It there are multiple render nodes, they are laid out as follows: - // #0 - backdrop (content + caption) - // #1 - content (local bounds are at (0,0), will be translated and clipped to backdrop) - // #2 - additional overlay nodes - // Usually the backdrop cannot be seen since it will be entirely covered by the content. While - // resizing however it might become partially visible. The following render loop will crop the - // backdrop against the content and draw the remaining part of it. It will then draw the content - // cropped to the backdrop (since that indicates a shrinking of the window). - // - // Additional nodes will be drawn on top with no particular clipping semantics. - - // Usually the contents bounds should be mContentDrawBounds - however - we will - // move it towards the fixed edge to give it a more stable appearance (for the moment). - // If there is no content bounds we ignore the layering as stated above and start with 2. - - // Backdrop bounds in render target space - const Rect backdrop = nodeBounds(*nodes[0]); - - // Bounds that content will fill in render target space (note content node bounds may be bigger) - Rect content(contentDrawBounds.getWidth(), contentDrawBounds.getHeight()); - content.translate(backdrop.left, backdrop.top); - if (!content.contains(backdrop) && !nodes[0]->nothingToDraw()) { - // Content doesn't entirely overlap backdrop, so fill around content (right/bottom) - - // Note: in the future, if content doesn't snap to backdrop's left/top, this may need to - // also fill left/top. Currently, both 2up and freeform position content at the top/left of - // the backdrop, so this isn't necessary. - if (content.right < backdrop.right) { - // draw backdrop to right side of content - deferRenderNode(0, 0, - Rect(content.right, backdrop.top, backdrop.right, backdrop.bottom), - *nodes[0]); - } - if (content.bottom < backdrop.bottom) { - // draw backdrop to bottom of content - // Note: bottom fill uses content left/right, to avoid overdrawing left/right fill - deferRenderNode(0, 0, - Rect(content.left, content.bottom, content.right, backdrop.bottom), - *nodes[0]); - } - } - - if (!nodes[1]->nothingToDraw()) { - if (!backdrop.isEmpty()) { - // content node translation to catch up with backdrop - float dx = contentDrawBounds.left - backdrop.left; - float dy = contentDrawBounds.top - backdrop.top; - - Rect contentLocalClip = backdrop; - contentLocalClip.translate(dx, dy); - deferRenderNode(-dx, -dy, contentLocalClip, *nodes[1]); - } else { - deferRenderNode(*nodes[1]); - } - } - - // remaining overlay nodes, simply defer - for (size_t index = 2; index < nodes.size(); index++) { - if (!nodes[index]->nothingToDraw()) { - deferRenderNode(*nodes[index]); - } - } -} - -void FrameBuilder::onViewportInitialized() {} - -void FrameBuilder::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} - -void FrameBuilder::deferNodePropsAndOps(RenderNode& node) { - const RenderProperties& properties = node.properties(); - const Outline& outline = properties.getOutline(); - if (properties.getAlpha() <= 0 || (outline.getShouldClip() && outline.isEmpty()) || - properties.getScaleX() == 0 || properties.getScaleY() == 0) { - return; // rejected - } - - if (properties.getLeft() != 0 || properties.getTop() != 0) { - mCanvasState.translate(properties.getLeft(), properties.getTop()); - } - if (properties.getStaticMatrix()) { - mCanvasState.concatMatrix(*properties.getStaticMatrix()); - } else if (properties.getAnimationMatrix()) { - mCanvasState.concatMatrix(*properties.getAnimationMatrix()); - } - if (properties.hasTransformMatrix()) { - if (properties.isTransformTranslateOnly()) { - mCanvasState.translate(properties.getTranslationX(), properties.getTranslationY()); - } else { - mCanvasState.concatMatrix(*properties.getTransformMatrix()); - } - } - - const int width = properties.getWidth(); - const int height = properties.getHeight(); - - Rect saveLayerBounds; // will be set to non-empty if saveLayer needed - const bool isLayer = properties.effectiveLayerType() != LayerType::None; - int clipFlags = properties.getClippingFlags(); - if (properties.getAlpha() < 1) { - if (isLayer) { - clipFlags &= ~CLIP_TO_BOUNDS; // bounds clipping done by layer - } - if (CC_LIKELY(isLayer || !properties.getHasOverlappingRendering())) { - // simply scale rendering content's alpha - mCanvasState.scaleAlpha(properties.getAlpha()); - } else { - // schedule saveLayer by initializing saveLayerBounds - saveLayerBounds.set(0, 0, width, height); - if (clipFlags) { - properties.getClippingRectForFlags(clipFlags, &saveLayerBounds); - clipFlags = 0; // all clipping done by savelayer - } - } - - if (CC_UNLIKELY(ATRACE_ENABLED() && properties.promotedToLayer())) { - // pretend alpha always causes savelayer to warn about - // performance problem affecting old versions - ATRACE_FORMAT("%s alpha caused saveLayer %dx%d", node.getName(), width, height); - } - } - if (clipFlags) { - Rect clipRect; - properties.getClippingRectForFlags(clipFlags, &clipRect); - mCanvasState.clipRect(clipRect.left, clipRect.top, clipRect.right, clipRect.bottom, - SkClipOp::kIntersect); - } - - if (properties.getRevealClip().willClip()) { - Rect bounds; - properties.getRevealClip().getBounds(&bounds); - mCanvasState.setClippingRoundRect(mAllocator, bounds, - properties.getRevealClip().getRadius()); - } else if (properties.getOutline().willClip()) { - mCanvasState.setClippingOutline(mAllocator, &(properties.getOutline())); - } - - bool quickRejected = mCanvasState.currentSnapshot()->getRenderTargetClip().isEmpty() || - (properties.getClipToBounds() && - mCanvasState.quickRejectConservative(0, 0, width, height)); - if (!quickRejected) { - // not rejected, so defer render as either Layer, or direct (possibly wrapped in saveLayer) - if (node.getLayer()) { - // HW layer - LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>(node); - BakedOpState* bakedOpState = tryBakeOpState(*drawLayerOp); - if (bakedOpState) { - // Node's layer already deferred, schedule it to render into parent layer - currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Bitmap); - } - } else if (CC_UNLIKELY(!saveLayerBounds.isEmpty())) { - // draw DisplayList contents within temporary, since persisted layer could not be used. - // (temp layers are clipped to viewport, since they don't persist offscreen content) - SkPaint saveLayerPaint; - saveLayerPaint.setAlpha(properties.getAlpha()); - deferBeginLayerOp(*mAllocator.create_trivial<BeginLayerOp>( - saveLayerBounds, Matrix4::identity(), - nullptr, // no record-time clip - need only respect defer-time one - &saveLayerPaint)); - deferNodeOps(node); - deferEndLayerOp(*mAllocator.create_trivial<EndLayerOp>()); - } else { - deferNodeOps(node); - } - } -} - -typedef key_value_pair_t<float, const RenderNodeOp*> ZRenderNodeOpPair; - -template <typename V> -static void buildZSortedChildList(V* zTranslatedNodes, const DisplayList& displayList, - const DisplayList::Chunk& chunk) { - if (chunk.beginChildIndex == chunk.endChildIndex) return; - - for (size_t i = chunk.beginChildIndex; i < chunk.endChildIndex; i++) { - RenderNodeOp* childOp = displayList.getChildren()[i]; - RenderNode* child = childOp->renderNode; - float childZ = child->properties().getZ(); - - if (!MathUtils::isZero(childZ) && chunk.reorderChildren) { - zTranslatedNodes->push_back(ZRenderNodeOpPair(childZ, childOp)); - childOp->skipInOrderDraw = true; - } else if (!child->properties().getProjectBackwards()) { - // regular, in order drawing DisplayList - childOp->skipInOrderDraw = false; - } - } - - // Z sort any 3d children (stable-ness makes z compare fall back to standard drawing order) - std::stable_sort(zTranslatedNodes->begin(), zTranslatedNodes->end()); -} - -template <typename V> -static size_t findNonNegativeIndex(const V& zTranslatedNodes) { - for (size_t i = 0; i < zTranslatedNodes.size(); i++) { - if (zTranslatedNodes[i].key >= 0.0f) return i; - } - return zTranslatedNodes.size(); -} - -template <typename V> -void FrameBuilder::defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode, - const V& zTranslatedNodes) { - const int size = zTranslatedNodes.size(); - if (size == 0 || (mode == ChildrenSelectMode::Negative && zTranslatedNodes[0].key > 0.0f) || - (mode == ChildrenSelectMode::Positive && zTranslatedNodes[size - 1].key < 0.0f)) { - // no 3d children to draw - return; - } - - /** - * Draw shadows and (potential) casters mostly in order, but allow the shadows of casters - * with very similar Z heights to draw together. - * - * This way, if Views A & B have the same Z height and are both casting shadows, the shadows are - * underneath both, and neither's shadow is drawn on top of the other. - */ - const size_t nonNegativeIndex = findNonNegativeIndex(zTranslatedNodes); - size_t drawIndex, shadowIndex, endIndex; - if (mode == ChildrenSelectMode::Negative) { - drawIndex = 0; - endIndex = nonNegativeIndex; - shadowIndex = endIndex; // draw no shadows - } else { - drawIndex = nonNegativeIndex; - endIndex = size; - shadowIndex = drawIndex; // potentially draw shadow for each pos Z child - } - - float lastCasterZ = 0.0f; - while (shadowIndex < endIndex || drawIndex < endIndex) { - if (shadowIndex < endIndex) { - const RenderNodeOp* casterNodeOp = zTranslatedNodes[shadowIndex].value; - const float casterZ = zTranslatedNodes[shadowIndex].key; - // attempt to render the shadow if the caster about to be drawn is its caster, - // OR if its caster's Z value is similar to the previous potential caster - if (shadowIndex == drawIndex || casterZ - lastCasterZ < 0.1f) { - deferShadow(reorderClip, *casterNodeOp); - - lastCasterZ = casterZ; // must do this even if current caster not casting a shadow - shadowIndex++; - continue; - } - } - - const RenderNodeOp* childOp = zTranslatedNodes[drawIndex].value; - deferRenderNodeOpImpl(*childOp); - drawIndex++; - } -} - -void FrameBuilder::deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterNodeOp) { - auto& node = *casterNodeOp.renderNode; - auto& properties = node.properties(); - - if (properties.getAlpha() <= 0.0f || properties.getOutline().getAlpha() <= 0.0f || - !properties.getOutline().getPath() || properties.getScaleX() == 0 || - properties.getScaleY() == 0) { - // no shadow to draw - return; - } - - const SkPath* casterOutlinePath = properties.getOutline().getPath(); - const SkPath* revealClipPath = properties.getRevealClip().getPath(); - if (revealClipPath && revealClipPath->isEmpty()) return; - - float casterAlpha = properties.getAlpha() * properties.getOutline().getAlpha(); - - // holds temporary SkPath to store the result of intersections - SkPath* frameAllocatedPath = nullptr; - const SkPath* casterPath = casterOutlinePath; - - // intersect the shadow-casting path with the reveal, if present - if (revealClipPath) { - frameAllocatedPath = createFrameAllocatedPath(); - - Op(*casterPath, *revealClipPath, kIntersect_SkPathOp, frameAllocatedPath); - casterPath = frameAllocatedPath; - } - - // intersect the shadow-casting path with the clipBounds, if present - if (properties.getClippingFlags() & CLIP_TO_CLIP_BOUNDS) { - if (!frameAllocatedPath) { - frameAllocatedPath = createFrameAllocatedPath(); - } - Rect clipBounds; - properties.getClippingRectForFlags(CLIP_TO_CLIP_BOUNDS, &clipBounds); - SkPath clipBoundsPath; - clipBoundsPath.addRect(clipBounds.left, clipBounds.top, clipBounds.right, - clipBounds.bottom); - - Op(*casterPath, clipBoundsPath, kIntersect_SkPathOp, frameAllocatedPath); - casterPath = frameAllocatedPath; - } - - // apply reorder clip to shadow, so it respects clip at beginning of reorderable chunk - int restoreTo = mCanvasState.save(SaveFlags::MatrixClip); - mCanvasState.writableSnapshot()->applyClip(reorderClip, - *mCanvasState.currentSnapshot()->transform); - if (CC_LIKELY(!mCanvasState.getRenderTargetClipBounds().isEmpty())) { - Matrix4 shadowMatrixXY(casterNodeOp.localMatrix); - Matrix4 shadowMatrixZ(casterNodeOp.localMatrix); - node.applyViewPropertyTransforms(shadowMatrixXY, false); - node.applyViewPropertyTransforms(shadowMatrixZ, true); - - sp<TessellationCache::ShadowTask> task = mCaches.tessellationCache.getShadowTask( - mCanvasState.currentTransform(), mCanvasState.getLocalClipBounds(), - casterAlpha >= 1.0f, casterPath, &shadowMatrixXY, &shadowMatrixZ, - mCanvasState.currentSnapshot()->getRelativeLightCenter(), mLightRadius); - ShadowOp* shadowOp = mAllocator.create<ShadowOp>(task, casterAlpha); - BakedOpState* bakedOpState = BakedOpState::tryShadowOpConstruct( - mAllocator, *mCanvasState.writableSnapshot(), shadowOp); - if (CC_LIKELY(bakedOpState)) { - currentLayer().deferUnmergeableOp(mAllocator, bakedOpState, OpBatchType::Shadow); - } - } - mCanvasState.restoreToCount(restoreTo); -} - -void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) { - int count = mCanvasState.save(SaveFlags::MatrixClip); - const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath(); - - SkPath transformedMaskPath; // on stack, since BakedOpState makes a deep copy - if (projectionReceiverOutline) { - // transform the mask for this projector into render target space - // TODO: consider combining both transforms by stashing transform instead of applying - SkMatrix skCurrentTransform; - mCanvasState.currentTransform()->copyTo(skCurrentTransform); - projectionReceiverOutline->transform(skCurrentTransform, &transformedMaskPath); - mCanvasState.setProjectionPathMask(&transformedMaskPath); - } - - for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) { - RenderNodeOp* childOp = renderNode.mProjectedNodes[i]; - RenderNode& childNode = *childOp->renderNode; - - // Draw child if it has content, but ignore state in childOp - matrix already applied to - // transformFromCompositingAncestor, and record-time clip is ignored when projecting - if (!childNode.nothingToDraw()) { - int restoreTo = mCanvasState.save(SaveFlags::MatrixClip); - - // Apply transform between ancestor and projected descendant - mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor); - - deferNodePropsAndOps(childNode); - - mCanvasState.restoreToCount(restoreTo); - } - } - mCanvasState.restoreToCount(count); -} - -/** - * Used to define a list of lambdas referencing private FrameBuilder::onXX::defer() methods. - * - * This allows opIds embedded in the RecordedOps to be used for dispatching to these lambdas. - * E.g. a BitmapOp op then would be dispatched to FrameBuilder::onBitmapOp(const BitmapOp&) - */ -#define OP_RECEIVER(Type) \ - [](FrameBuilder& frameBuilder, const RecordedOp& op) { \ - frameBuilder.defer##Type(static_cast<const Type&>(op)); \ - }, -void FrameBuilder::deferNodeOps(const RenderNode& renderNode) { - typedef void (*OpDispatcher)(FrameBuilder & frameBuilder, const RecordedOp& op); - static OpDispatcher receivers[] = BUILD_DEFERRABLE_OP_LUT(OP_RECEIVER); - - // can't be null, since DL=null node rejection happens before deferNodePropsAndOps - const DisplayList& displayList = *(renderNode.getDisplayList()); - for (auto& chunk : displayList.getChunks()) { - FatVector<ZRenderNodeOpPair, 16> zTranslatedNodes; - buildZSortedChildList(&zTranslatedNodes, displayList, chunk); - - defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Negative, zTranslatedNodes); - for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { - const RecordedOp* op = displayList.getOps()[opIndex]; - receivers[op->opId](*this, *op); - - if (CC_UNLIKELY(!renderNode.mProjectedNodes.empty() && - displayList.projectionReceiveIndex >= 0 && - static_cast<int>(opIndex) == displayList.projectionReceiveIndex)) { - deferProjectedChildren(renderNode); - } - } - defer3dChildren(chunk.reorderClip, ChildrenSelectMode::Positive, zTranslatedNodes); - } -} - -void FrameBuilder::deferRenderNodeOpImpl(const RenderNodeOp& op) { - if (op.renderNode->nothingToDraw()) return; - int count = mCanvasState.save(SaveFlags::MatrixClip); - - // apply state from RecordedOp (clip first, since op's clip is transformed by current matrix) - mCanvasState.writableSnapshot()->applyClip(op.localClip, - *mCanvasState.currentSnapshot()->transform); - mCanvasState.concatMatrix(op.localMatrix); - - // then apply state from node properties, and defer ops - deferNodePropsAndOps(*op.renderNode); - - mCanvasState.restoreToCount(count); -} - -void FrameBuilder::deferRenderNodeOp(const RenderNodeOp& op) { - if (!op.skipInOrderDraw) { - deferRenderNodeOpImpl(op); - } -} - -/** - * Defers an unmergeable, strokeable op, accounting correctly - * for paint's style on the bounds being computed. - */ -BakedOpState* FrameBuilder::deferStrokeableOp(const RecordedOp& op, batchid_t batchId, - BakedOpState::StrokeBehavior strokeBehavior, - bool expandForPathTexture) { - // Note: here we account for stroke when baking the op - BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( - mAllocator, *mCanvasState.writableSnapshot(), op, strokeBehavior, expandForPathTexture); - if (!bakedState) return nullptr; // quick rejected - - if (op.opId == RecordedOpId::RectOp && op.paint->getStyle() != SkPaint::kStroke_Style) { - bakedState->setupOpacity(op.paint); - } - - currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); - return bakedState; -} - -/** - * Returns batch id for tessellatable shapes, based on paint. Checks to see if path effect/AA will - * be used, since they trigger significantly different rendering paths. - * - * Note: not used for lines/points, since they don't currently support path effects. - */ -static batchid_t tessBatchId(const RecordedOp& op) { - const SkPaint& paint = *(op.paint); - return paint.getPathEffect() - ? OpBatchType::AlphaMaskTexture - : (paint.isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices); -} - -void FrameBuilder::deferArcOp(const ArcOp& op) { - // Pass true below since arcs have a tendency to draw outside their expected bounds within - // their path textures. Passing true makes it more likely that we'll scissor, instead of - // corrupting the frame by drawing outside of clip bounds. - deferStrokeableOp(op, tessBatchId(op), BakedOpState::StrokeBehavior::StyleDefined, true); -} - -static bool hasMergeableClip(const BakedOpState& state) { - return !state.computedState.clipState || - state.computedState.clipState->mode == ClipMode::Rectangle; -} - -void FrameBuilder::deferBitmapOp(const BitmapOp& op) { - BakedOpState* bakedState = tryBakeOpState(op); - if (!bakedState) return; // quick rejected - - if (op.bitmap->isOpaque()) { - bakedState->setupOpacity(op.paint); - } - - // Don't merge non-simply transformed or neg scale ops, SET_TEXTURE doesn't handle rotation - // Don't merge A8 bitmaps - the paint's color isn't compared by mergeId, or in - // MergingDrawBatch::canMergeWith() - if (bakedState->computedState.transform.isSimple() && - bakedState->computedState.transform.positiveScale() && - PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver && - op.bitmap->colorType() != kAlpha_8_SkColorType && hasMergeableClip(*bakedState)) { - mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID()); - currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::Bitmap, mergeId); - } else { - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); - } -} - -void FrameBuilder::deferBitmapMeshOp(const BitmapMeshOp& op) { - BakedOpState* bakedState = tryBakeOpState(op); - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); -} - -void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) { - BakedOpState* bakedState = tryBakeOpState(op); - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); -} - -void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) { - Bitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty(); - SkPaint* paint = op.vectorDrawable->getPaint(); - const BitmapRectOp* resolvedOp = mAllocator.create_trivial<BitmapRectOp>( - op.unmappedBounds, op.localMatrix, op.localClip, paint, &bitmap, - Rect(bitmap.width(), bitmap.height())); - deferBitmapRectOp(*resolvedOp); -} - -void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) { - // allocate a temporary oval op (with mAllocator, so it persists until render), so the - // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. - float x = *(op.x); - float y = *(op.y); - float radius = *(op.radius); - Rect unmappedBounds(x - radius, y - radius, x + radius, y + radius); - const OvalOp* resolvedOp = mAllocator.create_trivial<OvalOp>(unmappedBounds, op.localMatrix, - op.localClip, op.paint); - deferOvalOp(*resolvedOp); -} - -void FrameBuilder::deferColorOp(const ColorOp& op) { - BakedOpState* bakedState = tryBakeUnboundedOpState(op); - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices); -} - -void FrameBuilder::deferFunctorOp(const FunctorOp& op) { - BakedOpState* bakedState = tryBakeUnboundedOpState(op); - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Functor); -} - -void FrameBuilder::deferLinesOp(const LinesOp& op) { - batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; - deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); -} - -void FrameBuilder::deferOvalOp(const OvalOp& op) { - deferStrokeableOp(op, tessBatchId(op)); -} - -void FrameBuilder::deferPatchOp(const PatchOp& op) { - BakedOpState* bakedState = tryBakeOpState(op); - if (!bakedState) return; // quick rejected - - if (bakedState->computedState.transform.isPureTranslate() && - PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver && - hasMergeableClip(*bakedState)) { - mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.bitmap->getGenerationID()); - - // Only use the MergedPatch batchId when merged, so Bitmap+Patch don't try to merge together - currentLayer().deferMergeableOp(mAllocator, bakedState, OpBatchType::MergedPatch, mergeId); - } else { - // Use Bitmap batchId since Bitmap+Patch use same shader - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap); - } -} - -void FrameBuilder::deferPathOp(const PathOp& op) { - auto state = deferStrokeableOp(op, OpBatchType::AlphaMaskTexture); - if (CC_LIKELY(state)) { - mCaches.pathCache.precache(op.path, op.paint); - } -} - -void FrameBuilder::deferPointsOp(const PointsOp& op) { - batchid_t batch = op.paint->isAntiAlias() ? OpBatchType::AlphaVertices : OpBatchType::Vertices; - deferStrokeableOp(op, batch, BakedOpState::StrokeBehavior::Forced); -} - -void FrameBuilder::deferRectOp(const RectOp& op) { - deferStrokeableOp(op, tessBatchId(op)); -} - -void FrameBuilder::deferRoundRectOp(const RoundRectOp& op) { - auto state = deferStrokeableOp(op, tessBatchId(op)); - if (CC_LIKELY(state && !op.paint->getPathEffect())) { - // TODO: consider storing tessellation task in BakedOpState - mCaches.tessellationCache.precacheRoundRect(state->computedState.transform, *(op.paint), - op.unmappedBounds.getWidth(), - op.unmappedBounds.getHeight(), op.rx, op.ry); - } -} - -void FrameBuilder::deferRoundRectPropsOp(const RoundRectPropsOp& op) { - // allocate a temporary round rect op (with mAllocator, so it persists until render), so the - // renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple. - const RoundRectOp* resolvedOp = mAllocator.create_trivial<RoundRectOp>( - Rect(*(op.left), *(op.top), *(op.right), *(op.bottom)), op.localMatrix, op.localClip, - op.paint, *op.rx, *op.ry); - deferRoundRectOp(*resolvedOp); -} - -void FrameBuilder::deferSimpleRectsOp(const SimpleRectsOp& op) { - BakedOpState* bakedState = tryBakeOpState(op); - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Vertices); -} - -static batchid_t textBatchId(const SkPaint& paint) { - // TODO: better handling of shader (since we won't care about color then) - return paint.getColor() == SK_ColorBLACK ? OpBatchType::Text : OpBatchType::ColorText; -} - -void FrameBuilder::deferTextOp(const TextOp& op) { - BakedOpState* bakedState = BakedOpState::tryStrokeableOpConstruct( - mAllocator, *mCanvasState.writableSnapshot(), op, - BakedOpState::StrokeBehavior::StyleDefined, false); - if (!bakedState) return; // quick rejected - - batchid_t batchId = textBatchId(*(op.paint)); - if (bakedState->computedState.transform.isPureTranslate() && - PaintUtils::getBlendModeDirect(op.paint) == SkBlendMode::kSrcOver && - hasMergeableClip(*bakedState)) { - mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor()); - currentLayer().deferMergeableOp(mAllocator, bakedState, batchId, mergeId); - } else { - currentLayer().deferUnmergeableOp(mAllocator, bakedState, batchId); - } - - FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(); - auto& totalTransform = bakedState->computedState.transform; - if (totalTransform.isPureTranslate() || totalTransform.isPerspective()) { - fontRenderer.precache(op.paint, op.glyphs, op.glyphCount, SkMatrix::I()); - } else { - // Partial transform case, see BakedOpDispatcher::renderTextOp - float sx, sy; - totalTransform.decomposeScale(sx, sy); - fontRenderer.precache( - op.paint, op.glyphs, op.glyphCount, - SkMatrix::MakeScale(roundf(std::max(1.0f, sx)), roundf(std::max(1.0f, sy)))); - } -} - -void FrameBuilder::deferTextOnPathOp(const TextOnPathOp& op) { - BakedOpState* bakedState = tryBakeUnboundedOpState(op); - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, textBatchId(*(op.paint))); - - mCaches.fontRenderer.getFontRenderer().precache(op.paint, op.glyphs, op.glyphCount, - SkMatrix::I()); -} - -void FrameBuilder::deferTextureLayerOp(const TextureLayerOp& op) { - GlLayer* layer = static_cast<GlLayer*>(op.layerHandle->backingLayer()); - if (CC_UNLIKELY(!layer || !layer->isRenderable())) return; - - const TextureLayerOp* textureLayerOp = &op; - // Now safe to access transform (which was potentially unready at record time) - if (!layer->getTransform().isIdentity()) { - // non-identity transform present, so 'inject it' into op by copying + replacing matrix - Matrix4 combinedMatrix(op.localMatrix); - combinedMatrix.multiply(layer->getTransform()); - textureLayerOp = mAllocator.create<TextureLayerOp>(op, combinedMatrix); - } - BakedOpState* bakedState = tryBakeOpState(*textureLayerOp); - - if (!bakedState) return; // quick rejected - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::TextureLayer); -} - -void FrameBuilder::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, float contentTranslateX, - float contentTranslateY, const Rect& repaintRect, - const Vector3& lightCenter, const BeginLayerOp* beginLayerOp, - RenderNode* renderNode) { - mCanvasState.save(SaveFlags::MatrixClip); - mCanvasState.writableSnapshot()->initializeViewport(layerWidth, layerHeight); - mCanvasState.writableSnapshot()->roundRectClipState = nullptr; - mCanvasState.writableSnapshot()->setRelativeLightCenter(lightCenter); - mCanvasState.writableSnapshot()->transform->loadTranslate(contentTranslateX, contentTranslateY, - 0); - mCanvasState.writableSnapshot()->setClip(repaintRect.left, repaintRect.top, repaintRect.right, - repaintRect.bottom); - - // create a new layer repaint, and push its index on the stack - mLayerStack.push_back(mLayerBuilders.size()); - auto newFbo = mAllocator.create<LayerBuilder>(layerWidth, layerHeight, repaintRect, - beginLayerOp, renderNode); - mLayerBuilders.push_back(newFbo); -} - -void FrameBuilder::restoreForLayer() { - // restore canvas, and pop finished layer off of the stack - mCanvasState.restore(); - mLayerStack.pop_back(); -} - -// TODO: defer time rejection (when bounds become empty) + tests -// Option - just skip layers with no bounds at playback + defer? -void FrameBuilder::deferBeginLayerOp(const BeginLayerOp& op) { - uint32_t layerWidth = (uint32_t)op.unmappedBounds.getWidth(); - uint32_t layerHeight = (uint32_t)op.unmappedBounds.getHeight(); - - auto previous = mCanvasState.currentSnapshot(); - Vector3 lightCenter = previous->getRelativeLightCenter(); - - // Combine all transforms used to present saveLayer content: - // parent content transform * canvas transform * bounds offset - Matrix4 contentTransform(*(previous->transform)); - contentTransform.multiply(op.localMatrix); - contentTransform.translate(op.unmappedBounds.left, op.unmappedBounds.top); - - Matrix4 inverseContentTransform; - inverseContentTransform.loadInverse(contentTransform); - - // map the light center into layer-relative space - inverseContentTransform.mapPoint3d(lightCenter); - - // Clip bounds of temporary layer to parent's clip rect, so: - Rect saveLayerBounds(layerWidth, layerHeight); - // 1) transform Rect(width, height) into parent's space - // note: left/top offsets put in contentTransform above - contentTransform.mapRect(saveLayerBounds); - // 2) intersect with parent's clip - saveLayerBounds.doIntersect(previous->getRenderTargetClip()); - // 3) and transform back - inverseContentTransform.mapRect(saveLayerBounds); - saveLayerBounds.doIntersect(Rect(layerWidth, layerHeight)); - saveLayerBounds.roundOut(); - - // if bounds are reduced, will clip the layer's area by reducing required bounds... - layerWidth = saveLayerBounds.getWidth(); - layerHeight = saveLayerBounds.getHeight(); - // ...and shifting drawing content to account for left/top side clipping - float contentTranslateX = -saveLayerBounds.left; - float contentTranslateY = -saveLayerBounds.top; - - saveForLayer(layerWidth, layerHeight, contentTranslateX, contentTranslateY, - Rect(layerWidth, layerHeight), lightCenter, &op, nullptr); -} - -void FrameBuilder::deferEndLayerOp(const EndLayerOp& /* ignored */) { - const BeginLayerOp& beginLayerOp = *currentLayer().beginLayerOp; - int finishedLayerIndex = mLayerStack.back(); - - restoreForLayer(); - - // saveLayer will clip & translate the draw contents, so we need - // to translate the drawLayer by how much the contents was translated - // TODO: Unify this with beginLayerOp so we don't have to calculate this - // twice - uint32_t layerWidth = (uint32_t)beginLayerOp.unmappedBounds.getWidth(); - uint32_t layerHeight = (uint32_t)beginLayerOp.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(beginLayerOp.localMatrix); - contentTransform.translate(beginLayerOp.unmappedBounds.left, beginLayerOp.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(); - - Matrix4 localMatrix(beginLayerOp.localMatrix); - localMatrix.translate(saveLayerBounds.left, saveLayerBounds.top); - - // record the draw operation into the previous layer's list of draw commands - // uses state from the associated beginLayerOp, since it has all the state needed for drawing - LayerOp* drawLayerOp = mAllocator.create_trivial<LayerOp>( - beginLayerOp.unmappedBounds, 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.roundOut(); - dstRect.doIntersect(mCanvasState.currentSnapshot()->getRenderTargetClip()); - - if (dstRect.isEmpty()) { - // Unclipped layer rejected - push a null op, so next EndUnclippedLayerOp is ignored - currentLayer().activeUnclippedSaveLayers.push_back(nullptr); - } else { - // Allocate a holding position for the layer object (copyTo will produce, copyFrom will - // consume) - OffscreenBuffer** layerHandle = mAllocator.create<OffscreenBuffer*>(nullptr); - - /** - * First, defer an operation to copy out the content from the rendertarget into a layer. - */ - auto copyToOp = mAllocator.create_trivial<CopyToLayerOp>(op, layerHandle); - BakedOpState* bakedState = BakedOpState::directConstruct( - mAllocator, &(currentLayer().repaintClip), dstRect, *copyToOp); - currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::CopyToLayer); - - /** - * Defer a clear rect, so that clears from multiple unclipped layers can be drawn - * both 1) simultaneously, and 2) as long after the copyToLayer executes as possible - */ - currentLayer().deferLayerClear(dstRect); - - /** - * And stash an operation to copy that layer back under the rendertarget until - * a balanced EndUnclippedLayerOp is seen - */ - auto copyFromOp = mAllocator.create_trivial<CopyFromLayerOp>(op, layerHandle); - bakedState = BakedOpState::directConstruct(mAllocator, &(currentLayer().repaintClip), - dstRect, *copyFromOp); - currentLayer().activeUnclippedSaveLayers.push_back(bakedState); - } -} - -void FrameBuilder::deferEndUnclippedLayerOp(const EndUnclippedLayerOp& /* ignored */) { - LOG_ALWAYS_FATAL_IF(currentLayer().activeUnclippedSaveLayers.empty(), "no layer to end!"); - - BakedOpState* copyFromLayerOp = currentLayer().activeUnclippedSaveLayers.back(); - currentLayer().activeUnclippedSaveLayers.pop_back(); - if (copyFromLayerOp) { - currentLayer().deferUnmergeableOp(mAllocator, copyFromLayerOp, OpBatchType::CopyFromLayer); - } -} - -void FrameBuilder::finishDefer() { - mCaches.fontRenderer.endPrecaching(); -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h deleted file mode 100644 index 974daf8a17bb..000000000000 --- a/libs/hwui/FrameBuilder.h +++ /dev/null @@ -1,251 +0,0 @@ -/* - * 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 <unordered_map> -#include <vector> - -struct SkRect; - -namespace android { -namespace uirenderer { - -class BakedOpState; -class LayerUpdateQueue; -class OffscreenBuffer; -class Rect; - -/** - * Processes, optimizes, and stores rendering commands from RenderNodes and - * LayerUpdateQueue, building content needed to render a frame. - * - * Resolves final drawing state for each operation (including clip, alpha and matrix), and then - * reorder and merge each op as it is resolved for drawing efficiency. Each layer of content (either - * from the LayerUpdateQueue, or temporary layers created by saveLayer operations in the - * draw stream) will create different reorder contexts, each in its own LayerBuilder. - * - * Then the prepared or 'baked' drawing commands can be issued by calling the templated - * replayBakedOps() function, which will dispatch them (including any created merged op collections) - * to a Dispatcher and Renderer. See BakedOpDispatcher for how these baked drawing operations are - * resolved into Glops and rendered via BakedOpRenderer. - * - * This class is also the authoritative source for traversing RenderNodes, both for standard op - * traversal within a DisplayList, and for out of order RenderNode traversal for Z and projection. - */ -class FrameBuilder : public CanvasStateClient { -public: - struct LightGeometry { - Vector3 center; - float radius; - }; - - FrameBuilder(const SkRect& clip, uint32_t viewportWidth, uint32_t viewportHeight, - const LightGeometry& lightGeometry, Caches& caches); - - FrameBuilder(const LayerUpdateQueue& layerUpdateQueue, const LightGeometry& lightGeometry, - Caches& caches); - - void deferLayers(const LayerUpdateQueue& layers); - - void deferRenderNode(RenderNode& renderNode); - - void deferRenderNode(float tx, float ty, Rect clipRect, RenderNode& renderNode); - - void deferRenderNodeScene(const std::vector<sp<RenderNode> >& nodes, - const Rect& contentDrawBounds); - - virtual ~FrameBuilder() {} - - /** - * replayBakedOps() is templated based on what class will receive ops being replayed. - * - * It constructs a lookup array of lambdas, which allows a recorded BakeOpState to use - * state->op->opId to lookup a receiver that will be called when the op is replayed. - */ - template <typename StaticDispatcher, typename Renderer> - void replayBakedOps(Renderer& renderer) { - std::vector<OffscreenBuffer*> temporaryLayers; - finishDefer(); -/** - * Defines a LUT of lambdas which allow a recorded BakedOpState to use state->op->opId to - * dispatch the op via a method on a static dispatcher when the op is replayed. - * - * For example a BitmapOp would resolve, via the lambda lookup, to calling: - * - * StaticDispatcher::onBitmapOp(Renderer& renderer, const BitmapOp& op, const BakedOpState& state); - */ -#define X(Type) \ - [](void* renderer, const BakedOpState& state) { \ - StaticDispatcher::on##Type(*(static_cast<Renderer*>(renderer)), \ - static_cast<const Type&>(*(state.op)), state); \ - }, - static BakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X); -#undef X - -/** - * Defines a LUT of lambdas which allow merged arrays of BakedOpState* to be passed to a - * static dispatcher when the group of merged ops is replayed. - */ -#define X(Type) \ - [](void* renderer, const MergedBakedOpList& opList) { \ - StaticDispatcher::onMerged##Type##s(*(static_cast<Renderer*>(renderer)), opList); \ - }, - static MergedOpReceiver mergedReceivers[] = BUILD_MERGEABLE_OP_LUT(X); -#undef X - - // Relay through layers in reverse order, since layers - // later in the list will be drawn by earlier ones - for (int i = mLayerBuilders.size() - 1; i >= 1; i--) { - GL_CHECKPOINT(MODERATE); - LayerBuilder& layer = *(mLayerBuilders[i]); - if (layer.renderNode) { - // cached HW layer - can't skip layer if empty - renderer.startRepaintLayer(layer.offscreenBuffer, layer.repaintRect); - GL_CHECKPOINT(MODERATE); - layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); - GL_CHECKPOINT(MODERATE); - renderer.endLayer(); - } else if (!layer.empty()) { - // save layer - skip entire layer if empty (in which case, LayerOp has null layer). - layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height); - temporaryLayers.push_back(layer.offscreenBuffer); - GL_CHECKPOINT(MODERATE); - layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); - GL_CHECKPOINT(MODERATE); - renderer.endLayer(); - } - } - - GL_CHECKPOINT(MODERATE); - if (CC_LIKELY(mDrawFbo0)) { - const LayerBuilder& fbo0 = *(mLayerBuilders[0]); - renderer.startFrame(fbo0.width, fbo0.height, fbo0.repaintRect); - GL_CHECKPOINT(MODERATE); - fbo0.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers); - GL_CHECKPOINT(MODERATE); - renderer.endFrame(fbo0.repaintRect); - } - - for (auto& temporaryLayer : temporaryLayers) { - renderer.recycleTemporaryLayer(temporaryLayer); - } - } - - void dump() const { - for (auto&& layer : mLayerBuilders) { - layer->dump(); - } - } - - /////////////////////////////////////////////////////////////////// - /// CanvasStateClient interface - /////////////////////////////////////////////////////////////////// - virtual void onViewportInitialized() override; - virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override; - virtual GLuint getTargetFbo() const override { return 0; } - -private: - void finishDefer(); - enum class ChildrenSelectMode { Negative, Positive }; - void saveForLayer(uint32_t layerWidth, uint32_t layerHeight, float contentTranslateX, - float contentTranslateY, const Rect& repaintRect, const Vector3& lightCenter, - const BeginLayerOp* beginLayerOp, RenderNode* renderNode); - void restoreForLayer(); - - LayerBuilder& currentLayer() { return *(mLayerBuilders[mLayerStack.back()]); } - - BakedOpState* tryBakeOpState(const RecordedOp& recordedOp) { - return BakedOpState::tryConstruct(mAllocator, *mCanvasState.writableSnapshot(), recordedOp); - } - BakedOpState* tryBakeUnboundedOpState(const RecordedOp& recordedOp) { - return BakedOpState::tryConstructUnbounded(mAllocator, *mCanvasState.writableSnapshot(), - recordedOp); - } - - // should always be surrounded by a save/restore pair, and not called if DisplayList is null - void deferNodePropsAndOps(RenderNode& node); - - template <typename V> - void defer3dChildren(const ClipBase* reorderClip, ChildrenSelectMode mode, - const V& zTranslatedNodes); - - void deferShadow(const ClipBase* reorderClip, const RenderNodeOp& casterOp); - - void deferProjectedChildren(const RenderNode& renderNode); - - void deferNodeOps(const RenderNode& renderNode); - - void deferRenderNodeOpImpl(const RenderNodeOp& op); - - void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers); - - SkPath* createFrameAllocatedPath() { return mAllocator.create<SkPath>(); } - - BakedOpState* deferStrokeableOp(const RecordedOp& op, batchid_t batchId, - BakedOpState::StrokeBehavior strokeBehavior = - BakedOpState::StrokeBehavior::StyleDefined, - bool expandForPathTexture = false); - -/** - * Declares all FrameBuilder::deferXXXXOp() methods for every RecordedOp type. - * - * These private methods are called from within deferImpl to defer each individual op - * type differently. - */ -#define X(Type) void defer##Type(const Type& op); - MAP_DEFERRABLE_OPS(X) -#undef X - - // contains single-frame objects, such as BakedOpStates, LayerBuilders, Batches - LinearAllocator mAllocator; - LinearStdAllocator<void*> mStdAllocator; - - // List of every deferred layer's render state. Replayed in reverse order to render a frame. - LsaVector<LayerBuilder*> mLayerBuilders; - - /* - * Stack of indices within mLayerBuilders representing currently active layers. If drawing - * layerA within a layerB, will contain, in order: - * - 0 (representing FBO 0, always present) - * - layerB's index - * - layerA's index - * - * Note that this doesn't vector doesn't always map onto all values of mLayerBuilders. When a - * layer is finished deferring, it will still be represented in mLayerBuilders, but it's index - * won't be in mLayerStack. This is because it can be replayed, but can't have any more drawing - * ops added to it. - */ - LsaVector<size_t> mLayerStack; - - CanvasState mCanvasState; - - Caches& mCaches; - - float mLightRadius; - - const bool mDrawFbo0; -}; - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp index 5aea04d94cbe..b04c77430367 100644 --- a/libs/hwui/FrameInfoVisualizer.cpp +++ b/libs/hwui/FrameInfoVisualizer.cpp @@ -15,7 +15,6 @@ */ #include "FrameInfoVisualizer.h" -#include "BakedOpRenderer.h" #include "IProfileRenderer.h" #include "utils/Color.h" @@ -67,6 +66,7 @@ static int dpToPx(int dp, float density) { FrameInfoVisualizer::FrameInfoVisualizer(FrameInfoSource& source) : mFrameSource(source) { setDensity(1); + consumeProperties(); } FrameInfoVisualizer::~FrameInfoVisualizer() { diff --git a/libs/hwui/GammaFontRenderer.cpp b/libs/hwui/GammaFontRenderer.cpp deleted file mode 100644 index 88fb16234388..000000000000 --- a/libs/hwui/GammaFontRenderer.cpp +++ /dev/null @@ -1,43 +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 "GammaFontRenderer.h" -#include "Debug.h" -#include "Properties.h" - -namespace android { -namespace uirenderer { - -GammaFontRenderer::GammaFontRenderer() { - INIT_LOGD("Creating lookup gamma font renderer"); - -#ifndef ANDROID_ENABLE_LINEAR_BLENDING - // Compute the gamma tables - 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)); - } -#endif -} - -void GammaFontRenderer::endPrecaching() { - if (mRenderer) { - mRenderer->endPrecaching(); - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/GammaFontRenderer.h b/libs/hwui/GammaFontRenderer.h deleted file mode 100644 index e9002442c64f..000000000000 --- a/libs/hwui/GammaFontRenderer.h +++ /dev/null @@ -1,70 +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_GAMMA_FONT_RENDERER_H -#define ANDROID_HWUI_GAMMA_FONT_RENDERER_H - -#include "FontRenderer.h" - -namespace android { -namespace uirenderer { - -class GammaFontRenderer { -public: - GammaFontRenderer(); - - void clear() { mRenderer.reset(nullptr); } - - void flush() { - if (mRenderer) { - mRenderer->flushLargeCaches(); - } - } - - FontRenderer& getFontRenderer() { - if (!mRenderer) { - const uint8_t* table = nullptr; -#ifndef ANDROID_ENABLE_LINEAR_BLENDING - table = &mGammaTable[0]; -#endif - mRenderer.reset(new FontRenderer(table)); - } - return *mRenderer; - } - - void dumpMemoryUsage(String8& log) const { - if (mRenderer) { - mRenderer->dumpMemoryUsage(log); - } else { - log.appendFormat("FontRenderer doesn't exist.\n"); - } - } - - uint32_t getSize() const { return mRenderer ? mRenderer->getSize() : 0; } - - void endPrecaching(); - -private: - std::unique_ptr<FontRenderer> mRenderer; -#ifndef ANDROID_ENABLE_LINEAR_BLENDING - uint8_t mGammaTable[256]; -#endif -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_GAMMA_FONT_RENDERER_H diff --git a/libs/hwui/GlLayer.cpp b/libs/hwui/GlLayer.cpp deleted file mode 100644 index 8357f8ebde2e..000000000000 --- a/libs/hwui/GlLayer.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2017 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 "GlLayer.h" - -#include "Caches.h" -#include "RenderNode.h" -#include "renderstate/RenderState.h" -#include "utils/TraceUtils.h" - -#include <utils/Log.h> - -#define ATRACE_LAYER_WORK(label) \ - ATRACE_FORMAT("%s HW Layer DisplayList %s %ux%u", label, \ - (renderNode.get() != NULL) ? renderNode->getName() : "", getWidth(), \ - getHeight()) - -namespace android { -namespace uirenderer { - -GlLayer::GlLayer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight, - sk_sp<SkColorFilter> colorFilter, int alpha, SkBlendMode mode, bool blend) - : Layer(renderState, Api::OpenGL, colorFilter, alpha, mode) - , caches(Caches::getInstance()) - , texture(caches) { - texture.mWidth = layerWidth; - texture.mHeight = layerHeight; - texture.blend = blend; -} - -GlLayer::~GlLayer() { - // There's a rare possibility that Caches could have been destroyed already - // since this method is queued up as a task. - // Since this is a reset method, treat this as non-fatal. - if (caches.isInitialized() && texture.mId) { - texture.deleteTexture(); - } -} - -void GlLayer::onGlContextLost() { - texture.deleteTexture(); -} - -void GlLayer::setRenderTarget(GLenum renderTarget) { - if (renderTarget != getRenderTarget()) { - // new render target: bind with new target, and update filter/wrap - texture.mTarget = renderTarget; - if (texture.mId) { - caches.textureState().bindTexture(texture.target(), texture.mId); - } - texture.setFilter(GL_NEAREST, false, true); - texture.setWrap(GL_CLAMP_TO_EDGE, false, true); - } -} - -void GlLayer::generateTexture() { - if (!texture.mId) { - glGenTextures(1, &texture.mId); - } -} - -SkBlendMode GlLayer::getMode() const { - if (texture.blend || mode != SkBlendMode::kSrcOver) { - return mode; - } else { - return SkBlendMode::kSrc; - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/GlLayer.h b/libs/hwui/GlLayer.h deleted file mode 100644 index 4cf8f2522ff9..000000000000 --- a/libs/hwui/GlLayer.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2017 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 "Layer.h" - -#include "Texture.h" - -namespace android { -namespace uirenderer { - -// Forward declarations -class Caches; - -/** - * A layer has dimensions and is backed by an OpenGL texture or FBO. - */ -class GlLayer : public Layer { -public: - GlLayer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight, - sk_sp<SkColorFilter> colorFilter, int alpha, SkBlendMode mode, bool blend); - virtual ~GlLayer(); - - uint32_t getWidth() const override { return texture.mWidth; } - - uint32_t getHeight() const override { return texture.mHeight; } - - void setSize(uint32_t width, uint32_t height) override { - texture.updateLayout(width, height, texture.internalFormat(), texture.format(), - texture.target()); - } - - void setBlend(bool blend) override { texture.blend = blend; } - - bool isBlend() const override { return texture.blend; } - - inline GLuint getTextureId() const { return texture.id(); } - - inline Texture& getTexture() { return texture; } - - inline GLenum getRenderTarget() const { return texture.target(); } - - inline bool isRenderable() const { return texture.target() != GL_NONE; } - - void setRenderTarget(GLenum renderTarget); - - void generateTexture(); - - /** - * Lost the GL context but the layer is still around, mark it invalid internally - * so the dtor knows not to do any GL work - */ - void onGlContextLost(); - - SkBlendMode getMode() const override; - -private: - Caches& caches; - - /** - * The texture backing this layer. - */ - Texture texture; -}; // struct GlLayer - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Glop.h b/libs/hwui/Glop.h deleted file mode 100644 index c68d51604bea..000000000000 --- a/libs/hwui/Glop.h +++ /dev/null @@ -1,179 +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. - */ - -#pragma once - -#include "FloatColor.h" -#include "Matrix.h" -#include "Program.h" -#include "Rect.h" -#include "SkiaShader.h" -#include "utils/Macros.h" - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -namespace android { -namespace uirenderer { - -class Program; -class RoundRectClipState; -class Texture; - -/* - * Enumerates optional vertex attributes - * - * Position is always enabled by MeshState, these other attributes - * are enabled/disabled dynamically based on mesh content. - */ - -namespace VertexAttribFlags { -enum { - // Mesh is pure x,y vertex pairs - None = 0, - // Mesh has texture coordinates embedded. Note that texture can exist without this flag - // being set, if coordinates passed to sampler are determined another way. - TextureCoord = 1 << 0, - // Mesh has color embedded (to export to varying) - Color = 1 << 1, - // Mesh has alpha embedded (to export to varying) - Alpha = 1 << 2, -}; -}; - -/* - * Enumerates transform features - */ -namespace TransformFlags { -enum { - None = 0, - - // offset the eventual drawing matrix by a tiny amount to - // disambiguate sampling patterns with non-AA rendering - OffsetByFudgeFactor = 1 << 0, - - // Canvas transform isn't applied to the mesh at draw time, - // since it's already built in. - MeshIgnoresCanvasTransform = 1 << 1, // TODO: remove for HWUI_NEW_OPS -}; -}; - -/** - * Structure containing all data required to issue an OpenGL draw - * - * Includes all of the mesh, fill, and GL state required to perform - * the operation. Pieces of data are either directly copied into the - * structure, or stored as a pointer or GL object reference to data - * managed. - * - * Eventually, a Glop should be able to be drawn multiple times from - * a single construction, up until GL context destruction. Currently, - * vertex/index/Texture/RoundRectClipState pointers prevent this from - * being safe. - */ -struct Glop { - PREVENT_COPY_AND_ASSIGN(Glop); - -public: - Glop() {} - struct Mesh { - GLuint primitiveMode; // GL_TRIANGLES and GL_TRIANGLE_STRIP supported - - // buffer object and void* are mutually exclusive. - // Only GL_UNSIGNED_SHORT supported. - struct Indices { - GLuint bufferObject; - const void* indices; - } indices; - - // buffer object and void*s are mutually exclusive. - // TODO: enforce mutual exclusion with restricted setters and/or unions - struct Vertices { - GLuint bufferObject; - int attribFlags; - const void* position; - const void* texCoord; - const void* color; - GLsizei stride; - } vertices; - - int elementCount; - int vertexCount; // only used for meshes (for glDrawRangeElements) - TextureVertex mappedVertices[4]; - } mesh; - - struct Fill { - Program* program; - - struct TextureData { - Texture* texture; - GLenum filter; - GLenum clamp; - Matrix4* textureTransform; - } texture; - - bool colorEnabled; - FloatColor color; - - ProgramDescription::ColorFilterMode filterMode; - union Filter { - struct Matrix { - float matrix[16]; - float vector[4]; - } matrix; - FloatColor color; - } filter; - - SkiaShaderData skiaShaderData; - } fill; - - struct Transform { - // 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; - - // Canvas transform of Glop - not necessarily applied to geometry (see flags) - Matrix4 canvas; - int transformFlags; - - const Matrix4& meshTransform() const { - return (transformFlags & TransformFlags::MeshIgnoresCanvasTransform) - ? Matrix4::identity() - : canvas; - } - } transform; - - const RoundRectClipState* roundRectClipState = nullptr; - - /** - * Blending to be used by this draw - both GL_NONE if blending is disabled. - * - * Defined by fill step, but can be force-enabled by presence of kAlpha_Attrib - */ - struct Blend { - GLenum src; - GLenum dst; - } blend; - - /** - * Additional render state to enumerate: - * - scissor + (bits for whether each of LTRB needed?) - * - stencil mode (draw into, mask, count, etc) - */ -}; - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/GlopBuilder.cpp b/libs/hwui/GlopBuilder.cpp deleted file mode 100644 index 2f107a004731..000000000000 --- a/libs/hwui/GlopBuilder.cpp +++ /dev/null @@ -1,691 +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 "GlopBuilder.h" - -#include "Caches.h" -#include "GlLayer.h" -#include "Glop.h" -#include "Layer.h" -#include "Matrix.h" -#include "Patch.h" -#include "PathCache.h" -#include "SkiaShader.h" -#include "Texture.h" -#include "VertexBuffer.h" -#include "renderstate/MeshState.h" -#include "renderstate/RenderState.h" -#include "utils/PaintUtils.h" - -#include <GLES2/gl2.h> -#include <SkPaint.h> - -#define DEBUG_GLOP_BUILDER 0 - -#if DEBUG_GLOP_BUILDER - -#define TRIGGER_STAGE(stageFlag) \ - LOG_ALWAYS_FATAL_IF((stageFlag)&mStageFlags, "Stage %d cannot be run twice", (stageFlag)); \ - mStageFlags = static_cast<StageFlags>(mStageFlags | (stageFlag)) - -#define REQUIRE_STAGES(requiredFlags) \ - LOG_ALWAYS_FATAL_IF((mStageFlags & (requiredFlags)) != (requiredFlags), \ - "not prepared for current stage") - -#else - -#define TRIGGER_STAGE(stageFlag) ((void)0) -#define REQUIRE_STAGES(requiredFlags) ((void)0) - -#endif - -namespace android { -namespace uirenderer { - -static void setUnitQuadTextureCoords(Rect uvs, TextureVertex* quadVertex) { - quadVertex[0] = {0, 0, uvs.left, uvs.top}; - quadVertex[1] = {1, 0, uvs.right, uvs.top}; - quadVertex[2] = {0, 1, uvs.left, uvs.bottom}; - quadVertex[3] = {1, 1, uvs.right, uvs.bottom}; -} - -GlopBuilder::GlopBuilder(RenderState& renderState, Caches& caches, Glop* outGlop) - : mRenderState(renderState), mCaches(caches), mShader(nullptr), mOutGlop(outGlop) { - mStageFlags = kInitialStage; -} - -//////////////////////////////////////////////////////////////////////////////// -// 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); - - mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP; - mOutGlop->mesh.indices = {0, nullptr}; - mOutGlop->mesh.vertices = {mRenderState.meshState().getUnitQuadVBO(), - VertexAttribFlags::None, - nullptr, - nullptr, - nullptr, - kTextureVertexStride}; - mOutGlop->mesh.elementCount = 4; - return *this; -} - -GlopBuilder& GlopBuilder::setMeshTexturedUnitQuad(const UvMapper* uvMapper) { - if (uvMapper) { - // can't use unit quad VBO, so build UV vertices manually - return setMeshTexturedUvQuad(uvMapper, Rect(1, 1)); - } - - TRIGGER_STAGE(kMeshStage); - - mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP; - mOutGlop->mesh.indices = {0, nullptr}; - mOutGlop->mesh.vertices = {mRenderState.meshState().getUnitQuadVBO(), - VertexAttribFlags::TextureCoord, - nullptr, - (const void*)kMeshTextureOffset, - nullptr, - kTextureVertexStride}; - mOutGlop->mesh.elementCount = 4; - return *this; -} - -GlopBuilder& GlopBuilder::setMeshTexturedUvQuad(const UvMapper* uvMapper, Rect uvs) { - TRIGGER_STAGE(kMeshStage); - - if (CC_UNLIKELY(uvMapper)) { - uvMapper->map(uvs); - } - setUnitQuadTextureCoords(uvs, &mOutGlop->mesh.mappedVertices[0]); - - const TextureVertex* textureVertex = mOutGlop->mesh.mappedVertices; - mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP; - mOutGlop->mesh.indices = {0, nullptr}; - mOutGlop->mesh.vertices = {0, - VertexAttribFlags::TextureCoord, - &textureVertex[0].x, - &textureVertex[0].u, - nullptr, - kTextureVertexStride}; - mOutGlop->mesh.elementCount = 4; - return *this; -} - -GlopBuilder& GlopBuilder::setMeshIndexedQuads(Vertex* vertexData, int quadCount) { - TRIGGER_STAGE(kMeshStage); - - mOutGlop->mesh.primitiveMode = GL_TRIANGLES; - mOutGlop->mesh.indices = {mRenderState.meshState().getQuadListIBO(), nullptr}; - mOutGlop->mesh.vertices = { - 0, VertexAttribFlags::None, vertexData, nullptr, nullptr, kVertexStride}; - mOutGlop->mesh.elementCount = 6 * quadCount; - return *this; -} - -GlopBuilder& GlopBuilder::setMeshTexturedIndexedQuads(TextureVertex* vertexData, int elementCount) { - TRIGGER_STAGE(kMeshStage); - - mOutGlop->mesh.primitiveMode = GL_TRIANGLES; - mOutGlop->mesh.indices = {mRenderState.meshState().getQuadListIBO(), nullptr}; - mOutGlop->mesh.vertices = {0, - VertexAttribFlags::TextureCoord, - &vertexData[0].x, - &vertexData[0].u, - nullptr, - kTextureVertexStride}; - mOutGlop->mesh.elementCount = elementCount; - return *this; -} - -GlopBuilder& GlopBuilder::setMeshColoredTexturedMesh(ColorTextureVertex* vertexData, - int elementCount) { - TRIGGER_STAGE(kMeshStage); - - mOutGlop->mesh.primitiveMode = GL_TRIANGLES; - mOutGlop->mesh.indices = {0, nullptr}; - mOutGlop->mesh.vertices = {0, - VertexAttribFlags::TextureCoord | VertexAttribFlags::Color, - &vertexData[0].x, - &vertexData[0].u, - &vertexData[0].r, - kColorTextureVertexStride}; - mOutGlop->mesh.elementCount = elementCount; - return *this; -} - -GlopBuilder& GlopBuilder::setMeshVertexBuffer(const VertexBuffer& vertexBuffer) { - TRIGGER_STAGE(kMeshStage); - - const VertexBuffer::MeshFeatureFlags flags = vertexBuffer.getMeshFeatureFlags(); - - bool alphaVertex = flags & VertexBuffer::kAlpha; - bool indices = flags & VertexBuffer::kIndices; - - mOutGlop->mesh.primitiveMode = GL_TRIANGLE_STRIP; - mOutGlop->mesh.indices = {0, vertexBuffer.getIndices()}; - mOutGlop->mesh.vertices = {0, - alphaVertex ? VertexAttribFlags::Alpha : VertexAttribFlags::None, - vertexBuffer.getBuffer(), - nullptr, - nullptr, - alphaVertex ? kAlphaVertexStride : kVertexStride}; - mOutGlop->mesh.elementCount = - indices ? vertexBuffer.getIndexCount() : vertexBuffer.getVertexCount(); - mOutGlop->mesh.vertexCount = vertexBuffer.getVertexCount(); // used for glDrawRangeElements() - return *this; -} - -GlopBuilder& GlopBuilder::setMeshPatchQuads(const Patch& patch) { - TRIGGER_STAGE(kMeshStage); - - mOutGlop->mesh.primitiveMode = GL_TRIANGLES; - mOutGlop->mesh.indices = {mRenderState.meshState().getQuadListIBO(), nullptr}; - mOutGlop->mesh.vertices = {mCaches.patchCache.getMeshBuffer(), - VertexAttribFlags::TextureCoord, - (void*)patch.positionOffset, - (void*)patch.textureOffset, - nullptr, - kTextureVertexStride}; - mOutGlop->mesh.elementCount = patch.indexCount; - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// -// Fill -//////////////////////////////////////////////////////////////////////////////// - -void GlopBuilder::setFill(int color, float alphaScale, SkBlendMode mode, - Blend::ModeOrderSwap modeUsage, const SkShader* shader, - const SkColorFilter* colorFilter) { - if (mode != SkBlendMode::kClear) { - if (!shader) { - FloatColor c; - c.set(color); - c.r *= alphaScale; - c.g *= alphaScale; - c.b *= alphaScale; - c.a *= alphaScale; - mOutGlop->fill.color = c; - } else { - float alpha = (SkColorGetA(color) / 255.0f) * alphaScale; - mOutGlop->fill.color = {1, 1, 1, alpha}; - } - } else { - mOutGlop->fill.color = {0, 0, 0, 1}; - } - - mOutGlop->blend = {GL_ZERO, GL_ZERO}; - if (mOutGlop->fill.color.a < 1.0f || - (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Alpha) || - (mOutGlop->fill.texture.texture && mOutGlop->fill.texture.texture->blend) || - mOutGlop->roundRectClipState || PaintUtils::isBlendedShader(shader) || - PaintUtils::isBlendedColorFilter(colorFilter) || mode != SkBlendMode::kSrcOver) { - if (CC_LIKELY(mode <= SkBlendMode::kScreen)) { - Blend::getFactors(mode, modeUsage, &mOutGlop->blend.src, &mOutGlop->blend.dst); - } else { - // These blend modes are not supported by OpenGL directly and have - // to be implemented using shaders. Since the shader will perform - // the blending, don't enable GL blending off here - // If the blend mode cannot be implemented using shaders, fall - // back to the default SrcOver blend mode instead - if (CC_UNLIKELY(mCaches.extensions().hasFramebufferFetch())) { - mDescription.framebufferMode = mode; - mDescription.swapSrcDst = (modeUsage == Blend::ModeOrderSwap::Swap); - // blending in shader, don't enable - } else { - // unsupported - Blend::getFactors(SkBlendMode::kSrcOver, modeUsage, &mOutGlop->blend.src, - &mOutGlop->blend.dst); - } - } - } - mShader = shader; // shader resolved in ::build() - - if (colorFilter) { - SkColor color; - SkBlendMode bmode; - SkScalar srcColorMatrix[20]; - if (colorFilter->asColorMode(&color, &bmode)) { - mOutGlop->fill.filterMode = mDescription.colorOp = - ProgramDescription::ColorFilterMode::Blend; - mDescription.colorMode = bmode; - mOutGlop->fill.filter.color.set(color); - } else if (colorFilter->asColorMatrix(srcColorMatrix)) { - mOutGlop->fill.filterMode = mDescription.colorOp = - ProgramDescription::ColorFilterMode::Matrix; - - float* colorMatrix = mOutGlop->fill.filter.matrix.matrix; - memcpy(colorMatrix, srcColorMatrix, 4 * sizeof(float)); - memcpy(&colorMatrix[4], &srcColorMatrix[5], 4 * sizeof(float)); - memcpy(&colorMatrix[8], &srcColorMatrix[10], 4 * sizeof(float)); - memcpy(&colorMatrix[12], &srcColorMatrix[15], 4 * sizeof(float)); - - // Skia uses the range [0..255] for the addition vector, but we need - // the [0..1] range to apply the vector in GLSL - float* colorVector = mOutGlop->fill.filter.matrix.vector; - colorVector[0] = EOCF(srcColorMatrix[4] / 255.0f); - colorVector[1] = EOCF(srcColorMatrix[9] / 255.0f); - colorVector[2] = EOCF(srcColorMatrix[14] / 255.0f); - colorVector[3] = srcColorMatrix[19] / 255.0f; // alpha is linear - } else { - ALOGE("unsupported ColorFilter type: %s", colorFilter->getTypeName()); - LOG_ALWAYS_FATAL("unsupported ColorFilter"); - } - } else { - mOutGlop->fill.filterMode = ProgramDescription::ColorFilterMode::None; - } -} - -GlopBuilder& GlopBuilder::setFillTexturePaint(Texture& texture, const int textureFillFlags, - const SkPaint* paint, float alphaScale) { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - GLenum filter = (textureFillFlags & TextureFillFlags::ForceFilter) - ? GL_LINEAR - : PaintUtils::getFilter(paint); - mOutGlop->fill.texture = {&texture, filter, GL_CLAMP_TO_EDGE, nullptr}; - - if (paint) { - int color = paint->getColor(); - SkShader* shader = paint->getShader(); - - if (!(textureFillFlags & TextureFillFlags::IsAlphaMaskTexture)) { - // Texture defines color, so disable shaders, and reset all non-alpha color channels - color |= 0x00FFFFFF; - shader = nullptr; - } - setFill(color, alphaScale, paint->getBlendMode(), Blend::ModeOrderSwap::NoSwap, shader, - paint->getColorFilter()); - } else { - mOutGlop->fill.color = {alphaScale, alphaScale, alphaScale, alphaScale}; - - if (alphaScale < 1.0f || (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Alpha) || - texture.blend || mOutGlop->roundRectClipState) { - Blend::getFactors(SkBlendMode::kSrcOver, Blend::ModeOrderSwap::NoSwap, - &mOutGlop->blend.src, &mOutGlop->blend.dst); - } else { - mOutGlop->blend = {GL_ZERO, GL_ZERO}; - } - } - - if (textureFillFlags & TextureFillFlags::IsAlphaMaskTexture) { - mDescription.modulate = mOutGlop->fill.color.isNotBlack(); - mDescription.hasAlpha8Texture = true; - } else { - mDescription.modulate = mOutGlop->fill.color.a < 1.0f; - } - return *this; -} - -GlopBuilder& GlopBuilder::setFillPaint(const SkPaint& paint, float alphaScale, bool shadowInterp) { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - if (CC_LIKELY(!shadowInterp)) { - mOutGlop->fill.texture = {nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr}; - } else { - mOutGlop->fill.texture = {mCaches.textureState().getShadowLutTexture(), GL_INVALID_ENUM, - GL_INVALID_ENUM, nullptr}; - } - - setFill(paint.getColor(), alphaScale, paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap, - paint.getShader(), paint.getColorFilter()); - mDescription.useShadowAlphaInterp = shadowInterp; - mDescription.modulate = mOutGlop->fill.color.a < 1.0f; - return *this; -} - -GlopBuilder& GlopBuilder::setFillPathTexturePaint(PathTexture& texture, const SkPaint& paint, - float alphaScale) { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - // specify invalid filter/clamp, since these are always static for PathTextures - mOutGlop->fill.texture = {&texture, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr}; - - setFill(paint.getColor(), alphaScale, paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap, - paint.getShader(), paint.getColorFilter()); - - mDescription.hasAlpha8Texture = true; - mDescription.modulate = mOutGlop->fill.color.isNotBlack(); - return *this; -} - -GlopBuilder& GlopBuilder::setFillShadowTexturePaint(ShadowTexture& texture, int shadowColor, - const SkPaint& paint, float alphaScale) { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - // specify invalid filter/clamp, since these are always static for ShadowTextures - mOutGlop->fill.texture = {&texture, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr}; - - const int ALPHA_BITMASK = SK_ColorBLACK; - const int COLOR_BITMASK = ~ALPHA_BITMASK; - if ((shadowColor & ALPHA_BITMASK) == ALPHA_BITMASK) { - // shadow color is fully opaque: override its alpha with that of paint - shadowColor &= paint.getColor() | COLOR_BITMASK; - } - - setFill(shadowColor, alphaScale, paint.getBlendMode(), Blend::ModeOrderSwap::NoSwap, - paint.getShader(), paint.getColorFilter()); - - mDescription.hasAlpha8Texture = true; - mDescription.modulate = mOutGlop->fill.color.isNotBlack(); - return *this; -} - -GlopBuilder& GlopBuilder::setFillBlack() { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - mOutGlop->fill.texture = {nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr}; - setFill(SK_ColorBLACK, 1.0f, SkBlendMode::kSrcOver, Blend::ModeOrderSwap::NoSwap, nullptr, - nullptr); - return *this; -} - -GlopBuilder& GlopBuilder::setFillClear() { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - mOutGlop->fill.texture = {nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr}; - setFill(SK_ColorBLACK, 1.0f, SkBlendMode::kClear, Blend::ModeOrderSwap::NoSwap, nullptr, - nullptr); - return *this; -} - -GlopBuilder& GlopBuilder::setFillLayer(Texture& texture, const SkColorFilter* colorFilter, - float alpha, SkBlendMode mode, - Blend::ModeOrderSwap modeUsage) { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - mOutGlop->fill.texture = {&texture, GL_LINEAR, GL_CLAMP_TO_EDGE, nullptr}; - - setFill(SK_ColorWHITE, alpha, mode, modeUsage, nullptr, colorFilter); - - mDescription.modulate = mOutGlop->fill.color.a < 1.0f; - return *this; -} - -GlopBuilder& GlopBuilder::setFillTextureLayer(GlLayer& layer, float alpha) { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - mOutGlop->fill.texture = {&(layer.getTexture()), GL_LINEAR, GL_CLAMP_TO_EDGE, - &layer.getTexTransform()}; - - setFill(SK_ColorWHITE, alpha, layer.getMode(), Blend::ModeOrderSwap::NoSwap, nullptr, - layer.getColorFilter()); - - mDescription.modulate = mOutGlop->fill.color.a < 1.0f; - mDescription.hasTextureTransform = true; - return *this; -} - -GlopBuilder& GlopBuilder::setFillExternalTexture(Texture& texture, Matrix4& textureTransform, - bool requiresFilter) { - TRIGGER_STAGE(kFillStage); - REQUIRE_STAGES(kMeshStage | kRoundRectClipStage); - - GLenum filter = requiresFilter ? GL_LINEAR : GL_NEAREST; - mOutGlop->fill.texture = {&texture, filter, GL_CLAMP_TO_EDGE, &textureTransform}; - - setFill(SK_ColorWHITE, 1.0f, SkBlendMode::kSrc, Blend::ModeOrderSwap::NoSwap, nullptr, nullptr); - - mDescription.modulate = mOutGlop->fill.color.a < 1.0f; - mDescription.hasTextureTransform = true; - return *this; -} - -GlopBuilder& GlopBuilder::setGammaCorrection(bool enabled) { - REQUIRE_STAGES(kFillStage); - - mDescription.hasGammaCorrection = enabled; - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// -// Transform -//////////////////////////////////////////////////////////////////////////////// - -GlopBuilder& GlopBuilder::setTransform(const Matrix4& canvas, const int transformFlags) { - TRIGGER_STAGE(kTransformStage); - - mOutGlop->transform.canvas = canvas; - mOutGlop->transform.transformFlags = transformFlags; - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// -// ModelView -//////////////////////////////////////////////////////////////////////////////// - -GlopBuilder& GlopBuilder::setModelViewMapUnitToRect(const Rect destination) { - TRIGGER_STAGE(kModelViewStage); - - mOutGlop->transform.modelView.loadTranslate(destination.left, destination.top, 0.0f); - mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f); - return *this; -} - -GlopBuilder& GlopBuilder::setModelViewMapUnitToRectSnap(const Rect destination) { - TRIGGER_STAGE(kModelViewStage); - REQUIRE_STAGES(kTransformStage | kFillStage); - - float left = destination.left; - float top = destination.top; - - const Matrix4& meshTransform = mOutGlop->transform.meshTransform(); - if (CC_LIKELY(meshTransform.isPureTranslate())) { - // snap by adjusting the model view matrix - const float translateX = meshTransform.getTranslateX(); - const float translateY = meshTransform.getTranslateY(); - - left = (int)floorf(left + translateX + 0.5f) - translateX; - top = (int)floorf(top + translateY + 0.5f) - translateY; - mOutGlop->fill.texture.filter = GL_NEAREST; - } - - mOutGlop->transform.modelView.loadTranslate(left, top, 0.0f); - mOutGlop->transform.modelView.scale(destination.getWidth(), destination.getHeight(), 1.0f); - return *this; -} - -GlopBuilder& GlopBuilder::setModelViewOffsetRect(float offsetX, float offsetY, const Rect source) { - TRIGGER_STAGE(kModelViewStage); - - mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f); - return *this; -} - -GlopBuilder& GlopBuilder::setModelViewOffsetRectSnap(float offsetX, float offsetY, - const Rect source) { - TRIGGER_STAGE(kModelViewStage); - REQUIRE_STAGES(kTransformStage | kFillStage); - - const Matrix4& meshTransform = mOutGlop->transform.meshTransform(); - if (CC_LIKELY(meshTransform.isPureTranslate())) { - // snap by adjusting the model view matrix - const float translateX = meshTransform.getTranslateX(); - const float translateY = meshTransform.getTranslateY(); - - offsetX = (int)floorf(offsetX + translateX + source.left + 0.5f) - translateX - source.left; - offsetY = (int)floorf(offsetY + translateY + source.top + 0.5f) - translateY - source.top; - mOutGlop->fill.texture.filter = GL_NEAREST; - } - - mOutGlop->transform.modelView.loadTranslate(offsetX, offsetY, 0.0f); - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// -// RoundRectClip -//////////////////////////////////////////////////////////////////////////////// - -GlopBuilder& GlopBuilder::setRoundRectClipState(const RoundRectClipState* roundRectClipState) { - TRIGGER_STAGE(kRoundRectClipStage); - - mOutGlop->roundRectClipState = roundRectClipState; - mDescription.hasRoundRectClip = roundRectClipState != nullptr; - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// -// Build -//////////////////////////////////////////////////////////////////////////////// - -void verify(const ProgramDescription& description, const Glop& glop) { - if (glop.fill.texture.texture != nullptr) { - LOG_ALWAYS_FATAL_IF( - ((description.hasTexture && description.hasExternalTexture) || - (!description.hasTexture && !description.hasExternalTexture && - !description.useShadowAlphaInterp) || - ((glop.mesh.vertices.attribFlags & VertexAttribFlags::TextureCoord) == 0 && - !description.useShadowAlphaInterp)), - "Texture %p, hT%d, hET %d, attribFlags %x", glop.fill.texture.texture, - description.hasTexture, description.hasExternalTexture, - glop.mesh.vertices.attribFlags); - } else { - LOG_ALWAYS_FATAL_IF( - (description.hasTexture || description.hasExternalTexture || - ((glop.mesh.vertices.attribFlags & VertexAttribFlags::TextureCoord) != 0)), - "No texture, hT%d, hET %d, attribFlags %x", description.hasTexture, - description.hasExternalTexture, glop.mesh.vertices.attribFlags); - } - - if ((glop.mesh.vertices.attribFlags & VertexAttribFlags::Alpha) && - glop.mesh.vertices.bufferObject) { - LOG_ALWAYS_FATAL("VBO and alpha attributes are not currently compatible"); - } - - if (description.hasTextureTransform != (glop.fill.texture.textureTransform != nullptr)) { - LOG_ALWAYS_FATAL("Texture transform incorrectly specified"); - } -} - -void GlopBuilder::build() { - REQUIRE_STAGES(kAllStages); - if (mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::TextureCoord) { - Texture* texture = mOutGlop->fill.texture.texture; - if (texture->target() == GL_TEXTURE_2D) { - mDescription.hasTexture = true; - } else { - mDescription.hasExternalTexture = true; - } - mDescription.hasLinearTexture = texture->isLinear(); - mDescription.hasColorSpaceConversion = texture->hasColorSpaceConversion(); - mDescription.transferFunction = texture->getTransferFunctionType(); - mDescription.hasTranslucentConversion = texture->blend; - } - - mDescription.hasColors = mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Color; - mDescription.hasVertexAlpha = mOutGlop->mesh.vertices.attribFlags & VertexAttribFlags::Alpha; - - // Enable debug highlight when what we're about to draw is tested against - // the stencil buffer and if stencil highlight debugging is on - mDescription.hasDebugHighlight = - !Properties::debugOverdraw && - Properties::debugStencilClip == StencilClipDebug::ShowHighlight && - mRenderState.stencil().isTestEnabled(); - - // serialize shader info into ShaderData - GLuint textureUnit = mOutGlop->fill.texture.texture ? 1 : 0; - - if (CC_LIKELY(!mShader)) { - mOutGlop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType; - } else { - Matrix4 shaderMatrix; - if (mOutGlop->transform.transformFlags & TransformFlags::MeshIgnoresCanvasTransform) { - // canvas level transform was built into the modelView and geometry, - // so the shader matrix must reverse this - shaderMatrix.loadInverse(mOutGlop->transform.canvas); - shaderMatrix.multiply(mOutGlop->transform.modelView); - } else { - shaderMatrix = mOutGlop->transform.modelView; - } - SkiaShader::store(mCaches, *mShader, shaderMatrix, &textureUnit, &mDescription, - &(mOutGlop->fill.skiaShaderData)); - } - - // duplicates ProgramCache's definition of color uniform presence - const bool singleColor = !mDescription.hasTexture && !mDescription.hasExternalTexture && - !mDescription.hasGradient && !mDescription.hasBitmap; - mOutGlop->fill.colorEnabled = mDescription.modulate || singleColor; - - verify(mDescription, *mOutGlop); - - // Final step: populate program and map bounds into render target space - mOutGlop->fill.program = mCaches.programCache.get(mDescription); -} - -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.texture->target(), fill.texture.filter, fill.texture.clamp); - if (fill.texture.textureTransform) { - fill.texture.textureTransform->dump("texture transform"); - } - } - ALOGD_IF(fill.colorEnabled, " color (argb) %.2f %.2f %.2f %.2f", fill.color.a, fill.color.r, - fill.color.g, fill.color.b); - ALOGD_IF(fill.filterMode != ProgramDescription::ColorFilterMode::None, " filterMode %d", - (int)fill.filterMode); - ALOGD_IF(fill.skiaShaderData.skiaShaderType, " shader type %d", - fill.skiaShaderData.skiaShaderType); - - ALOGD("Glop transform"); - glop.transform.modelView.dump(" model view"); - glop.transform.canvas.dump(" canvas"); - ALOGD_IF(glop.transform.transformFlags, " transformFlags 0x%x", glop.transform.transformFlags); - - ALOGD_IF(glop.roundRectClipState, "Glop RRCS %p", glop.roundRectClipState); - - ALOGD("Glop blend %d %d", glop.blend.src, glop.blend.dst); -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/GlopBuilder.h b/libs/hwui/GlopBuilder.h deleted file mode 100644 index dac38223b877..000000000000 --- a/libs/hwui/GlopBuilder.h +++ /dev/null @@ -1,142 +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. - */ - -#pragma once - -#include "Glop.h" -#include "Program.h" -#include "renderstate/Blend.h" -#include "utils/Macros.h" - -class SkPaint; -class SkShader; - -namespace android { -namespace uirenderer { - -class Caches; -class GlLayer; -class Matrix4; -class Patch; -class RenderState; -class Texture; -class UvMapper; -class VertexBuffer; -struct PathTexture; -struct ShadowTexture; - -namespace TextureFillFlags { -enum { - None = 0, - IsAlphaMaskTexture = 1 << 0, - ForceFilter = 1 << 1, -}; -} - -class GlopBuilder { - PREVENT_COPY_AND_ASSIGN(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); - GlopBuilder& setMeshIndexedQuads(Vertex* vertexData, int quadCount); - GlopBuilder& setMeshColoredTexturedMesh(ColorTextureVertex* vertexData, - int elementCount); // TODO: use indexed quads - GlopBuilder& setMeshTexturedIndexedQuads(TextureVertex* vertexData, - int elementCount); // TODO: take quadCount - GlopBuilder& setMeshPatchQuads(const Patch& patch); - - GlopBuilder& setFillPaint(const SkPaint& paint, float alphaScale, - bool shadowInterp = false); // TODO: avoid boolean with default - GlopBuilder& setFillTexturePaint(Texture& texture, const int textureFillFlags, - const SkPaint* paint, float alphaScale); - GlopBuilder& setFillPathTexturePaint(PathTexture& texture, const SkPaint& paint, - float alphaScale); - GlopBuilder& setFillShadowTexturePaint(ShadowTexture& texture, int shadowColor, - const SkPaint& paint, float alphaScale); - GlopBuilder& setFillBlack(); - GlopBuilder& setFillClear(); - GlopBuilder& setFillLayer(Texture& texture, const SkColorFilter* colorFilter, float alpha, - SkBlendMode mode, Blend::ModeOrderSwap modeUsage); - GlopBuilder& setFillTextureLayer(GlLayer& layer, float alpha); - // TODO: setFillLayer normally forces its own wrap & filter mode, - // which isn't always correct. - GlopBuilder& setFillExternalTexture(Texture& texture, Matrix4& textureTransform, - bool requiresFilter); - - GlopBuilder& setTransform(const Matrix4& canvas, const int transformFlags); - - GlopBuilder& setModelViewMapUnitToRect(const Rect destination); - GlopBuilder& setModelViewMapUnitToRectSnap(const Rect destination); - GlopBuilder& setModelViewMapUnitToRectOptionalSnap(bool snap, const Rect& destination) { - if (snap) { - return setModelViewMapUnitToRectSnap(destination); - } else { - return setModelViewMapUnitToRect(destination); - } - } - GlopBuilder& setModelViewOffsetRect(float offsetX, float offsetY, const Rect source); - GlopBuilder& setModelViewOffsetRectSnap(float offsetX, float offsetY, const Rect source); - GlopBuilder& setModelViewOffsetRectOptionalSnap(bool snap, float offsetX, float offsetY, - const Rect& source) { - if (snap) { - return setModelViewOffsetRectSnap(offsetX, offsetY, source); - } else { - 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); - - GlopBuilder& setGammaCorrection(bool enabled); - - void build(); - - static void dump(const Glop& glop); - -private: - void setFill(int color, float alphaScale, SkBlendMode mode, Blend::ModeOrderSwap modeUsage, - const SkShader* shader, const SkColorFilter* colorFilter); - - enum StageFlags { - kInitialStage = 0, - kMeshStage = 1 << 0, - kTransformStage = 1 << 1, - kModelViewStage = 1 << 2, - kFillStage = 1 << 3, - kRoundRectClipStage = 1 << 4, - kAllStages = - kMeshStage | kFillStage | kTransformStage | kModelViewStage | kRoundRectClipStage, - } mStageFlags; - - ProgramDescription mDescription; - RenderState& mRenderState; - Caches& mCaches; - const SkShader* mShader; - Glop* mOutGlop; -}; - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp index 612bfde1a3fa..a9a7af8f22f3 100644 --- a/libs/hwui/GpuMemoryTracker.cpp +++ b/libs/hwui/GpuMemoryTracker.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include "Texture.h" #include "utils/StringUtils.h" #include <GpuMemoryTracker.h> @@ -117,22 +116,6 @@ void GpuMemoryTracker::onFrameCompleted() { 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 diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp deleted file mode 100644 index 21e3c730cbec..000000000000 --- a/libs/hwui/GradientCache.cpp +++ /dev/null @@ -1,272 +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 <utils/JenkinsHash.h> - -#include "Caches.h" -#include "Debug.h" -#include "DeviceInfo.h" -#include "GradientCache.h" -#include "Properties.h" - -#include <cutils/properties.h> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Functions -/////////////////////////////////////////////////////////////////////////////// - -template <typename T> -static inline T min(T a, T b) { - return a < b ? a : b; -} - -/////////////////////////////////////////////////////////////////////////////// -// Cache entry -/////////////////////////////////////////////////////////////////////////////// - -hash_t GradientCacheEntry::hash() const { - uint32_t hash = JenkinsHashMix(0, count); - for (uint32_t i = 0; i < count; i++) { - hash = JenkinsHashMix(hash, android::hash_type(colors[i])); - hash = JenkinsHashMix(hash, android::hash_type(positions[i])); - } - return JenkinsHashWhiten(hash); -} - -int GradientCacheEntry::compare(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs) { - int deltaInt = int(lhs.count) - int(rhs.count); - if (deltaInt != 0) return deltaInt; - - deltaInt = memcmp(lhs.colors.get(), rhs.colors.get(), lhs.count * sizeof(uint32_t)); - if (deltaInt != 0) return deltaInt; - - return memcmp(lhs.positions.get(), rhs.positions.get(), lhs.count * sizeof(float)); -} - -/////////////////////////////////////////////////////////////////////////////// -// Constructors/destructor -/////////////////////////////////////////////////////////////////////////////// - -GradientCache::GradientCache(const Extensions& extensions) - : mCache(LruCache<GradientCacheEntry, Texture*>::kUnlimitedCapacity) - , mSize(0) - , mMaxSize(MB(1)) - , mUseFloatTexture(extensions.hasFloatTextures()) - , mHasNpot(extensions.hasNPot()) - , mHasLinearBlending(extensions.hasLinearBlending()) { - mMaxTextureSize = DeviceInfo::get()->maxTextureSize(); - - mCache.setOnEntryRemovedListener(this); -} - -GradientCache::~GradientCache() { - mCache.clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Size management -/////////////////////////////////////////////////////////////////////////////// - -uint32_t GradientCache::getSize() { - return mSize; -} - -uint32_t GradientCache::getMaxSize() { - return mMaxSize; -} - -/////////////////////////////////////////////////////////////////////////////// -// Callbacks -/////////////////////////////////////////////////////////////////////////////// - -void GradientCache::operator()(GradientCacheEntry&, Texture*& texture) { - if (texture) { - mSize -= texture->objectSize(); - texture->deleteTexture(); - delete texture; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Caching -/////////////////////////////////////////////////////////////////////////////// - -Texture* GradientCache::get(uint32_t* colors, float* positions, int count) { - GradientCacheEntry gradient(colors, positions, count); - Texture* texture = mCache.get(gradient); - - if (!texture) { - texture = addLinearGradient(gradient, colors, positions, count); - } - - return texture; -} - -void GradientCache::clear() { - mCache.clear(); -} - -void GradientCache::getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info) { - uint32_t width = 256 * (count - 1); - - // If the npot extension is not supported we cannot use non-clamp - // wrap modes. We therefore find the nearest largest power of 2 - // unless width is already a power of 2 - if (!mHasNpot && (width & (width - 1)) != 0) { - width = 1 << (32 - __builtin_clz(width)); - } - - bool hasAlpha = false; - for (int i = 0; i < count; i++) { - if (((colors[i] >> 24) & 0xff) < 255) { - hasAlpha = true; - break; - } - } - - info.width = min(width, uint32_t(mMaxTextureSize)); - info.hasAlpha = hasAlpha; -} - -Texture* GradientCache::addLinearGradient(GradientCacheEntry& gradient, uint32_t* colors, - float* positions, int count) { - GradientInfo info; - getGradientInfo(colors, count, info); - - Texture* texture = new Texture(Caches::getInstance()); - texture->blend = info.hasAlpha; - texture->generation = 1; - - // Assume the cache is always big enough - const uint32_t size = info.width * 2 * bytesPerPixel(); - while (getSize() + size > mMaxSize) { - LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(), - "Ran out of things to remove from the cache? getSize() = %" PRIu32 - ", size = %" PRIu32 ", mMaxSize = %" PRIu32 ", width = %" PRIu32, - getSize(), size, mMaxSize, info.width); - } - - generateTexture(colors, positions, info.width, 2, texture); - - mSize += size; - LOG_ALWAYS_FATAL_IF((int)size != texture->objectSize(), - "size != texture->objectSize(), size %" PRIu32 - ", objectSize %d" - " width = %" PRIu32 " bytesPerPixel() = %zu", - size, texture->objectSize(), info.width, bytesPerPixel()); - mCache.put(gradient, texture); - - return texture; -} - -size_t GradientCache::bytesPerPixel() const { - // We use 4 channels (RGBA) - return 4 * (mUseFloatTexture ? /* fp16 */ 2 : sizeof(uint8_t)); -} - -size_t GradientCache::sourceBytesPerPixel() const { - // We use 4 channels (RGBA) and upload from floats (not half floats) - return 4 * (mUseFloatTexture ? sizeof(float) : sizeof(uint8_t)); -} - -void GradientCache::mixBytes(const FloatColor& start, const FloatColor& end, float amount, - uint8_t*& dst) const { - float oppAmount = 1.0f - amount; - float a = start.a * oppAmount + end.a * amount; - *dst++ = uint8_t(OECF(start.r * oppAmount + end.r * amount) * 255.0f); - *dst++ = uint8_t(OECF(start.g * oppAmount + end.g * amount) * 255.0f); - *dst++ = uint8_t(OECF(start.b * oppAmount + end.b * amount) * 255.0f); - *dst++ = uint8_t(a * 255.0f); -} - -void GradientCache::mixFloats(const FloatColor& start, const FloatColor& end, float amount, - uint8_t*& dst) const { - float oppAmount = 1.0f - amount; - float a = start.a * oppAmount + end.a * amount; - float* d = (float*)dst; -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - // We want to stay linear - *d++ = (start.r * oppAmount + end.r * amount); - *d++ = (start.g * oppAmount + end.g * amount); - *d++ = (start.b * oppAmount + end.b * amount); -#else - *d++ = OECF(start.r * oppAmount + end.r * amount); - *d++ = OECF(start.g * oppAmount + end.g * amount); - *d++ = OECF(start.b * oppAmount + end.b * amount); -#endif - *d++ = a; - dst += 4 * sizeof(float); -} - -void GradientCache::generateTexture(uint32_t* colors, float* positions, const uint32_t width, - const uint32_t height, Texture* texture) { - const GLsizei rowBytes = width * sourceBytesPerPixel(); - uint8_t pixels[rowBytes * height]; - - static ChannelMixer gMixers[] = { - // colors are stored gamma-encoded - &android::uirenderer::GradientCache::mixBytes, - // colors are stored in linear (linear blending on) - // or gamma-encoded (linear blending off) - &android::uirenderer::GradientCache::mixFloats, - }; - ChannelMixer mix = gMixers[mUseFloatTexture]; - - FloatColor start; - start.set(colors[0]); - - FloatColor end; - end.set(colors[1]); - - int currentPos = 1; - float startPos = positions[0]; - float distance = positions[1] - startPos; - - uint8_t* dst = pixels; - for (uint32_t x = 0; x < width; x++) { - float pos = x / float(width - 1); - if (pos > positions[currentPos]) { - start = end; - startPos = positions[currentPos]; - - currentPos++; - - end.set(colors[currentPos]); - distance = positions[currentPos] - startPos; - } - - float amount = (pos - startPos) / distance; - (this->*mix)(start, end, amount, dst); - } - - memcpy(pixels + rowBytes, pixels, rowBytes); - - if (mUseFloatTexture) { - texture->upload(GL_RGBA16F, width, height, GL_RGBA, GL_FLOAT, pixels); - } else { - GLint internalFormat = mHasLinearBlending ? GL_SRGB8_ALPHA8 : GL_RGBA; - texture->upload(internalFormat, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - } - - texture->setFilter(GL_LINEAR); - texture->setWrap(GL_CLAMP_TO_EDGE); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h deleted file mode 100644 index ff426cd3425b..000000000000 --- a/libs/hwui/GradientCache.h +++ /dev/null @@ -1,177 +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_GRADIENT_CACHE_H -#define ANDROID_HWUI_GRADIENT_CACHE_H - -#include <memory> - -#include <GLES3/gl3.h> - -#include <SkShader.h> - -#include <utils/LruCache.h> -#include <utils/Mutex.h> - -#include "FloatColor.h" - -namespace android { -namespace uirenderer { - -class Texture; - -struct GradientCacheEntry { - GradientCacheEntry() { - count = 0; - colors = nullptr; - positions = nullptr; - } - - GradientCacheEntry(uint32_t* colors, float* positions, uint32_t count) { - copy(colors, positions, count); - } - - GradientCacheEntry(const GradientCacheEntry& entry) { - copy(entry.colors.get(), entry.positions.get(), entry.count); - } - - GradientCacheEntry& operator=(const GradientCacheEntry& entry) { - if (this != &entry) { - copy(entry.colors.get(), entry.positions.get(), entry.count); - } - - return *this; - } - - hash_t hash() const; - - static int compare(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs); - - bool operator==(const GradientCacheEntry& other) const { return compare(*this, other) == 0; } - - bool operator!=(const GradientCacheEntry& other) const { return compare(*this, other) != 0; } - - std::unique_ptr<uint32_t[]> colors; - std::unique_ptr<float[]> positions; - uint32_t count; - -private: - void copy(uint32_t* colors, float* positions, uint32_t count) { - this->count = count; - this->colors.reset(new uint32_t[count]); - this->positions.reset(new float[count]); - - memcpy(this->colors.get(), colors, count * sizeof(uint32_t)); - memcpy(this->positions.get(), positions, count * sizeof(float)); - } - -}; // GradientCacheEntry - -// Caching support - -inline int strictly_order_type(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs) { - return GradientCacheEntry::compare(lhs, rhs) < 0; -} - -inline int compare_type(const GradientCacheEntry& lhs, const GradientCacheEntry& rhs) { - return GradientCacheEntry::compare(lhs, rhs); -} - -inline hash_t hash_type(const GradientCacheEntry& entry) { - return entry.hash(); -} - -/** - * A simple LRU gradient cache. The cache has a maximum size expressed in bytes. - * Any texture added to the cache causing the cache to grow beyond the maximum - * allowed size will also cause the oldest texture to be kicked out. - */ -class GradientCache : public OnEntryRemoved<GradientCacheEntry, Texture*> { -public: - explicit GradientCache(const Extensions& extensions); - ~GradientCache(); - - /** - * Used as a callback when an entry is removed from the cache. - * Do not invoke directly. - */ - void operator()(GradientCacheEntry& shader, Texture*& texture) override; - - /** - * Returns the texture associated with the specified shader. - */ - Texture* get(uint32_t* colors, float* positions, int count); - - /** - * Clears the cache. This causes all textures to be deleted. - */ - void clear(); - - /** - * Returns the maximum size of the cache in bytes. - */ - uint32_t getMaxSize(); - /** - * Returns the current size of the cache in bytes. - */ - uint32_t getSize(); - -private: - /** - * Adds a new linear gradient to the cache. The generated texture is - * returned. - */ - Texture* addLinearGradient(GradientCacheEntry& gradient, uint32_t* colors, float* positions, - int count); - - void generateTexture(uint32_t* colors, float* positions, const uint32_t width, - const uint32_t height, Texture* texture); - - struct GradientInfo { - uint32_t width; - bool hasAlpha; - }; - - void getGradientInfo(const uint32_t* colors, const int count, GradientInfo& info); - - size_t bytesPerPixel() const; - size_t sourceBytesPerPixel() const; - - typedef void (GradientCache::*ChannelMixer)(const FloatColor& start, const FloatColor& end, - float amount, uint8_t*& dst) const; - - void mixBytes(const FloatColor& start, const FloatColor& end, float amount, - uint8_t*& dst) const; - void mixFloats(const FloatColor& start, const FloatColor& end, float amount, - uint8_t*& dst) const; - - LruCache<GradientCacheEntry, Texture*> mCache; - - uint32_t mSize; - const uint32_t mMaxSize; - - GLint mMaxTextureSize; - bool mUseFloatTexture; - bool mHasNpot; - bool mHasLinearBlending; - - mutable Mutex mLock; -}; // class GradientCache - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_GRADIENT_CACHE_H diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp new file mode 100644 index 000000000000..165fc4860fb2 --- /dev/null +++ b/libs/hwui/HardwareBitmapUploader.cpp @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2018 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 "HardwareBitmapUploader.h" + +#include "hwui/Bitmap.h" +#include "renderthread/EglManager.h" +#include "thread/ThreadBase.h" +#include "utils/TimeUtils.h" + +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES3/gl3.h> +#include <SkCanvas.h> +#include <utils/GLUtils.h> +#include <utils/Trace.h> +#include <utils/TraceUtils.h> +#include <thread> + +namespace android::uirenderer { + +static std::mutex sLock{}; +static ThreadBase* sUploadThread = nullptr; +static renderthread::EglManager sEglManager; +static int sPendingUploads = 0; +static nsecs_t sLastUpload = 0; + +static bool shouldTimeOutLocked() { + nsecs_t durationSince = systemTime() - sLastUpload; + return durationSince > 2000_ms; +} + +static void checkIdleTimeout() { + std::lock_guard _lock{sLock}; + if (sPendingUploads == 0 && shouldTimeOutLocked()) { + sEglManager.destroy(); + } else { + sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout); + } +} + +static void beginUpload() { + std::lock_guard _lock{sLock}; + sPendingUploads++; + + if (!sUploadThread) { + sUploadThread = new ThreadBase{}; + } + + if (!sUploadThread->isRunning()) { + sUploadThread->start("GrallocUploadThread"); + } + + if (!sEglManager.hasEglContext()) { + sUploadThread->queue().runSync([]() { + sEglManager.initialize(); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + }); + sUploadThread->queue().postDelayed(5000_ms, checkIdleTimeout); + } +} + +static void endUpload() { + std::lock_guard _lock{sLock}; + sPendingUploads--; + sLastUpload = systemTime(); +} + +static EGLDisplay getUploadEglDisplay() { + std::lock_guard _lock{sLock}; + LOG_ALWAYS_FATAL_IF(!sEglManager.hasEglContext(), "Forgot to begin an upload?"); + return sEglManager.eglDisplay(); +} + +static bool hasFP16Support() { + static std::once_flag sOnce; + static bool hasFP16Support = false; + + // Gralloc shouldn't let us create a USAGE_HW_TEXTURE if GLES is unable to consume it, so + // we don't need to double-check the GLES version/extension. + std::call_once(sOnce, []() { + sp<GraphicBuffer> buffer = new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_FP16, + GraphicBuffer::USAGE_HW_TEXTURE | + GraphicBuffer::USAGE_SW_WRITE_NEVER | + GraphicBuffer::USAGE_SW_READ_NEVER, + "tempFp16Buffer"); + status_t error = buffer->initCheck(); + hasFP16Support = !error; + }); + + return hasFP16Support; +} + +#define FENCE_TIMEOUT 2000000000 + +struct FormatInfo { + PixelFormat pixelFormat; + GLint format, type; + bool isSupported = false; + bool valid = true; +}; + +static FormatInfo determineFormat(const SkBitmap& skBitmap) { + FormatInfo formatInfo; + // TODO: add support for linear blending (when ANDROID_ENABLE_LINEAR_BLENDING is defined) + switch (skBitmap.info().colorType()) { + case kRGBA_8888_SkColorType: + formatInfo.isSupported = true; + // ARGB_4444 is upconverted to RGBA_8888 + case kARGB_4444_SkColorType: + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888; + formatInfo.format = GL_RGBA; + formatInfo.type = GL_UNSIGNED_BYTE; + break; + case kRGBA_F16_SkColorType: + formatInfo.isSupported = hasFP16Support(); + if (formatInfo.isSupported) { + formatInfo.type = GL_HALF_FLOAT; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_FP16; + } else { + formatInfo.type = GL_UNSIGNED_BYTE; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888; + } + formatInfo.format = GL_RGBA; + break; + case kRGB_565_SkColorType: + formatInfo.isSupported = true; + formatInfo.pixelFormat = PIXEL_FORMAT_RGB_565; + formatInfo.format = GL_RGB; + formatInfo.type = GL_UNSIGNED_SHORT_5_6_5; + break; + case kGray_8_SkColorType: + formatInfo.isSupported = true; + formatInfo.pixelFormat = PIXEL_FORMAT_RGBA_8888; + formatInfo.format = GL_LUMINANCE; + formatInfo.type = GL_UNSIGNED_BYTE; + break; + default: + ALOGW("unable to create hardware bitmap of colortype: %d", skBitmap.info().colorType()); + formatInfo.valid = false; + } + return formatInfo; +} + +static SkBitmap makeHwCompatible(const FormatInfo& format, const SkBitmap& source) { + if (format.isSupported) { + return source; + } else { + SkBitmap bitmap; + const SkImageInfo& info = source.info(); + bitmap.allocPixels( + SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr)); + bitmap.eraseColor(0); + if (info.colorType() == kRGBA_F16_SkColorType) { + // Drawing RGBA_F16 onto ARGB_8888 is not supported + source.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()), + bitmap.getPixels(), bitmap.rowBytes(), 0, 0); + } else { + SkCanvas canvas(bitmap); + canvas.drawBitmap(source, 0.0f, 0.0f, nullptr); + } + return bitmap; + } +} + +class ScopedUploadRequest { +public: + ScopedUploadRequest() { beginUpload(); } + ~ScopedUploadRequest() { endUpload(); } +}; + +sk_sp<Bitmap> HardwareBitmapUploader::allocateHardwareBitmap(const SkBitmap& sourceBitmap) { + ATRACE_CALL(); + + FormatInfo format = determineFormat(sourceBitmap); + if (!format.valid) { + return nullptr; + } + + ScopedUploadRequest _uploadRequest{}; + + SkBitmap bitmap = makeHwCompatible(format, sourceBitmap); + sp<GraphicBuffer> buffer = new GraphicBuffer( + static_cast<uint32_t>(bitmap.width()), static_cast<uint32_t>(bitmap.height()), + format.pixelFormat, + GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | + GraphicBuffer::USAGE_SW_READ_NEVER, + std::string("Bitmap::allocateSkiaHardwareBitmap pid [") + std::to_string(getpid()) + + "]"); + + status_t error = buffer->initCheck(); + if (error < 0) { + ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()"); + return nullptr; + } + + EGLDisplay display = getUploadEglDisplay(); + + LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s", + uirenderer::renderthread::EglManager::eglErrorString()); + // We use an EGLImage to access the content of the GraphicBuffer + // The EGL image is later bound to a 2D texture + EGLClientBuffer clientBuffer = (EGLClientBuffer)buffer->getNativeBuffer(); + AutoEglImage autoImage(display, clientBuffer); + if (autoImage.image == EGL_NO_IMAGE_KHR) { + ALOGW("Could not create EGL image, err =%s", + uirenderer::renderthread::EglManager::eglErrorString()); + return nullptr; + } + + { + ATRACE_FORMAT("CPU -> gralloc transfer (%dx%d)", bitmap.width(), bitmap.height()); + EGLSyncKHR fence = sUploadThread->queue().runSync([&]() -> EGLSyncKHR { + AutoSkiaGlTexture glTexture; + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image); + GL_CHECKPOINT(MODERATE); + + // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we + // provide. + // But asynchronous in sense that driver may upload texture onto hardware buffer when we + // first + // use it in drawing + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), format.format, + format.type, bitmap.getPixels()); + GL_CHECKPOINT(MODERATE); + + EGLSyncKHR uploadFence = + eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL); + LOG_ALWAYS_FATAL_IF(uploadFence == EGL_NO_SYNC_KHR, "Could not create sync fence %#x", + eglGetError()); + glFlush(); + return uploadFence; + }); + + EGLint waitStatus = eglClientWaitSyncKHR(display, fence, 0, FENCE_TIMEOUT); + LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR, + "Failed to wait for the fence %#x", eglGetError()); + + eglDestroySyncKHR(display, fence); + } + + return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info(), Bitmap::computePalette(bitmap))); +} + +}; // namespace android::uirenderer diff --git a/libs/hwui/tests/unit/DeviceInfoTests.cpp b/libs/hwui/HardwareBitmapUploader.h index af37938915e5..c0113d81fefb 100644 --- a/libs/hwui/tests/unit/DeviceInfoTests.cpp +++ b/libs/hwui/HardwareBitmapUploader.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,15 @@ * limitations under the License. */ -#include <DeviceInfo.h> +#pragma once -#include <gtest/gtest.h> -#include "tests/common/TestUtils.h" +#include <hwui/Bitmap.h> -using namespace android; -using namespace android::uirenderer; +namespace android::uirenderer { -OPENGL_PIPELINE_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"; -} +class HardwareBitmapUploader { +public: + static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& sourceBitmap); +}; + +}; // namespace android::uirenderer diff --git a/libs/hwui/Image.cpp b/libs/hwui/Image.cpp deleted file mode 100644 index d30796d01479..000000000000 --- a/libs/hwui/Image.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <utils/Log.h> - -#include "Caches.h" -#include "Image.h" - -namespace android { -namespace uirenderer { - -Image::Image(sp<GraphicBuffer> buffer) { - // Create the EGLImage object that maps the GraphicBuffer - EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - EGLClientBuffer clientBuffer = (EGLClientBuffer)buffer->getNativeBuffer(); - EGLint attrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; - - mImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, - attrs); - - if (mImage == EGL_NO_IMAGE_KHR) { - ALOGW("Error creating image (%#x)", eglGetError()); - mTexture = 0; - } else { - // Create a 2D texture to sample from the EGLImage - glGenTextures(1, &mTexture); - Caches::getInstance().textureState().bindTexture(mTexture); - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, mImage); - - GLenum status = GL_NO_ERROR; - while ((status = glGetError()) != GL_NO_ERROR) { - ALOGW("Error creating image (%#x)", status); - } - } -} - -Image::~Image() { - if (mImage != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), mImage); - mImage = EGL_NO_IMAGE_KHR; - - Caches::getInstance().textureState().deleteTexture(mTexture); - mTexture = 0; - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Image.h b/libs/hwui/Image.h deleted file mode 100644 index 989b6ff3648d..000000000000 --- a/libs/hwui/Image.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_IMAGE_H -#define ANDROID_HWUI_IMAGE_H - -#include <EGL/egl.h> -#include <EGL/eglext.h> - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -#include <ui/GraphicBuffer.h> - -namespace android { -namespace uirenderer { - -/** - * A simple wrapper that creates an EGLImage and a texture for a GraphicBuffer. - */ -class Image { -public: - /** - * Creates a new image from the specified graphic buffer. If the image - * cannot be created, getTexture() will return 0 and getImage() will - * return EGL_NO_IMAGE_KHR. - */ - explicit Image(sp<GraphicBuffer> buffer); - ~Image(); - - /** - * Returns the name of the GL texture that can be used to sample - * from this image. - */ - GLuint getTexture() const { return mTexture; } - - /** - * Returns the name of the EGL image represented by this object. - */ - EGLImageKHR getImage() const { return mImage; } - -private: - GLuint mTexture; - EGLImageKHR mImage; -}; // class Image - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_IMAGE_H diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index f2d50cd42523..e7ae7675f0b8 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -18,6 +18,7 @@ #include <errno.h> #include <inttypes.h> +#include <statslog.h> #include <sys/mman.h> #include <algorithm> @@ -181,6 +182,7 @@ void JankTracker::finishFrame(const FrameInfo& frame) { ALOGI("%s", ss.str().c_str()); // Just so we have something that counts up, the value is largely irrelevant ATRACE_INT(ss.str().c_str(), ++sDaveyCount); + android::util::stats_write(android::util::DAVEY_OCCURRED, getuid(), ns2ms(totalDuration)); } } diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp index b86ae121af55..32aaa54e696c 100644 --- a/libs/hwui/Layer.cpp +++ b/libs/hwui/Layer.cpp @@ -17,25 +17,26 @@ #include "Layer.h" #include "renderstate/RenderState.h" +#include "utils/Color.h" #include <SkToSRGBColorFilter.h> namespace android { namespace uirenderer { -Layer::Layer(RenderState& renderState, Api api, sk_sp<SkColorFilter> colorFilter, int alpha, - SkBlendMode mode) - : GpuMemoryTracker(GpuObjectType::Layer) - , mRenderState(renderState) - , mode(mode) - , mApi(api) +Layer::Layer(RenderState& renderState, sk_sp<SkColorFilter> colorFilter, int alpha, + SkBlendMode mode) + : mRenderState(renderState) , mColorFilter(colorFilter) - , alpha(alpha) { + , alpha(alpha) + , mode(mode) { // TODO: This is a violation of Android's typical ref counting, but it // preserves the old inc/dec ref locations. This should be changed... incStrong(nullptr); buildColorSpaceWithFilter(); renderState.registerLayer(this); + texTransform.setIdentity(); + transform.setIdentity(); } Layer::~Layer() { @@ -76,5 +77,13 @@ void Layer::postDecStrong() { mRenderState.postDecStrong(this); } +SkBlendMode Layer::getMode() const { + if (mBlend || mode != SkBlendMode::kSrcOver) { + return mode; + } else { + return SkBlendMode::kSrc; + } +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h index d41c9703e908..e4f96e914c36 100644 --- a/libs/hwui/Layer.h +++ b/libs/hwui/Layer.h @@ -16,15 +16,15 @@ #pragma once -#include <GpuMemoryTracker.h> #include <utils/RefBase.h> +#include <SkBlendMode.h> #include <SkColorFilter.h> #include <SkColorSpace.h> -#include <SkBlendMode.h> #include <SkPaint.h> - -#include "Matrix.h" +#include <SkImage.h> +#include <SkMatrix.h> +#include <system/graphics.h> namespace android { namespace uirenderer { @@ -38,26 +38,21 @@ class RenderState; /** * A layer has dimensions and is backed by a backend specific texture or framebuffer. */ -class Layer : public VirtualLightRefBase, GpuMemoryTracker { +class Layer : public VirtualLightRefBase { public: - enum class Api { - OpenGL = 0, - Vulkan = 1, - }; - - Api getApi() const { return mApi; } + Layer(RenderState& renderState, sk_sp<SkColorFilter>, int alpha, SkBlendMode mode); ~Layer(); - virtual uint32_t getWidth() const = 0; + uint32_t getWidth() const { return mWidth; } - virtual uint32_t getHeight() const = 0; + uint32_t getHeight() const { return mHeight; } - virtual void setSize(uint32_t width, uint32_t height) = 0; + void setSize(uint32_t width, uint32_t height) { mWidth = width; mHeight = height; } - virtual void setBlend(bool blend) = 0; + void setBlend(bool blend) { mBlend = blend; } - virtual bool isBlend() const = 0; + bool isBlend() const { return mBlend; } inline void setForceFilter(bool forceFilter) { this->forceFilter = forceFilter; } @@ -72,7 +67,7 @@ public: inline int getAlpha() const { return alpha; } - virtual SkBlendMode getMode() const { return mode; } + SkBlendMode getMode() const; inline SkColorFilter* getColorFilter() const { return mColorFilter.get(); } @@ -84,9 +79,9 @@ public: inline sk_sp<SkColorFilter> getColorSpaceWithFilter() const { return mColorSpaceWithFilter; } - inline mat4& getTexTransform() { return texTransform; } + inline SkMatrix& getTexTransform() { return texTransform; } - inline mat4& getTransform() { return transform; } + inline SkMatrix& getTransform() { return transform; } /** * Posts a decStrong call to the appropriate thread. @@ -94,31 +89,17 @@ public: */ void postDecStrong(); - inline void setBufferSize(uint32_t width, uint32_t height) { - mBufferWidth = width; - mBufferHeight = height; - } - - inline uint32_t getBufferWidth() const { return mBufferWidth; } + inline void setImage(const sk_sp<SkImage>& image) { this->layerImage = image; } - inline uint32_t getBufferHeight() const { return mBufferHeight; } + inline sk_sp<SkImage> getImage() const { return this->layerImage; } protected: - Layer(RenderState& renderState, Api api, sk_sp<SkColorFilter>, int alpha, - SkBlendMode mode); RenderState& mRenderState; - /** - * Blending mode of the layer. - */ - SkBlendMode mode; - private: void buildColorSpaceWithFilter(); - Api mApi; - /** * Color filter used to draw this layer. Optional. */ @@ -145,18 +126,40 @@ private: int alpha; /** + * Blending mode of the layer. + */ + SkBlendMode mode; + + /** * Optional texture coordinates transform. */ - mat4 texTransform; + SkMatrix texTransform; /** * Optional transform. */ - mat4 transform; + SkMatrix transform; + + /** + * An image backing the layer. + */ + sk_sp<SkImage> layerImage; + + /** + * layer width. + */ + uint32_t mWidth = 0; - uint32_t mBufferWidth = 0; + /** + * layer height. + */ + uint32_t mHeight = 0; + + /** + * enable blending + */ + bool mBlend = false; - uint32_t mBufferHeight = 0; }; // struct Layer }; // namespace uirenderer diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp deleted file mode 100644 index 15ede4ca148a..000000000000 --- a/libs/hwui/LayerBuilder.cpp +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "LayerBuilder.h" - -#include "BakedOpState.h" -#include "RenderNode.h" -#include "utils/PaintUtils.h" -#include "utils/TraceUtils.h" - -#include <utils/TypeHelpers.h> - -namespace android { -namespace uirenderer { - -class BatchBase { -public: - BatchBase(batchid_t batchId, BakedOpState* op, bool merging) - : mBatchId(batchId), mMerging(merging) { - mBounds = op->computedState.clippedBounds; - mOps.push_back(op); - } - - bool intersects(const Rect& rect) const { - if (!rect.intersects(mBounds)) return false; - - for (const BakedOpState* op : mOps) { - if (rect.intersects(op->computedState.clippedBounds)) { - return true; - } - } - return false; - } - - batchid_t getBatchId() const { return mBatchId; } - bool isMerging() const { return mMerging; } - - const std::vector<BakedOpState*>& getOps() const { return mOps; } - - void dump() const { - ALOGD(" Batch %p, id %d, merging %d, count %d, bounds " RECT_STRING, this, mBatchId, - mMerging, (int)mOps.size(), RECT_ARGS(mBounds)); - } - -protected: - batchid_t mBatchId; - Rect mBounds; - std::vector<BakedOpState*> mOps; - bool mMerging; -}; - -class OpBatch : public BatchBase { -public: - OpBatch(batchid_t batchId, BakedOpState* op) : BatchBase(batchId, op, false) {} - - void batchOp(BakedOpState* op) { - mBounds.unionWith(op->computedState.clippedBounds); - mOps.push_back(op); - } -}; - -class MergingOpBatch : public BatchBase { -public: - MergingOpBatch(batchid_t batchId, BakedOpState* op) - : BatchBase(batchId, op, true), mClipSideFlags(op->computedState.clipSideFlags) {} - - /* - * Helper for determining if a new op can merge with a MergingDrawBatch based on their bounds - * and clip side flags. Positive bounds delta means new bounds fit in old. - */ - static inline bool checkSide(const int currentFlags, const int newFlags, const int side, - float boundsDelta) { - bool currentClipExists = currentFlags & side; - bool newClipExists = newFlags & side; - - // if current is clipped, we must be able to fit new bounds in current - if (boundsDelta > 0 && currentClipExists) return false; - - // if new is clipped, we must be able to fit current bounds in new - if (boundsDelta < 0 && newClipExists) return false; - - return true; - } - - static bool paintIsDefault(const SkPaint& paint) { - return paint.getAlpha() == 255 && paint.getColorFilter() == nullptr && - paint.getShader() == nullptr; - } - - static bool paintsAreEquivalent(const SkPaint& a, const SkPaint& b) { - // Note: don't check color, since all currently mergeable ops can merge across colors - return a.getAlpha() == b.getAlpha() && a.getColorFilter() == b.getColorFilter() && - a.getShader() == b.getShader(); - } - - /* - * Checks if a (mergeable) op can be merged into this batch - * - * If true, the op's multiDraw must be guaranteed to handle both ops simultaneously, so it is - * important to consider all paint attributes used in the draw calls in deciding both a) if an - * op tries to merge at all, and b) if the op can merge with another set of ops - * - * False positives can lead to information from the paints of subsequent merged operations being - * dropped, so we make simplifying qualifications on the ops that can merge, per op type. - */ - bool canMergeWith(BakedOpState* op) const { - bool isTextBatch = - getBatchId() == OpBatchType::Text || getBatchId() == OpBatchType::ColorText; - - // Overlapping other operations is only allowed for text without shadow. For other ops, - // multiDraw isn't guaranteed to overdraw correctly - if (!isTextBatch || PaintUtils::hasTextShadow(op->op->paint)) { - if (intersects(op->computedState.clippedBounds)) return false; - } - - const BakedOpState* lhs = op; - const BakedOpState* rhs = mOps[0]; - - if (!MathUtils::areEqual(lhs->alpha, rhs->alpha)) return false; - - // Identical round rect clip state means both ops will clip in the same way, or not at all. - // As the state objects are const, we can compare their pointers to determine mergeability - if (lhs->roundRectClipState != rhs->roundRectClipState) return false; - - // Local masks prevent merge, since they're potentially in different coordinate spaces - if (lhs->computedState.localProjectionPathMask || - rhs->computedState.localProjectionPathMask) - return false; - - /* Clipping compatibility check - * - * Exploits the fact that if a op or batch is clipped on a side, its bounds will equal its - * clip for that side. - */ - const int currentFlags = mClipSideFlags; - const int newFlags = op->computedState.clipSideFlags; - if (currentFlags != OpClipSideFlags::None || newFlags != OpClipSideFlags::None) { - const Rect& opBounds = op->computedState.clippedBounds; - float boundsDelta = mBounds.left - opBounds.left; - if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Left, boundsDelta)) - return false; - boundsDelta = mBounds.top - opBounds.top; - if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Top, boundsDelta)) return false; - - // right and bottom delta calculation reversed to account for direction - boundsDelta = opBounds.right - mBounds.right; - if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Right, boundsDelta)) - return false; - boundsDelta = opBounds.bottom - mBounds.bottom; - if (!checkSide(currentFlags, newFlags, OpClipSideFlags::Bottom, boundsDelta)) - return false; - } - - const SkPaint* newPaint = op->op->paint; - const SkPaint* oldPaint = mOps[0]->op->paint; - - if (newPaint == oldPaint) { - // if paints are equal, then modifiers + paint attribs don't need to be compared - return true; - } else if (newPaint && !oldPaint) { - return paintIsDefault(*newPaint); - } else if (!newPaint && oldPaint) { - return paintIsDefault(*oldPaint); - } - return paintsAreEquivalent(*newPaint, *oldPaint); - } - - void mergeOp(BakedOpState* op) { - mBounds.unionWith(op->computedState.clippedBounds); - mOps.push_back(op); - - // Because a new op must have passed canMergeWith(), we know it's passed the clipping compat - // check, and doesn't extend past a side of the clip that's in use by the merged batch. - // Therefore it's safe to simply always merge flags, and use the bounds as the clip rect. - mClipSideFlags |= op->computedState.clipSideFlags; - } - - int getClipSideFlags() const { return mClipSideFlags; } - const Rect& getClipRect() const { return mBounds; } - -private: - int mClipSideFlags; -}; - -LayerBuilder::LayerBuilder(uint32_t width, uint32_t height, const Rect& repaintRect, - const BeginLayerOp* beginLayerOp, RenderNode* renderNode) - : width(width) - , height(height) - , repaintRect(repaintRect) - , repaintClip(repaintRect) - , offscreenBuffer(renderNode ? renderNode->getLayer() : nullptr) - , beginLayerOp(beginLayerOp) - , renderNode(renderNode) {} - -// iterate back toward target to see if anything drawn since should overlap the new op -// if no target, merging ops still iterate to find similar batch to insert after -void LayerBuilder::locateInsertIndex(int batchId, const Rect& clippedBounds, - BatchBase** targetBatch, size_t* insertBatchIndex) const { - for (int i = mBatches.size() - 1; i >= 0; i--) { - BatchBase* overBatch = mBatches[i]; - - if (overBatch == *targetBatch) break; - - // TODO: also consider shader shared between batch types - if (batchId == overBatch->getBatchId()) { - *insertBatchIndex = i + 1; - if (!*targetBatch) break; // found insert position, quit - } - - if (overBatch->intersects(clippedBounds)) { - // NOTE: it may be possible to optimize for special cases where two operations - // of the same batch/paint could swap order, such as with a non-mergeable - // (clipped) and a mergeable text operation - *targetBatch = nullptr; - break; - } - } -} - -void LayerBuilder::deferLayerClear(const Rect& rect) { - mClearRects.push_back(rect); -} - -void LayerBuilder::onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState) { - if (bakedState->op->opId != RecordedOpId::CopyToLayerOp) { - // First non-CopyToLayer, so stop stashing up layer clears for unclipped save layers, - // and issue them together in one draw. - flushLayerClears(allocator); - - if (CC_UNLIKELY(activeUnclippedSaveLayers.empty() && - bakedState->computedState.opaqueOverClippedBounds && - bakedState->computedState.clippedBounds.contains(repaintRect) && - !Properties::debugOverdraw)) { - // discard all deferred drawing ops, since new one will occlude them - clear(); - } - } -} - -void LayerBuilder::flushLayerClears(LinearAllocator& allocator) { - if (CC_UNLIKELY(!mClearRects.empty())) { - const int vertCount = mClearRects.size() * 4; - // put the verts in the frame allocator, since - // 1) SimpleRectsOps needs verts, not rects - // 2) even if mClearRects stored verts, std::vectors will move their contents - Vertex* const verts = (Vertex*)allocator.create_trivial_array<Vertex>(vertCount); - - Vertex* currentVert = verts; - Rect bounds = mClearRects[0]; - for (auto&& rect : mClearRects) { - bounds.unionWith(rect); - Vertex::set(currentVert++, rect.left, rect.top); - Vertex::set(currentVert++, rect.right, rect.top); - Vertex::set(currentVert++, rect.left, rect.bottom); - Vertex::set(currentVert++, rect.right, rect.bottom); - } - mClearRects.clear(); // discard rects before drawing so this method isn't reentrant - - // One or more unclipped saveLayers have been enqueued, with deferred clears. - // Flush all of these clears with a single draw - SkPaint* paint = allocator.create<SkPaint>(); - paint->setBlendMode(SkBlendMode::kClear); - SimpleRectsOp* op = allocator.create_trivial<SimpleRectsOp>( - bounds, Matrix4::identity(), nullptr, paint, verts, vertCount); - BakedOpState* bakedState = - BakedOpState::directConstruct(allocator, &repaintClip, bounds, *op); - deferUnmergeableOp(allocator, bakedState, OpBatchType::Vertices); - } -} - -void LayerBuilder::deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, - batchid_t batchId) { - onDeferOp(allocator, op); - OpBatch* targetBatch = mBatchLookup[batchId]; - - size_t insertBatchIndex = mBatches.size(); - if (targetBatch) { - locateInsertIndex(batchId, op->computedState.clippedBounds, (BatchBase**)(&targetBatch), - &insertBatchIndex); - } - - if (targetBatch) { - targetBatch->batchOp(op); - } else { - // new non-merging batch - targetBatch = allocator.create<OpBatch>(batchId, op); - mBatchLookup[batchId] = targetBatch; - mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); - } -} - -void LayerBuilder::deferMergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId, - mergeid_t mergeId) { - onDeferOp(allocator, op); - MergingOpBatch* targetBatch = nullptr; - - // Try to merge with any existing batch with same mergeId - auto getResult = mMergingBatchLookup[batchId].find(mergeId); - if (getResult != mMergingBatchLookup[batchId].end()) { - targetBatch = getResult->second; - if (!targetBatch->canMergeWith(op)) { - targetBatch = nullptr; - } - } - - size_t insertBatchIndex = mBatches.size(); - locateInsertIndex(batchId, op->computedState.clippedBounds, (BatchBase**)(&targetBatch), - &insertBatchIndex); - - if (targetBatch) { - targetBatch->mergeOp(op); - } else { - // new merging batch - targetBatch = allocator.create<MergingOpBatch>(batchId, op); - mMergingBatchLookup[batchId].insert(std::make_pair(mergeId, targetBatch)); - - mBatches.insert(mBatches.begin() + insertBatchIndex, targetBatch); - } -} - -void LayerBuilder::replayBakedOpsImpl(void* arg, BakedOpReceiver* unmergedReceivers, - MergedOpReceiver* mergedReceivers) const { - if (renderNode) { - ATRACE_FORMAT_BEGIN("Issue HW Layer DisplayList %s %ux%u", renderNode->getName(), width, - height); - } else { - ATRACE_BEGIN("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); - } - } - } - ATRACE_END(); -} - -void LayerBuilder::clear() { - mBatches.clear(); - for (int i = 0; i < OpBatchType::Count; i++) { - mBatchLookup[i] = nullptr; - mMergingBatchLookup[i].clear(); - } -} - -void LayerBuilder::dump() const { - ALOGD("LayerBuilder %p, %ux%u buffer %p, blo %p, rn %p (%s)", this, width, height, - offscreenBuffer, beginLayerOp, renderNode, renderNode ? renderNode->getName() : "-"); - for (const BatchBase* batch : mBatches) { - batch->dump(); - } -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/LayerBuilder.h b/libs/hwui/LayerBuilder.h deleted file mode 100644 index c799d48f7821..000000000000 --- a/libs/hwui/LayerBuilder.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 <unordered_map> -#include <vector> - -struct SkRect; - -namespace android { -namespace uirenderer { - -class BakedOpState; -struct BeginLayerOp; -class BatchBase; -class LinearAllocator; -struct MergedBakedOpList; -class MergingOpBatch; -class OffscreenBuffer; -class OpBatch; -class RenderNode; - -typedef int batchid_t; -typedef const void* mergeid_t; - -namespace OpBatchType { -enum { - Bitmap, - MergedPatch, - AlphaVertices, - Vertices, - AlphaMaskTexture, - Text, - ColorText, - Shadow, - TextureLayer, - Functor, - CopyToLayer, - CopyFromLayer, - - Count // must be last -}; -} - -typedef void (*BakedOpReceiver)(void*, const BakedOpState&); -typedef void (*MergedOpReceiver)(void*, const MergedBakedOpList& opList); - -/** - * Stores the deferred render operations and state used to compute ordering - * for a single FBO/layer. - */ -class LayerBuilder { - // Prevent copy/assign because users may stash pointer to offscreenBuffer and viewportClip - PREVENT_COPY_AND_ASSIGN(LayerBuilder); - -public: - // Create LayerBuilder for Fbo0 - LayerBuilder(uint32_t width, uint32_t height, const Rect& repaintRect) - : LayerBuilder(width, height, repaintRect, nullptr, nullptr){}; - - // Create LayerBuilder for an offscreen layer, where beginLayerOp is present for a - // saveLayer, renderNode is present for a HW layer. - LayerBuilder(uint32_t width, uint32_t height, const Rect& repaintRect, - const BeginLayerOp* beginLayerOp, RenderNode* renderNode); - - // iterate back toward target to see if anything drawn since should overlap the new op - // if no target, merging ops still iterate to find similar batch to insert after - void locateInsertIndex(int batchId, const Rect& clippedBounds, BatchBase** targetBatch, - size_t* insertBatchIndex) const; - - void deferUnmergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId); - - // insertion point of a new batch, will hopefully be immediately after similar batch - // (generally, should be similar shader) - void deferMergeableOp(LinearAllocator& allocator, BakedOpState* op, batchid_t batchId, - mergeid_t mergeId); - - void replayBakedOpsImpl(void* arg, BakedOpReceiver* receivers, MergedOpReceiver*) const; - - void deferLayerClear(const Rect& dstRect); - - bool empty() const { return mBatches.empty(); } - - void clear(); - - void dump() const; - - const uint32_t width; - const uint32_t height; - const Rect repaintRect; - const ClipRect repaintClip; - OffscreenBuffer* offscreenBuffer; - const BeginLayerOp* beginLayerOp; - const RenderNode* renderNode; - - // list of deferred CopyFromLayer ops, to be deferred upon encountering EndUnclippedLayerOps - std::vector<BakedOpState*> activeUnclippedSaveLayers; - -private: - void onDeferOp(LinearAllocator& allocator, const BakedOpState* bakedState); - void flushLayerClears(LinearAllocator& allocator); - - std::vector<BatchBase*> mBatches; - - /** - * Maps the mergeid_t returned by an op's getMergeId() to the most recently seen - * MergingDrawBatch of that id. These ids are unique per draw type and guaranteed to not - * collide, which avoids the need to resolve mergeid collisions. - */ - std::unordered_map<mergeid_t, MergingOpBatch*> mMergingBatchLookup[OpBatchType::Count]; - - // Maps batch ids to the most recent *non-merging* batch of that id - OpBatch* mBatchLookup[OpBatchType::Count] = {nullptr}; - - std::vector<Rect> mClearRects; -}; - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/LayerUpdateQueue.h b/libs/hwui/LayerUpdateQueue.h index b14b80cc598a..6857999500f0 100644 --- a/libs/hwui/LayerUpdateQueue.h +++ b/libs/hwui/LayerUpdateQueue.h @@ -19,6 +19,7 @@ #include <utils/StrongPointer.h> #include "Rect.h" +#include "RenderNode.h" #include "utils/Macros.h" #include <unordered_map> diff --git a/libs/hwui/OpDumper.h b/libs/hwui/Lighting.h index edbe381fcc72..d972c2181aea 100644 --- a/libs/hwui/OpDumper.h +++ b/libs/hwui/Lighting.h @@ -16,17 +16,22 @@ #pragma once -#include <ostream> +#include "Vector.h" namespace android { namespace uirenderer { -struct RecordedOp; +struct LightGeometry { + Vector3 center; + float radius; +}; -class OpDumper { -public: - static void dump(const RecordedOp& op, std::ostream& output, int level = 0); - static const char* opName(const RecordedOp& op); +struct LightInfo { + LightInfo() : LightInfo(0, 0) {} + LightInfo(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) + : ambientShadowAlpha(ambientShadowAlpha), spotShadowAlpha(spotShadowAlpha) {} + uint8_t ambientShadowAlpha; + uint8_t spotShadowAlpha; }; }; // namespace uirenderer diff --git a/libs/hwui/NinePatchUtils.h b/libs/hwui/NinePatchUtils.h index db9509fff378..082e95fb1440 100644 --- a/libs/hwui/NinePatchUtils.h +++ b/libs/hwui/NinePatchUtils.h @@ -53,9 +53,8 @@ static inline int NumDistinctRects(const SkCanvas::Lattice& lattice) { return xRects * yRects; } -static inline void SetLatticeFlags(SkCanvas::Lattice* lattice, - SkCanvas::Lattice::RectType* flags, int numFlags, const Res_png_9patch& chunk, - SkColor* colors) { +static inline void SetLatticeFlags(SkCanvas::Lattice* lattice, SkCanvas::Lattice::RectType* flags, + int numFlags, const Res_png_9patch& chunk, SkColor* colors) { lattice->fRectTypes = flags; lattice->fColors = colors; sk_bzero(flags, numFlags * sizeof(SkCanvas::Lattice::RectType)); diff --git a/libs/hwui/OpDumper.cpp b/libs/hwui/OpDumper.cpp deleted file mode 100644 index 5d2ccc77e82f..000000000000 --- a/libs/hwui/OpDumper.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "OpDumper.h" - -#include "ClipArea.h" -#include "RecordedOp.h" - -namespace android { -namespace uirenderer { - -#define STRINGIFY(n) #n, -static const char* sOpNameLut[] = BUILD_FULL_OP_LUT(STRINGIFY); - -void OpDumper::dump(const RecordedOp& op, std::ostream& output, int level) { - for (int i = 0; i < level; i++) { - output << " "; - } - - Rect localBounds(op.unmappedBounds); - op.localMatrix.mapRect(localBounds); - output << sOpNameLut[op.opId] << " " << localBounds; - - if (op.localClip && - (!op.localClip->rect.contains(localBounds) || op.localClip->intersectWithRoot)) { - output << std::fixed << std::setprecision(0) << " clip=" << op.localClip->rect - << " mode=" << (int)op.localClip->mode; - - if (op.localClip->intersectWithRoot) { - output << " iwr"; - } - } -} - -const char* OpDumper::opName(const RecordedOp& op) { - return sOpNameLut[op.opId]; -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp deleted file mode 100644 index 11432d629650..000000000000 --- a/libs/hwui/OpenGLReadback.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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 "OpenGLReadback.h" - -#include "Caches.h" -#include "GlLayer.h" -#include "GlopBuilder.h" -#include "Image.h" -#include "renderstate/RenderState.h" -#include "renderthread/EglManager.h" -#include "utils/GLUtils.h" - -#include <GLES2/gl2.h> -#include <gui/Surface.h> -#include <ui/Fence.h> -#include <ui/GraphicBuffer.h> - -namespace android { -namespace uirenderer { - -CopyResult OpenGLReadback::copySurfaceInto(Surface& surface, const Rect& srcRect, - SkBitmap* bitmap) { - ATRACE_CALL(); - // Setup the source - sp<GraphicBuffer> sourceBuffer; - sp<Fence> sourceFence; - Matrix4 texTransform; - status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence, texTransform.data); - texTransform.invalidateType(); - if (err != NO_ERROR) { - ALOGW("Failed to get last queued buffer, error = %d", err); - return CopyResult::UnknownError; - } - if (!sourceBuffer.get()) { - ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); - return CopyResult::SourceEmpty; - } - if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) { - ALOGW("Surface is protected, unable to copy from it"); - return CopyResult::SourceInvalid; - } - err = sourceFence->wait(500 /* ms */); - if (err != NO_ERROR) { - ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); - return CopyResult::Timeout; - } - - return copyGraphicBufferInto(sourceBuffer.get(), texTransform, srcRect, bitmap); -} - -CopyResult OpenGLReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer, - Matrix4& texTransform, const Rect& srcRect, - SkBitmap* bitmap) { - mRenderThread.eglManager().initialize(); - // TODO: Can't use Image helper since it forces GL_TEXTURE_2D usage via - // GL_OES_EGL_image, which doesn't work since we need samplerExternalOES - // to be able to properly sample from the buffer. - - // Create the EGLImage object that maps the GraphicBuffer - EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - EGLClientBuffer clientBuffer = (EGLClientBuffer)graphicBuffer->getNativeBuffer(); - EGLint attrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; - - EGLImageKHR sourceImage = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, - clientBuffer, attrs); - - if (sourceImage == EGL_NO_IMAGE_KHR) { - ALOGW("eglCreateImageKHR failed (%#x)", eglGetError()); - return CopyResult::UnknownError; - } - - uint32_t width = graphicBuffer->getWidth(); - uint32_t height = graphicBuffer->getHeight(); - CopyResult copyResult = - copyImageInto(sourceImage, texTransform, width, height, srcRect, bitmap); - - // All we're flushing & finishing is the deletion of the texture since - // copyImageInto already did a major flush & finish as an implicit - // part of glReadPixels, so this shouldn't pose any major stalls. - glFinish(); - eglDestroyImageKHR(display, sourceImage); - return copyResult; -} - -CopyResult OpenGLReadback::copyGraphicBufferInto(GraphicBuffer* graphicBuffer, SkBitmap* bitmap) { - Rect srcRect; - Matrix4 transform; - transform.loadScale(1, -1, 1); - transform.translate(0, -1); - return copyGraphicBufferInto(graphicBuffer, transform, srcRect, bitmap); -} - -static float sFlipVInit[16] = { - 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, -}; - -static const Matrix4 sFlipV(sFlipVInit); - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -inline CopyResult copyTextureInto(Caches& caches, RenderState& renderState, Texture& sourceTexture, - const Matrix4& texTransform, const Rect& srcRect, - SkBitmap* bitmap) { - int destWidth = bitmap->width(); - int destHeight = bitmap->height(); - if (destWidth > caches.maxTextureSize || destHeight > caches.maxTextureSize) { - ALOGW("Can't copy surface into bitmap, %dx%d exceeds max texture size %d", destWidth, - destHeight, caches.maxTextureSize); - return CopyResult::DestinationInvalid; - } - - if (bitmap->colorType() == kRGBA_F16_SkColorType && - !caches.extensions().hasRenderableFloatTextures()) { - ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported"); - return CopyResult::DestinationInvalid; - } - - GLuint fbo = renderState.createFramebuffer(); - if (!fbo) { - ALOGW("Could not obtain an FBO"); - return CopyResult::UnknownError; - } - - GLuint texture; - - GLenum format; - GLenum internalFormat; - GLenum type; - - switch (bitmap->colorType()) { - case kAlpha_8_SkColorType: - format = GL_ALPHA; - internalFormat = GL_ALPHA; - type = GL_UNSIGNED_BYTE; - break; - case kRGB_565_SkColorType: - format = GL_RGB; - internalFormat = GL_RGB; - type = GL_UNSIGNED_SHORT_5_6_5; - break; - case kARGB_4444_SkColorType: - format = GL_RGBA; - internalFormat = GL_RGBA; - type = GL_UNSIGNED_SHORT_4_4_4_4; - break; - case kRGBA_F16_SkColorType: - format = GL_RGBA; - internalFormat = GL_RGBA16F; - type = GL_HALF_FLOAT; - break; - case kN32_SkColorType: - default: - format = GL_RGBA; - internalFormat = GL_RGBA; - type = GL_UNSIGNED_BYTE; - break; - } - - renderState.bindFramebuffer(fbo); - - // TODO: Use layerPool or something to get this maybe? But since we - // need explicit format control we can't currently. - - // Setup the rendertarget - glGenTextures(1, &texture); - caches.textureState().activateTexture(0); - caches.textureState().bindTexture(texture); - glPixelStorei(GL_PACK_ALIGNMENT, bitmap->bytesPerPixel()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, destWidth, destHeight, 0, format, type, nullptr); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - - { - bool requiresFilter; - // Draw & readback - renderState.setViewport(destWidth, destHeight); - renderState.scissor().setEnabled(false); - renderState.blend().syncEnabled(); - renderState.stencil().disable(); - - Matrix4 croppedTexTransform(texTransform); - if (!srcRect.isEmpty()) { - // We flipV to convert to 0,0 top-left for the srcRect - // coordinates then flip back to 0,0 bottom-left for - // GLES coordinates. - croppedTexTransform.multiply(sFlipV); - croppedTexTransform.translate(srcRect.left / sourceTexture.width(), - srcRect.top / sourceTexture.height(), 0); - croppedTexTransform.scale(srcRect.getWidth() / sourceTexture.width(), - srcRect.getHeight() / sourceTexture.height(), 1); - croppedTexTransform.multiply(sFlipV); - requiresFilter = srcRect.getWidth() != (float)destWidth || - srcRect.getHeight() != (float)destHeight; - } else { - requiresFilter = sourceTexture.width() != (uint32_t)destWidth || - sourceTexture.height() != (uint32_t)destHeight; - } - Glop glop; - GlopBuilder(renderState, caches, &glop) - .setRoundRectClipState(nullptr) - .setMeshTexturedUnitQuad(nullptr) - .setFillExternalTexture(sourceTexture, croppedTexTransform, requiresFilter) - .setTransform(Matrix4::identity(), TransformFlags::None) - .setModelViewMapUnitToRect(Rect(destWidth, destHeight)) - .build(); - Matrix4 ortho; - ortho.loadOrtho(destWidth, destHeight); - renderState.render(glop, ortho, false); - - // TODO: We should convert to linear space when the target is RGBA16F - glReadPixels(0, 0, bitmap->width(), bitmap->height(), format, type, bitmap->getPixels()); - bitmap->notifyPixelsChanged(); - } - - // Cleanup - caches.textureState().deleteTexture(texture); - renderState.deleteFramebuffer(fbo); - - GL_CHECKPOINT(MODERATE); - - return CopyResult::Success; -} - -CopyResult OpenGLReadbackImpl::copyImageInto(EGLImageKHR eglImage, const Matrix4& imgTransform, - int imgWidth, int imgHeight, const Rect& srcRect, - SkBitmap* bitmap) { - // If this is a 90 or 270 degree rotation we need to swap width/height - // This is a fuzzy way of checking that. - if (imgTransform[Matrix4::kSkewX] >= 0.5f || imgTransform[Matrix4::kSkewX] <= -0.5f) { - std::swap(imgWidth, imgHeight); - } - - Caches& caches = Caches::getInstance(); - GLuint sourceTexId; - // Create a 2D texture to sample from the EGLImage - glGenTextures(1, &sourceTexId); - caches.textureState().bindTexture(GL_TEXTURE_EXTERNAL_OES, sourceTexId); - glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage); - - GLenum status = GL_NO_ERROR; - while ((status = glGetError()) != GL_NO_ERROR) { - ALOGW("glEGLImageTargetTexture2DOES failed (%#x)", status); - return CopyResult::UnknownError; - } - - Texture sourceTexture(caches); - sourceTexture.wrap(sourceTexId, imgWidth, imgHeight, 0, 0 /* total lie */, - GL_TEXTURE_EXTERNAL_OES); - - CopyResult copyResult = copyTextureInto(caches, mRenderThread.renderState(), sourceTexture, - imgTransform, srcRect, bitmap); - sourceTexture.deleteTexture(); - return copyResult; -} - -bool OpenGLReadbackImpl::copyLayerInto(renderthread::RenderThread& renderThread, GlLayer& layer, - SkBitmap* bitmap) { - if (!layer.isRenderable()) { - // layer has never been updated by DeferredLayerUpdater, abort copy - return false; - } - - return CopyResult::Success == copyTextureInto(Caches::getInstance(), renderThread.renderState(), - layer.getTexture(), layer.getTexTransform(), - Rect(), bitmap); -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/OpenGLReadback.h b/libs/hwui/OpenGLReadback.h deleted file mode 100644 index ca40738b4901..000000000000 --- a/libs/hwui/OpenGLReadback.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 "Readback.h" - -#include <EGL/egl.h> -#include <EGL/eglext.h> - -namespace android { -namespace uirenderer { - -class Matrix4; -class GlLayer; - -class OpenGLReadback : public Readback { -public: - virtual CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect, - SkBitmap* bitmap) override; - virtual CopyResult copyGraphicBufferInto(GraphicBuffer* graphicBuffer, - SkBitmap* bitmap) override; - -protected: - explicit OpenGLReadback(renderthread::RenderThread& thread) : Readback(thread) {} - virtual ~OpenGLReadback() {} - - virtual CopyResult copyImageInto(EGLImageKHR eglImage, const Matrix4& imgTransform, - int imgWidth, int imgHeight, const Rect& srcRect, - SkBitmap* bitmap) = 0; - -private: - CopyResult copyGraphicBufferInto(GraphicBuffer* graphicBuffer, Matrix4& texTransform, - const Rect& srcRect, SkBitmap* bitmap); -}; - -class OpenGLReadbackImpl : public OpenGLReadback { -public: - OpenGLReadbackImpl(renderthread::RenderThread& thread) : OpenGLReadback(thread) {} - - /** - * Copies the layer's contents into the provided bitmap. - */ - static bool copyLayerInto(renderthread::RenderThread& renderThread, GlLayer& layer, - SkBitmap* bitmap); - -protected: - virtual CopyResult copyImageInto(EGLImageKHR eglImage, const Matrix4& imgTransform, - int imgWidth, int imgHeight, const Rect& srcRect, - SkBitmap* bitmap) override; -}; - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h index 4ddacc8a922d..f1c38031980e 100644 --- a/libs/hwui/Outline.h +++ b/libs/hwui/Outline.h @@ -42,9 +42,8 @@ public: mBounds.set(left, top, right, bottom); mRadius = radius; - // Reuse memory if previous outline was the same shape (rect or round rect). - if ( mPath.countVerbs() > 10) { + if (mPath.countVerbs() > 10) { mPath.reset(); } else { mPath.rewind(); diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp deleted file mode 100644 index c243dad01f04..000000000000 --- a/libs/hwui/Patch.cpp +++ /dev/null @@ -1,229 +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 "Patch.h" - -#include "Caches.h" -#include "Properties.h" -#include "UvMapper.h" -#include "utils/MathUtils.h" - -#include <utils/Log.h> -#include <algorithm> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Vertices management -/////////////////////////////////////////////////////////////////////////////// - -uint32_t Patch::getSize() const { - return verticesCount * sizeof(TextureVertex); -} - -Patch::Patch(const float bitmapWidth, const float bitmapHeight, float width, float height, - const UvMapper& mapper, const Res_png_9patch* patch) - : mColors(patch->getColors()) { - int8_t emptyQuads = 0; - const int8_t numColors = patch->numColors; - if (uint8_t(numColors) < sizeof(uint32_t) * 4) { - for (int8_t i = 0; i < numColors; i++) { - if (mColors[i] == 0x0) { - emptyQuads++; - } - } - } - - hasEmptyQuads = emptyQuads > 0; - - uint32_t xCount = patch->numXDivs; - uint32_t yCount = patch->numYDivs; - - uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 4; - if (maxVertices == 0) return; - - vertices.reset(new TextureVertex[maxVertices]); - TextureVertex* vertex = vertices.get(); - - const int32_t* xDivs = patch->getXDivs(); - const int32_t* yDivs = patch->getYDivs(); - - const uint32_t xStretchCount = (xCount + 1) >> 1; - const uint32_t yStretchCount = (yCount + 1) >> 1; - - float stretchX = 0.0f; - float stretchY = 0.0f; - - float rescaleX = 1.0f; - float rescaleY = 1.0f; - - if (xStretchCount > 0) { - uint32_t stretchSize = 0; - for (uint32_t i = 1; i < xCount; i += 2) { - stretchSize += xDivs[i] - xDivs[i - 1]; - } - const float xStretchTex = stretchSize; - const float fixed = bitmapWidth - stretchSize; - const float xStretch = std::max(width - fixed, 0.0f); - stretchX = xStretch / xStretchTex; - rescaleX = fixed == 0.0f ? 0.0f : std::min(std::max(width, 0.0f) / fixed, 1.0f); - } - - if (yStretchCount > 0) { - uint32_t stretchSize = 0; - for (uint32_t i = 1; i < yCount; i += 2) { - stretchSize += yDivs[i] - yDivs[i - 1]; - } - const float yStretchTex = stretchSize; - const float fixed = bitmapHeight - stretchSize; - const float yStretch = std::max(height - fixed, 0.0f); - stretchY = yStretch / yStretchTex; - rescaleY = fixed == 0.0f ? 0.0f : std::min(std::max(height, 0.0f) / fixed, 1.0f); - } - - uint32_t quadCount = 0; - - float previousStepY = 0.0f; - - float y1 = 0.0f; - float y2 = 0.0f; - float v1 = 0.0f; - - mUvMapper = mapper; - - for (uint32_t i = 0; i < yCount; i++) { - float stepY = yDivs[i]; - const float segment = stepY - previousStepY; - - if (i & 1) { - y2 = y1 + floorf(segment * stretchY + 0.5f); - } else { - y2 = y1 + segment * rescaleY; - } - - float vOffset = y1 == y2 ? 0.0f : 0.5 - (0.5 * segment / (y2 - y1)); - float v2 = std::max(0.0f, stepY - vOffset) / bitmapHeight; - v1 += vOffset / bitmapHeight; - - if (stepY > 0.0f) { - generateRow(xDivs, xCount, vertex, y1, y2, v1, v2, stretchX, rescaleX, width, - bitmapWidth, quadCount); - } - - y1 = y2; - v1 = stepY / bitmapHeight; - - previousStepY = stepY; - } - - if (previousStepY != bitmapHeight) { - y2 = height; - generateRow(xDivs, xCount, vertex, y1, y2, v1, 1.0f, stretchX, rescaleX, width, bitmapWidth, - quadCount); - } - - if (verticesCount != maxVertices) { - std::unique_ptr<TextureVertex[]> reducedVertices(new TextureVertex[verticesCount]); - memcpy(reducedVertices.get(), vertices.get(), verticesCount * sizeof(TextureVertex)); - vertices = std::move(reducedVertices); - } -} - -void Patch::generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex, float y1, - float y2, float v1, float v2, float stretchX, float rescaleX, float width, - float bitmapWidth, uint32_t& quadCount) { - float previousStepX = 0.0f; - - float x1 = 0.0f; - float x2 = 0.0f; - float u1 = 0.0f; - - // Generate the row quad by quad - for (uint32_t i = 0; i < xCount; i++) { - float stepX = xDivs[i]; - const float segment = stepX - previousStepX; - - if (i & 1) { - x2 = x1 + floorf(segment * stretchX + 0.5f); - } else { - x2 = x1 + segment * rescaleX; - } - - float uOffset = x1 == x2 ? 0.0f : 0.5 - (0.5 * segment / (x2 - x1)); - float u2 = std::max(0.0f, stepX - uOffset) / bitmapWidth; - u1 += uOffset / bitmapWidth; - - if (stepX > 0.0f) { - generateQuad(vertex, x1, y1, x2, y2, u1, v1, u2, v2, quadCount); - } - - x1 = x2; - u1 = stepX / bitmapWidth; - - previousStepX = stepX; - } - - if (previousStepX != bitmapWidth) { - x2 = width; - generateQuad(vertex, x1, y1, x2, y2, u1, v1, 1.0f, v2, quadCount); - } -} - -void Patch::generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, float y2, float u1, - float v1, float u2, float v2, uint32_t& quadCount) { - const uint32_t oldQuadCount = quadCount; - quadCount++; - - 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) { -#if DEBUG_PATCHES_EMPTY_VERTICES - PATCH_LOGD(" quad %d (empty)", oldQuadCount); - PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.8f, %.8f", x1, y1, u1, v1); - PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.8f, %.8f", x2, y2, u2, v2); -#endif - return; - } - - // Record all non empty quads - if (hasEmptyQuads) { - quads.emplace_back(x1, y1, x2, y2); - } - - mUvMapper.map(u1, v1, u2, v2); - - TextureVertex::set(vertex++, x1, y1, u1, v1); - TextureVertex::set(vertex++, x2, y1, u2, v1); - TextureVertex::set(vertex++, x1, y2, u1, v2); - TextureVertex::set(vertex++, x2, y2, u2, v2); - - verticesCount += 4; - indexCount += 6; - -#if DEBUG_PATCHES_VERTICES - PATCH_LOGD(" quad %d", oldQuadCount); - PATCH_LOGD(" left, top = %.2f, %.2f\t\tu1, v1 = %.8f, %.8f", x1, y1, u1, v1); - PATCH_LOGD(" right, bottom = %.2f, %.2f\t\tu2, v2 = %.8f, %.8f", x2, y2, u2, v2); -#endif -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h deleted file mode 100644 index a659ed227bec..000000000000 --- a/libs/hwui/Patch.h +++ /dev/null @@ -1,73 +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_PATCH_H -#define ANDROID_HWUI_PATCH_H - -#include <sys/types.h> - -#include <GLES2/gl2.h> - -#include <androidfw/ResourceTypes.h> - -#include "Rect.h" -#include "UvMapper.h" - -#include <vector> - -namespace android { -namespace uirenderer { - -struct TextureVertex; - -/////////////////////////////////////////////////////////////////////////////// -// 9-patch structures -/////////////////////////////////////////////////////////////////////////////// - -class Patch { -public: - Patch(const float bitmapWidth, const float bitmapHeight, float width, float height, - const UvMapper& mapper, const Res_png_9patch* patch); - - /** - * Returns the size of this patch's mesh in bytes. - */ - uint32_t getSize() const; - - std::unique_ptr<TextureVertex[]> vertices; - uint32_t verticesCount = 0; - uint32_t indexCount = 0; - bool hasEmptyQuads = false; - std::vector<Rect> quads; - - GLintptr positionOffset = 0; - GLintptr textureOffset = 0; - -private: - void generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex, float y1, - float y2, float v1, float v2, float stretchX, float rescaleX, float width, - float bitmapWidth, uint32_t& quadCount); - void generateQuad(TextureVertex*& vertex, float x1, float y1, float x2, float y2, float u1, - float v1, float u2, float v2, uint32_t& quadCount); - - const uint32_t* mColors; - UvMapper mUvMapper; -}; // struct Patch - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_PATCH_H diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp deleted file mode 100644 index 673d73c5475d..000000000000 --- a/libs/hwui/PatchCache.cpp +++ /dev/null @@ -1,264 +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 <utils/JenkinsHash.h> -#include <utils/Log.h> - -#include "Caches.h" -#include "Patch.h" -#include "PatchCache.h" -#include "Properties.h" -#include "renderstate/RenderState.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Constructors/destructor -/////////////////////////////////////////////////////////////////////////////// - -PatchCache::PatchCache(RenderState& renderState) - : mRenderState(renderState) - , mMaxSize(KB(128)) - , mSize(0) - , mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity) - , mMeshBuffer(0) - , mFreeBlocks(nullptr) {} - -PatchCache::~PatchCache() { - clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Caching -/////////////////////////////////////////////////////////////////////////////// - -hash_t PatchCache::PatchDescription::hash() const { - uint32_t hash = JenkinsHashMix(0, android::hash_type(mPatch)); - hash = JenkinsHashMix(hash, mBitmapWidth); - hash = JenkinsHashMix(hash, mBitmapHeight); - hash = JenkinsHashMix(hash, mPixelWidth); - hash = JenkinsHashMix(hash, mPixelHeight); - return JenkinsHashWhiten(hash); -} - -int PatchCache::PatchDescription::compare(const PatchCache::PatchDescription& lhs, - const PatchCache::PatchDescription& rhs) { - return memcmp(&lhs, &rhs, sizeof(PatchDescription)); -} - -void PatchCache::clear() { - clearCache(); - - if (mMeshBuffer) { - mRenderState.meshState().deleteMeshBuffer(mMeshBuffer); - mMeshBuffer = 0; - mSize = 0; - } -} - -void PatchCache::clearCache() { - LruCache<PatchDescription, Patch*>::Iterator i(mCache); - while (i.next()) { - delete i.value(); - } - mCache.clear(); - - BufferBlock* block = mFreeBlocks; - while (block) { - BufferBlock* next = block->next; - delete block; - block = next; - } - mFreeBlocks = nullptr; -} - -void PatchCache::remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch) { - LruCache<PatchDescription, Patch*>::Iterator i(mCache); - while (i.next()) { - const PatchDescription& key = i.key(); - if (key.getPatch() == patch) { - patchesToRemove.push(patch_pair_t(&key, i.value())); - } - } -} - -void PatchCache::removeDeferred(Res_png_9patch* patch) { - Mutex::Autolock _l(mLock); - - // Assert that patch is not already garbage - size_t count = mGarbage.size(); - for (size_t i = 0; i < count; i++) { - if (patch == mGarbage[i]) { - patch = nullptr; - break; - } - } - LOG_ALWAYS_FATAL_IF(patch == nullptr); - - mGarbage.push(patch); -} - -void PatchCache::clearGarbage() { - Vector<patch_pair_t> patchesToRemove; - - { // scope for the mutex - Mutex::Autolock _l(mLock); - size_t count = mGarbage.size(); - for (size_t i = 0; i < count; i++) { - Res_png_9patch* patch = mGarbage[i]; - remove(patchesToRemove, patch); - // A Res_png_9patch is actually an array of byte that's larger - // than sizeof(Res_png_9patch). It must be freed as an array. - delete[](int8_t*) patch; - } - mGarbage.clear(); - } - - // TODO: We could sort patchesToRemove by offset to merge - // adjacent free blocks - for (size_t i = 0; i < patchesToRemove.size(); i++) { - const patch_pair_t& pair = patchesToRemove[i]; - - // Release the patch and mark the space in the free list - Patch* patch = pair.getSecond(); - BufferBlock* block = new BufferBlock(patch->positionOffset, patch->getSize()); - block->next = mFreeBlocks; - mFreeBlocks = block; - - mSize -= patch->getSize(); - - mCache.remove(*pair.getFirst()); - delete patch; - } - -#if DEBUG_PATCHES - if (patchesToRemove.size() > 0) { - dumpFreeBlocks("Removed garbage"); - } -#endif -} - -void PatchCache::createVertexBuffer() { - mRenderState.meshState().genOrUpdateMeshBuffer(&mMeshBuffer, mMaxSize, nullptr, - GL_DYNAMIC_DRAW); - mSize = 0; - mFreeBlocks = new BufferBlock(0, mMaxSize); -} - -/** - * Sets the mesh's offsets and copies its associated vertices into - * the mesh buffer (VBO). - */ -void PatchCache::setupMesh(Patch* newMesh) { - // This call ensures the VBO exists and that it is bound - if (!mMeshBuffer) { - createVertexBuffer(); - } - - // If we're running out of space, let's clear the entire cache - uint32_t size = newMesh->getSize(); - if (mSize + size > mMaxSize) { - clearCache(); - createVertexBuffer(); - } - - // Find a block where we can fit the mesh - BufferBlock* previous = nullptr; - BufferBlock* block = mFreeBlocks; - while (block) { - // The mesh fits - if (block->size >= size) { - break; - } - previous = block; - block = block->next; - } - - // We have enough space left in the buffer, but it's - // too fragmented, let's clear the cache - if (!block) { - clearCache(); - createVertexBuffer(); - previous = nullptr; - block = mFreeBlocks; - } - - // Copy the 9patch mesh in the VBO - newMesh->positionOffset = (GLintptr)(block->offset); - newMesh->textureOffset = newMesh->positionOffset + kMeshTextureOffset; - - mRenderState.meshState().updateMeshBufferSubData(mMeshBuffer, newMesh->positionOffset, size, - newMesh->vertices.get()); - - // Remove the block since we've used it entirely - if (block->size == size) { - if (previous) { - previous->next = block->next; - } else { - mFreeBlocks = block->next; - } - delete block; - } else { - // Resize the block now that it's occupied - block->offset += size; - block->size -= size; - } - - mSize += size; -} - -static const UvMapper sIdentity; - -const Patch* PatchCache::get(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, - const Res_png_9patch* patch) { - const PatchDescription description(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, patch); - const Patch* mesh = mCache.get(description); - - if (!mesh) { - Patch* newMesh = - new Patch(bitmapWidth, bitmapHeight, pixelWidth, pixelHeight, sIdentity, patch); - - if (newMesh->vertices) { - setupMesh(newMesh); - } - -#if DEBUG_PATCHES - dumpFreeBlocks("Adding patch"); -#endif - - mCache.put(description, newMesh); - return newMesh; - } - - return mesh; -} - -#if DEBUG_PATCHES -void PatchCache::dumpFreeBlocks(const char* prefix) { - String8 dump; - BufferBlock* block = mFreeBlocks; - while (block) { - dump.appendFormat("->(%d, %d)", block->positionOffset, block->size); - block = block->next; - } - ALOGD("%s: Free blocks%s", prefix, dump.string()); -} -#endif - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h deleted file mode 100644 index 273c3f56f2cd..000000000000 --- a/libs/hwui/PatchCache.h +++ /dev/null @@ -1,171 +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. - */ - -#pragma once - -#include <GLES2/gl2.h> - -#include <utils/LruCache.h> - -#include <androidfw/ResourceTypes.h> - -#include "Debug.h" -#include "utils/Pair.h" - -namespace android { -namespace uirenderer { - -class Patch; - -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -// Debug -#if DEBUG_PATCHES -#define PATCH_LOGD(...) ALOGD(__VA_ARGS__) -#else -#define PATCH_LOGD(...) -#endif - -/////////////////////////////////////////////////////////////////////////////// -// Cache -/////////////////////////////////////////////////////////////////////////////// - -class Caches; -class RenderState; - -class PatchCache { -public: - explicit PatchCache(RenderState& renderState); - ~PatchCache(); - - const Patch* get(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch); - void clear(); - - uint32_t getSize() const { return mSize; } - - uint32_t getMaxSize() const { return mMaxSize; } - - GLuint getMeshBuffer() const { return mMeshBuffer; } - - /** - * Removes the entries associated with the specified 9-patch. This is meant - * to be called from threads that are not the EGL context thread (GC thread - * on the VM side for instance.) - */ - void removeDeferred(Res_png_9patch* patch); - - /** - * Process deferred removals. - */ - void clearGarbage(); - -private: - struct PatchDescription { - PatchDescription() - : mPatch(nullptr) - , mBitmapWidth(0) - , mBitmapHeight(0) - , mPixelWidth(0) - , mPixelHeight(0) {} - - PatchDescription(const uint32_t bitmapWidth, const uint32_t bitmapHeight, - const float pixelWidth, const float pixelHeight, - const Res_png_9patch* patch) - : mPatch(patch) - , mBitmapWidth(bitmapWidth) - , mBitmapHeight(bitmapHeight) - , mPixelWidth(pixelWidth) - , mPixelHeight(pixelHeight) {} - - hash_t hash() const; - - const Res_png_9patch* getPatch() const { return mPatch; } - - static int compare(const PatchDescription& lhs, const PatchDescription& rhs); - - bool operator==(const PatchDescription& other) const { return compare(*this, other) == 0; } - - bool operator!=(const PatchDescription& other) const { return compare(*this, other) != 0; } - - friend inline int strictly_order_type(const PatchDescription& lhs, - const PatchDescription& rhs) { - return PatchDescription::compare(lhs, rhs) < 0; - } - - friend inline int compare_type(const PatchDescription& lhs, const PatchDescription& rhs) { - return PatchDescription::compare(lhs, rhs); - } - - friend inline hash_t hash_type(const PatchDescription& entry) { return entry.hash(); } - - private: - const Res_png_9patch* mPatch; - uint32_t mBitmapWidth; - uint32_t mBitmapHeight; - float mPixelWidth; - float mPixelHeight; - - }; // struct PatchDescription - - /** - * A buffer block represents an empty range in the mesh buffer - * that can be used to store vertices. - * - * The patch cache maintains a linked-list of buffer blocks - * to track available regions of memory in the VBO. - */ - struct BufferBlock { - BufferBlock(uint32_t offset, uint32_t size) : offset(offset), size(size), next(nullptr) {} - - uint32_t offset; - uint32_t size; - - BufferBlock* next; - }; // struct BufferBlock - - typedef Pair<const PatchDescription*, Patch*> patch_pair_t; - - void clearCache(); - void createVertexBuffer(); - - void setupMesh(Patch* newMesh); - - void remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch); - -#if DEBUG_PATCHES - void dumpFreeBlocks(const char* prefix); -#endif - - RenderState& mRenderState; - const uint32_t mMaxSize; - uint32_t mSize; - - LruCache<PatchDescription, Patch*> mCache; - - GLuint mMeshBuffer; - // First available free block inside the mesh buffer - BufferBlock* mFreeBlocks; - - // Garbage tracking, required to handle GC events on the VM side - Vector<Res_png_9patch*> mGarbage; - mutable Mutex mLock; -}; // class PatchCache - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp deleted file mode 100644 index e67c5bddd354..000000000000 --- a/libs/hwui/PathCache.cpp +++ /dev/null @@ -1,559 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <SkBitmap.h> -#include <SkCanvas.h> -#include <SkColor.h> -#include <SkColorFilter.h> -#include <SkMaskFilter.h> -#include <SkPaint.h> -#include <SkPath.h> -#include <SkPathEffect.h> -#include <SkRect.h> - -#include <utils/JenkinsHash.h> -#include <utils/Trace.h> - -#include "Caches.h" -#include "PathCache.h" - -#include "thread/Signal.h" -#include "thread/TaskProcessor.h" - -#include <cutils/properties.h> - -namespace android { -namespace uirenderer { - -static constexpr size_t PATH_CACHE_COUNT_LIMIT = 256; - -template <class T> -static bool compareWidthHeight(const T& lhs, const T& rhs) { - return (lhs.mWidth == rhs.mWidth) && (lhs.mHeight == rhs.mHeight); -} - -static bool compareRoundRects(const PathDescription::Shape::RoundRect& lhs, - const PathDescription::Shape::RoundRect& rhs) { - return compareWidthHeight(lhs, rhs) && lhs.mRx == rhs.mRx && lhs.mRy == rhs.mRy; -} - -static bool compareArcs(const PathDescription::Shape::Arc& lhs, - const PathDescription::Shape::Arc& rhs) { - return compareWidthHeight(lhs, rhs) && lhs.mStartAngle == rhs.mStartAngle && - lhs.mSweepAngle == rhs.mSweepAngle && lhs.mUseCenter == rhs.mUseCenter; -} - -/////////////////////////////////////////////////////////////////////////////// -// Cache entries -/////////////////////////////////////////////////////////////////////////////// - -PathDescription::PathDescription() - : type(ShapeType::None) - , join(SkPaint::kDefault_Join) - , cap(SkPaint::kDefault_Cap) - , style(SkPaint::kFill_Style) - , miter(4.0f) - , strokeWidth(1.0f) - , pathEffect(nullptr) { - // Shape bits should be set to zeroes, because they are used for hash calculation. - memset(&shape, 0, sizeof(Shape)); -} - -PathDescription::PathDescription(ShapeType type, const SkPaint* paint) - : type(type) - , join(paint->getStrokeJoin()) - , cap(paint->getStrokeCap()) - , style(paint->getStyle()) - , miter(paint->getStrokeMiter()) - , strokeWidth(paint->getStrokeWidth()) - , pathEffect(paint->getPathEffect()) { - // Shape bits should be set to zeroes, because they are used for hash calculation. - memset(&shape, 0, sizeof(Shape)); -} - -hash_t PathDescription::hash() const { - uint32_t hash = JenkinsHashMix(0, static_cast<int>(type)); - hash = JenkinsHashMix(hash, join); - hash = JenkinsHashMix(hash, cap); - hash = JenkinsHashMix(hash, style); - hash = JenkinsHashMix(hash, android::hash_type(miter)); - hash = JenkinsHashMix(hash, android::hash_type(strokeWidth)); - hash = JenkinsHashMix(hash, android::hash_type(pathEffect)); - hash = JenkinsHashMixBytes(hash, (uint8_t*)&shape, sizeof(Shape)); - return JenkinsHashWhiten(hash); -} - -bool PathDescription::operator==(const PathDescription& rhs) const { - if (type != rhs.type) return false; - if (join != rhs.join) return false; - if (cap != rhs.cap) return false; - if (style != rhs.style) return false; - if (miter != rhs.miter) return false; - if (strokeWidth != rhs.strokeWidth) return false; - if (pathEffect != rhs.pathEffect) return false; - switch (type) { - case ShapeType::None: - return 0; - case ShapeType::Rect: - return compareWidthHeight(shape.rect, rhs.shape.rect); - case ShapeType::RoundRect: - return compareRoundRects(shape.roundRect, rhs.shape.roundRect); - case ShapeType::Circle: - return shape.circle.mRadius == rhs.shape.circle.mRadius; - case ShapeType::Oval: - return compareWidthHeight(shape.oval, rhs.shape.oval); - case ShapeType::Arc: - return compareArcs(shape.arc, rhs.shape.arc); - case ShapeType::Path: - return shape.path.mGenerationID == rhs.shape.path.mGenerationID; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Utilities -/////////////////////////////////////////////////////////////////////////////// - -static void computePathBounds(const SkPath* path, const SkPaint* paint, PathTexture* texture, - uint32_t& width, uint32_t& height) { - const SkRect& bounds = path->getBounds(); - const float pathWidth = std::max(bounds.width(), 1.0f); - const float pathHeight = std::max(bounds.height(), 1.0f); - - texture->left = floorf(bounds.fLeft); - texture->top = floorf(bounds.fTop); - - texture->offset = (int)floorf(std::max(paint->getStrokeWidth(), 1.0f) * 1.5f + 0.5f); - - width = uint32_t(pathWidth + texture->offset * 2.0 + 0.5); - height = uint32_t(pathHeight + texture->offset * 2.0 + 0.5); -} - -static void initPaint(SkPaint& paint) { - // Make sure the paint is opaque, color, alpha, filter, etc. - // will be applied later when compositing the alpha8 texture - paint.setColor(SK_ColorBLACK); - paint.setAlpha(255); - paint.setColorFilter(nullptr); - paint.setMaskFilter(nullptr); - paint.setShader(nullptr); - paint.setBlendMode(SkBlendMode::kSrc); -} - -static sk_sp<Bitmap> drawPath(const SkPath* path, const SkPaint* paint, PathTexture* texture, - uint32_t maxTextureSize) { - uint32_t width, height; - computePathBounds(path, paint, texture, width, height); - if (width > maxTextureSize || height > maxTextureSize) { - ALOGW("Shape too large to be rendered into a texture (%dx%d, max=%dx%d)", width, height, - maxTextureSize, maxTextureSize); - return nullptr; - } - - sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(width, height)); - SkPaint pathPaint(*paint); - initPaint(pathPaint); - - SkBitmap skBitmap; - bitmap->getSkBitmap(&skBitmap); - skBitmap.eraseColor(0); - SkCanvas canvas(skBitmap); - canvas.translate(-texture->left + texture->offset, -texture->top + texture->offset); - canvas.drawPath(*path, pathPaint); - return bitmap; -} - -/////////////////////////////////////////////////////////////////////////////// -// Cache constructor/destructor -/////////////////////////////////////////////////////////////////////////////// - -PathCache::PathCache() - : mCache(LruCache<PathDescription, PathTexture*>::kUnlimitedCapacity) - , mSize(0) - , mMaxSize(DeviceInfo::multiplyByResolution(4)) { - mCache.setOnEntryRemovedListener(this); - mMaxTextureSize = DeviceInfo::get()->maxTextureSize(); - mDebugEnabled = Properties::debugLevel & kDebugCaches; -} - -PathCache::~PathCache() { - mCache.clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Size management -/////////////////////////////////////////////////////////////////////////////// - -uint32_t PathCache::getSize() { - return mSize; -} - -uint32_t PathCache::getMaxSize() { - return mMaxSize; -} - -/////////////////////////////////////////////////////////////////////////////// -// Callbacks -/////////////////////////////////////////////////////////////////////////////// - -void PathCache::operator()(PathDescription& entry, PathTexture*& texture) { - removeTexture(texture); -} - -/////////////////////////////////////////////////////////////////////////////// -// Caching -/////////////////////////////////////////////////////////////////////////////// - -void PathCache::removeTexture(PathTexture* texture) { - if (texture) { - 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 - const sp<PathTask>& task = texture->task(); - if (task != nullptr) { - task->getResult(); - texture->clearTask(); - } else { - // If there is a pending task, the path was not added - // to the cache and the size wasn't increased - if (size > mSize) { - ALOGE("Removing path texture of size %d will leave " - "the cache in an inconsistent state", - size); - } - mSize -= size; - } - - PATH_LOGD("PathCache::delete name, size, mSize = %d, %d, %d", texture->id, size, mSize); - if (mDebugEnabled) { - ALOGD("Shape deleted, size = %d", size); - } - - texture->deleteTexture(); - delete texture; - } -} - -void PathCache::purgeCache(uint32_t width, uint32_t height) { - const uint32_t size = width * height; - // Don't even try to cache a bitmap that's bigger than the cache - if (size < mMaxSize) { - while (mSize + size > mMaxSize) { - mCache.removeOldest(); - } - } -} - -void PathCache::trim() { - while (mSize > mMaxSize || mCache.size() > PATH_CACHE_COUNT_LIMIT) { - LOG_ALWAYS_FATAL_IF(!mCache.size(), - "Inconsistent mSize! Ran out of items to remove!" - " mSize = %u, mMaxSize = %u", - mSize, mMaxSize); - mCache.removeOldest(); - } -} - -PathTexture* PathCache::addTexture(const PathDescription& entry, const SkPath* path, - const SkPaint* paint) { - ATRACE_NAME("Generate Path Texture"); - - PathTexture* texture = new PathTexture(Caches::getInstance(), path->getGenerationID()); - sk_sp<Bitmap> bitmap(drawPath(path, paint, texture, mMaxTextureSize)); - if (!bitmap) { - delete texture; - return nullptr; - } - - purgeCache(bitmap->width(), bitmap->height()); - generateTexture(entry, *bitmap, texture); - return texture; -} - -void PathCache::generateTexture(const PathDescription& entry, Bitmap& bitmap, PathTexture* texture, - bool addToCache) { - generateTexture(bitmap, texture); - - // 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(); - mSize += size; - PATH_LOGD("PathCache::get/create: name, size, mSize = %d, %d, %d", texture->id, size, mSize); - if (mDebugEnabled) { - ALOGD("Shape created, size = %d", size); - } - if (addToCache) { - mCache.put(entry, texture); - } -} - -void PathCache::clear() { - mCache.clear(); -} - -void PathCache::generateTexture(Bitmap& bitmap, Texture* texture) { - ATRACE_NAME("Upload Path Texture"); - texture->upload(bitmap); - texture->setFilter(GL_LINEAR); -} - -/////////////////////////////////////////////////////////////////////////////// -// Path precaching -/////////////////////////////////////////////////////////////////////////////// - -PathCache::PathProcessor::PathProcessor(Caches& caches) - : TaskProcessor<sk_sp<Bitmap> >(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) {} - -void PathCache::PathProcessor::onProcess(const sp<Task<sk_sp<Bitmap> > >& task) { - PathTask* t = static_cast<PathTask*>(task.get()); - ATRACE_NAME("pathPrecache"); - - t->setResult(drawPath(&t->path, &t->paint, t->texture, mMaxTextureSize)); -} - -/////////////////////////////////////////////////////////////////////////////// -// Paths -/////////////////////////////////////////////////////////////////////////////// - -void PathCache::removeDeferred(const SkPath* path) { - Mutex::Autolock l(mLock); - mGarbage.push_back(path->getGenerationID()); -} - -void PathCache::clearGarbage() { - Vector<PathDescription> pathsToRemove; - - { // scope for the mutex - Mutex::Autolock l(mLock); - for (const uint32_t generationID : mGarbage) { - LruCache<PathDescription, PathTexture*>::Iterator iter(mCache); - while (iter.next()) { - const PathDescription& key = iter.key(); - if (key.type == ShapeType::Path && key.shape.path.mGenerationID == generationID) { - pathsToRemove.push(key); - } - } - } - mGarbage.clear(); - } - - for (size_t i = 0; i < pathsToRemove.size(); i++) { - mCache.remove(pathsToRemove.itemAt(i)); - } -} - -PathTexture* PathCache::get(const SkPath* path, const SkPaint* paint) { - PathDescription entry(ShapeType::Path, paint); - entry.shape.path.mGenerationID = path->getGenerationID(); - - PathTexture* texture = mCache.get(entry); - - if (!texture) { - texture = addTexture(entry, path, paint); - } else { - // A bitmap is attached to the texture, this means we need to - // upload it as a GL texture - const sp<PathTask>& task = texture->task(); - if (task != nullptr) { - // But we must first wait for the worker thread to be done - // producing the bitmap, so let's wait - sk_sp<Bitmap> bitmap = task->getResult(); - if (bitmap) { - generateTexture(entry, *bitmap, texture, false); - texture->clearTask(); - } else { - texture->clearTask(); - texture = nullptr; - mCache.remove(entry); - } - } - } - - return texture; -} - -void PathCache::remove(const SkPath* path, const SkPaint* paint) { - PathDescription entry(ShapeType::Path, paint); - entry.shape.path.mGenerationID = path->getGenerationID(); - mCache.remove(entry); -} - -void PathCache::precache(const SkPath* path, const SkPaint* paint) { - if (!Caches::getInstance().tasks.canRunTasks()) { - return; - } - - PathDescription entry(ShapeType::Path, paint); - entry.shape.path.mGenerationID = path->getGenerationID(); - - PathTexture* texture = mCache.get(entry); - - bool generate = false; - if (!texture) { - generate = true; - } - - if (generate) { - // It is important to specify the generation ID so we do not - // attempt to precache the same path several times - texture = new PathTexture(Caches::getInstance(), path->getGenerationID()); - sp<PathTask> task = new PathTask(path, paint, texture); - texture->setTask(task); - - // During the precaching phase we insert path texture objects into - // the cache that do not point to any GL texture. They are instead - // treated as a task for the precaching worker thread. This is why - // we do not check the cache limit when inserting these objects. - // The conversion into GL texture will happen in get(), when a client - // asks for a path texture. This is also when the cache limit will - // be enforced. - mCache.put(entry, texture); - - if (mProcessor == nullptr) { - mProcessor = new PathProcessor(Caches::getInstance()); - } - mProcessor->add(task); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Rounded rects -/////////////////////////////////////////////////////////////////////////////// - -PathTexture* PathCache::getRoundRect(float width, float height, float rx, float ry, - const SkPaint* paint) { - PathDescription entry(ShapeType::RoundRect, paint); - entry.shape.roundRect.mWidth = width; - entry.shape.roundRect.mHeight = height; - entry.shape.roundRect.mRx = rx; - entry.shape.roundRect.mRy = ry; - - PathTexture* texture = get(entry); - - if (!texture) { - SkPath path; - SkRect r; - r.set(0.0f, 0.0f, width, height); - path.addRoundRect(r, rx, ry, SkPath::kCW_Direction); - - texture = addTexture(entry, &path, paint); - } - - return texture; -} - -/////////////////////////////////////////////////////////////////////////////// -// Circles -/////////////////////////////////////////////////////////////////////////////// - -PathTexture* PathCache::getCircle(float radius, const SkPaint* paint) { - PathDescription entry(ShapeType::Circle, paint); - entry.shape.circle.mRadius = radius; - - PathTexture* texture = get(entry); - - if (!texture) { - SkPath path; - path.addCircle(radius, radius, radius, SkPath::kCW_Direction); - - texture = addTexture(entry, &path, paint); - } - - return texture; -} - -/////////////////////////////////////////////////////////////////////////////// -// Ovals -/////////////////////////////////////////////////////////////////////////////// - -PathTexture* PathCache::getOval(float width, float height, const SkPaint* paint) { - PathDescription entry(ShapeType::Oval, paint); - entry.shape.oval.mWidth = width; - entry.shape.oval.mHeight = height; - - PathTexture* texture = get(entry); - - if (!texture) { - SkPath path; - SkRect r; - r.set(0.0f, 0.0f, width, height); - path.addOval(r, SkPath::kCW_Direction); - - texture = addTexture(entry, &path, paint); - } - - return texture; -} - -/////////////////////////////////////////////////////////////////////////////// -// Rects -/////////////////////////////////////////////////////////////////////////////// - -PathTexture* PathCache::getRect(float width, float height, const SkPaint* paint) { - PathDescription entry(ShapeType::Rect, paint); - entry.shape.rect.mWidth = width; - entry.shape.rect.mHeight = height; - - PathTexture* texture = get(entry); - - if (!texture) { - SkPath path; - SkRect r; - r.set(0.0f, 0.0f, width, height); - path.addRect(r, SkPath::kCW_Direction); - - texture = addTexture(entry, &path, paint); - } - - return texture; -} - -/////////////////////////////////////////////////////////////////////////////// -// Arcs -/////////////////////////////////////////////////////////////////////////////// - -PathTexture* PathCache::getArc(float width, float height, float startAngle, float sweepAngle, - bool useCenter, const SkPaint* paint) { - PathDescription entry(ShapeType::Arc, paint); - entry.shape.arc.mWidth = width; - entry.shape.arc.mHeight = height; - entry.shape.arc.mStartAngle = startAngle; - entry.shape.arc.mSweepAngle = sweepAngle; - entry.shape.arc.mUseCenter = useCenter; - - PathTexture* texture = get(entry); - - if (!texture) { - SkPath path; - SkRect r; - r.set(0.0f, 0.0f, width, height); - if (useCenter) { - path.moveTo(r.centerX(), r.centerY()); - } - path.arcTo(r, startAngle, sweepAngle, !useCenter); - if (useCenter) { - path.close(); - } - - texture = addTexture(entry, &path, paint); - } - - return texture; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h deleted file mode 100644 index 28c8bbb8ca6e..000000000000 --- a/libs/hwui/PathCache.h +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_PATH_CACHE_H -#define ANDROID_HWUI_PATH_CACHE_H - -#include "Debug.h" -#include "Texture.h" -#include "hwui/Bitmap.h" -#include "thread/Task.h" -#include "thread/TaskProcessor.h" -#include "utils/Macros.h" -#include "utils/Pair.h" - -#include <GLES2/gl2.h> -#include <SkPaint.h> -#include <SkPath.h> -#include <utils/LruCache.h> -#include <utils/Mutex.h> - -#include <vector> - -class SkCanvas; -class SkPaint; -struct SkRect; - -namespace android { -namespace uirenderer { - -class Caches; -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -// Debug -#if DEBUG_PATHS -#define PATH_LOGD(...) ALOGD(__VA_ARGS__) -#else -#define PATH_LOGD(...) -#endif - -/////////////////////////////////////////////////////////////////////////////// -// Classes -/////////////////////////////////////////////////////////////////////////////// - -struct PathTexture; -class PathTask : public Task<sk_sp<Bitmap>> { -public: - PathTask(const SkPath* path, const SkPaint* paint, PathTexture* texture) - : path(*path), paint(*paint), texture(texture) {} - - // copied, since input path not guaranteed to survive for duration of task - const SkPath path; - - // copied, since input paint may not be immutable - const SkPaint paint; - PathTexture* texture; -}; - -/** - * Alpha texture used to represent a path. - */ -struct PathTexture : public Texture { - PathTexture(Caches& caches, int generation) : Texture(caches) { this->generation = generation; } - - ~PathTexture() { clearTask(); } - - /** - * Left coordinate of the path bounds. - */ - float left = 0; - /** - * Top coordinate of the path bounds. - */ - float top = 0; - /** - * Offset to draw the path at the correct origin. - */ - float offset = 0; - - sp<PathTask> task() const { return mTask; } - - void setTask(const sp<PathTask>& task) { mTask = task; } - - void clearTask() { - if (mTask != nullptr) { - mTask.clear(); - } - } - -private: - sp<PathTask> mTask; -}; // struct PathTexture - -enum class ShapeType { None, Rect, RoundRect, Circle, Oval, Arc, Path }; - -struct PathDescription { - HASHABLE_TYPE(PathDescription); - ShapeType type; - SkPaint::Join join; - SkPaint::Cap cap; - SkPaint::Style style; - float miter; - float strokeWidth; - SkPathEffect* pathEffect; - union Shape { - struct Path { - uint32_t mGenerationID; - } path; - struct RoundRect { - float mWidth; - float mHeight; - float mRx; - float mRy; - } roundRect; - struct Circle { - float mRadius; - } circle; - struct Oval { - float mWidth; - float mHeight; - } oval; - struct Rect { - float mWidth; - float mHeight; - } rect; - struct Arc { - float mWidth; - float mHeight; - float mStartAngle; - float mSweepAngle; - bool mUseCenter; - } arc; - } shape; - - PathDescription(); - PathDescription(ShapeType shapeType, const SkPaint* paint); -}; - -/** - * A simple LRU shape cache. The cache has a maximum size expressed in bytes. - * Any texture added to the cache causing the cache to grow beyond the maximum - * allowed size will also cause the oldest texture to be kicked out. - */ -class PathCache : public OnEntryRemoved<PathDescription, PathTexture*> { -public: - PathCache(); - ~PathCache(); - - /** - * Used as a callback when an entry is removed from the cache. - * Do not invoke directly. - */ - void operator()(PathDescription& path, PathTexture*& texture) override; - - /** - * Clears the cache. This causes all textures to be deleted. - */ - void clear(); - - /** - * Returns the maximum size of the cache in bytes. - */ - uint32_t getMaxSize(); - /** - * Returns the current size of the cache in bytes. - */ - uint32_t getSize(); - - PathTexture* getRoundRect(float width, float height, float rx, float ry, const SkPaint* paint); - PathTexture* getCircle(float radius, const SkPaint* paint); - PathTexture* getOval(float width, float height, const SkPaint* paint); - PathTexture* getRect(float width, float height, const SkPaint* paint); - PathTexture* getArc(float width, float height, float startAngle, float sweepAngle, - bool useCenter, const SkPaint* paint); - PathTexture* get(const SkPath* path, const SkPaint* paint); - void remove(const SkPath* path, const SkPaint* paint); - - /** - * Removes the specified path. This is meant to be called from threads - * that are not the EGL context thread. - */ - ANDROID_API void removeDeferred(const SkPath* path); - /** - * Process deferred removals. - */ - void clearGarbage(); - /** - * Trims the contents of the cache, removing items until it's under its - * specified limit. - * - * Trimming is used for caches that support pre-caching from a worker - * thread. During pre-caching the maximum limit of the cache can be - * exceeded for the duration of the frame. It is therefore required to - * trim the cache at the end of the frame to keep the total amount of - * memory used under control. - */ - void trim(); - - /** - * Precaches the specified path using background threads. - */ - void precache(const SkPath* path, const SkPaint* paint); - -private: - PathTexture* addTexture(const PathDescription& entry, const SkPath* path, const SkPaint* paint); - - /** - * Generates the texture from a bitmap into the specified texture structure. - */ - void generateTexture(Bitmap& bitmap, Texture* texture); - void generateTexture(const PathDescription& entry, Bitmap& bitmap, PathTexture* texture, - bool addToCache = true); - - PathTexture* get(const PathDescription& entry) { return mCache.get(entry); } - - /** - * Ensures there is enough space in the cache for a texture of the specified - * dimensions. - */ - void purgeCache(uint32_t width, uint32_t height); - - void removeTexture(PathTexture* texture); - - void init(); - - class PathProcessor : public TaskProcessor<sk_sp<Bitmap>> { - public: - explicit PathProcessor(Caches& caches); - ~PathProcessor() {} - - virtual void onProcess(const sp<Task<sk_sp<Bitmap>>>& task) override; - - private: - uint32_t mMaxTextureSize; - }; - - LruCache<PathDescription, PathTexture*> mCache; - uint32_t mSize; - const uint32_t mMaxSize; - GLuint mMaxTextureSize; - - bool mDebugEnabled; - - sp<PathProcessor> mProcessor; - - std::vector<uint32_t> mGarbage; - mutable Mutex mLock; -}; // class PathCache - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_PATH_CACHE_H diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp index 47fcca92bcca..ad599e9ec316 100644 --- a/libs/hwui/PathParser.cpp +++ b/libs/hwui/PathParser.cpp @@ -210,9 +210,8 @@ void PathParser::validateVerbAndPoints(char verb, size_t points, PathParser::Par if (numberOfPointsExpected > 0) { result->failureMessage += "a multiple of "; } - result->failureMessage += std::to_string(numberOfPointsExpected) - + " floats. However, " + std::to_string(points) - + " float(s) are found. "; + result->failureMessage += std::to_string(numberOfPointsExpected) + " floats. However, " + + std::to_string(points) + " float(s) are found. "; } void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result, @@ -242,8 +241,8 @@ void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result, validateVerbAndPoints(pathStr[start], points.size(), result); if (result->failureOccurred) { // If either verb or points is not valid, return immediately. - result->failureMessage += "Failure occurred at position " + - std::to_string(start) + " of path: " + pathStr; + result->failureMessage += "Failure occurred at position " + std::to_string(start) + + " of path: " + pathStr; return; } data->verbs.push_back(pathStr[start]); @@ -257,8 +256,8 @@ void PathParser::getPathDataFromAsciiString(PathData* data, ParseResult* result, validateVerbAndPoints(pathStr[start], 0, result); if (result->failureOccurred) { // If either verb or points is not valid, return immediately. - result->failureMessage += "Failure occurred at position " + - std::to_string(start) + " of path: " + pathStr; + result->failureMessage += "Failure occurred at position " + std::to_string(start) + + " of path: " + pathStr; return; } data->verbs.push_back(pathStr[start]); diff --git a/libs/hwui/PathTessellator.cpp b/libs/hwui/PathTessellator.cpp deleted file mode 100644 index 973b1d14b77e..000000000000 --- a/libs/hwui/PathTessellator.cpp +++ /dev/null @@ -1,1069 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#define LOG_NDEBUG 1 - -#define VERTEX_DEBUG 0 - -#if VERTEX_DEBUG -#define DEBUG_DUMP_ALPHA_BUFFER() \ - for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \ - ALOGD("point %d at %f %f, alpha %f", i, buffer[i].x, buffer[i].y, buffer[i].alpha); \ - } -#define DEBUG_DUMP_BUFFER() \ - for (unsigned int i = 0; i < vertexBuffer.getSize(); i++) { \ - ALOGD("point %d at %f %f", i, buffer[i].x, buffer[i].y); \ - } -#else -#define DEBUG_DUMP_ALPHA_BUFFER() -#define DEBUG_DUMP_BUFFER() -#endif - -#include "PathTessellator.h" - -#include "Matrix.h" -#include "Vector.h" -#include "Vertex.h" -#include "utils/MathUtils.h" - -#include <algorithm> - -#include <SkGeometry.h> // WARNING: Internal Skia Header -#include <SkPaint.h> -#include <SkPath.h> -#include <SkPoint.h> - -#include <stdint.h> -#include <stdlib.h> -#include <sys/types.h> - -#include <utils/Log.h> -#include <utils/Trace.h> - -namespace android { -namespace uirenderer { - -#define OUTLINE_REFINE_THRESHOLD 0.5f -#define ROUND_CAP_THRESH 0.25f -#define PI 3.1415926535897932f -#define MAX_DEPTH 15 - -/** - * Extracts the x and y scale from the transform as positive values, and clamps them - */ -void PathTessellator::extractTessellationScales(const Matrix4& transform, float* scaleX, - float* scaleY) { - if (CC_LIKELY(transform.isPureTranslate())) { - *scaleX = 1.0f; - *scaleY = 1.0f; - } else { - float m00 = transform.data[Matrix4::kScaleX]; - float m01 = transform.data[Matrix4::kSkewY]; - float m10 = transform.data[Matrix4::kSkewX]; - float m11 = transform.data[Matrix4::kScaleY]; - *scaleX = MathUtils::clampTessellationScale(sqrt(m00 * m00 + m01 * m01)); - *scaleY = MathUtils::clampTessellationScale(sqrt(m10 * m10 + m11 * m11)); - } -} - -/** - * Produces a pseudo-normal for a vertex, given the normals of the two incoming lines. If the offset - * from each vertex in a perimeter is calculated, the resultant lines connecting the offset vertices - * will be offset by 1.0 - * - * Note that we can't add and normalize the two vectors, that would result in a rectangle having an - * offset of (sqrt(2)/2, sqrt(2)/2) at each corner, instead of (1, 1) - * - * NOTE: assumes angles between normals 90 degrees or less - */ -inline static Vector2 totalOffsetFromNormals(const Vector2& normalA, const Vector2& normalB) { - return (normalA + normalB) / (1 + fabs(normalA.dot(normalB))); -} - -/** - * Structure used for storing useful information about the SkPaint and scale used for tessellating - */ -struct PaintInfo { -public: - PaintInfo(const SkPaint* paint, const mat4& transform) - : style(paint->getStyle()) - , cap(paint->getStrokeCap()) - , isAA(paint->isAntiAlias()) - , halfStrokeWidth(paint->getStrokeWidth() * 0.5f) - , maxAlpha(1.0f) { - // compute inverse scales - if (CC_LIKELY(transform.isPureTranslate())) { - inverseScaleX = 1.0f; - inverseScaleY = 1.0f; - } else { - float scaleX, scaleY; - PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY); - inverseScaleX = 1.0f / scaleX; - inverseScaleY = 1.0f / scaleY; - } - - if (isAA && halfStrokeWidth != 0 && inverseScaleX == inverseScaleY && - 2 * halfStrokeWidth < inverseScaleX) { - // AA, with non-hairline stroke, width < 1 pixel. Scale alpha and treat as hairline. - maxAlpha *= (2 * halfStrokeWidth) / inverseScaleX; - halfStrokeWidth = 0.0f; - } - } - - SkPaint::Style style; - SkPaint::Cap cap; - bool isAA; - float inverseScaleX; - float inverseScaleY; - float halfStrokeWidth; - float maxAlpha; - - inline void scaleOffsetForStrokeWidth(Vector2& offset) const { - if (halfStrokeWidth == 0.0f) { - // hairline - compensate for scale - offset.x *= 0.5f * inverseScaleX; - offset.y *= 0.5f * inverseScaleY; - } else { - offset *= halfStrokeWidth; - } - } - - /** - * NOTE: the input will not always be a normal, especially for sharp edges - it should be the - * result of totalOffsetFromNormals (see documentation there) - */ - inline Vector2 deriveAAOffset(const Vector2& offset) const { - return (Vector2){offset.x * 0.5f * inverseScaleX, offset.y * 0.5f * inverseScaleY}; - } - - /** - * Returns the number of cap divisions beyond the minimum 2 (kButt_Cap/kSquareCap will return 0) - * Should only be used when stroking and drawing caps - */ - inline int capExtraDivisions() const { - if (cap == SkPaint::kRound_Cap) { - // always use 2 points for hairline - if (halfStrokeWidth == 0.0f) return 2; - - float threshold = std::min(inverseScaleX, inverseScaleY) * ROUND_CAP_THRESH; - return MathUtils::divisionsNeededToApproximateArc(halfStrokeWidth, PI, threshold); - } - return 0; - } - - /** - * Outset the bounds of point data (for line endpoints or points) to account for stroke - * geometry. - * - * bounds are in pre-scaled space. - */ - void expandBoundsForStroke(Rect* bounds) const { - if (halfStrokeWidth == 0) { - // hairline, outset by (0.5f + fudge factor) in post-scaling space - bounds->outset(fabs(inverseScaleX) * (0.5f + Vertex::GeometryFudgeFactor()), - fabs(inverseScaleY) * (0.5f + Vertex::GeometryFudgeFactor())); - } else { - // non hairline, outset by half stroke width pre-scaled, and fudge factor post scaled - bounds->outset(halfStrokeWidth + fabs(inverseScaleX) * Vertex::GeometryFudgeFactor(), - halfStrokeWidth + fabs(inverseScaleY) * Vertex::GeometryFudgeFactor()); - } - } -}; - -void getFillVerticesFromPerimeter(const std::vector<Vertex>& perimeter, - VertexBuffer& vertexBuffer) { - Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size()); - - int currentIndex = 0; - // zig zag between all previous points on the inside of the hull to create a - // triangle strip that fills the hull - int srcAindex = 0; - int srcBindex = perimeter.size() - 1; - while (srcAindex <= srcBindex) { - buffer[currentIndex++] = perimeter[srcAindex]; - if (srcAindex == srcBindex) break; - buffer[currentIndex++] = perimeter[srcBindex]; - srcAindex++; - srcBindex--; - } -} - -/* - * Fills a vertexBuffer with non-alpha vertices, zig-zagging at each perimeter point to create a - * tri-strip as wide as the stroke. - * - * 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 std::vector<Vertex>& perimeter, - VertexBuffer& vertexBuffer) { - Vertex* buffer = vertexBuffer.alloc<Vertex>(perimeter.size() * 2 + 2); - - int currentIndex = 0; - const Vertex* last = &(perimeter[perimeter.size() - 1]); - const Vertex* current = &(perimeter[0]); - Vector2 lastNormal = {current->y - last->y, last->x - current->x}; - lastNormal.normalize(); - for (unsigned int i = 0; i < perimeter.size(); i++) { - const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]); - Vector2 nextNormal = {next->y - current->y, current->x - next->x}; - nextNormal.normalize(); - - Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal); - paintInfo.scaleOffsetForStrokeWidth(totalOffset); - - Vertex::set(&buffer[currentIndex++], current->x + totalOffset.x, - current->y + totalOffset.y); - - Vertex::set(&buffer[currentIndex++], current->x - totalOffset.x, - current->y - totalOffset.y); - - current = next; - lastNormal = nextNormal; - } - - // wrap around to beginning - buffer[currentIndex++] = buffer[0]; - buffer[currentIndex++] = buffer[1]; - - DEBUG_DUMP_BUFFER(); -} - -static inline void storeBeginEnd(const PaintInfo& paintInfo, const Vertex& center, - const Vector2& normal, Vertex* buffer, int& currentIndex, - bool begin) { - Vector2 strokeOffset = normal; - paintInfo.scaleOffsetForStrokeWidth(strokeOffset); - - Vector2 referencePoint = {center.x, center.y}; - if (paintInfo.cap == SkPaint::kSquare_Cap) { - Vector2 rotated = {-strokeOffset.y, strokeOffset.x}; - referencePoint += rotated * (begin ? -1 : 1); - } - - Vertex::set(&buffer[currentIndex++], referencePoint + strokeOffset); - Vertex::set(&buffer[currentIndex++], referencePoint - strokeOffset); -} - -/** - * Fills a vertexBuffer with non-alpha vertices similar to getStrokeVerticesFromPerimeter, except: - * - * 1 - Doesn't need to wrap around, since the input vertices are unclosed - * - * 2 - can zig-zag across 'extra' vertices at either end, to create round caps - */ -void getStrokeVerticesFromUnclosedVertices(const PaintInfo& paintInfo, - 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); - - const int lastIndex = vertices.size() - 1; - if (extra > 0) { - // tessellate both round caps - float beginTheta = atan2(-(vertices[0].x - vertices[1].x), vertices[0].y - vertices[1].y); - float endTheta = atan2(-(vertices[lastIndex].x - vertices[lastIndex - 1].x), - vertices[lastIndex].y - vertices[lastIndex - 1].y); - const float dTheta = PI / (extra + 1); - - int capOffset; - for (int i = 0; i < extra; i++) { - if (i < extra / 2) { - capOffset = extra - 2 * i - 1; - } else { - capOffset = 2 * i - extra; - } - - beginTheta += dTheta; - Vector2 beginRadialOffset = {cosf(beginTheta), sinf(beginTheta)}; - paintInfo.scaleOffsetForStrokeWidth(beginRadialOffset); - Vertex::set(&buffer[capOffset], vertices[0].x + beginRadialOffset.x, - vertices[0].y + beginRadialOffset.y); - - endTheta += dTheta; - Vector2 endRadialOffset = {cosf(endTheta), sinf(endTheta)}; - paintInfo.scaleOffsetForStrokeWidth(endRadialOffset); - Vertex::set(&buffer[allocSize - 1 - capOffset], - vertices[lastIndex].x + endRadialOffset.x, - vertices[lastIndex].y + endRadialOffset.y); - } - } - - int currentIndex = extra; - const Vertex* last = &(vertices[0]); - const Vertex* current = &(vertices[1]); - Vector2 lastNormal = {current->y - last->y, last->x - current->x}; - lastNormal.normalize(); - - storeBeginEnd(paintInfo, vertices[0], lastNormal, buffer, currentIndex, true); - - for (unsigned int i = 1; i < vertices.size() - 1; i++) { - const Vertex* next = &(vertices[i + 1]); - Vector2 nextNormal = {next->y - current->y, current->x - next->x}; - nextNormal.normalize(); - - Vector2 strokeOffset = totalOffsetFromNormals(lastNormal, nextNormal); - paintInfo.scaleOffsetForStrokeWidth(strokeOffset); - - Vector2 center = {current->x, current->y}; - Vertex::set(&buffer[currentIndex++], center + strokeOffset); - Vertex::set(&buffer[currentIndex++], center - strokeOffset); - - current = next; - lastNormal = nextNormal; - } - - storeBeginEnd(paintInfo, vertices[lastIndex], lastNormal, buffer, currentIndex, false); - - DEBUG_DUMP_BUFFER(); -} - -/** - * Populates a vertexBuffer with AlphaVertices to create an anti-aliased fill shape tessellation - * - * 1 - create the AA perimeter of unit width, by zig-zagging at each point around the perimeter of - * the shape (using 2 * perimeter.size() vertices) - * - * 2 - wrap around to the beginning to complete the perimeter (2 vertices) - * - * 3 - zig zag back and forth inside the shape to fill it (using perimeter.size() vertices) - */ -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 - // alpha 0 vertex, offset by a scaled normal. - int currentIndex = 0; - const Vertex* last = &(perimeter[perimeter.size() - 1]); - const Vertex* current = &(perimeter[0]); - Vector2 lastNormal = {current->y - last->y, last->x - current->x}; - lastNormal.normalize(); - for (unsigned int i = 0; i < perimeter.size(); i++) { - const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]); - Vector2 nextNormal = {next->y - current->y, current->x - next->x}; - nextNormal.normalize(); - - // AA point offset from original point is that point's normal, such that each side is offset - // by .5 pixels - Vector2 totalOffset = - paintInfo.deriveAAOffset(totalOffsetFromNormals(lastNormal, nextNormal)); - - AlphaVertex::set(&buffer[currentIndex++], current->x + totalOffset.x, - current->y + totalOffset.y, 0.0f); - AlphaVertex::set(&buffer[currentIndex++], current->x - totalOffset.x, - current->y - totalOffset.y, maxAlpha); - - current = next; - lastNormal = nextNormal; - } - - // wrap around to beginning - buffer[currentIndex++] = buffer[0]; - buffer[currentIndex++] = buffer[1]; - - // zig zag between all previous points on the inside of the hull to create a - // triangle strip that fills the hull, repeating the first inner point to - // create degenerate tris to start inside path - int srcAindex = 0; - int srcBindex = perimeter.size() - 1; - while (srcAindex <= srcBindex) { - buffer[currentIndex++] = buffer[srcAindex * 2 + 1]; - if (srcAindex == srcBindex) break; - buffer[currentIndex++] = buffer[srcBindex * 2 + 1]; - srcAindex++; - srcBindex--; - } - - DEBUG_DUMP_BUFFER(); -} - -/** - * Stores geometry for a single, AA-perimeter (potentially rounded) cap - * - * For explanation of constants and general methodoloyg, see comments for - * getStrokeVerticesFromUnclosedVerticesAA() below. - */ -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; - const int capIndex = - isFirst ? 2 * offset + 6 + 2 * (extra + extraOffset) : offset + 2 + 2 * extraOffset; - if (isFirst) normal *= -1; - - // TODO: this normal should be scaled by radialScale if extra != 0, see totalOffsetFromNormals() - Vector2 AAOffset = paintInfo.deriveAAOffset(normal); - - Vector2 strokeOffset = normal; - paintInfo.scaleOffsetForStrokeWidth(strokeOffset); - Vector2 outerOffset = strokeOffset + AAOffset; - Vector2 innerOffset = strokeOffset - AAOffset; - - Vector2 capAAOffset = {0, 0}; - if (paintInfo.cap != SkPaint::kRound_Cap) { - // if the cap is square or butt, the inside primary cap vertices will be inset in two - // directions - both normal to the stroke, and parallel to it. - capAAOffset = (Vector2){-AAOffset.y, AAOffset.x}; - } - - // determine referencePoint, the center point for the 4 primary cap vertices - 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) - Vector2 rotated = {-strokeOffset.y, strokeOffset.x}; - referencePoint += rotated; - } - - AlphaVertex::set(&buffer[capIndex + 0], referencePoint.x + outerOffset.x + capAAOffset.x, - referencePoint.y + outerOffset.y + capAAOffset.y, 0.0f); - AlphaVertex::set(&buffer[capIndex + 1], referencePoint.x + innerOffset.x - capAAOffset.x, - referencePoint.y + innerOffset.y - capAAOffset.y, paintInfo.maxAlpha); - - bool isRound = paintInfo.cap == SkPaint::kRound_Cap; - - const int postCapIndex = (isRound && isFirst) ? (2 * extraOffset - 2) : capIndex + (2 * extra); - AlphaVertex::set(&buffer[postCapIndex + 2], referencePoint.x - outerOffset.x + capAAOffset.x, - referencePoint.y - outerOffset.y + capAAOffset.y, 0.0f); - AlphaVertex::set(&buffer[postCapIndex + 3], referencePoint.x - innerOffset.x - capAAOffset.x, - referencePoint.y - innerOffset.y - capAAOffset.y, paintInfo.maxAlpha); - - if (isRound) { - const float dTheta = PI / (extra + 1); - const float radialScale = 2.0f / (1 + cos(dTheta)); - float theta = atan2(normal.y, normal.x); - int capPerimIndex = capIndex + 2; - - for (int i = 0; i < extra; i++) { - theta += dTheta; - - Vector2 radialOffset = {cosf(theta), sinf(theta)}; - - // scale to compensate for pinching at sharp angles, see totalOffsetFromNormals() - radialOffset *= radialScale; - - AAOffset = paintInfo.deriveAAOffset(radialOffset); - paintInfo.scaleOffsetForStrokeWidth(radialOffset); - AlphaVertex::set(&buffer[capPerimIndex++], - referencePoint.x + radialOffset.x + AAOffset.x, - referencePoint.y + radialOffset.y + AAOffset.y, 0.0f); - AlphaVertex::set(&buffer[capPerimIndex++], - referencePoint.x + radialOffset.x - AAOffset.x, - referencePoint.y + radialOffset.y - AAOffset.y, paintInfo.maxAlpha); - - if (isFirst && i == extra - extraOffset) { - // copy most recent two points to first two points - buffer[0] = buffer[capPerimIndex - 2]; - buffer[1] = buffer[capPerimIndex - 1]; - - capPerimIndex = 2; // start writing the rest of the round cap at index 2 - } - } - - if (isFirst) { - const int startCapFillIndex = capIndex + 2 * (extra - extraOffset) + 4; - int capFillIndex = startCapFillIndex; - for (int i = 0; i < extra + 2; i += 2) { - buffer[capFillIndex++] = buffer[1 + i]; - // TODO: to support odd numbers of divisions, break here on the last iteration - buffer[capFillIndex++] = buffer[startCapFillIndex - 3 - i]; - } - } else { - int capFillIndex = 6 * vertices.size() + 2 + 6 * extra - (extra + 2); - for (int i = 0; i < extra + 2; i += 2) { - buffer[capFillIndex++] = buffer[capIndex + 1 + i]; - // TODO: to support odd numbers of divisions, break here on the last iteration - buffer[capFillIndex++] = buffer[capIndex + 3 + 2 * extra - i]; - } - } - return; - } - if (isFirst) { - buffer[0] = buffer[postCapIndex + 2]; - buffer[1] = buffer[postCapIndex + 3]; - buffer[postCapIndex + 4] = buffer[1]; // degenerate tris (the only two!) - buffer[postCapIndex + 5] = buffer[postCapIndex + 1]; - } else { - buffer[6 * vertices.size()] = buffer[postCapIndex + 1]; - buffer[6 * vertices.size() + 1] = buffer[postCapIndex + 3]; - } -} - -/* -the geometry for an aa, capped stroke consists of the following: - - # vertices | function ----------------------------------------------------------------------- -a) 2 | Start AA perimeter -b) 2, 2 * roundDivOff | First half of begin cap's perimeter - | - 2 * middlePts | 'Outer' or 'Top' AA perimeter half (between caps) - | -a) 4 | End cap's -b) 2, 2 * roundDivs, 2 | AA perimeter - | - 2 * middlePts | 'Inner' or 'bottom' AA perimeter half - | -a) 6 | Begin cap's perimeter -b) 2, 2*(rD - rDO + 1), | Last half of begin cap's perimeter - roundDivs, 2 | - | - 2 * middlePts | Stroke's full opacity center strip - | -a) 2 | end stroke -b) 2, roundDivs | (and end cap fill, for round) - -Notes: -* rows starting with 'a)' denote the Butt or Square cap vertex use, 'b)' denote Round - -* 'middlePts' is (number of points in the unclosed input vertex list, minus 2) times two - -* 'roundDivs' or 'rD' is the number of extra vertices (beyond the minimum of 2) that define the - round cap's shape, and is at least two. This will increase with cap size to sufficiently - define the cap's level of tessellation. - -* 'roundDivOffset' or 'rDO' is the point about halfway along the start cap's round perimeter, where - the stream of vertices for the AA perimeter starts. By starting and ending the perimeter at - this offset, the fill of the stroke is drawn from this point with minimal extra vertices. - -This means the outer perimeter starts at: - outerIndex = (2) OR (2 + 2 * roundDivOff) -the inner perimeter (since it is filled in reverse) starts at: - innerIndex = outerIndex + (4 * middlePts) + ((4) OR (4 + 2 * roundDivs)) - 1 -the stroke starts at: - strokeIndex = innerIndex + 1 + ((6) OR (6 + 3 * roundDivs - 2 * roundDivOffset)) - -The total needed allocated space is either: - 2 + 4 + 6 + 2 + 3 * (2 * middlePts) = 14 + 6 * middlePts = 2 + 6 * pts -or, for rounded caps: - (2 + 2 * rDO) + (4 + 2 * rD) + (2 * (rD - rDO + 1) - + roundDivs + 4) + (2 + roundDivs) + 3 * (2 * middlePts) - = 14 + 6 * middlePts + 6 * roundDivs - = 2 + 6 * pts + 6 * roundDivs - */ -void getStrokeVerticesFromUnclosedVerticesAA(const PaintInfo& paintInfo, - const std::vector<Vertex>& vertices, - VertexBuffer& vertexBuffer) { - const int extra = paintInfo.capExtraDivisions(); - const int allocSize = 6 * vertices.size() + 2 + 6 * extra; - - AlphaVertex* buffer = vertexBuffer.alloc<AlphaVertex>(allocSize); - - const int extraOffset = (extra + 1) / 2; - int offset = 2 * (vertices.size() - 2); - // there is no outer/inner here, using them for consistency with below approach - int currentAAOuterIndex = 2 + 2 * extraOffset; - int currentAAInnerIndex = currentAAOuterIndex + (2 * offset) + 3 + (2 * extra); - int currentStrokeIndex = currentAAInnerIndex + 7 + (3 * extra - 2 * extraOffset); - - const Vertex* last = &(vertices[0]); - const Vertex* current = &(vertices[1]); - Vector2 lastNormal = {current->y - last->y, last->x - current->x}; - lastNormal.normalize(); - - // TODO: use normal from bezier traversal for cap, instead of from vertices - storeCapAA(paintInfo, vertices, buffer, true, lastNormal, offset); - - for (unsigned int i = 1; i < vertices.size() - 1; i++) { - const Vertex* next = &(vertices[i + 1]); - Vector2 nextNormal = {next->y - current->y, current->x - next->x}; - nextNormal.normalize(); - - Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal); - Vector2 AAOffset = paintInfo.deriveAAOffset(totalOffset); - - Vector2 innerOffset = totalOffset; - paintInfo.scaleOffsetForStrokeWidth(innerOffset); - Vector2 outerOffset = innerOffset + AAOffset; - innerOffset -= AAOffset; - - AlphaVertex::set(&buffer[currentAAOuterIndex++], current->x + outerOffset.x, - current->y + outerOffset.y, 0.0f); - AlphaVertex::set(&buffer[currentAAOuterIndex++], current->x + innerOffset.x, - current->y + innerOffset.y, paintInfo.maxAlpha); - - AlphaVertex::set(&buffer[currentStrokeIndex++], current->x + innerOffset.x, - current->y + innerOffset.y, paintInfo.maxAlpha); - AlphaVertex::set(&buffer[currentStrokeIndex++], current->x - innerOffset.x, - current->y - innerOffset.y, paintInfo.maxAlpha); - - AlphaVertex::set(&buffer[currentAAInnerIndex--], current->x - innerOffset.x, - current->y - innerOffset.y, paintInfo.maxAlpha); - AlphaVertex::set(&buffer[currentAAInnerIndex--], current->x - outerOffset.x, - current->y - outerOffset.y, 0.0f); - - current = next; - lastNormal = nextNormal; - } - - // TODO: use normal from bezier traversal for cap, instead of from vertices - storeCapAA(paintInfo, vertices, buffer, false, lastNormal, offset); - - DEBUG_DUMP_ALPHA_BUFFER(); -} - -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; - int currentAAOuterIndex = 0; - int currentStrokeIndex = offset; - int currentAAInnerIndex = offset * 2; - - const Vertex* last = &(perimeter[perimeter.size() - 1]); - const Vertex* current = &(perimeter[0]); - Vector2 lastNormal = {current->y - last->y, last->x - current->x}; - lastNormal.normalize(); - for (unsigned int i = 0; i < perimeter.size(); i++) { - const Vertex* next = &(perimeter[i + 1 >= perimeter.size() ? 0 : i + 1]); - Vector2 nextNormal = {next->y - current->y, current->x - next->x}; - nextNormal.normalize(); - - Vector2 totalOffset = totalOffsetFromNormals(lastNormal, nextNormal); - Vector2 AAOffset = paintInfo.deriveAAOffset(totalOffset); - - Vector2 innerOffset = totalOffset; - paintInfo.scaleOffsetForStrokeWidth(innerOffset); - Vector2 outerOffset = innerOffset + AAOffset; - innerOffset -= AAOffset; - - AlphaVertex::set(&buffer[currentAAOuterIndex++], current->x + outerOffset.x, - current->y + outerOffset.y, 0.0f); - AlphaVertex::set(&buffer[currentAAOuterIndex++], current->x + innerOffset.x, - current->y + innerOffset.y, paintInfo.maxAlpha); - - AlphaVertex::set(&buffer[currentStrokeIndex++], current->x + innerOffset.x, - current->y + innerOffset.y, paintInfo.maxAlpha); - AlphaVertex::set(&buffer[currentStrokeIndex++], current->x - innerOffset.x, - current->y - innerOffset.y, paintInfo.maxAlpha); - - AlphaVertex::set(&buffer[currentAAInnerIndex++], current->x - innerOffset.x, - current->y - innerOffset.y, paintInfo.maxAlpha); - AlphaVertex::set(&buffer[currentAAInnerIndex++], current->x - outerOffset.x, - current->y - outerOffset.y, 0.0f); - - current = next; - lastNormal = nextNormal; - } - - // wrap each strip around to beginning, creating degenerate tris to bridge strips - buffer[currentAAOuterIndex++] = buffer[0]; - buffer[currentAAOuterIndex++] = buffer[1]; - buffer[currentAAOuterIndex++] = buffer[1]; - - buffer[currentStrokeIndex++] = buffer[offset]; - buffer[currentStrokeIndex++] = buffer[offset + 1]; - buffer[currentStrokeIndex++] = buffer[offset + 1]; - - buffer[currentAAInnerIndex++] = buffer[2 * offset]; - buffer[currentAAInnerIndex++] = buffer[2 * offset + 1]; - // don't need to create last degenerate tri - - DEBUG_DUMP_ALPHA_BUFFER(); -} - -void PathTessellator::tessellatePath(const SkPath& path, const SkPaint* paint, - const mat4& transform, VertexBuffer& vertexBuffer) { - ATRACE_CALL(); - - const PaintInfo paintInfo(paint, transform); - - std::vector<Vertex> tempVertices; - float threshInvScaleX = paintInfo.inverseScaleX; - float threshInvScaleY = paintInfo.inverseScaleY; - if (paintInfo.style == SkPaint::kStroke_Style) { - // alter the bezier recursion threshold values we calculate in order to compensate for - // expansion done after the path vertices are found - SkRect bounds = path.getBounds(); - if (!bounds.isEmpty()) { - threshInvScaleX *= bounds.width() / (bounds.width() + paint->getStrokeWidth()); - threshInvScaleY *= bounds.height() / (bounds.height() + paint->getStrokeWidth()); - } - } - - // force close if we're filling the path, since fill path expects closed perimeter. - bool forceClose = paintInfo.style != SkPaint::kStroke_Style; - PathApproximationInfo approximationInfo(threshInvScaleX, threshInvScaleY, - OUTLINE_REFINE_THRESHOLD); - bool wasClosed = - approximatePathOutlineVertices(path, forceClose, approximationInfo, tempVertices); - - if (!tempVertices.size()) { - // path was empty, return without allocating vertex buffer - return; - } - -#if VERTEX_DEBUG - for (unsigned int i = 0; i < tempVertices.size(); i++) { - ALOGD("orig path: point at %f %f", tempVertices[i].x, tempVertices[i].y); - } -#endif - - if (paintInfo.style == SkPaint::kStroke_Style) { - if (!paintInfo.isAA) { - if (wasClosed) { - getStrokeVerticesFromPerimeter(paintInfo, tempVertices, vertexBuffer); - } else { - getStrokeVerticesFromUnclosedVertices(paintInfo, tempVertices, vertexBuffer); - } - - } else { - if (wasClosed) { - getStrokeVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer); - } else { - getStrokeVerticesFromUnclosedVerticesAA(paintInfo, tempVertices, vertexBuffer); - } - } - } else { - // For kStrokeAndFill style, the path should be adjusted externally. - // It will be treated as a fill here. - if (!paintInfo.isAA) { - getFillVerticesFromPerimeter(tempVertices, vertexBuffer); - } else { - getFillVerticesFromPerimeterAA(paintInfo, tempVertices, vertexBuffer); - } - } - - Rect bounds(path.getBounds()); - paintInfo.expandBoundsForStroke(&bounds); - vertexBuffer.setBounds(bounds); - vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone); -} - -template <class TYPE> -static void instanceVertices(VertexBuffer& srcBuffer, VertexBuffer& dstBuffer, const float* points, - int count, Rect& bounds) { - bounds.set(points[0], points[1], points[0], points[1]); - - int numPoints = count / 2; - int verticesPerPoint = srcBuffer.getVertexCount(); - dstBuffer.alloc<TYPE>(numPoints * verticesPerPoint + (numPoints - 1) * 2); - - for (int i = 0; i < count; i += 2) { - bounds.expandToCover(points[i + 0], points[i + 1]); - dstBuffer.copyInto<TYPE>(srcBuffer, points[i + 0], points[i + 1]); - } - dstBuffer.createDegenerateSeparators<TYPE>(verticesPerPoint); -} - -void PathTessellator::tessellatePoints(const float* points, int count, const SkPaint* paint, - const mat4& transform, VertexBuffer& vertexBuffer) { - const PaintInfo paintInfo(paint, transform); - - // determine point shape - SkPath path; - float radius = paintInfo.halfStrokeWidth; - if (radius == 0.0f) radius = 0.5f; - - if (paintInfo.cap == SkPaint::kRound_Cap) { - path.addCircle(0, 0, radius); - } else { - path.addRect(-radius, -radius, radius, radius); - } - - // calculate outline - std::vector<Vertex> outlineVertices; - PathApproximationInfo approximationInfo(paintInfo.inverseScaleX, paintInfo.inverseScaleY, - OUTLINE_REFINE_THRESHOLD); - approximatePathOutlineVertices(path, true, approximationInfo, outlineVertices); - - if (!outlineVertices.size()) return; - - Rect bounds; - // tessellate, then duplicate outline across points - VertexBuffer tempBuffer; - if (!paintInfo.isAA) { - getFillVerticesFromPerimeter(outlineVertices, tempBuffer); - instanceVertices<Vertex>(tempBuffer, vertexBuffer, points, count, bounds); - } else { - // note: pass maxAlpha directly, since we want fill to be alpha modulated - getFillVerticesFromPerimeterAA(paintInfo, outlineVertices, tempBuffer, paintInfo.maxAlpha); - instanceVertices<AlphaVertex>(tempBuffer, vertexBuffer, points, count, bounds); - } - - // expand bounds from vertex coords to pixel data - paintInfo.expandBoundsForStroke(&bounds); - vertexBuffer.setBounds(bounds); - vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone); -} - -void PathTessellator::tessellateLines(const float* points, int count, const SkPaint* paint, - const mat4& transform, VertexBuffer& vertexBuffer) { - ATRACE_CALL(); - const PaintInfo paintInfo(paint, transform); - - const int extra = paintInfo.capExtraDivisions(); - int numLines = count / 4; - int lineAllocSize; - // pre-allocate space for lines in the buffer, and degenerate tris in between - if (paintInfo.isAA) { - lineAllocSize = 6 * (2) + 2 + 6 * extra; - vertexBuffer.alloc<AlphaVertex>(numLines * lineAllocSize + (numLines - 1) * 2); - } else { - lineAllocSize = 2 * ((2) + extra); - vertexBuffer.alloc<Vertex>(numLines * lineAllocSize + (numLines - 1) * 2); - } - - 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) { - Vertex::set(&(tempVerticesData[0]), points[i + 0], points[i + 1]); - Vertex::set(&(tempVerticesData[1]), points[i + 2], points[i + 3]); - - if (paintInfo.isAA) { - getStrokeVerticesFromUnclosedVerticesAA(paintInfo, tempVertices, vertexBuffer); - } else { - getStrokeVerticesFromUnclosedVertices(paintInfo, tempVertices, vertexBuffer); - } - - // calculate bounds - 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 - if (paintInfo.isAA) { - vertexBuffer.createDegenerateSeparators<AlphaVertex>(lineAllocSize); - } else { - vertexBuffer.createDegenerateSeparators<Vertex>(lineAllocSize); - } - - // expand bounds from vertex coords to pixel data - paintInfo.expandBoundsForStroke(&bounds); - vertexBuffer.setBounds(bounds); - vertexBuffer.setMeshFeatureFlags(paintInfo.isAA ? VertexBuffer::kAlpha : VertexBuffer::kNone); -} - -/////////////////////////////////////////////////////////////////////////////// -// Simple path line approximation -/////////////////////////////////////////////////////////////////////////////// - -bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, float threshold, - std::vector<Vertex>& outputVertices) { - PathApproximationInfo approximationInfo(1.0f, 1.0f, threshold); - return approximatePathOutlineVertices(path, true, approximationInfo, outputVertices); -} - -class ClockwiseEnforcer { -public: - void addPoint(const SkPoint& point) { - double x = point.x(); - double y = point.y(); - - if (initialized) { - sum += (x + lastX) * (y - lastY); - } else { - initialized = true; - } - - lastX = x; - lastY = y; - } - 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[i] = vertices[k]; - vertices[k] = tmp; - } - } - } - -private: - bool initialized = false; - double lastX = 0; - double lastY = 0; - double sum = 0; -}; - -bool PathTessellator::approximatePathOutlineVertices(const SkPath& path, bool forceClose, - const PathApproximationInfo& approximationInfo, - std::vector<Vertex>& outputVertices) { - ATRACE_CALL(); - - // TODO: to support joins other than sharp miter, join vertices should be labelled in the - // perimeter, or resolved into more vertices. Reconsider forceClose-ing in that case. - SkPath::Iter iter(path, forceClose); - SkPoint pts[4]; - SkPath::Verb v; - ClockwiseEnforcer clockwiseEnforcer; - while (SkPath::kDone_Verb != (v = iter.next(pts))) { - switch (v) { - case SkPath::kMove_Verb: - 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; - case SkPath::kClose_Verb: - ALOGV("Close at pos %f %f", pts[0].x(), pts[0].y()); - clockwiseEnforcer.addPoint(pts[0]); - break; - case SkPath::kLine_Verb: - ALOGV("kLine_Verb %f %f -> %f %f", pts[0].x(), pts[0].y(), 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: - ALOGV("kQuad_Verb"); - recursiveQuadraticBezierVertices(pts[0].x(), pts[0].y(), pts[2].x(), pts[2].y(), - pts[1].x(), pts[1].y(), approximationInfo, - outputVertices); - clockwiseEnforcer.addPoint(pts[1]); - clockwiseEnforcer.addPoint(pts[2]); - break; - case SkPath::kCubic_Verb: - ALOGV("kCubic_Verb"); - recursiveCubicBezierVertices(pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y(), - pts[3].x(), pts[3].y(), pts[2].x(), pts[2].y(), - approximationInfo, outputVertices); - clockwiseEnforcer.addPoint(pts[1]); - clockwiseEnforcer.addPoint(pts[2]); - clockwiseEnforcer.addPoint(pts[3]); - break; - case SkPath::kConic_Verb: { - ALOGV("kConic_Verb"); - SkAutoConicToQuads converter; - const SkPoint* quads = converter.computeQuads( - pts, iter.conicWeight(), approximationInfo.thresholdForConicQuads); - for (int i = 0; i < converter.countQuads(); ++i) { - const int offset = 2 * i; - recursiveQuadraticBezierVertices(quads[offset].x(), quads[offset].y(), - quads[offset + 2].x(), quads[offset + 2].y(), - quads[offset + 1].x(), quads[offset + 1].y(), - approximationInfo, outputVertices); - } - clockwiseEnforcer.addPoint(pts[1]); - clockwiseEnforcer.addPoint(pts[2]); - break; - } - default: - static_assert(SkPath::kMove_Verb == 0 && SkPath::kLine_Verb == 1 && - SkPath::kQuad_Verb == 2 && SkPath::kConic_Verb == 3 && - SkPath::kCubic_Verb == 4 && SkPath::kClose_Verb == 5 && - SkPath::kDone_Verb == 6, - "Path enum changed, new types may have been added"); - break; - } - } - - bool wasClosed = false; - int size = outputVertices.size(); - if (size >= 2 && outputVertices[0].x == outputVertices[size - 1].x && - outputVertices[0].y == outputVertices[size - 1].y) { - outputVertices.pop_back(); - wasClosed = true; - } - - // ensure output vector is clockwise - clockwiseEnforcer.reverseVectorIfNotClockwise(outputVertices); - return wasClosed; -} - -/////////////////////////////////////////////////////////////////////////////// -// Bezier approximation -// -// All the inputs and outputs here are in path coordinates. -// We convert the error threshold from screen coordinates into path coordinates. -/////////////////////////////////////////////////////////////////////////////// - -// Get a threshold in path coordinates, by scaling the thresholdSquared from screen coordinates. -// TODO: Document the math behind this algorithm. -static inline float getThreshold(const PathApproximationInfo& info, float dx, float dy) { - // multiplying by sqrInvScaleY/X equivalent to multiplying in dimensional scale factors - float scale = (dx * dx * info.sqrInvScaleY + dy * dy * info.sqrInvScaleX); - return info.thresholdSquared * scale; -} - -void PathTessellator::recursiveCubicBezierVertices(float p1x, float p1y, float c1x, float c1y, - float p2x, float p2y, float c2x, float c2y, - const PathApproximationInfo& approximationInfo, - std::vector<Vertex>& outputVertices, int depth) { - float dx = p2x - p1x; - float dy = p2y - p1y; - float d1 = fabs((c1x - p2x) * dy - (c1y - p2y) * dx); - float d2 = fabs((c2x - p2x) * dy - (c2y - p2y) * dx); - float d = d1 + d2; - - if (depth >= MAX_DEPTH || d * d <= getThreshold(approximationInfo, dx, dy)) { - // below thresh, draw line by adding endpoint - outputVertices.push_back(Vertex{p2x, p2y}); - } else { - float p1c1x = (p1x + c1x) * 0.5f; - float p1c1y = (p1y + c1y) * 0.5f; - float p2c2x = (p2x + c2x) * 0.5f; - float p2c2y = (p2y + c2y) * 0.5f; - - float c1c2x = (c1x + c2x) * 0.5f; - float c1c2y = (c1y + c2y) * 0.5f; - - float p1c1c2x = (p1c1x + c1c2x) * 0.5f; - float p1c1c2y = (p1c1y + c1c2y) * 0.5f; - - float p2c1c2x = (p2c2x + c1c2x) * 0.5f; - float p2c1c2y = (p2c2y + c1c2y) * 0.5f; - - float mx = (p1c1c2x + p2c1c2x) * 0.5f; - float my = (p1c1c2y + p2c1c2y) * 0.5f; - - recursiveCubicBezierVertices(p1x, p1y, p1c1x, p1c1y, mx, my, p1c1c2x, p1c1c2y, - approximationInfo, outputVertices, depth + 1); - recursiveCubicBezierVertices(mx, my, p2c1c2x, p2c1c2y, p2x, p2y, p2c2x, p2c2y, - approximationInfo, outputVertices, depth + 1); - } -} - -void PathTessellator::recursiveQuadraticBezierVertices( - float ax, float ay, float bx, float by, float cx, float cy, - const PathApproximationInfo& approximationInfo, 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). - float d = (cx - bx) * dy - (cy - by) * dx; - - if (depth >= MAX_DEPTH || d * d <= getThreshold(approximationInfo, dx, dy)) { - // below thresh, draw line by adding endpoint - outputVertices.push_back(Vertex{bx, by}); - } else { - float acx = (ax + cx) * 0.5f; - float bcx = (bx + cx) * 0.5f; - float acy = (ay + cy) * 0.5f; - float bcy = (by + cy) * 0.5f; - - // midpoint - float mx = (acx + bcx) * 0.5f; - float my = (acy + bcy) * 0.5f; - - recursiveQuadraticBezierVertices(ax, ay, mx, my, acx, acy, approximationInfo, - outputVertices, depth + 1); - recursiveQuadraticBezierVertices(mx, my, bx, by, bcx, bcy, approximationInfo, - outputVertices, depth + 1); - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/PathTessellator.h b/libs/hwui/PathTessellator.h deleted file mode 100644 index ed268324b341..000000000000 --- a/libs/hwui/PathTessellator.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2012 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_PATH_TESSELLATOR_H -#define ANDROID_HWUI_PATH_TESSELLATOR_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 { - -/** - * Structure used for threshold values in outline path tessellation. - * - * TODO: PaintInfo should store one of this object, and initialized all values in constructor - * depending on its type (point, line or path). - */ -struct PathApproximationInfo { - PathApproximationInfo(float invScaleX, float invScaleY, float pixelThreshold) - : thresholdSquared(pixelThreshold * pixelThreshold) - , sqrInvScaleX(invScaleX * invScaleX) - , sqrInvScaleY(invScaleY * invScaleY) - , thresholdForConicQuads(pixelThreshold * std::min(invScaleX, invScaleY) / 2.0f){}; - - const float thresholdSquared; - const float sqrInvScaleX; - const float sqrInvScaleY; - const float thresholdForConicQuads; -}; - -class PathTessellator { -public: - /** - * Populates scaleX and scaleY with the 'tessellation scale' of the transform - the effective X - * and Y scales that tessellation will take into account when generating the 1.0 pixel thick - * ramp. - * - * Two instances of the same shape (size, paint, etc.) will only generate the same vertices if - * their tessellation scales are equal. - */ - static void extractTessellationScales(const Matrix4& transform, float* scaleX, float* scaleY); - - /** - * Populates a VertexBuffer with a tessellated approximation of the input convex path, as a - * single - * triangle strip. Note: joins are not currently supported. - * - * @param path The path to be approximated - * @param paint The paint the path will be drawn with, indicating AA, painting style - * (stroke vs fill), stroke width, stroke cap & join style, etc. - * @param transform The transform the path is to be drawn with, used to drive stretch-aware path - * vertex approximation, and correct AA ramp offsetting. - * @param vertexBuffer The output buffer - */ - static void tessellatePath(const SkPath& path, const SkPaint* paint, const mat4& transform, - VertexBuffer& vertexBuffer); - - /** - * Populates a VertexBuffer with a tessellated approximation of points as a single triangle - * strip (with degenerate tris separating), respecting the shape defined by the paint cap. - * - * @param points The center vertices of the points to be drawn - * @param count The number of floats making up the point vertices - * @param paint The paint the points will be drawn with indicating AA, stroke width & cap - * @param transform The transform the points will be drawn with, used to drive stretch-aware - * path - * vertex approximation, and correct AA ramp offsetting - * @param vertexBuffer The output buffer - */ - static void tessellatePoints(const float* points, int count, const SkPaint* paint, - const mat4& transform, VertexBuffer& vertexBuffer); - - /** - * Populates a VertexBuffer with a tessellated approximation of lines as a single triangle - * strip (with degenerate tris separating). - * - * @param points Pairs of endpoints defining the lines to be drawn - * @param count The number of floats making up the line vertices - * @param paint The paint the lines will be drawn with indicating AA, stroke width & cap - * @param transform The transform the points will be drawn with, used to drive stretch-aware - * path - * vertex approximation, and correct AA ramp offsetting - * @param vertexBuffer The output buffer - */ - static void tessellateLines(const float* points, int count, const SkPaint* paint, - const mat4& transform, VertexBuffer& vertexBuffer); - - /** - * Approximates a convex outline into a clockwise Vector of 2d vertices. - * - * @param path The outline to be approximated - * @param threshold The threshold of acceptable error (in pixels) when approximating - * @param outputVertices An empty Vector which will be populated with the output - */ - static bool approximatePathOutlineVertices(const SkPath& path, float threshold, - std::vector<Vertex>& outputVertices); - -private: - static bool approximatePathOutlineVertices(const SkPath& path, bool forceClose, - const PathApproximationInfo& approximationInfo, - std::vector<Vertex>& outputVertices); - - /* - endpoints a & b, - control c - */ - static void recursiveQuadraticBezierVertices(float ax, float ay, float bx, float by, float cx, - float cy, - const PathApproximationInfo& approximationInfo, - std::vector<Vertex>& outputVertices, - int depth = 0); - - /* - endpoints p1, p2 - control c1, c2 - */ - static void recursiveCubicBezierVertices(float p1x, float p1y, float c1x, float c1y, float p2x, - float p2y, float c2x, float c2y, - const PathApproximationInfo& approximationInfo, - std::vector<Vertex>& outputVertices, int depth = 0); -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_PATH_TESSELLATOR_H diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp deleted file mode 100644 index 910a9889db1f..000000000000 --- a/libs/hwui/PixelBuffer.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "PixelBuffer.h" - -#include "Debug.h" -#include "Extensions.h" -#include "Properties.h" -#include "renderstate/RenderState.h" -#include "utils/GLUtils.h" - -#include <utils/Log.h> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// CPU pixel buffer -/////////////////////////////////////////////////////////////////////////////// - -class CpuPixelBuffer : public PixelBuffer { -public: - CpuPixelBuffer(GLenum format, uint32_t width, uint32_t height); - - uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) override; - - void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) override; - -protected: - void unmap() override; - -private: - std::unique_ptr<uint8_t[]> mBuffer; -}; - -CpuPixelBuffer::CpuPixelBuffer(GLenum format, uint32_t width, uint32_t height) - : PixelBuffer(format, width, height) - , mBuffer(new uint8_t[width * height * formatSize(format)]) {} - -uint8_t* CpuPixelBuffer::map(AccessMode mode) { - if (mAccessMode == kAccessMode_None) { - mAccessMode = mode; - } - return mBuffer.get(); -} - -void CpuPixelBuffer::unmap() { - mAccessMode = kAccessMode_None; -} - -void CpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) { - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, mFormat, GL_UNSIGNED_BYTE, - &mBuffer[offset]); -} - -/////////////////////////////////////////////////////////////////////////////// -// GPU pixel buffer -/////////////////////////////////////////////////////////////////////////////// - -class GpuPixelBuffer : public PixelBuffer { -public: - GpuPixelBuffer(GLenum format, uint32_t width, uint32_t height); - ~GpuPixelBuffer(); - - uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) override; - - void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) override; - -protected: - void unmap() override; - -private: - GLuint mBuffer; - uint8_t* mMappedPointer; - Caches& mCaches; -}; - -GpuPixelBuffer::GpuPixelBuffer(GLenum format, uint32_t width, uint32_t height) - : PixelBuffer(format, width, height) - , mMappedPointer(nullptr) - , mCaches(Caches::getInstance()) { - glGenBuffers(1, &mBuffer); - - mCaches.pixelBufferState().bind(mBuffer); - glBufferData(GL_PIXEL_UNPACK_BUFFER, getSize(), nullptr, GL_DYNAMIC_DRAW); - mCaches.pixelBufferState().unbind(); -} - -GpuPixelBuffer::~GpuPixelBuffer() { - glDeleteBuffers(1, &mBuffer); -} - -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 (CC_UNLIKELY(!mMappedPointer)) { - GLUtils::dumpGLErrors(); - LOG_ALWAYS_FATAL("Failed to map PBO"); - } - mAccessMode = mode; - mCaches.pixelBufferState().unbind(); - } - - return mMappedPointer; -} - -void GpuPixelBuffer::unmap() { - if (mAccessMode != kAccessMode_None) { - if (mMappedPointer) { - mCaches.pixelBufferState().bind(mBuffer); - GLboolean status = glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - if (status == GL_FALSE) { - ALOGE("Corrupted GPU pixel buffer"); - } - } - mAccessMode = kAccessMode_None; - mMappedPointer = nullptr; - } -} - -void GpuPixelBuffer::upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) { - // If the buffer is not mapped, unmap() will not bind it - mCaches.pixelBufferState().bind(mBuffer); - unmap(); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, mFormat, GL_UNSIGNED_BYTE, - reinterpret_cast<void*>(offset)); - mCaches.pixelBufferState().unbind(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Factory -/////////////////////////////////////////////////////////////////////////////// - -PixelBuffer* PixelBuffer::create(GLenum format, uint32_t width, uint32_t height, BufferType type) { - if (type == kBufferType_Auto && Caches::getInstance().gpuPixelBuffersEnabled) { - return new GpuPixelBuffer(format, width, height); - } - return new CpuPixelBuffer(format, width, height); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/PixelBuffer.h b/libs/hwui/PixelBuffer.h deleted file mode 100644 index e7e341b90ad3..000000000000 --- a/libs/hwui/PixelBuffer.h +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_PIXEL_BUFFER_H -#define ANDROID_HWUI_PIXEL_BUFFER_H - -#include <GLES3/gl3.h> - -#include <log/log.h> - -namespace android { -namespace uirenderer { - -/** - * Represents a pixel buffer. A pixel buffer will be backed either by a - * PBO on OpenGL ES 3.0 and higher or by an array of uint8_t on other - * versions. If the buffer is backed by a PBO it will of type - * GL_PIXEL_UNPACK_BUFFER. - * - * To read from or write into a PixelBuffer you must first map the - * buffer using the map(AccessMode) method. This method returns a - * pointer to the beginning of the buffer. - * - * Before the buffer can be used by the GPU, for instance to upload - * a texture, you must first unmap the buffer. To do so, call the - * unmap() method. - * - * Mapping and unmapping a PixelBuffer can have the side effect of - * changing the currently active GL_PIXEL_UNPACK_BUFFER. It is - * therefore recommended to call Caches::unbindPixelbuffer() after - * using a PixelBuffer to upload to a texture. - */ -class PixelBuffer { -public: - enum BufferType { kBufferType_Auto, kBufferType_CPU }; - - enum AccessMode { - kAccessMode_None = 0, - kAccessMode_Read = GL_MAP_READ_BIT, - kAccessMode_Write = GL_MAP_WRITE_BIT, - kAccessMode_ReadWrite = GL_MAP_READ_BIT | GL_MAP_WRITE_BIT - }; - - /** - * Creates a new PixelBuffer object with the specified format and - * dimensions. The buffer is immediately allocated. - * - * The buffer type specifies how the buffer should be allocated. - * By default this method will automatically choose whether to allocate - * a CPU or GPU buffer. - */ - static PixelBuffer* create(GLenum format, uint32_t width, uint32_t height, - BufferType type = kBufferType_Auto); - - virtual ~PixelBuffer() {} - - /** - * Returns the format of this render buffer. - */ - GLenum getFormat() const { return mFormat; } - - /** - * Maps this before with the specified access mode. This method - * returns a pointer to the region of memory where the buffer was - * mapped. - * - * If the buffer is already mapped when this method is invoked, - * this method will return the previously mapped pointer. The - * access mode can only be changed by calling unmap() first. - * - * The specified access mode cannot be kAccessMode_None. - */ - virtual uint8_t* map(AccessMode mode = kAccessMode_ReadWrite) = 0; - - /** - * Returns the current access mode for this buffer. If the buffer - * is not mapped, this method returns kAccessMode_None. - */ - AccessMode getAccessMode() const { return mAccessMode; } - - /** - * Upload the specified rectangle of this pixel buffer as a - * GL_TEXTURE_2D texture. Calling this method will trigger - * an unmap() if necessary. - */ - virtual void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height, int offset) = 0; - - /** - * Upload the specified rectangle of this pixel buffer as a - * GL_TEXTURE_2D texture. Calling this method will trigger - * an unmap() if necessary. - * - * This is a convenience function provided to save callers the - * trouble of computing the offset parameter. - */ - void upload(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { - upload(x, y, width, height, getOffset(x, y)); - } - - /** - * Returns the width of the render buffer in pixels. - */ - uint32_t getWidth() const { return mWidth; } - - /** - * Returns the height of the render buffer in pixels. - */ - uint32_t getHeight() const { return mHeight; } - - /** - * Returns the size of this pixel buffer in bytes. - */ - uint32_t getSize() const { return mWidth * mHeight * formatSize(mFormat); } - - /** - * Returns the offset of a pixel in this pixel buffer, in bytes. - */ - uint32_t getOffset(uint32_t x, uint32_t y) const { - return (y * mWidth + x) * formatSize(mFormat); - } - - /** - * Returns the number of bytes per pixel in the specified format. - * - * Supported formats: - * GL_ALPHA - * GL_RGBA - */ - static uint32_t formatSize(GLenum format) { - switch (format) { - case GL_ALPHA: - return 1; - case GL_RGBA: - return 4; - } - return 0; - } - - /** - * Returns the alpha channel offset in the specified format. - * - * Supported formats: - * GL_ALPHA - * GL_RGBA - */ - static uint32_t formatAlphaOffset(GLenum format) { - switch (format) { - case GL_ALPHA: - return 0; - case GL_RGBA: - return 3; - } - - ALOGE("unsupported format: %d", format); - return 0; - } - -protected: - /** - * Creates a new render buffer in the specified format and dimensions. - * The format must be GL_ALPHA or GL_RGBA. - */ - PixelBuffer(GLenum format, uint32_t width, uint32_t height) - : mFormat(format), mWidth(width), mHeight(height), mAccessMode(kAccessMode_None) {} - - /** - * Unmaps this buffer, if needed. After the buffer is unmapped, - * the pointer previously returned by map() becomes invalid and - * should not be used. - */ - virtual void unmap() = 0; - - GLenum mFormat; - - uint32_t mWidth; - uint32_t mHeight; - - AccessMode mAccessMode; - -}; // class PixelBuffer - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_PIXEL_BUFFER_H diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp index 16966619aace..70ca4e3e8074 100644 --- a/libs/hwui/ProfileData.cpp +++ b/libs/hwui/ProfileData.cpp @@ -104,8 +104,8 @@ void ProfileData::dump(int fd) const { dprintf(fd, "\nStats since: %" PRIu64 "ns", mStatStartTime); dprintf(fd, "\nTotal frames rendered: %u", mTotalFrameCount); dprintf(fd, "\nJanky frames: %u (%.2f%%)", mJankFrameCount, - mTotalFrameCount == 0 ? 0.0f : - (float)mJankFrameCount / (float)mTotalFrameCount * 100.0f); + mTotalFrameCount == 0 ? 0.0f + : (float)mJankFrameCount / (float)mTotalFrameCount * 100.0f); dprintf(fd, "\n50th percentile: %ums", findPercentile(50)); dprintf(fd, "\n90th percentile: %ums", findPercentile(90)); dprintf(fd, "\n95th percentile: %ums", findPercentile(95)); diff --git a/libs/hwui/ProfileRenderer.cpp b/libs/hwui/ProfileRenderer.cpp deleted file mode 100644 index 8a00ffa54c58..000000000000 --- a/libs/hwui/ProfileRenderer.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 "ProfileRenderer.h" - -namespace android { -namespace uirenderer { - -void ProfileRenderer::drawRect(float left, float top, float right, float bottom, - const SkPaint& paint) { - mRenderer.drawRect(left, top, right, bottom, &paint); -} - -void ProfileRenderer::drawRects(const float* rects, int count, const SkPaint& paint) { - mRenderer.drawRects(rects, count, &paint); -} - -uint32_t ProfileRenderer::getViewportWidth() { - return mRenderer.getViewportWidth(); -} - -uint32_t ProfileRenderer::getViewportHeight() { - return mRenderer.getViewportHeight(); -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/ProfileRenderer.h b/libs/hwui/ProfileRenderer.h deleted file mode 100644 index 5c8bb2529fb4..000000000000 --- a/libs/hwui/ProfileRenderer.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 "IProfileRenderer.h" - -#include "BakedOpRenderer.h" - -namespace android { -namespace uirenderer { - -class ProfileRenderer : public IProfileRenderer { -public: - ProfileRenderer(BakedOpRenderer& renderer) : mRenderer(renderer) {} - - void drawRect(float left, float top, float right, float bottom, const SkPaint& paint) override; - void drawRects(const float* rects, int count, const SkPaint& paint) override; - uint32_t getViewportWidth() override; - uint32_t getViewportHeight() override; - - virtual ~ProfileRenderer() {} - -private: - BakedOpRenderer& mRenderer; -}; - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp deleted file mode 100644 index 052798b9cea9..000000000000 --- a/libs/hwui/Program.cpp +++ /dev/null @@ -1,199 +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 <utils/Trace.h> - -#include "Program.h" -#include "Vertex.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Base program -/////////////////////////////////////////////////////////////////////////////// - -Program::Program(const ProgramDescription& description, const char* vertex, const char* fragment) { - mInitialized = false; - mHasColorUniform = false; - mHasSampler = false; - mUse = false; - - // No need to cache compiled shaders, rely instead on Android's - // persistent shaders cache - mVertexShader = buildShader(vertex, GL_VERTEX_SHADER); - if (mVertexShader) { - mFragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER); - if (mFragmentShader) { - mProgramId = glCreateProgram(); - - glAttachShader(mProgramId, mVertexShader); - glAttachShader(mProgramId, mFragmentShader); - - bindAttrib("position", kBindingPosition); - if (description.hasTexture || description.hasExternalTexture) { - texCoords = bindAttrib("texCoords", kBindingTexCoords); - } else { - texCoords = -1; - } - - ATRACE_BEGIN("linkProgram"); - glLinkProgram(mProgramId); - ATRACE_END(); - - GLint status; - glGetProgramiv(mProgramId, GL_LINK_STATUS, &status); - if (status != GL_TRUE) { - GLint infoLen = 0; - glGetProgramiv(mProgramId, GL_INFO_LOG_LENGTH, &infoLen); - if (infoLen > 1) { - GLchar log[infoLen]; - glGetProgramInfoLog(mProgramId, infoLen, nullptr, &log[0]); - ALOGE("%s", log); - } - LOG_ALWAYS_FATAL("Error while linking shaders"); - } else { - mInitialized = true; - } - } else { - glDeleteShader(mVertexShader); - } - } - - if (mInitialized) { - transform = addUniform("transform"); - projection = addUniform("projection"); - } -} - -Program::~Program() { - if (mInitialized) { - // This would ideally happen after linking the program - // but Tegra drivers, especially when perfhud is enabled, - // sometimes crash if we do so - glDetachShader(mProgramId, mVertexShader); - glDetachShader(mProgramId, mFragmentShader); - - glDeleteShader(mVertexShader); - glDeleteShader(mFragmentShader); - - glDeleteProgram(mProgramId); - } -} - -int Program::addAttrib(const char* name) { - int slot = glGetAttribLocation(mProgramId, name); - mAttributes.add(name, slot); - return slot; -} - -int Program::bindAttrib(const char* name, ShaderBindings bindingSlot) { - glBindAttribLocation(mProgramId, bindingSlot, name); - mAttributes.add(name, bindingSlot); - return bindingSlot; -} - -int Program::getAttrib(const char* name) { - ssize_t index = mAttributes.indexOfKey(name); - if (index >= 0) { - return mAttributes.valueAt(index); - } - return addAttrib(name); -} - -int Program::addUniform(const char* name) { - int slot = glGetUniformLocation(mProgramId, name); - mUniforms.add(name, slot); - return slot; -} - -int Program::getUniform(const char* name) { - ssize_t index = mUniforms.indexOfKey(name); - if (index >= 0) { - return mUniforms.valueAt(index); - } - return addUniform(name); -} - -GLuint Program::buildShader(const char* source, GLenum type) { - ATRACE_NAME("Build GL Shader"); - - GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &source, nullptr); - glCompileShader(shader); - - GLint status; - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (status != GL_TRUE) { - ALOGE("Error while compiling this shader:\n===\n%s\n===", source); - // Some drivers return wrong values for GL_INFO_LOG_LENGTH - // use a fixed size instead - GLchar log[512]; - glGetShaderInfoLog(shader, sizeof(log), nullptr, &log[0]); - LOG_ALWAYS_FATAL("Shader info log: %s", log); - return 0; - } - - return shader; -} - -void Program::set(const mat4& projectionMatrix, const mat4& modelViewMatrix, - const mat4& transformMatrix, bool offset) { - if (projectionMatrix != mProjection || offset != mOffset) { - if (CC_LIKELY(!offset)) { - glUniformMatrix4fv(projection, 1, GL_FALSE, &projectionMatrix.data[0]); - } else { - mat4 p(projectionMatrix); - // offset screenspace xy by an amount that compensates for typical precision - // issues in GPU hardware that tends to paint hor/vert lines in pixels shifted - // up and to the left. - // This offset value is based on an assumption that some hardware may use as - // little as 12.4 precision, so we offset by slightly more than 1/16. - p.translate(Vertex::GeometryFudgeFactor(), Vertex::GeometryFudgeFactor()); - glUniformMatrix4fv(projection, 1, GL_FALSE, &p.data[0]); - } - mProjection = projectionMatrix; - mOffset = offset; - } - - mat4 t(transformMatrix); - t.multiply(modelViewMatrix); - glUniformMatrix4fv(transform, 1, GL_FALSE, &t.data[0]); -} - -void Program::setColor(FloatColor color) { - if (!mHasColorUniform) { - mColorUniform = getUniform("color"); - mHasColorUniform = true; - } - glUniform4f(mColorUniform, color.r, color.g, color.b, color.a); -} - -void Program::use() { - glUseProgram(mProgramId); - if (texCoords >= 0 && !mHasSampler) { - glUniform1i(getUniform("baseSampler"), 0); - mHasSampler = true; - } - mUse = true; -} - -void Program::remove() { - mUse = false; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h deleted file mode 100644 index dcc2bd410ebd..000000000000 --- a/libs/hwui/Program.h +++ /dev/null @@ -1,446 +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_PROGRAM_H -#define ANDROID_HWUI_PROGRAM_H - -#include <utils/KeyedVector.h> - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -#include <SkBlendMode.h> - -#include "Debug.h" -#include "FloatColor.h" -#include "Matrix.h" -#include "Properties.h" -#include "utils/Color.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -// Debug -#if DEBUG_PROGRAMS -#define PROGRAM_LOGD(...) ALOGD(__VA_ARGS__) -#else -#define PROGRAM_LOGD(...) -#endif - -#define COLOR_COMPONENT_THRESHOLD 1.0f -#define COLOR_COMPONENT_INV_THRESHOLD 0.0f - -#define PROGRAM_KEY_TEXTURE 0x01 -#define PROGRAM_KEY_A8_TEXTURE 0x02 -#define PROGRAM_KEY_BITMAP 0x04 -#define PROGRAM_KEY_GRADIENT 0x08 -#define PROGRAM_KEY_BITMAP_FIRST 0x10 -#define PROGRAM_KEY_COLOR_MATRIX 0x20 -#define PROGRAM_KEY_COLOR_BLEND 0x40 -#define PROGRAM_KEY_BITMAP_NPOT 0x80 -#define PROGRAM_KEY_BITMAP_EXTERNAL 0x100 - -#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600 -#define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800 - -#define PROGRAM_KEY_SWAP_SRC_DST_SHIFT 13 - -// Encode the xfermodes on 6 bits -#define PROGRAM_MAX_XFERMODE 0x1f -#define PROGRAM_XFERMODE_SHADER_SHIFT 26 -#define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20 -#define PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT 14 - -#define PROGRAM_BITMAP_WRAPS_SHIFT 9 -#define PROGRAM_BITMAP_WRAPT_SHIFT 11 - -#define PROGRAM_GRADIENT_TYPE_SHIFT 33 // 2 bits for gradient type -#define PROGRAM_MODULATE_SHIFT 35 - -#define PROGRAM_HAS_VERTEX_ALPHA_SHIFT 36 -#define PROGRAM_USE_SHADOW_ALPHA_INTERP_SHIFT 37 - -#define PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT 38 -#define PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT 39 - -#define PROGRAM_IS_SIMPLE_GRADIENT 40 - -#define PROGRAM_HAS_COLORS 41 - -#define PROGRAM_HAS_DEBUG_HIGHLIGHT 42 -#define PROGRAM_HAS_ROUND_RECT_CLIP 43 - -#define PROGRAM_HAS_GAMMA_CORRECTION 44 -#define PROGRAM_HAS_LINEAR_TEXTURE 45 - -#define PROGRAM_HAS_COLOR_SPACE_CONVERSION 46 -#define PROGRAM_TRANSFER_FUNCTION 47 // 2 bits for transfer function -#define PROGRAM_HAS_TRANSLUCENT_CONVERSION 49 - -/////////////////////////////////////////////////////////////////////////////// -// Types -/////////////////////////////////////////////////////////////////////////////// - -typedef uint64_t programid; - -/////////////////////////////////////////////////////////////////////////////// -// Program description -/////////////////////////////////////////////////////////////////////////////// - -/** - * Describe the features required for a given program. The features - * determine the generation of both the vertex and fragment shaders. - * A ProgramDescription must be used in conjunction with a ProgramCache. - */ -struct ProgramDescription { - enum class ColorFilterMode : int8_t { None = 0, Matrix, Blend }; - - enum Gradient : int8_t { kGradientLinear = 0, kGradientCircular, kGradientSweep }; - - ProgramDescription() { reset(); } - - // Texturing - bool hasTexture; - bool hasAlpha8Texture; - bool hasExternalTexture; - bool hasTextureTransform; - - // Color attribute - bool hasColors; - - // Modulate, this should only be set when setColor() return true - bool modulate; - - // Shaders - bool hasBitmap; - bool isShaderBitmapExternal; - bool useShaderBasedWrap; - - bool hasVertexAlpha; - bool useShadowAlphaInterp; - - bool hasGradient; - Gradient gradientType; - bool isSimpleGradient; - - SkBlendMode shadersMode; - - bool isBitmapFirst; - GLenum bitmapWrapS; - GLenum bitmapWrapT; - - // Color operations - ColorFilterMode colorOp; - SkBlendMode colorMode; - - // Framebuffer blending (requires Extensions.hasFramebufferFetch()) - // Ignored for all values < SkBlendMode::kPlus - SkBlendMode framebufferMode; - bool swapSrcDst; - - bool hasDebugHighlight; - bool hasRoundRectClip; - - // Extra gamma correction used for text - bool hasGammaCorrection; - // Set when sampling an image in linear space - bool hasLinearTexture; - - bool hasColorSpaceConversion; - TransferFunctionType transferFunction; - // Indicates whether the bitmap to convert between color spaces is translucent - bool hasTranslucentConversion; - - /** - * Resets this description. All fields are reset back to the default - * values they hold after building a new instance. - */ - void reset() { - hasTexture = false; - hasAlpha8Texture = false; - hasExternalTexture = false; - hasTextureTransform = false; - - hasColors = false; - - hasVertexAlpha = false; - useShadowAlphaInterp = false; - - modulate = false; - - hasBitmap = false; - isShaderBitmapExternal = false; - useShaderBasedWrap = false; - - hasGradient = false; - gradientType = kGradientLinear; - isSimpleGradient = false; - - shadersMode = SkBlendMode::kClear; - - isBitmapFirst = false; - bitmapWrapS = GL_CLAMP_TO_EDGE; - bitmapWrapT = GL_CLAMP_TO_EDGE; - - colorOp = ColorFilterMode::None; - colorMode = SkBlendMode::kClear; - - framebufferMode = SkBlendMode::kClear; - swapSrcDst = false; - - hasDebugHighlight = false; - hasRoundRectClip = false; - - hasGammaCorrection = false; - hasLinearTexture = false; - - hasColorSpaceConversion = false; - transferFunction = TransferFunctionType::None; - hasTranslucentConversion = false; - } - - /** - * Indicates, for a given color, whether color modulation is required in - * the fragment shader. When this method returns true, the program should - * be provided with a modulation color. - */ - bool setColorModulate(const float a) { - modulate = a < COLOR_COMPONENT_THRESHOLD; - return modulate; - } - - /** - * Indicates, for a given color, whether color modulation is required in - * the fragment shader. When this method returns true, the program should - * be provided with a modulation color. - */ - bool setAlpha8ColorModulate(const float r, const float g, const float b, const float a) { - modulate = a < COLOR_COMPONENT_THRESHOLD || r > COLOR_COMPONENT_INV_THRESHOLD || - g > COLOR_COMPONENT_INV_THRESHOLD || b > COLOR_COMPONENT_INV_THRESHOLD; - return modulate; - } - - /** - * Computes the unique key identifying this program. - */ - programid key() const { - programid key = 0; - if (hasTexture) key |= PROGRAM_KEY_TEXTURE; - if (hasAlpha8Texture) key |= PROGRAM_KEY_A8_TEXTURE; - if (hasBitmap) { - key |= PROGRAM_KEY_BITMAP; - if (useShaderBasedWrap) { - key |= PROGRAM_KEY_BITMAP_NPOT; - key |= getEnumForWrap(bitmapWrapS) << PROGRAM_BITMAP_WRAPS_SHIFT; - key |= getEnumForWrap(bitmapWrapT) << PROGRAM_BITMAP_WRAPT_SHIFT; - } - if (isShaderBitmapExternal) { - key |= PROGRAM_KEY_BITMAP_EXTERNAL; - } - } - if (hasGradient) key |= PROGRAM_KEY_GRADIENT; - key |= programid(gradientType) << PROGRAM_GRADIENT_TYPE_SHIFT; - if (isBitmapFirst) key |= PROGRAM_KEY_BITMAP_FIRST; - if (hasBitmap && hasGradient) { - key |= ((int)shadersMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_SHADER_SHIFT; - } - switch (colorOp) { - case ColorFilterMode::Matrix: - key |= PROGRAM_KEY_COLOR_MATRIX; - break; - case ColorFilterMode::Blend: - key |= PROGRAM_KEY_COLOR_BLEND; - key |= ((int)colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT; - break; - case ColorFilterMode::None: - break; - } - key |= ((int)framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT; - key |= programid(swapSrcDst) << PROGRAM_KEY_SWAP_SRC_DST_SHIFT; - key |= programid(modulate) << PROGRAM_MODULATE_SHIFT; - key |= programid(hasVertexAlpha) << PROGRAM_HAS_VERTEX_ALPHA_SHIFT; - key |= programid(useShadowAlphaInterp) << PROGRAM_USE_SHADOW_ALPHA_INTERP_SHIFT; - key |= programid(hasExternalTexture) << PROGRAM_HAS_EXTERNAL_TEXTURE_SHIFT; - key |= programid(hasTextureTransform) << PROGRAM_HAS_TEXTURE_TRANSFORM_SHIFT; - key |= programid(isSimpleGradient) << PROGRAM_IS_SIMPLE_GRADIENT; - key |= programid(hasColors) << PROGRAM_HAS_COLORS; - key |= programid(hasDebugHighlight) << PROGRAM_HAS_DEBUG_HIGHLIGHT; - key |= programid(hasRoundRectClip) << PROGRAM_HAS_ROUND_RECT_CLIP; - key |= programid(hasGammaCorrection) << PROGRAM_HAS_GAMMA_CORRECTION; - key |= programid(hasLinearTexture) << PROGRAM_HAS_LINEAR_TEXTURE; - key |= programid(hasColorSpaceConversion) << PROGRAM_HAS_COLOR_SPACE_CONVERSION; - key |= programid(transferFunction) << PROGRAM_TRANSFER_FUNCTION; - key |= programid(hasTranslucentConversion) << PROGRAM_HAS_TRANSLUCENT_CONVERSION; - return key; - } - - /** - * Logs the specified message followed by the key identifying this program. - */ - void log(const char* message) const { -#if DEBUG_PROGRAMS - programid k = key(); - PROGRAM_LOGD("%s (key = 0x%.8x%.8x)", message, uint32_t(k >> 32), uint32_t(k & 0xffffffff)); -#endif - } - -private: - static inline uint32_t getEnumForWrap(GLenum wrap) { - switch (wrap) { - case GL_CLAMP_TO_EDGE: - return 0; - case GL_REPEAT: - return 1; - case GL_MIRRORED_REPEAT: - return 2; - } - return 0; - } - -}; // struct ProgramDescription - -/** - * A program holds a vertex and a fragment shader. It offers several utility - * methods to query attributes and uniforms. - */ -class Program { -public: - enum ShaderBindings { kBindingPosition, kBindingTexCoords }; - - /** - * Creates a new program with the specified vertex and fragment - * shaders sources. - */ - Program(const ProgramDescription& description, const char* vertex, const char* fragment); - virtual ~Program(); - - /** - * Binds this program to the GL context. - */ - virtual void use(); - - /** - * Marks this program as unused. This will not unbind - * the program from the GL context. - */ - virtual void remove(); - - /** - * Returns the OpenGL name of the specified attribute. - */ - int getAttrib(const char* name); - - /** - * Returns the OpenGL name of the specified uniform. - */ - int getUniform(const char* name); - - /** - * Indicates whether this program is currently in use with - * the GL context. - */ - inline bool isInUse() const { return mUse; } - - /** - * Indicates whether this program was correctly compiled and linked. - */ - inline bool isInitialized() const { return mInitialized; } - - /** - * Binds the program with the specified projection, modelView and - * transform matrices. - */ - void set(const mat4& projectionMatrix, const mat4& modelViewMatrix, const mat4& transformMatrix, - bool offset = false); - - /** - * Sets the color associated with this shader. - */ - void setColor(FloatColor color); - - /** - * Name of the texCoords attribute if it exists (kBindingTexCoords), -1 otherwise. - */ - int texCoords; - - /** - * Name of the transform uniform. - */ - int transform; - - /** - * Name of the projection uniform. - */ - int projection; - -protected: - /** - * Adds an attribute with the specified name. - * - * @return The OpenGL name of the attribute. - */ - int addAttrib(const char* name); - - /** - * Binds the specified attribute name to the specified slot. - */ - int bindAttrib(const char* name, ShaderBindings bindingSlot); - - /** - * Adds a uniform with the specified name. - * - * @return The OpenGL name of the uniform. - */ - int addUniform(const char* name); - -private: - /** - * Compiles the specified shader of the specified type. - * - * @return The name of the compiled shader. - */ - GLuint buildShader(const char* source, GLenum type); - - // Name of the OpenGL program and shaders - GLuint mProgramId; - GLuint mVertexShader; - GLuint mFragmentShader; - - // Keeps track of attributes and uniforms slots - KeyedVector<const char*, int> mAttributes; - KeyedVector<const char*, int> mUniforms; - - bool mUse; - bool mInitialized; - - // Uniforms caching - bool mHasColorUniform; - int mColorUniform; - - bool mHasSampler; - - mat4 mProjection; - bool mOffset; -}; // class Program - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_PROGRAM_H diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp deleted file mode 100644 index 1164ebfdf1e5..000000000000 --- a/libs/hwui/ProgramCache.cpp +++ /dev/null @@ -1,865 +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 <utils/String8.h> - -#include "Caches.h" -#include "ProgramCache.h" -#include "Properties.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -#define MODULATE_OP_NO_MODULATE 0 -#define MODULATE_OP_MODULATE 1 -#define MODULATE_OP_MODULATE_A8 2 - -#define STR(x) STR1(x) -#define STR1(x) #x - -/////////////////////////////////////////////////////////////////////////////// -// Vertex shaders snippets -/////////////////////////////////////////////////////////////////////////////// - -const char* gVS_Header_Start = - "#version 100\n" - "attribute vec4 position;\n"; -const char* gVS_Header_Attributes_TexCoords = "attribute vec2 texCoords;\n"; -const char* gVS_Header_Attributes_Colors = "attribute vec4 colors;\n"; -const char* gVS_Header_Attributes_VertexAlphaParameters = "attribute float vtxAlpha;\n"; -const char* gVS_Header_Uniforms_TextureTransform = "uniform mat4 mainTextureTransform;\n"; -const char* gVS_Header_Uniforms = - "uniform mat4 projection;\n" - "uniform mat4 transform;\n"; -const char* gVS_Header_Uniforms_HasGradient = "uniform mat4 screenSpace;\n"; -const char* gVS_Header_Uniforms_HasBitmap = - "uniform mat4 textureTransform;\n" - "uniform mediump vec2 textureDimension;\n"; -const char* gVS_Header_Uniforms_HasRoundRectClip = - "uniform mat4 roundRectInvTransform;\n" - "uniform mediump vec4 roundRectInnerRectLTWH;\n" - "uniform mediump float roundRectRadius;\n"; -const char* gVS_Header_Varyings_HasTexture = "varying vec2 outTexCoords;\n"; -const char* gVS_Header_Varyings_HasColors = "varying vec4 outColors;\n"; -const char* gVS_Header_Varyings_HasVertexAlpha = "varying float alpha;\n"; -const char* gVS_Header_Varyings_HasBitmap = "varying highp vec2 outBitmapTexCoords;\n"; -const char* gVS_Header_Varyings_HasGradient[6] = { - // Linear - "varying highp vec2 linear;\n", "varying float linear;\n", - - // Circular - "varying highp vec2 circular;\n", "varying highp vec2 circular;\n", - - // Sweep - "varying highp vec2 sweep;\n", "varying highp vec2 sweep;\n", -}; -const char* gVS_Header_Varyings_HasRoundRectClip = "varying mediump vec2 roundRectPos;\n"; -const char* gVS_Main = "\nvoid main(void) {\n"; -const char* gVS_Main_OutTexCoords = " outTexCoords = texCoords;\n"; -const char* gVS_Main_OutColors = " outColors = colors;\n"; -const char* gVS_Main_OutTransformedTexCoords = - " outTexCoords = (mainTextureTransform * vec4(texCoords, 0.0, 1.0)).xy;\n"; -const char* gVS_Main_OutGradient[6] = { - // Linear - " linear = vec2((screenSpace * position).x, 0.5);\n", - " linear = (screenSpace * position).x;\n", - - // Circular - " circular = (screenSpace * position).xy;\n", - " circular = (screenSpace * position).xy;\n", - - // Sweep - " sweep = (screenSpace * position).xy;\n", " sweep = (screenSpace * position).xy;\n"}; -const char* gVS_Main_OutBitmapTexCoords = - " outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n"; -const char* gVS_Main_Position = - " vec4 transformedPosition = projection * transform * position;\n" - " gl_Position = transformedPosition;\n"; - -const char* gVS_Main_VertexAlpha = " alpha = vtxAlpha;\n"; - -const char* gVS_Main_HasRoundRectClip = - " roundRectPos = ((roundRectInvTransform * transformedPosition).xy / roundRectRadius) - " - "roundRectInnerRectLTWH.xy;\n"; -const char* gVS_Footer = "}\n\n"; - -/////////////////////////////////////////////////////////////////////////////// -// 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 = - "#extension GL_OES_EGL_image_external : require\n\n"; -const char* gFS_Header = "precision mediump float;\n\n"; -const char* gFS_Uniforms_Color = "uniform vec4 color;\n"; -const char* gFS_Uniforms_TextureSampler = "uniform sampler2D baseSampler;\n"; -const char* gFS_Uniforms_ExternalTextureSampler = "uniform samplerExternalOES baseSampler;\n"; -const char* gFS_Uniforms_GradientSampler[2] = { - "uniform vec2 screenSize;\n" - "uniform sampler2D gradientSampler;\n", - - "uniform vec2 screenSize;\n" - "uniform vec4 startColor;\n" - "uniform vec4 endColor;\n"}; -const char* gFS_Uniforms_BitmapSampler = "uniform sampler2D bitmapSampler;\n"; -const char* gFS_Uniforms_BitmapExternalSampler = "uniform samplerExternalOES bitmapSampler;\n"; -const char* gFS_Uniforms_ColorOp[3] = { - // None - "", - // Matrix - "uniform mat4 colorMatrix;\n" - "uniform vec4 colorMatrixVector;\n", - // PorterDuff - "uniform vec4 colorBlend;\n"}; - -const char* gFS_Uniforms_HasRoundRectClip = - "uniform mediump vec4 roundRectInnerRectLTWH;\n" - "uniform mediump float roundRectRadius;\n"; - -const char* gFS_Uniforms_ColorSpaceConversion = - // TODO: Should we use a 3D LUT to combine the matrix and transfer functions? - // 32x32x32 fp16 LUTs (for scRGB output) are large and heavy to generate... - "uniform mat3 colorSpaceMatrix;\n"; - -const char* gFS_Uniforms_TransferFunction[4] = { - // In this order: g, a, b, c, d, e, f - // See ColorSpace::TransferParameters - // We'll use hardware sRGB conversion as much as possible - "", "uniform float transferFunction[7];\n", "uniform float transferFunction[5];\n", - "uniform float transferFunctionGamma;\n"}; - -const char* gFS_OETF[2] = { - R"__SHADER__( - vec4 OETF(const vec4 linear) { - return linear; - } - )__SHADER__", - // We expect linear data to be scRGB so we mirror the gamma function - R"__SHADER__( - vec4 OETF(const vec4 linear) { - return vec4(sign(linear.rgb) * OETF_sRGB(abs(linear.rgb)), linear.a); - } - )__SHADER__"}; - -const char* gFS_ColorConvert[3] = { - // Just OETF - R"__SHADER__( - vec4 colorConvert(const vec4 color) { - return OETF(color); - } - )__SHADER__", - // Full color conversion for opaque bitmaps - R"__SHADER__( - vec4 colorConvert(const vec4 color) { - return OETF(vec4(colorSpaceMatrix * EOTF_Parametric(color.rgb), color.a)); - } - )__SHADER__", - // Full color conversion for translucent bitmaps - // Note: 0.5/256=0.0019 - R"__SHADER__( - vec4 colorConvert(in vec4 color) { - color.rgb /= color.a + 0.0019; - color = OETF(vec4(colorSpaceMatrix * EOTF_Parametric(color.rgb), color.a)); - color.rgb *= color.a + 0.0019; - return color; - } - )__SHADER__", -}; - -const char* gFS_sRGB_TransferFunctions = R"__SHADER__( - float OETF_sRGB(const float linear) { - // IEC 61966-2-1:1999 - return linear <= 0.0031308 ? linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055; - } - - vec3 OETF_sRGB(const vec3 linear) { - return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b)); - } - - float EOTF_sRGB(float srgb) { - // IEC 61966-2-1:1999 - return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4); - } -)__SHADER__"; - -const char* gFS_TransferFunction[4] = { - // Conversion done by the texture unit (sRGB) - R"__SHADER__( - vec3 EOTF_Parametric(const vec3 x) { - return x; - } - )__SHADER__", - // Full transfer function - // TODO: We should probably use a 1D LUT (256x1 with texelFetch() since input is 8 bit) - // TODO: That would cause 3 dependent texture fetches. Is it worth it? - R"__SHADER__( - float EOTF_Parametric(float x) { - return x <= transferFunction[4] - ? transferFunction[3] * x + transferFunction[6] - : pow(transferFunction[1] * x + transferFunction[2], transferFunction[0]) - + transferFunction[5]; - } - - vec3 EOTF_Parametric(const vec3 x) { - return vec3(EOTF_Parametric(x.r), EOTF_Parametric(x.g), EOTF_Parametric(x.b)); - } - )__SHADER__", - // Limited transfer function, e = f = 0.0 - R"__SHADER__( - float EOTF_Parametric(float x) { - return x <= transferFunction[4] - ? transferFunction[3] * x - : pow(transferFunction[1] * x + transferFunction[2], transferFunction[0]); - } - - vec3 EOTF_Parametric(const vec3 x) { - return vec3(EOTF_Parametric(x.r), EOTF_Parametric(x.g), EOTF_Parametric(x.b)); - } - )__SHADER__", - // Gamma transfer function, e = f = 0.0 - R"__SHADER__( - vec3 EOTF_Parametric(const vec3 x) { - return vec3(pow(x.r, transferFunctionGamma), - pow(x.g, transferFunctionGamma), - pow(x.b, transferFunctionGamma)); - } - )__SHADER__"}; - -// Dithering must be done in the quantization space -// When we are writing to an sRGB framebuffer, we must do the following: -// EOTF(OETF(color) + dither) -// The dithering pattern is generated with a triangle noise generator in the range [-1.0,1.0] -// TODO: Handle linear fp16 render targets -const char* gFS_GradientFunctions = R"__SHADER__( - float triangleNoise(const highp vec2 n) { - highp vec2 p = fract(n * vec2(5.3987, 5.4421)); - p += dot(p.yx, p.xy + vec2(21.5351, 14.3137)); - highp float xy = p.x * p.y; - return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0; - } -)__SHADER__"; - -const char* gFS_GradientPreamble[2] = { - // Linear framebuffer - R"__SHADER__( - vec4 dither(const vec4 color) { - return color + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0); - } - )__SHADER__", - // sRGB framebuffer - R"__SHADER__( - vec4 dither(const vec4 color) { - vec3 dithered = sqrt(color.rgb) + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0); - return vec4(dithered * dithered, color.a); - } - )__SHADER__", -}; - -// Uses luminance coefficients from Rec.709 to choose the appropriate gamma -// The gamma() function assumes that bright text will be displayed on a dark -// background and that dark text will be displayed on bright background -// The gamma coefficient is chosen to thicken or thin the text accordingly -// The dot product used to compute the luminance could be approximated with -// a simple max(color.r, color.g, color.b) -const char* gFS_Gamma_Preamble = R"__SHADER__( - #define GAMMA (%.2f) - #define GAMMA_INV (%.2f) - - float gamma(float a, const vec3 color) { - float luminance = dot(color, vec3(0.2126, 0.7152, 0.0722)); - return pow(a, luminance < 0.5 ? GAMMA_INV : GAMMA); - } -)__SHADER__"; - -const char* gFS_Main = - "\nvoid main(void) {\n" - " vec4 fragColor;\n"; - -const char* gFS_Main_AddDither = " fragColor = dither(fragColor);\n"; - -// General case -const char* gFS_Main_FetchColor = " fragColor = color;\n"; -const char* gFS_Main_ModulateColor = " fragColor *= color.a;\n"; -const char* gFS_Main_ApplyVertexAlphaLinearInterp = " fragColor *= alpha;\n"; -const char* gFS_Main_ApplyVertexAlphaShadowInterp = - // map alpha through shadow alpha sampler - " fragColor *= texture2D(baseSampler, vec2(alpha, 0.5)).a;\n"; -const char* gFS_Main_FetchTexture[2] = { - // Don't modulate - " fragColor = colorConvert(texture2D(baseSampler, outTexCoords));\n", - // Modulate - " fragColor = color * colorConvert(texture2D(baseSampler, outTexCoords));\n"}; -const char* gFS_Main_FetchA8Texture[4] = { - // Don't modulate - " fragColor = texture2D(baseSampler, outTexCoords);\n", - " fragColor = texture2D(baseSampler, outTexCoords);\n", - // Modulate - " fragColor = color * texture2D(baseSampler, outTexCoords).a;\n", - " fragColor = color * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n", -}; -const char* gFS_Main_FetchGradient[6] = { - // Linear - " vec4 gradientColor = texture2D(gradientSampler, linear);\n", - - " vec4 gradientColor = mix(startColor, endColor, clamp(linear, 0.0, 1.0));\n", - - // Circular - " vec4 gradientColor = texture2D(gradientSampler, vec2(length(circular), 0.5));\n", - - " vec4 gradientColor = mix(startColor, endColor, clamp(length(circular), 0.0, 1.0));\n", - - // Sweep - " highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n" - " vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n", - - " highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n" - " vec4 gradientColor = mix(startColor, endColor, clamp(index - floor(index), 0.0, " - "1.0));\n"}; -const char* gFS_Main_FetchBitmap = - " vec4 bitmapColor = colorConvert(texture2D(bitmapSampler, outBitmapTexCoords));\n"; -const char* gFS_Main_FetchBitmapNpot = - " vec4 bitmapColor = colorConvert(texture2D(bitmapSampler, " - "wrap(outBitmapTexCoords)));\n"; -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] = { - // Don't modulate - ";\n", ";\n", - // Modulate - " * color.a;\n", " * color.a;\n", - // Modulate with alpha 8 texture - " * texture2D(baseSampler, outTexCoords).a;\n", - " * gamma(texture2D(baseSampler, outTexCoords).a, color.rgb);\n", -}; -const char* gFS_Main_GradientShader_Modulate[6] = { - // 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 * gamma(texture2D(baseSampler, outTexCoords).a, " - "gradientColor.rgb);\n", -}; -const char* gFS_Main_BitmapShader_Modulate[6] = { - // 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 * gamma(texture2D(baseSampler, outTexCoords).a, " - "bitmapColor.rgb);\n", -}; -const char* gFS_Main_FragColor = " gl_FragColor = fragColor;\n"; -const char* gFS_Main_FragColor_HasColors = " gl_FragColor *= outColors;\n"; -const char* gFS_Main_FragColor_Blend = - " gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n"; -const char* gFS_Main_FragColor_Blend_Swap = - " gl_FragColor = blendFramebuffer(gl_LastFragColor, fragColor);\n"; -const char* gFS_Main_ApplyColorOp[3] = { - // None - "", - // Matrix - " fragColor.rgb /= (fragColor.a + 0.0019);\n" // un-premultiply - " fragColor *= colorMatrix;\n" - " fragColor += colorMatrixVector;\n" - " fragColor.rgb *= (fragColor.a + 0.0019);\n", // re-premultiply - // PorterDuff - " fragColor = blendColors(colorBlend, fragColor);\n"}; - -// Note: LTWH (left top width height) -> xyzw -// roundRectPos is now divided by roundRectRadius in vertex shader -// after we also subtract roundRectInnerRectLTWH.xy from roundRectPos -const char* gFS_Main_FragColor_HasRoundRectClip = - " mediump vec2 fragToLT = -roundRectPos;\n" - " mediump vec2 fragFromRB = roundRectPos - roundRectInnerRectLTWH.zw;\n" - - // since distance is divided by radius, it's in [0;1] so precision is not an issue - // this also lets us clamp(0.0, 1.0) instead of max() which is cheaper on GPUs - " mediump vec2 dist = clamp(max(fragToLT, fragFromRB), 0.0, 1.0);\n" - " mediump float linearDist = clamp(roundRectRadius - (length(dist) * roundRectRadius), " - "0.0, 1.0);\n" - " gl_FragColor *= linearDist;\n"; - -const char* gFS_Main_DebugHighlight = " gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n"; -const char* gFS_Footer = "}\n\n"; - -/////////////////////////////////////////////////////////////////////////////// -// PorterDuff snippets -/////////////////////////////////////////////////////////////////////////////// - -const char* gBlendOps[18] = { - // Clear - "return vec4(0.0, 0.0, 0.0, 0.0);\n", - // Src - "return src;\n", - // Dst - "return dst;\n", - // SrcOver - "return src + dst * (1.0 - src.a);\n", - // DstOver - "return dst + src * (1.0 - dst.a);\n", - // SrcIn - "return src * dst.a;\n", - // DstIn - "return dst * src.a;\n", - // SrcOut - "return src * (1.0 - dst.a);\n", - // DstOut - "return dst * (1.0 - src.a);\n", - // SrcAtop - "return vec4(src.rgb * dst.a + (1.0 - src.a) * dst.rgb, dst.a);\n", - // DstAtop - "return vec4(dst.rgb * src.a + (1.0 - dst.a) * src.rgb, src.a);\n", - // Xor - "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb, " - "src.a + dst.a - 2.0 * src.a * dst.a);\n", - // Plus - "return min(src + dst, 1.0);\n", - // Modulate - "return src * dst;\n", - // Screen - "return src + dst - src * dst;\n", - // Overlay - "return clamp(vec4(mix(" - "2.0 * src.rgb * dst.rgb + src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a), " - "src.a * dst.a - 2.0 * (dst.a - dst.rgb) * (src.a - src.rgb) + src.rgb * (1.0 - dst.a) + " - "dst.rgb * (1.0 - src.a), " - "step(dst.a, 2.0 * dst.rgb)), " - "src.a + dst.a - src.a * dst.a), 0.0, 1.0);\n", - // Darken - "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb + " - "min(src.rgb * dst.a, dst.rgb * src.a), src.a + dst.a - src.a * dst.a);\n", - // Lighten - "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb + " - "max(src.rgb * dst.a, dst.rgb * src.a), src.a + dst.a - src.a * dst.a);\n", -}; - -/////////////////////////////////////////////////////////////////////////////// -// Constructors/destructors -/////////////////////////////////////////////////////////////////////////////// - -ProgramCache::ProgramCache(const Extensions& extensions) - : mHasES3(extensions.getMajorGlVersion() >= 3) - , mHasLinearBlending(extensions.hasLinearBlending()) {} - -ProgramCache::~ProgramCache() { - clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Cache management -/////////////////////////////////////////////////////////////////////////////// - -void ProgramCache::clear() { - PROGRAM_LOGD("Clearing program cache"); - mCache.clear(); -} - -Program* ProgramCache::get(const ProgramDescription& description) { - programid key = description.key(); - if (key == (PROGRAM_KEY_TEXTURE | PROGRAM_KEY_A8_TEXTURE)) { - // program for A8, unmodulated, texture w/o shader (black text/path textures) is equivalent - // to standard texture program (bitmaps, patches). Consider them equivalent. - key = PROGRAM_KEY_TEXTURE; - } - - auto iter = mCache.find(key); - Program* program = nullptr; - if (iter == mCache.end()) { - description.log("Could not find program"); - program = generateProgram(description, key); - mCache[key] = std::unique_ptr<Program>(program); - } else { - program = iter->second.get(); - } - return program; -} - -/////////////////////////////////////////////////////////////////////////////// -// Program generation -/////////////////////////////////////////////////////////////////////////////// - -Program* ProgramCache::generateProgram(const ProgramDescription& description, programid key) { - String8 vertexShader = generateVertexShader(description); - String8 fragmentShader = generateFragmentShader(description); - - return new Program(description, vertexShader.string(), fragmentShader.string()); -} - -static inline size_t gradientIndex(const ProgramDescription& description) { - return description.gradientType * 2 + description.isSimpleGradient; -} - -String8 ProgramCache::generateVertexShader(const ProgramDescription& description) { - // Add attributes - String8 shader(gVS_Header_Start); - if (description.hasTexture || description.hasExternalTexture) { - shader.append(gVS_Header_Attributes_TexCoords); - } - if (description.hasVertexAlpha) { - shader.append(gVS_Header_Attributes_VertexAlphaParameters); - } - if (description.hasColors) { - shader.append(gVS_Header_Attributes_Colors); - } - // Uniforms - shader.append(gVS_Header_Uniforms); - if (description.hasTextureTransform) { - shader.append(gVS_Header_Uniforms_TextureTransform); - } - if (description.hasGradient) { - shader.append(gVS_Header_Uniforms_HasGradient); - } - if (description.hasBitmap) { - shader.append(gVS_Header_Uniforms_HasBitmap); - } - if (description.hasRoundRectClip) { - shader.append(gVS_Header_Uniforms_HasRoundRectClip); - } - // Varyings - if (description.hasTexture || description.hasExternalTexture) { - shader.append(gVS_Header_Varyings_HasTexture); - } - if (description.hasVertexAlpha) { - shader.append(gVS_Header_Varyings_HasVertexAlpha); - } - if (description.hasColors) { - shader.append(gVS_Header_Varyings_HasColors); - } - if (description.hasGradient) { - shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]); - } - if (description.hasBitmap) { - shader.append(gVS_Header_Varyings_HasBitmap); - } - if (description.hasRoundRectClip) { - shader.append(gVS_Header_Varyings_HasRoundRectClip); - } - - // Begin the shader - shader.append(gVS_Main); - { - if (description.hasTextureTransform) { - shader.append(gVS_Main_OutTransformedTexCoords); - } else if (description.hasTexture || description.hasExternalTexture) { - shader.append(gVS_Main_OutTexCoords); - } - if (description.hasVertexAlpha) { - shader.append(gVS_Main_VertexAlpha); - } - if (description.hasColors) { - shader.append(gVS_Main_OutColors); - } - if (description.hasBitmap) { - shader.append(gVS_Main_OutBitmapTexCoords); - } - // Output transformed position - shader.append(gVS_Main_Position); - if (description.hasGradient) { - shader.append(gVS_Main_OutGradient[gradientIndex(description)]); - } - if (description.hasRoundRectClip) { - shader.append(gVS_Main_HasRoundRectClip); - } - } - // End the shader - shader.append(gVS_Footer); - - PROGRAM_LOGD("*** Generated vertex shader:\n\n%s", shader.string()); - - return shader; -} - -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(gFS_Header_Start); - - const bool blendFramebuffer = description.framebufferMode >= SkBlendMode::kPlus; - if (blendFramebuffer) { - shader.append(gFS_Header_Extension_FramebufferFetch); - } - if (description.hasExternalTexture || - (description.hasBitmap && description.isShaderBitmapExternal)) { - shader.append(gFS_Header_Extension_ExternalTexture); - } - - shader.append(gFS_Header); - - // Varyings - if (description.hasTexture || description.hasExternalTexture) { - shader.append(gVS_Header_Varyings_HasTexture); - } - if (description.hasVertexAlpha) { - shader.append(gVS_Header_Varyings_HasVertexAlpha); - } - if (description.hasColors) { - shader.append(gVS_Header_Varyings_HasColors); - } - if (description.hasGradient) { - shader.append(gVS_Header_Varyings_HasGradient[gradientIndex(description)]); - } - if (description.hasBitmap) { - shader.append(gVS_Header_Varyings_HasBitmap); - } - if (description.hasRoundRectClip) { - shader.append(gVS_Header_Varyings_HasRoundRectClip); - } - - // Uniforms - int modulateOp = MODULATE_OP_NO_MODULATE; - const bool singleColor = !description.hasTexture && !description.hasExternalTexture && - !description.hasGradient && !description.hasBitmap; - - if (description.modulate || singleColor) { - shader.append(gFS_Uniforms_Color); - if (!singleColor) modulateOp = MODULATE_OP_MODULATE; - } - if (description.hasTexture || description.useShadowAlphaInterp) { - shader.append(gFS_Uniforms_TextureSampler); - } else if (description.hasExternalTexture) { - shader.append(gFS_Uniforms_ExternalTextureSampler); - } - if (description.hasGradient) { - shader.append(gFS_Uniforms_GradientSampler[description.isSimpleGradient]); - } - if (description.hasRoundRectClip) { - shader.append(gFS_Uniforms_HasRoundRectClip); - } - - if (description.hasGammaCorrection) { - shader.appendFormat(gFS_Gamma_Preamble, Properties::textGamma, - 1.0f / Properties::textGamma); - } - - if (description.hasBitmap) { - if (description.isShaderBitmapExternal) { - shader.append(gFS_Uniforms_BitmapExternalSampler); - } else { - shader.append(gFS_Uniforms_BitmapSampler); - } - } - shader.append(gFS_Uniforms_ColorOp[static_cast<int>(description.colorOp)]); - - if (description.hasColorSpaceConversion) { - shader.append(gFS_Uniforms_ColorSpaceConversion); - } - shader.append(gFS_Uniforms_TransferFunction[static_cast<int>(description.transferFunction)]); - - // Generate required functions - if (description.hasGradient && description.hasBitmap) { - generateBlend(shader, "blendShaders", description.shadersMode); - } - if (description.colorOp == ProgramDescription::ColorFilterMode::Blend) { - generateBlend(shader, "blendColors", description.colorMode); - } - if (blendFramebuffer) { - generateBlend(shader, "blendFramebuffer", description.framebufferMode); - } - if (description.useShaderBasedWrap) { - generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT); - } - if (description.hasGradient || description.hasLinearTexture || - description.hasColorSpaceConversion) { - shader.append(gFS_sRGB_TransferFunctions); - } - if (description.hasBitmap || ((description.hasTexture || description.hasExternalTexture) && - !description.hasAlpha8Texture)) { - shader.append(gFS_TransferFunction[static_cast<int>(description.transferFunction)]); - shader.append( - gFS_OETF[(description.hasLinearTexture || description.hasColorSpaceConversion) && - !mHasLinearBlending]); - shader.append(gFS_ColorConvert[description.hasColorSpaceConversion - ? 1 + description.hasTranslucentConversion - : 0]); - } - if (description.hasGradient) { - shader.append(gFS_GradientFunctions); - shader.append(gFS_GradientPreamble[mHasLinearBlending]); - } - - // Begin the shader - shader.append(gFS_Main); - { - // Stores the result in fragColor directly - if (description.hasTexture || description.hasExternalTexture) { - if (description.hasAlpha8Texture) { - if (!description.hasGradient && !description.hasBitmap) { - shader.append(gFS_Main_FetchA8Texture[modulateOp * 2 + - description.hasGammaCorrection]); - } - } else { - shader.append(gFS_Main_FetchTexture[modulateOp]); - } - } else { - if (!description.hasGradient && !description.hasBitmap) { - shader.append(gFS_Main_FetchColor); - } - } - if (description.hasGradient) { - shader.append(gFS_Main_FetchGradient[gradientIndex(description)]); - } - if (description.hasBitmap) { - if (!description.useShaderBasedWrap) { - shader.append(gFS_Main_FetchBitmap); - } else { - shader.append(gFS_Main_FetchBitmapNpot); - } - } - bool applyModulate = false; - // Case when we have two shaders set - if (description.hasGradient && description.hasBitmap) { - if (description.isBitmapFirst) { - shader.append(gFS_Main_BlendShadersBG); - } else { - shader.append(gFS_Main_BlendShadersGB); - } - applyModulate = - shaderOp(description, shader, modulateOp, gFS_Main_BlendShaders_Modulate); - } else { - if (description.hasGradient) { - applyModulate = - shaderOp(description, shader, modulateOp, gFS_Main_GradientShader_Modulate); - } else if (description.hasBitmap) { - applyModulate = - shaderOp(description, shader, modulateOp, gFS_Main_BitmapShader_Modulate); - } - } - - if (description.modulate && applyModulate) { - shader.append(gFS_Main_ModulateColor); - } - - // Apply the color op if needed - shader.append(gFS_Main_ApplyColorOp[static_cast<int>(description.colorOp)]); - - if (description.hasVertexAlpha) { - if (description.useShadowAlphaInterp) { - shader.append(gFS_Main_ApplyVertexAlphaShadowInterp); - } else { - shader.append(gFS_Main_ApplyVertexAlphaLinearInterp); - } - } - - if (description.hasGradient) { - shader.append(gFS_Main_AddDither); - } - - // Output the fragment - if (!blendFramebuffer) { - shader.append(gFS_Main_FragColor); - } else { - shader.append(!description.swapSrcDst ? gFS_Main_FragColor_Blend - : gFS_Main_FragColor_Blend_Swap); - } - if (description.hasColors) { - shader.append(gFS_Main_FragColor_HasColors); - } - if (description.hasRoundRectClip) { - shader.append(gFS_Main_FragColor_HasRoundRectClip); - } - if (description.hasDebugHighlight) { - shader.append(gFS_Main_DebugHighlight); - } - } - // End the shader - shader.append(gFS_Footer); - -#if DEBUG_PROGRAMS - PROGRAM_LOGD("*** Generated fragment shader:\n\n"); - printLongString(shader); -#endif - - return shader; -} - -void ProgramCache::generateBlend(String8& shader, const char* name, SkBlendMode mode) { - shader.append("\nvec4 "); - shader.append(name); - shader.append("(vec4 src, vec4 dst) {\n"); - shader.append(" "); - shader.append(gBlendOps[(int)mode]); - shader.append("}\n"); -} - -void ProgramCache::generateTextureWrap(String8& shader, GLenum wrapS, GLenum wrapT) { - shader.append("\nhighp vec2 wrap(highp vec2 texCoords) {\n"); - if (wrapS == GL_MIRRORED_REPEAT) { - shader.append(" highp float xMod2 = mod(texCoords.x, 2.0);\n"); - shader.append(" if (xMod2 > 1.0) xMod2 = 2.0 - xMod2;\n"); - } - if (wrapT == GL_MIRRORED_REPEAT) { - shader.append(" highp float yMod2 = mod(texCoords.y, 2.0);\n"); - shader.append(" if (yMod2 > 1.0) yMod2 = 2.0 - yMod2;\n"); - } - shader.append(" return vec2("); - switch (wrapS) { - case GL_CLAMP_TO_EDGE: - shader.append("texCoords.x"); - break; - case GL_REPEAT: - shader.append("mod(texCoords.x, 1.0)"); - break; - case GL_MIRRORED_REPEAT: - shader.append("xMod2"); - break; - } - shader.append(", "); - switch (wrapT) { - case GL_CLAMP_TO_EDGE: - shader.append("texCoords.y"); - break; - case GL_REPEAT: - shader.append("mod(texCoords.y, 1.0)"); - break; - case GL_MIRRORED_REPEAT: - shader.append("yMod2"); - break; - } - shader.append(");\n"); - shader.append("}\n"); -} - -void ProgramCache::printLongString(const String8& shader) const { - ssize_t index = 0; - ssize_t lastIndex = 0; - const char* str = shader.string(); - while ((index = shader.find("\n", index)) > -1) { - String8 line(str, index - lastIndex); - if (line.length() == 0) line.append("\n"); - ALOGD("%s", line.string()); - index++; - str += (index - lastIndex); - lastIndex = index; - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h deleted file mode 100644 index 488a4994ba95..000000000000 --- a/libs/hwui/ProgramCache.h +++ /dev/null @@ -1,68 +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_PROGRAM_CACHE_H -#define ANDROID_HWUI_PROGRAM_CACHE_H - -#include <utils/KeyedVector.h> -#include <utils/Log.h> -#include <utils/String8.h> -#include <map> - -#include <GLES2/gl2.h> - -#include "Debug.h" -#include "Program.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Cache -/////////////////////////////////////////////////////////////////////////////// - -/** - * Generates and caches program. Programs are generated based on - * ProgramDescriptions. - */ -class ProgramCache { -public: - explicit ProgramCache(const Extensions& extensions); - ~ProgramCache(); - - Program* get(const ProgramDescription& description); - - void clear(); - -private: - Program* generateProgram(const ProgramDescription& description, programid key); - String8 generateVertexShader(const ProgramDescription& description); - String8 generateFragmentShader(const ProgramDescription& description); - void generateBlend(String8& shader, const char* name, SkBlendMode mode); - void generateTextureWrap(String8& shader, GLenum wrapS, GLenum wrapT); - - void printLongString(const String8& shader) const; - - std::map<programid, std::unique_ptr<Program>> mCache; - - const bool mHasES3; - const bool mHasLinearBlending; -}; // class ProgramCache - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_PROGRAM_CACHE_H diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index 0a6c45beedf9..3f2c616eb8ff 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -17,6 +17,7 @@ #include "Properties.h" #include "Debug.h" #include "DeviceInfo.h" +#include "SkTraceEventCommon.h" #include <algorithm> #include <cstdlib> @@ -59,6 +60,8 @@ bool Properties::forceDrawFrame = false; bool Properties::filterOutTestOverhead = false; bool Properties::disableVsync = false; bool Properties::skpCaptureEnabled = false; +bool Properties::forceDarkMode = false; +bool Properties::enableForceDarkSupport = true; bool Properties::enableRTAnimations = true; bool Properties::runningInEmulator = false; @@ -140,8 +143,15 @@ bool Properties::load() { skpCaptureEnabled = debuggingEnabled && property_get_bool(PROPERTY_CAPTURE_SKP_ENABLED, false); + SkAndroidFrameworkTraceUtil::setEnableTracing( + property_get_bool(PROPERTY_SKIA_ATRACE_ENABLED, false)); + runningInEmulator = property_get_bool(PROPERTY_QEMU_KERNEL, false); + forceDarkMode = property_get_bool(PROPERTY_FORCE_DARK, false); + + enableForceDarkSupport = property_get_bool(PROPERTY_ENABLE_FORCE_DARK, true); + return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) || (prevDebugStencilClip != debugStencilClip); } @@ -191,15 +201,12 @@ RenderPipelineType Properties::getRenderPipelineType() { } char prop[PROPERTY_VALUE_MAX]; property_get(PROPERTY_RENDERER, prop, "skiagl"); - if (!strcmp(prop, "skiagl")) { - ALOGD("Skia GL Pipeline"); - sRenderPipelineType = RenderPipelineType::SkiaGL; - } else if (!strcmp(prop, "skiavk")) { + if (!strcmp(prop, "skiavk")) { ALOGD("Skia Vulkan Pipeline"); sRenderPipelineType = RenderPipelineType::SkiaVulkan; - } else { //"opengl" - ALOGD("HWUI GL Pipeline"); - sRenderPipelineType = RenderPipelineType::OpenGL; + } else { //"skiagl" + ALOGD("Skia GL Pipeline"); + sRenderPipelineType = RenderPipelineType::SkiaGL; } return sRenderPipelineType; } @@ -216,10 +223,5 @@ void Properties::overrideRenderPipelineType(RenderPipelineType type) { sRenderPipelineType = type; } -bool Properties::isSkiaEnabled() { - auto renderType = getRenderPipelineType(); - return RenderPipelineType::SkiaGL == renderType || RenderPipelineType::SkiaVulkan == renderType; -} - }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 764c50259540..542bc71f7c72 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -171,6 +171,11 @@ enum DebugLevel { #define PROPERTY_CAPTURE_SKP_ENABLED "debug.hwui.capture_skp_enabled" /** + * Allows to record Skia drawing commands with systrace. + */ +#define PROPERTY_SKIA_ATRACE_ENABLED "debug.hwui.skia_atrace_enabled" + +/** * Defines how many frames in a sequence to capture. */ #define PROPERTY_CAPTURE_SKP_FRAMES "debug.hwui.capture_skp_frames" @@ -185,6 +190,10 @@ enum DebugLevel { */ #define PROPERTY_QEMU_KERNEL "ro.kernel.qemu" +#define PROPERTY_FORCE_DARK "debug.hwui.force_dark" + +#define PROPERTY_ENABLE_FORCE_DARK "debug.hwui.force_dark_enabled" + /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -200,7 +209,7 @@ enum class OverdrawColorSet { Default = 0, Deuteranomaly }; enum class StencilClipDebug { Hide, ShowHighlight, ShowRegion }; -enum class RenderPipelineType { OpenGL = 0, SkiaGL, SkiaVulkan, NotInitialized = 128 }; +enum class RenderPipelineType { SkiaGL, SkiaVulkan, NotInitialized = 128 }; /** * Renderthread-only singleton which manages several static rendering properties. Most of these @@ -240,7 +249,6 @@ public: static ProfileType getProfileType(); ANDROID_API static RenderPipelineType getRenderPipelineType(); - static bool isSkiaEnabled(); ANDROID_API static bool enableHighContrastText; @@ -259,6 +267,8 @@ public: static bool disableVsync; static bool skpCaptureEnabled; + static bool forceDarkMode; + static bool enableForceDarkSupport; // For experimentation b/68769804 ANDROID_API static bool enableRTAnimations; diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp new file mode 100644 index 000000000000..80f2b5714659 --- /dev/null +++ b/libs/hwui/Readback.cpp @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Readback.h" + +#include "pipeline/skia/LayerDrawable.h" +#include "renderthread/EglManager.h" +#include "renderthread/VulkanManager.h" + +#include <SkToSRGBColorFilter.h> +#include <gui/Surface.h> +#include <ui/Fence.h> +#include <ui/GraphicBuffer.h> +#include "DeferredLayerUpdater.h" +#include "Properties.h" +#include "hwui/Bitmap.h" +#include "utils/Color.h" +#include "utils/MathUtils.h" + +using namespace android::uirenderer::renderthread; + +namespace android { +namespace uirenderer { + +CopyResult Readback::copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap) { + ATRACE_CALL(); + // Setup the source + sp<GraphicBuffer> sourceBuffer; + sp<Fence> sourceFence; + Matrix4 texTransform; + status_t err = surface.getLastQueuedBuffer(&sourceBuffer, &sourceFence, texTransform.data); + texTransform.invalidateType(); + if (err != NO_ERROR) { + ALOGW("Failed to get last queued buffer, error = %d", err); + return CopyResult::UnknownError; + } + if (!sourceBuffer.get()) { + ALOGW("Surface doesn't have any previously queued frames, nothing to readback from"); + return CopyResult::SourceEmpty; + } + if (sourceBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) { + ALOGW("Surface is protected, unable to copy from it"); + return CopyResult::SourceInvalid; + } + err = sourceFence->wait(500 /* ms */); + if (err != NO_ERROR) { + ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt"); + return CopyResult::Timeout; + } + if (!sourceBuffer.get()) { + return CopyResult::UnknownError; + } + + sk_sp<SkColorSpace> colorSpace = + DataSpaceToColorSpace(static_cast<android_dataspace>(surface.getBuffersDataSpace())); + sk_sp<SkColorFilter> colorSpaceFilter; + if (colorSpace && !colorSpace->isSRGB()) { + colorSpaceFilter = SkToSRGBColorFilter::Make(colorSpace); + } + sk_sp<SkImage> image = SkImage::MakeFromAHardwareBuffer( + reinterpret_cast<AHardwareBuffer*>(sourceBuffer.get()), kPremul_SkAlphaType); + return copyImageInto(image, colorSpaceFilter, texTransform, srcRect, bitmap); +} + +CopyResult Readback::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { + LOG_ALWAYS_FATAL_IF(!hwBitmap->isHardware()); + + Rect srcRect; + Matrix4 transform; + transform.loadScale(1, -1, 1); + transform.translate(0, -1); + + // TODO: Try to take and reuse the image inside HW bitmap with "hwBitmap->makeImage". + // TODO: When this was attempted, it resulted in instability. + sk_sp<SkColorFilter> colorSpaceFilter; + sk_sp<SkColorSpace> colorSpace = hwBitmap->info().refColorSpace(); + if (colorSpace && !colorSpace->isSRGB()) { + colorSpaceFilter = SkToSRGBColorFilter::Make(colorSpace); + } + sk_sp<SkImage> image = SkImage::MakeFromAHardwareBuffer( + reinterpret_cast<AHardwareBuffer*>(hwBitmap->graphicBuffer()), kPremul_SkAlphaType); + + // HW Bitmap currently can only attach to a GraphicBuffer with PIXEL_FORMAT_RGBA_8888 format + // and SRGB color space. ImageDecoder can create a new HW Bitmap with non-SRGB color space: for + // example see android.graphics.cts.BitmapColorSpaceTest#testEncodeP3hardware test. + return copyImageInto(image, colorSpaceFilter, transform, srcRect, bitmap); +} + +CopyResult Readback::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) { + if (!mRenderThread.getGrContext()) { + return CopyResult::UnknownError; + } + + // acquire most recent buffer for drawing + deferredLayer->updateTexImage(); + deferredLayer->apply(); + const SkRect dstRect = SkRect::MakeIWH(bitmap->width(), bitmap->height()); + CopyResult copyResult = CopyResult::UnknownError; + Layer* layer = deferredLayer->backingLayer(); + if (layer) { + if (copyLayerInto(layer, nullptr, &dstRect, bitmap)) { + copyResult = CopyResult::Success; + } + } + return copyResult; +} + +CopyResult Readback::copyImageInto(const sk_sp<SkImage>& image, + sk_sp<SkColorFilter>& colorSpaceFilter, Matrix4& texTransform, + const Rect& srcRect, SkBitmap* bitmap) { + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { + mRenderThread.requireGlContext(); + } else { + mRenderThread.vulkanManager().initialize(); + } + if (!image.get()) { + return CopyResult::UnknownError; + } + int imgWidth = image->width(); + int imgHeight = image->height(); + sk_sp<GrContext> grContext = sk_ref_sp(mRenderThread.getGrContext()); + + if (bitmap->colorType() == kRGBA_F16_SkColorType && + !grContext->colorTypeSupportedAsSurface(bitmap->colorType())) { + ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported"); + return CopyResult::DestinationInvalid; + } + + CopyResult copyResult = CopyResult::UnknownError; + + int displayedWidth = imgWidth, displayedHeight = imgHeight; + // If this is a 90 or 270 degree rotation we need to swap width/height to get the device + // size. + if (texTransform[Matrix4::kSkewX] >= 0.5f || texTransform[Matrix4::kSkewX] <= -0.5f) { + std::swap(displayedWidth, displayedHeight); + } + SkRect skiaDestRect = SkRect::MakeWH(bitmap->width(), bitmap->height()); + SkRect skiaSrcRect = srcRect.toSkRect(); + if (skiaSrcRect.isEmpty()) { + skiaSrcRect = SkRect::MakeIWH(displayedWidth, displayedHeight); + } + bool srcNotEmpty = skiaSrcRect.intersect(SkRect::MakeIWH(displayedWidth, displayedHeight)); + if (!srcNotEmpty) { + return copyResult; + } + + // See Readback::copyLayerInto for an overview of color space conversion. + // HW Bitmap are allowed to be in a non-SRGB color space (for example coming from ImageDecoder). + // For Surface and HW Bitmap readback flows we pass colorSpaceFilter, which does the conversion. + // TextureView readback is using Layer::setDataSpace, which creates a SkColorFilter internally. + Layer layer(mRenderThread.renderState(), colorSpaceFilter, 255, SkBlendMode::kSrc); + bool disableFilter = MathUtils::areEqual(skiaSrcRect.width(), skiaDestRect.width()) && + MathUtils::areEqual(skiaSrcRect.height(), skiaDestRect.height()); + layer.setForceFilter(!disableFilter); + layer.setSize(displayedWidth, displayedHeight); + texTransform.copyTo(layer.getTexTransform()); + layer.setImage(image); + if (copyLayerInto(&layer, &skiaSrcRect, &skiaDestRect, bitmap)) { + copyResult = CopyResult::Success; + } + + return copyResult; +} + +bool Readback::copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect, + SkBitmap* bitmap) { + /* + * In the past only TextureView readback was setting the temporary surface color space to null. + * Now all 3 readback flows are drawing into a SkSurface with null color space. + * At readback there are 3 options to convert the source image color space to the destination + * color space requested in "bitmap->info().colorSpace()": + * 1. Set color space for temporary surface render target to null (disables color management), + * colorspace tag from source SkImage is ignored by Skia, + * convert SkImage to SRGB at draw time with SkColorFilter/SkToSRGBColorFilter, + * do a readback from temporary SkSurface to a temporary SRGB SkBitmap "bitmap2", + * read back from SRGB "bitmap2" into non-SRGB "bitmap" which will do a CPU color conversion. + * + * 2. Set color space for temporary surface render target to SRGB (not nullptr), + * colorspace tag on the source SkImage is used by Skia to enable conversion, + * convert SkImage to SRGB at draw time with drawImage (no filters), + * do a readback from temporary SkSurface, which will do a color conversion from SRGB to + * bitmap->info().colorSpace() on the CPU. + * + * 3. Set color space for temporary surface render target to bitmap->info().colorSpace(), + * colorspace tag on the source SkImage is used by Skia to enable conversion, + * convert SkImage to bitmap->info().colorSpace() at draw time with drawImage (no filters), + * do a readback from SkSurface, which will not do any color conversion, because + * surface was created with the same color space as the "bitmap". + * + * Option 1 is used for all readback flows. + * Options 2 and 3 are new, because skia added support for non-SRGB render targets without + * linear blending. + * TODO: evaluate if options 2 or 3 for color space conversion are better. + */ + + // drop the colorSpace from the temporary surface. + SkImageInfo surfaceInfo = bitmap->info().makeColorSpace(nullptr); + + /* This intermediate surface is present to work around a bug in SwiftShader that + * prevents us from reading the contents of the layer's texture directly. The + * workaround involves first rendering that texture into an intermediate buffer and + * then reading from the intermediate buffer into the bitmap. + * Another reason to render in an offscreen buffer is to scale and to avoid an issue b/62262733 + * with reading incorrect data from EGLImage backed SkImage (likely a driver bug). + */ + sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), + SkBudgeted::kYes, surfaceInfo); + + if (!tmpSurface.get()) { + surfaceInfo = surfaceInfo.makeColorType(SkColorType::kN32_SkColorType); + tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), SkBudgeted::kYes, + surfaceInfo); + if (!tmpSurface.get()) { + ALOGW("Unable to readback GPU contents into the provided bitmap"); + return false; + } + } + + if (skiapipeline::LayerDrawable::DrawLayer(mRenderThread.getGrContext(), + tmpSurface->getCanvas(), layer, srcRect, dstRect, + false)) { + // If bitmap->info().colorSpace() is non-SRGB, convert the data from SRGB to non-SRGB on + // CPU. We can't just pass bitmap->info() to SkSurface::readPixels, because "tmpSurface" has + // disabled color conversion. + SkColorSpace* destColorSpace = bitmap->info().colorSpace(); + SkBitmap tempSRGBBitmap; + SkBitmap tmpN32Bitmap; + SkBitmap* bitmapInSRGB; + if (destColorSpace && !destColorSpace->isSRGB()) { + tempSRGBBitmap.allocPixels(bitmap->info().makeColorSpace(SkColorSpace::MakeSRGB())); + bitmapInSRGB = &tempSRGBBitmap; // Need to convert latter from SRGB to non-SRGB. + } else { + bitmapInSRGB = bitmap; // No need for color conversion - write directly into output. + } + bool success = false; + + // TODO: does any of the readbacks below clamp F16 exSRGB? + // Readback into a SRGB SkBitmap. + if (tmpSurface->readPixels(bitmapInSRGB->info(), bitmapInSRGB->getPixels(), + bitmapInSRGB->rowBytes(), 0, 0)) { + success = true; + } else { + // if we fail to readback from the GPU directly (e.g. 565) then we attempt to read into + // 8888 and then convert that into the destination format before giving up. + SkImageInfo bitmapInfo = + SkImageInfo::MakeN32(bitmap->width(), bitmap->height(), bitmap->alphaType(), + SkColorSpace::MakeSRGB()); + if (tmpN32Bitmap.tryAllocPixels(bitmapInfo) && + tmpSurface->readPixels(bitmapInfo, tmpN32Bitmap.getPixels(), + tmpN32Bitmap.rowBytes(), 0, 0)) { + success = true; + bitmapInSRGB = &tmpN32Bitmap; + } + } + + if (success) { + if (bitmapInSRGB != bitmap) { + // Convert from SRGB to non-SRGB color space if needed. Convert from N32 to + // destination bitmap color format if needed. + if (!bitmapInSRGB->readPixels(bitmap->info(), bitmap->getPixels(), + bitmap->rowBytes(), 0, 0)) { + return false; + } + } + bitmap->notifyPixelsChanged(); + return true; + } + } + + return false; +} + +} /* namespace uirenderer */ +} /* namespace android */ diff --git a/libs/hwui/Readback.h b/libs/hwui/Readback.h index ad3a8b690617..d9e10cedc0e8 100644 --- a/libs/hwui/Readback.h +++ b/libs/hwui/Readback.h @@ -16,16 +16,21 @@ #pragma once +#include "Matrix.h" #include "Rect.h" #include "renderthread/RenderThread.h" #include <SkBitmap.h> namespace android { +class Bitmap; class GraphicBuffer; class Surface; namespace uirenderer { +class DeferredLayerUpdater; +class Layer; + // Keep in sync with PixelCopy.java codes enum class CopyResult { Success = 0, @@ -38,15 +43,22 @@ enum class CopyResult { class Readback { public: + explicit Readback(renderthread::RenderThread& thread) : mRenderThread(thread) {} /** * Copies the surface's most recently queued buffer into the provided bitmap. */ - virtual CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap) = 0; - virtual CopyResult copyGraphicBufferInto(GraphicBuffer* graphicBuffer, SkBitmap* bitmap) = 0; + CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect, SkBitmap* bitmap); -protected: - explicit Readback(renderthread::RenderThread& thread) : mRenderThread(thread) {} - virtual ~Readback() {} + CopyResult copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap); + + CopyResult copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); + +private: + CopyResult copyImageInto(const sk_sp<SkImage>& image, sk_sp<SkColorFilter>& colorSpaceFilter, + Matrix4& texTransform, const Rect& srcRect, SkBitmap* bitmap); + + bool copyLayerInto(Layer* layer, const SkRect* srcRect, const SkRect* dstRect, + SkBitmap* bitmap); renderthread::RenderThread& mRenderThread; }; diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h deleted file mode 100644 index 2d0185aaa9e2..000000000000 --- a/libs/hwui/RecordedOp.h +++ /dev/null @@ -1,500 +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. - */ - -#pragma once - -#include "GlLayer.h" -#include "Matrix.h" -#include "Rect.h" -#include "RenderNode.h" -#include "TessellationCache.h" -#include "Vector.h" -#include "font/FontUtil.h" -#include "utils/LinearAllocator.h" -#include "utils/PaintUtils.h" - -#include <androidfw/ResourceTypes.h> - -class SkBitmap; -class SkPaint; - -namespace android { -namespace uirenderer { - -struct ClipBase; -class OffscreenBuffer; -class RenderNode; -class DeferredLayerUpdater; - -struct Vertex; - -namespace VectorDrawable { -class Tree; -} - -/** - * Authoritative op list, used for generating the op ID enum, ID based LUTS, and - * the functions to which they dispatch. Parameter macros are executed for each op, - * in order, based on the op's type. - * - * There are 4 types of op, which defines dispatch/LUT capability: - * - * | DisplayList | Render | Merge | - * -------------|-------------|-------------|-------------| - * PRE RENDER | Yes | | | - * RENDER ONLY | | Yes | | - * UNMERGEABLE | Yes | Yes | | - * MERGEABLE | Yes | Yes | Yes | - * - * PRE RENDER - These ops are recorded into DisplayLists, but can't be directly rendered. This - * may be because they need to be transformed into other op types (e.g. CirclePropsOp), - * be traversed to access multiple renderable ops within (e.g. RenderNodeOp), or because they - * modify renderbuffer lifecycle, instead of directly rendering content (the various LayerOps). - * - * RENDER ONLY - These ops cannot be recorded into DisplayLists, and are instead implicitly - * constructed from other commands/RenderNode properties. They cannot be merged. - * - * UNMERGEABLE - These ops can be recorded into DisplayLists and rendered directly, but do not - * support merged rendering. - * - * MERGEABLE - These ops can be recorded into DisplayLists and rendered individually, or merged - * under certain circumstances. - */ -#define MAP_OPS_BASED_ON_TYPE(PRE_RENDER_OP_FN, RENDER_ONLY_OP_FN, UNMERGEABLE_OP_FN, \ - MERGEABLE_OP_FN) \ - PRE_RENDER_OP_FN(RenderNodeOp) \ - PRE_RENDER_OP_FN(CirclePropsOp) \ - PRE_RENDER_OP_FN(RoundRectPropsOp) \ - PRE_RENDER_OP_FN(BeginLayerOp) \ - PRE_RENDER_OP_FN(EndLayerOp) \ - PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \ - PRE_RENDER_OP_FN(EndUnclippedLayerOp) \ - PRE_RENDER_OP_FN(VectorDrawableOp) \ - \ - RENDER_ONLY_OP_FN(ShadowOp) \ - RENDER_ONLY_OP_FN(LayerOp) \ - RENDER_ONLY_OP_FN(CopyToLayerOp) \ - RENDER_ONLY_OP_FN(CopyFromLayerOp) \ - \ - UNMERGEABLE_OP_FN(ArcOp) \ - UNMERGEABLE_OP_FN(BitmapMeshOp) \ - UNMERGEABLE_OP_FN(BitmapRectOp) \ - UNMERGEABLE_OP_FN(ColorOp) \ - UNMERGEABLE_OP_FN(FunctorOp) \ - UNMERGEABLE_OP_FN(LinesOp) \ - UNMERGEABLE_OP_FN(OvalOp) \ - UNMERGEABLE_OP_FN(PathOp) \ - UNMERGEABLE_OP_FN(PointsOp) \ - UNMERGEABLE_OP_FN(RectOp) \ - UNMERGEABLE_OP_FN(RoundRectOp) \ - UNMERGEABLE_OP_FN(SimpleRectsOp) \ - UNMERGEABLE_OP_FN(TextOnPathOp) \ - UNMERGEABLE_OP_FN(TextureLayerOp) \ - \ - MERGEABLE_OP_FN(BitmapOp) \ - MERGEABLE_OP_FN(PatchOp) \ - MERGEABLE_OP_FN(TextOp) - -/** - * LUT generators, which will insert nullptr for unsupported ops - */ -#define NULLPTR_OP_FN(Type) nullptr, - -#define BUILD_DEFERRABLE_OP_LUT(OP_FN) \ - { MAP_OPS_BASED_ON_TYPE(OP_FN, NULLPTR_OP_FN, OP_FN, OP_FN) } - -#define BUILD_MERGEABLE_OP_LUT(OP_FN) \ - { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, NULLPTR_OP_FN, NULLPTR_OP_FN, OP_FN) } - -#define BUILD_RENDERABLE_OP_LUT(OP_FN) \ - { MAP_OPS_BASED_ON_TYPE(NULLPTR_OP_FN, OP_FN, OP_FN, OP_FN) } - -#define BUILD_FULL_OP_LUT(OP_FN) \ - { MAP_OPS_BASED_ON_TYPE(OP_FN, OP_FN, OP_FN, OP_FN) } - -/** - * Op mapping functions, which skip unsupported ops. - * - * Note: Do not use for LUTS, since these do not preserve ID order. - */ -#define NULL_OP_FN(Type) - -#define MAP_DEFERRABLE_OPS(OP_FN) MAP_OPS_BASED_ON_TYPE(OP_FN, NULL_OP_FN, OP_FN, OP_FN) - -#define MAP_MERGEABLE_OPS(OP_FN) MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, NULL_OP_FN, NULL_OP_FN, OP_FN) - -#define MAP_RENDERABLE_OPS(OP_FN) MAP_OPS_BASED_ON_TYPE(NULL_OP_FN, OP_FN, OP_FN, OP_FN) - -// Generate OpId enum -#define IDENTITY_FN(Type) Type, -namespace RecordedOpId { -enum { - MAP_OPS_BASED_ON_TYPE(IDENTITY_FN, IDENTITY_FN, IDENTITY_FN, IDENTITY_FN) Count, -}; -} -static_assert(RecordedOpId::RenderNodeOp == 0, "First index must be zero for LUTs to work"); - -#define BASE_PARAMS \ - const Rect &unmappedBounds, const Matrix4 &localMatrix, const ClipBase *localClip, \ - const SkPaint *paint -#define BASE_PARAMS_PAINTLESS \ - const Rect &unmappedBounds, const Matrix4 &localMatrix, const ClipBase *localClip -#define SUPER(Type) RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, paint) -#define SUPER_PAINTLESS(Type) \ - RecordedOp(RecordedOpId::Type, unmappedBounds, localMatrix, localClip, nullptr) - -struct RecordedOp { - /* ID from RecordedOpId - generally used for jumping into function tables */ - const int opId; - - /* bounds in *local* space, without accounting for DisplayList transformation, or stroke */ - const Rect unmappedBounds; - - /* transform in recording space (vs DisplayList origin) */ - const Matrix4 localMatrix; - - /* clip in recording space - nullptr if not clipped */ - const ClipBase* localClip; - - /* optional paint, stored in base object to simplify merging logic */ - const SkPaint* paint; - -protected: - RecordedOp(unsigned int opId, BASE_PARAMS) - : opId(opId) - , unmappedBounds(unmappedBounds) - , localMatrix(localMatrix) - , localClip(localClip) - , paint(paint) {} -}; - -struct RenderNodeOp : RecordedOp { - RenderNodeOp(BASE_PARAMS_PAINTLESS, RenderNode* renderNode) - : SUPER_PAINTLESS(RenderNodeOp), renderNode(renderNode) {} - RenderNode* renderNode; // not const, since drawing modifies it - - /** - * Holds the transformation between the projection surface ViewGroup and this RenderNode - * drawing instance. Represents any translations / transformations done within the drawing of - * the compositing ancestor ViewGroup's draw, before the draw of the View represented by this - * DisplayList draw instance. - * - * Note: doesn't include transformation within the RenderNode, or its properties. - */ - Matrix4 transformFromCompositingAncestor; - bool skipInOrderDraw = false; -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Standard Ops -//////////////////////////////////////////////////////////////////////////////////////////////////// - -struct ArcOp : RecordedOp { - ArcOp(BASE_PARAMS, float startAngle, float sweepAngle, bool useCenter) - : SUPER(ArcOp), startAngle(startAngle), sweepAngle(sweepAngle), useCenter(useCenter) {} - const float startAngle; - const float sweepAngle; - const bool useCenter; -}; - -struct BitmapOp : RecordedOp { - BitmapOp(BASE_PARAMS, Bitmap* bitmap) : SUPER(BitmapOp), bitmap(bitmap) {} - Bitmap* bitmap; -}; - -struct BitmapMeshOp : RecordedOp { - BitmapMeshOp(BASE_PARAMS, Bitmap* bitmap, int meshWidth, int meshHeight, const float* vertices, - const int* colors) - : SUPER(BitmapMeshOp) - , bitmap(bitmap) - , meshWidth(meshWidth) - , meshHeight(meshHeight) - , vertices(vertices) - , colors(colors) {} - Bitmap* bitmap; - const int meshWidth; - const int meshHeight; - const float* vertices; - const int* colors; -}; - -struct BitmapRectOp : RecordedOp { - BitmapRectOp(BASE_PARAMS, Bitmap* bitmap, const Rect& src) - : SUPER(BitmapRectOp), bitmap(bitmap), src(src) {} - Bitmap* bitmap; - const Rect src; -}; - -struct CirclePropsOp : RecordedOp { - CirclePropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint, - float* x, float* y, float* radius) - : RecordedOp(RecordedOpId::CirclePropsOp, Rect(), localMatrix, localClip, paint) - , x(x) - , y(y) - , radius(radius) {} - const float* x; - const float* y; - const float* radius; -}; - -struct ColorOp : RecordedOp { - // Note: unbounded op that will fillclip, so no bounds/matrix needed - ColorOp(const ClipBase* localClip, int color, SkBlendMode mode) - : RecordedOp(RecordedOpId::ColorOp, Rect(), Matrix4::identity(), localClip, nullptr) - , color(color) - , mode(mode) {} - const int color; - const SkBlendMode mode; -}; - -struct FunctorOp : RecordedOp { - // Note: undefined record-time bounds, since this op fills the clip - // TODO: explicitly define bounds - FunctorOp(const Matrix4& localMatrix, const ClipBase* localClip, Functor* functor) - : RecordedOp(RecordedOpId::FunctorOp, Rect(), localMatrix, localClip, nullptr) - , functor(functor) {} - Functor* functor; -}; - -struct LinesOp : RecordedOp { - LinesOp(BASE_PARAMS, const float* points, const int floatCount) - : SUPER(LinesOp), points(points), floatCount(floatCount) {} - const float* points; - const int floatCount; -}; - -struct OvalOp : RecordedOp { - OvalOp(BASE_PARAMS) : SUPER(OvalOp) {} -}; - -struct PatchOp : RecordedOp { - PatchOp(BASE_PARAMS, Bitmap* bitmap, const Res_png_9patch* patch) - : SUPER(PatchOp), bitmap(bitmap), patch(patch) {} - Bitmap* bitmap; - const Res_png_9patch* patch; -}; - -struct PathOp : RecordedOp { - PathOp(BASE_PARAMS, const SkPath* path) : SUPER(PathOp), path(path) {} - const SkPath* path; -}; - -struct PointsOp : RecordedOp { - PointsOp(BASE_PARAMS, const float* points, const int floatCount) - : SUPER(PointsOp), points(points), floatCount(floatCount) {} - const float* points; - const int floatCount; -}; - -struct RectOp : RecordedOp { - RectOp(BASE_PARAMS) : SUPER(RectOp) {} -}; - -struct RoundRectOp : RecordedOp { - RoundRectOp(BASE_PARAMS, float rx, float ry) : SUPER(RoundRectOp), rx(rx), ry(ry) {} - const float rx; - const float ry; -}; - -struct RoundRectPropsOp : RecordedOp { - RoundRectPropsOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint, - float* left, float* top, float* right, float* bottom, float* rx, float* ry) - : RecordedOp(RecordedOpId::RoundRectPropsOp, Rect(), localMatrix, localClip, paint) - , left(left) - , top(top) - , right(right) - , bottom(bottom) - , rx(rx) - , ry(ry) {} - const float* left; - const float* top; - const float* right; - const float* bottom; - const float* rx; - const float* ry; -}; - -struct VectorDrawableOp : RecordedOp { - VectorDrawableOp(VectorDrawable::Tree* tree, BASE_PARAMS_PAINTLESS) - : SUPER_PAINTLESS(VectorDrawableOp), vectorDrawable(tree) {} - VectorDrawable::Tree* vectorDrawable; -}; - -/** - * Real-time, dynamic-lit shadow. - * - * Uses invalid/empty bounds and matrix since ShadowOp bounds aren't known at defer time, - * and are resolved dynamically, and transform isn't needed. - * - * State construction handles these properties specially, ignoring matrix/bounds. - */ -struct ShadowOp : RecordedOp { - ShadowOp(sp<TessellationCache::ShadowTask>& shadowTask, float casterAlpha) - : RecordedOp(RecordedOpId::ShadowOp, Rect(), Matrix4::identity(), nullptr, nullptr) - , shadowTask(shadowTask) - , casterAlpha(casterAlpha){}; - sp<TessellationCache::ShadowTask> shadowTask; - const float casterAlpha; -}; - -struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?) - SimpleRectsOp(BASE_PARAMS, Vertex* vertices, size_t vertexCount) - : SUPER(SimpleRectsOp), vertices(vertices), vertexCount(vertexCount) {} - Vertex* vertices; - const size_t vertexCount; -}; - -struct TextOp : RecordedOp { - TextOp(BASE_PARAMS, const glyph_t* glyphs, const float* positions, int glyphCount, float x, - float y) - : SUPER(TextOp) - , glyphs(glyphs) - , positions(positions) - , glyphCount(glyphCount) - , x(x) - , y(y) {} - const glyph_t* glyphs; - const float* positions; - const int glyphCount; - const float x; - const float y; -}; - -struct TextOnPathOp : RecordedOp { - // TODO: explicitly define bounds - TextOnPathOp(const Matrix4& localMatrix, const ClipBase* localClip, const SkPaint* paint, - const glyph_t* glyphs, int glyphCount, const SkPath* path, float hOffset, - float vOffset) - : RecordedOp(RecordedOpId::TextOnPathOp, Rect(), localMatrix, localClip, paint) - , glyphs(glyphs) - , glyphCount(glyphCount) - , path(path) - , hOffset(hOffset) - , vOffset(vOffset) {} - const glyph_t* glyphs; - const int glyphCount; - - const SkPath* path; - const float hOffset; - const float vOffset; -}; - -struct TextureLayerOp : RecordedOp { - TextureLayerOp(BASE_PARAMS_PAINTLESS, DeferredLayerUpdater* layer) - : SUPER_PAINTLESS(TextureLayerOp), layerHandle(layer) {} - - // Copy an existing TextureLayerOp, replacing the underlying matrix - TextureLayerOp(const TextureLayerOp& op, const Matrix4& replacementMatrix) - : RecordedOp(RecordedOpId::TextureLayerOp, op.unmappedBounds, replacementMatrix, - op.localClip, op.paint) - , layerHandle(op.layerHandle) {} - DeferredLayerUpdater* layerHandle; -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Layers -//////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Stateful operation! denotes the creation of an off-screen layer, - * and that commands following will render into it. - */ -struct BeginLayerOp : RecordedOp { - BeginLayerOp(BASE_PARAMS) : SUPER(BeginLayerOp) {} -}; - -/** - * Stateful operation! Denotes end of off-screen layer, and that - * commands since last BeginLayerOp should be drawn into parent FBO. - * - * State in this op is empty, it just serves to signal that a layer has been finished. - */ -struct EndLayerOp : RecordedOp { - EndLayerOp() - : RecordedOp(RecordedOpId::EndLayerOp, Rect(), Matrix4::identity(), nullptr, nullptr) {} -}; - -struct BeginUnclippedLayerOp : RecordedOp { - BeginUnclippedLayerOp(BASE_PARAMS) : SUPER(BeginUnclippedLayerOp) {} -}; - -struct EndUnclippedLayerOp : RecordedOp { - EndUnclippedLayerOp() - : RecordedOp(RecordedOpId::EndUnclippedLayerOp, Rect(), Matrix4::identity(), nullptr, - nullptr) {} -}; - -struct CopyToLayerOp : RecordedOp { - CopyToLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle) - : RecordedOp(RecordedOpId::CopyToLayerOp, op.unmappedBounds, op.localMatrix, - nullptr, // clip intentionally ignored - op.paint) - , layerHandle(layerHandle) {} - - // Records a handle to the Layer object, since the Layer itself won't be - // constructed until after this operation is constructed. - OffscreenBuffer** layerHandle; -}; - -// draw the parameter layer underneath -struct CopyFromLayerOp : RecordedOp { - CopyFromLayerOp(const RecordedOp& op, OffscreenBuffer** layerHandle) - : RecordedOp(RecordedOpId::CopyFromLayerOp, op.unmappedBounds, op.localMatrix, - nullptr, // clip intentionally ignored - op.paint) - , layerHandle(layerHandle) {} - - // Records a handle to the Layer object, since the Layer itself won't be - // constructed until after this operation is constructed. - OffscreenBuffer** layerHandle; -}; - -/** - * Draws an OffscreenBuffer. - * - * Alpha, mode, and colorfilter are embedded, since LayerOps are always dynamically generated, - * when creating/tracking a SkPaint* during defer isn't worth the bother. - */ -struct LayerOp : RecordedOp { - // Records a one-use (saveLayer) layer for drawing. - LayerOp(BASE_PARAMS, OffscreenBuffer** layerHandle) - : SUPER_PAINTLESS(LayerOp) - , layerHandle(layerHandle) - , alpha(paint ? paint->getAlpha() / 255.0f : 1.0f) - , mode(PaintUtils::getBlendModeDirect(paint)) - , colorFilter(paint ? paint->getColorFilter() : nullptr) {} - - explicit LayerOp(RenderNode& node) - : RecordedOp(RecordedOpId::LayerOp, Rect(node.getWidth(), node.getHeight()), - Matrix4::identity(), nullptr, nullptr) - , layerHandle(node.getLayerHandle()) - , alpha(node.properties().layerProperties().alpha() / 255.0f) - , mode(node.properties().layerProperties().xferMode()) - , colorFilter(node.properties().layerProperties().colorFilter()) {} - - // Records a handle to the Layer object, since the Layer itself won't be - // constructed until after this operation is constructed. - OffscreenBuffer** layerHandle; - const float alpha; - const SkBlendMode mode; - - // pointer to object owned by either LayerProperties, or a recorded Paint object in a - // BeginLayerOp. Lives longer than LayerOp in either case, so no skia ref counting is used. - SkColorFilter* colorFilter; -}; - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index e1df1e7725b5..f928de9b92a6 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2018 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. @@ -16,615 +16,1016 @@ #include "RecordingCanvas.h" -#include "DeferredLayerUpdater.h" -#include "RecordedOp.h" -#include "RenderNode.h" #include "VectorDrawable.h" -#include "hwui/MinikinUtils.h" + +#include "SkCanvas.h" +#include "SkData.h" +#include "SkDrawShadowInfo.h" +#include "SkImage.h" +#include "SkImageFilter.h" +#include "SkLatticeIter.h" +#include "SkMath.h" +#include "SkPicture.h" +#include "SkRSXform.h" +#include "SkRegion.h" +#include "SkTextBlob.h" +#include "SkVertices.h" + +#include <experimental/type_traits> namespace android { namespace uirenderer { -RecordingCanvas::RecordingCanvas(size_t width, size_t height) - : mState(*this), mResourceCache(ResourceCache::getInstance()) { - resetRecording(width, height); -} +#ifndef SKLITEDL_PAGE +#define SKLITEDL_PAGE 4096 +#endif -RecordingCanvas::~RecordingCanvas() { - LOG_ALWAYS_FATAL_IF(mDisplayList, "Destroyed a RecordingCanvas during a record!"); +// A stand-in for an optional SkRect which was not set, e.g. bounds for a saveLayer(). +static const SkRect kUnset = {SK_ScalarInfinity, 0, 0, 0}; +static const SkRect* maybe_unset(const SkRect& r) { + return r.left() == SK_ScalarInfinity ? nullptr : &r; } -void RecordingCanvas::resetRecording(int width, int height, RenderNode* node) { - LOG_ALWAYS_FATAL_IF(mDisplayList, "prepareDirty called a second time during a recording!"); - mDisplayList = new DisplayList(); - - mState.initializeRecordingSaveStack(width, height); +// copy_v(dst, src,n, src,n, ...) copies an arbitrary number of typed srcs into dst. +static void copy_v(void* dst) {} - mDeferredBarrierType = DeferredBarrierType::InOrder; +template <typename S, typename... Rest> +static void copy_v(void* dst, const S* src, int n, Rest&&... rest) { + SkASSERTF(((uintptr_t)dst & (alignof(S) - 1)) == 0, + "Expected %p to be aligned for at least %zu bytes.", dst, alignof(S)); + sk_careful_memcpy(dst, src, n * sizeof(S)); + copy_v(SkTAddOffset<void>(dst, n * sizeof(S)), std::forward<Rest>(rest)...); } -DisplayList* RecordingCanvas::finishRecording() { - restoreToCount(1); - mPaintMap.clear(); - mRegionMap.clear(); - mPathMap.clear(); - DisplayList* displayList = mDisplayList; - mDisplayList = nullptr; - mSkiaCanvasProxy.reset(nullptr); - return displayList; +// Helper for getting back at arrays which have been copy_v'd together after an Op. +template <typename D, typename T> +static const D* pod(const T* op, size_t offset = 0) { + return SkTAddOffset<const D>(op + 1, offset); } -void RecordingCanvas::insertReorderBarrier(bool enableReorder) { - if (enableReorder) { - mDeferredBarrierType = DeferredBarrierType::OutOfOrder; - mDeferredBarrierClip = getRecordedClip(); - } else { - mDeferredBarrierType = DeferredBarrierType::InOrder; - mDeferredBarrierClip = nullptr; +namespace { + +#define X(T) T, +enum class Type : uint8_t { +#include "DisplayListOps.in" +}; +#undef X + +struct Op { + uint32_t type : 8; + uint32_t skip : 24; +}; +static_assert(sizeof(Op) == 4, ""); + +struct Flush final : Op { + static const auto kType = Type::Flush; + void draw(SkCanvas* c, const SkMatrix&) const { c->flush(); } +}; + +struct Save final : Op { + static const auto kType = Type::Save; + void draw(SkCanvas* c, const SkMatrix&) const { c->save(); } +}; +struct Restore final : Op { + static const auto kType = Type::Restore; + void draw(SkCanvas* c, const SkMatrix&) const { c->restore(); } +}; +struct SaveLayer final : Op { + static const auto kType = Type::SaveLayer; + SaveLayer(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, + const SkImage* clipMask, const SkMatrix* clipMatrix, SkCanvas::SaveLayerFlags flags) { + if (bounds) { + this->bounds = *bounds; + } + if (paint) { + this->paint = *paint; + } + this->backdrop = sk_ref_sp(backdrop); + this->clipMask = sk_ref_sp(clipMask); + this->clipMatrix = clipMatrix ? *clipMatrix : SkMatrix::I(); + this->flags = flags; } -} - -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)); + SkRect bounds = kUnset; + SkPaint paint; + sk_sp<const SkImageFilter> backdrop; + sk_sp<const SkImage> clipMask; + SkMatrix clipMatrix; + SkCanvas::SaveLayerFlags flags; + void draw(SkCanvas* c, const SkMatrix&) const { + c->saveLayer({maybe_unset(bounds), &paint, backdrop.get(), clipMask.get(), + clipMatrix.isIdentity() ? nullptr : &clipMatrix, flags}); + } +}; + +struct Concat final : Op { + static const auto kType = Type::Concat; + Concat(const SkMatrix& matrix) : matrix(matrix) {} + SkMatrix matrix; + void draw(SkCanvas* c, const SkMatrix&) const { c->concat(matrix); } +}; +struct SetMatrix final : Op { + static const auto kType = Type::SetMatrix; + SetMatrix(const SkMatrix& matrix) : matrix(matrix) {} + SkMatrix matrix; + void draw(SkCanvas* c, const SkMatrix& original) const { + c->setMatrix(SkMatrix::Concat(original, matrix)); + } +}; +struct Translate final : Op { + static const auto kType = Type::Translate; + Translate(SkScalar dx, SkScalar dy) : dx(dx), dy(dy) {} + SkScalar dx, dy; + void draw(SkCanvas* c, const SkMatrix&) const { c->translate(dx, dy); } +}; + +struct ClipPath final : Op { + static const auto kType = Type::ClipPath; + ClipPath(const SkPath& path, SkClipOp op, bool aa) : path(path), op(op), aa(aa) {} + SkPath path; + SkClipOp op; + bool aa; + void draw(SkCanvas* c, const SkMatrix&) const { c->clipPath(path, op, aa); } +}; +struct ClipRect final : Op { + static const auto kType = Type::ClipRect; + ClipRect(const SkRect& rect, SkClipOp op, bool aa) : rect(rect), op(op), aa(aa) {} + SkRect rect; + SkClipOp op; + bool aa; + void draw(SkCanvas* c, const SkMatrix&) const { c->clipRect(rect, op, aa); } +}; +struct ClipRRect final : Op { + static const auto kType = Type::ClipRRect; + ClipRRect(const SkRRect& rrect, SkClipOp op, bool aa) : rrect(rrect), op(op), aa(aa) {} + SkRRect rrect; + SkClipOp op; + bool aa; + void draw(SkCanvas* c, const SkMatrix&) const { c->clipRRect(rrect, op, aa); } +}; +struct ClipRegion final : Op { + static const auto kType = Type::ClipRegion; + ClipRegion(const SkRegion& region, SkClipOp op) : region(region), op(op) {} + SkRegion region; + SkClipOp op; + void draw(SkCanvas* c, const SkMatrix&) const { c->clipRegion(region, op); } +}; + +struct DrawPaint final : Op { + static const auto kType = Type::DrawPaint; + DrawPaint(const SkPaint& paint) : paint(paint) {} + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawPaint(paint); } +}; +struct DrawPath final : Op { + static const auto kType = Type::DrawPath; + DrawPath(const SkPath& path, const SkPaint& paint) : path(path), paint(paint) {} + SkPath path; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawPath(path, paint); } +}; +struct DrawRect final : Op { + static const auto kType = Type::DrawRect; + DrawRect(const SkRect& rect, const SkPaint& paint) : rect(rect), paint(paint) {} + SkRect rect; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawRect(rect, paint); } +}; +struct DrawRegion final : Op { + static const auto kType = Type::DrawRegion; + DrawRegion(const SkRegion& region, const SkPaint& paint) : region(region), paint(paint) {} + SkRegion region; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawRegion(region, paint); } +}; +struct DrawOval final : Op { + static const auto kType = Type::DrawOval; + DrawOval(const SkRect& oval, const SkPaint& paint) : oval(oval), paint(paint) {} + SkRect oval; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawOval(oval, paint); } +}; +struct DrawArc final : Op { + static const auto kType = Type::DrawArc; + DrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, + const SkPaint& paint) + : oval(oval) + , startAngle(startAngle) + , sweepAngle(sweepAngle) + , useCenter(useCenter) + , paint(paint) {} + SkRect oval; + SkScalar startAngle; + SkScalar sweepAngle; + bool useCenter; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawArc(oval, startAngle, sweepAngle, useCenter, paint); + } +}; +struct DrawRRect final : Op { + static const auto kType = Type::DrawRRect; + DrawRRect(const SkRRect& rrect, const SkPaint& paint) : rrect(rrect), paint(paint) {} + SkRRect rrect; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawRRect(rrect, paint); } +}; +struct DrawDRRect final : Op { + static const auto kType = Type::DrawDRRect; + DrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) + : outer(outer), inner(inner), paint(paint) {} + SkRRect outer, inner; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawDRRect(outer, inner, paint); } +}; + +struct DrawAnnotation final : Op { + static const auto kType = Type::DrawAnnotation; + DrawAnnotation(const SkRect& rect, SkData* value) : rect(rect), value(sk_ref_sp(value)) {} + SkRect rect; + sk_sp<SkData> value; + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawAnnotation(rect, pod<char>(this), value.get()); + } +}; +struct DrawDrawable final : Op { + static const auto kType = Type::DrawDrawable; + DrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) : drawable(sk_ref_sp(drawable)) { + if (matrix) { + this->matrix = *matrix; + } + } + sk_sp<SkDrawable> drawable; + SkMatrix matrix = SkMatrix::I(); + void draw(SkCanvas* c, const SkMatrix&) const { c->drawDrawable(drawable.get(), &matrix); } +}; +struct DrawPicture final : Op { + static const auto kType = Type::DrawPicture; + DrawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) + : picture(sk_ref_sp(picture)) { + if (matrix) { + this->matrix = *matrix; + } + if (paint) { + this->paint = *paint; + has_paint = true; + } + } + sk_sp<const SkPicture> picture; + SkMatrix matrix = SkMatrix::I(); + SkPaint paint; + bool has_paint = false; // TODO: why is a default paint not the same? + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawPicture(picture.get(), &matrix, has_paint ? &paint : nullptr); + } +}; + +struct DrawImage final : Op { + static const auto kType = Type::DrawImage; + DrawImage(sk_sp<const SkImage>&& image, SkScalar x, SkScalar y, const SkPaint* paint, + BitmapPalette palette) + : image(std::move(image)), x(x), y(y), palette(palette) { + if (paint) { + this->paint = *paint; + } + } + sk_sp<const SkImage> image; + SkScalar x, y; + SkPaint paint; + BitmapPalette palette; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawImage(image.get(), x, y, &paint); } +}; +struct DrawImageNine final : Op { + static const auto kType = Type::DrawImageNine; + DrawImageNine(sk_sp<const SkImage>&& image, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) + : image(std::move(image)), center(center), dst(dst) { + if (paint) { + this->paint = *paint; + } + } + sk_sp<const SkImage> image; + SkIRect center; + SkRect dst; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawImageNine(image.get(), center, dst, &paint); + } +}; +struct DrawImageRect final : Op { + static const auto kType = Type::DrawImageRect; + DrawImageRect(sk_sp<const SkImage>&& image, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SkCanvas::SrcRectConstraint constraint, + BitmapPalette palette) + : image(std::move(image)), dst(dst), constraint(constraint), palette(palette) { + this->src = src ? *src : SkRect::MakeIWH(this->image->width(), this->image->height()); + if (paint) { + this->paint = *paint; + } + } + sk_sp<const SkImage> image; + SkRect src, dst; + SkPaint paint; + SkCanvas::SrcRectConstraint constraint; + BitmapPalette palette; + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawImageRect(image.get(), src, dst, &paint, constraint); + } +}; +struct DrawImageLattice final : Op { + static const auto kType = Type::DrawImageLattice; + DrawImageLattice(sk_sp<const SkImage>&& image, int xs, int ys, int fs, const SkIRect& src, + const SkRect& dst, const SkPaint* paint, BitmapPalette palette) + : image(std::move(image)) + , xs(xs) + , ys(ys) + , fs(fs) + , src(src) + , dst(dst) + , palette(palette) { + if (paint) { + this->paint = *paint; + } + } + sk_sp<const SkImage> image; + int xs, ys, fs; + SkIRect src; + SkRect dst; + SkPaint paint; + BitmapPalette palette; + void draw(SkCanvas* c, const SkMatrix&) const { + auto xdivs = pod<int>(this, 0), ydivs = pod<int>(this, xs * sizeof(int)); + auto colors = (0 == fs) ? nullptr : pod<SkColor>(this, (xs + ys) * sizeof(int)); + auto flags = + (0 == fs) ? nullptr : pod<SkCanvas::Lattice::RectType>( + this, (xs + ys) * sizeof(int) + fs * sizeof(SkColor)); + c->drawImageLattice(image.get(), {xdivs, ydivs, flags, xs, ys, &src, colors}, dst, &paint); + } +}; + +struct DrawText final : Op { + static const auto kType = Type::DrawText; + DrawText(size_t bytes, SkScalar x, SkScalar y, const SkPaint& paint) + : bytes(bytes), x(x), y(y), paint(paint) {} + size_t bytes; + SkScalar x, y; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawText(pod<void>(this), bytes, x, y, paint); + } +}; +struct DrawPosText final : Op { + static const auto kType = Type::DrawPosText; + DrawPosText(size_t bytes, const SkPaint& paint, int n) : bytes(bytes), paint(paint), n(n) {} + size_t bytes; + SkPaint paint; + int n; + void draw(SkCanvas* c, const SkMatrix&) const { + auto points = pod<SkPoint>(this); + auto text = pod<void>(this, n * sizeof(SkPoint)); + c->drawPosText(text, bytes, points, paint); + } +}; +struct DrawPosTextH final : Op { + static const auto kType = Type::DrawPosTextH; + DrawPosTextH(size_t bytes, SkScalar y, const SkPaint& paint, int n) + : bytes(bytes), y(y), paint(paint), n(n) {} + size_t bytes; + SkScalar y; + SkPaint paint; + int n; + void draw(SkCanvas* c, const SkMatrix&) const { + auto xs = pod<SkScalar>(this); + auto text = pod<void>(this, n * sizeof(SkScalar)); + c->drawPosTextH(text, bytes, xs, y, paint); + } +}; +struct DrawTextRSXform final : Op { + static const auto kType = Type::DrawTextRSXform; + DrawTextRSXform(size_t bytes, int xforms, const SkRect* cull, const SkPaint& paint) + : bytes(bytes), xforms(xforms), paint(paint) { + if (cull) { + this->cull = *cull; + } + } + size_t bytes; + int xforms; + SkRect cull = kUnset; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { + // For alignment, the SkRSXforms are first in the pod section, followed by the text. + c->drawTextRSXform(pod<void>(this, xforms * sizeof(SkRSXform)), bytes, pod<SkRSXform>(this), + maybe_unset(cull), paint); + } +}; +struct DrawTextBlob final : Op { + static const auto kType = Type::DrawTextBlob; + DrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) + : blob(sk_ref_sp(blob)), x(x), y(y), paint(paint) {} + sk_sp<const SkTextBlob> blob; + SkScalar x, y; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { c->drawTextBlob(blob.get(), x, y, paint); } +}; + +struct DrawPatch final : Op { + static const auto kType = Type::DrawPatch; + DrawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texs[4], + SkBlendMode bmode, const SkPaint& paint) + : xfermode(bmode), paint(paint) { + copy_v(this->cubics, cubics, 12); + if (colors) { + copy_v(this->colors, colors, 4); + has_colors = true; + } + if (texs) { + copy_v(this->texs, texs, 4); + has_texs = true; + } + } + SkPoint cubics[12]; + SkColor colors[4]; + SkPoint texs[4]; + SkBlendMode xfermode; + SkPaint paint; + bool has_colors = false; + bool has_texs = false; + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawPatch(cubics, has_colors ? colors : nullptr, has_texs ? texs : nullptr, xfermode, + paint); + } +}; +struct DrawPoints final : Op { + static const auto kType = Type::DrawPoints; + DrawPoints(SkCanvas::PointMode mode, size_t count, const SkPaint& paint) + : mode(mode), count(count), paint(paint) {} + SkCanvas::PointMode mode; + size_t count; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawPoints(mode, count, pod<SkPoint>(this), paint); + } +}; +struct DrawVertices final : Op { + static const auto kType = Type::DrawVertices; + DrawVertices(const SkVertices* v, int bc, SkBlendMode m, const SkPaint& p) + : vertices(sk_ref_sp(const_cast<SkVertices*>(v))), boneCount(bc), mode(m), paint(p) {} + sk_sp<SkVertices> vertices; + int boneCount; + SkBlendMode mode; + SkPaint paint; + void draw(SkCanvas* c, const SkMatrix&) const { + c->drawVertices(vertices, pod<SkVertices::Bone>(this), boneCount, mode, paint); + } +}; +struct DrawAtlas final : Op { + static const auto kType = Type::DrawAtlas; + DrawAtlas(const SkImage* atlas, int count, SkBlendMode xfermode, const SkRect* cull, + const SkPaint* paint, bool has_colors) + : atlas(sk_ref_sp(atlas)), count(count), xfermode(xfermode), has_colors(has_colors) { + if (cull) { + this->cull = *cull; + } + if (paint) { + this->paint = *paint; + } + } + sk_sp<const SkImage> atlas; + int count; + SkBlendMode xfermode; + SkRect cull = kUnset; + SkPaint paint; + bool has_colors; + void draw(SkCanvas* c, const SkMatrix&) const { + auto xforms = pod<SkRSXform>(this, 0); + auto texs = pod<SkRect>(this, count * sizeof(SkRSXform)); + auto colors = has_colors ? pod<SkColor>(this, count * (sizeof(SkRSXform) + sizeof(SkRect))) + : nullptr; + c->drawAtlas(atlas.get(), xforms, texs, colors, count, xfermode, maybe_unset(cull), &paint); + } +}; +struct DrawShadowRec final : Op { + static const auto kType = Type::DrawShadowRec; + DrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) : fPath(path), fRec(rec) {} + SkPath fPath; + SkDrawShadowRec fRec; + void draw(SkCanvas* c, const SkMatrix&) const { c->private_draw_shadow_rec(fPath, fRec); } +}; + +struct DrawVectorDrawable final : Op { + static const auto kType = Type::DrawVectorDrawable; + DrawVectorDrawable(VectorDrawableRoot* tree) + : mRoot(tree) + , mBounds(tree->stagingProperties().getBounds()) + , palette(tree->computePalette()) { + // Recording, so use staging properties + tree->getPaintFor(&paint, tree->stagingProperties()); } - // 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); + void draw(SkCanvas* canvas, const SkMatrix&) const { mRoot->draw(canvas, mBounds, paint); } - return mSkiaCanvasProxy.get(); + sp<VectorDrawableRoot> mRoot; + SkRect mBounds; + SkPaint paint; + BitmapPalette palette; +}; } -// ---------------------------------------------------------------------------- -// CanvasStateClient implementation -// ---------------------------------------------------------------------------- - -void RecordingCanvas::onViewportInitialized() {} - -void RecordingCanvas::onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) { - if (removed.flags & Snapshot::kFlagIsFboLayer) { - addOp(alloc().create_trivial<EndLayerOp>()); - } else if (removed.flags & Snapshot::kFlagIsLayer) { - addOp(alloc().create_trivial<EndUnclippedLayerOp>()); +template <typename T, typename... Args> +void* DisplayListData::push(size_t pod, Args&&... args) { + size_t skip = SkAlignPtr(sizeof(T) + pod); + SkASSERT(skip < (1 << 24)); + if (fUsed + skip > fReserved) { + static_assert(SkIsPow2(SKLITEDL_PAGE), "This math needs updating for non-pow2."); + // Next greater multiple of SKLITEDL_PAGE. + fReserved = (fUsed + skip + SKLITEDL_PAGE) & ~(SKLITEDL_PAGE - 1); + fBytes.realloc(fReserved); } + SkASSERT(fUsed + skip <= fReserved); + auto op = (T*)(fBytes.get() + fUsed); + fUsed += skip; + new (op) T{std::forward<Args>(args)...}; + op->type = (uint32_t)T::kType; + op->skip = skip; + return op + 1; } -// ---------------------------------------------------------------------------- -// android/graphics/Canvas state operations -// ---------------------------------------------------------------------------- -// Save (layer) -int RecordingCanvas::save(SaveFlags::Flags flags) { - return mState.save((int)flags); +template <typename Fn, typename... Args> +inline void DisplayListData::map(const Fn fns[], Args... args) const { + auto end = fBytes.get() + fUsed; + for (const uint8_t* ptr = fBytes.get(); ptr < end;) { + auto op = (const Op*)ptr; + auto type = op->type; + auto skip = op->skip; + if (auto fn = fns[type]) { // We replace no-op functions with nullptrs + fn(op, args...); // to avoid the overhead of a pointless call. + } + ptr += skip; + } } -void RecordingCanvas::RecordingCanvas::restore() { - mState.restore(); +void DisplayListData::flush() { + this->push<Flush>(0); } -void RecordingCanvas::restoreToCount(int saveCount) { - mState.restoreToCount(saveCount); +void DisplayListData::save() { + this->push<Save>(0); } - -int RecordingCanvas::saveLayer(float left, float top, float right, float bottom, - const SkPaint* paint, SaveFlags::Flags flags) { - // force matrix/clip isolation for layer - flags |= SaveFlags::MatrixClip; - bool clippedLayer = flags & SaveFlags::ClipToLayer; - - const Snapshot& previous = *mState.currentSnapshot(); - - // initialize the snapshot as though it almost represents an FBO layer so deferred draw - // operations will be able to store and restore the current clip and transform info, and - // quick rejection will be correct (for display lists) - - Rect unmappedBounds(left, top, right, bottom); - unmappedBounds.roundOut(); - - // determine clipped bounds relative to previous viewport. - Rect visibleBounds = unmappedBounds; - previous.transform->mapRect(visibleBounds); - - if (CC_UNLIKELY(!clippedLayer && previous.transform->rectToRect() && - visibleBounds.contains(previous.getRenderTargetClip()))) { - // unlikely case where an unclipped savelayer is recorded with a clip it can use, - // as none of its unaffected/unclipped area is visible - clippedLayer = true; - flags |= SaveFlags::ClipToLayer; - } - - visibleBounds.doIntersect(previous.getRenderTargetClip()); - visibleBounds.snapToPixelBoundaries(); - visibleBounds.doIntersect(Rect(previous.getViewportWidth(), previous.getViewportHeight())); - - // Map visible bounds back to layer space, and intersect with parameter bounds - Rect layerBounds = visibleBounds; - if (CC_LIKELY(!layerBounds.isEmpty())) { - // if non-empty, can safely map by the inverse transform - 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()) { - if (CC_LIKELY(clippedLayer)) { - auto previousClip = getRecordedClip(); // capture before new snapshot clip has changed - if (addOp(alloc().create_trivial<BeginLayerOp>( - unmappedBounds, - *previous.transform, // transform to *draw* with - previousClip, // clip to *draw* with - refPaint(paint))) >= 0) { - 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; - return saveValue; - } - } else { - if (addOp(alloc().create_trivial<BeginUnclippedLayerOp>( - unmappedBounds, *mState.currentSnapshot()->transform, getRecordedClip(), - refPaint(paint))) >= 0) { - snapshot.flags |= Snapshot::kFlagIsLayer; - return saveValue; - } - } - } - - // Layer not needed, so skip recording it... - if (CC_LIKELY(clippedLayer)) { - // ... and set empty clip to reject inner content, if possible - snapshot.resetClip(0, 0, 0, 0); - } - return saveValue; +void DisplayListData::restore() { + this->push<Restore>(0); } - -// Matrix -void RecordingCanvas::rotate(float degrees) { - if (degrees == 0) return; - - mState.rotate(degrees); +void DisplayListData::saveLayer(const SkRect* bounds, const SkPaint* paint, + const SkImageFilter* backdrop, const SkImage* clipMask, + const SkMatrix* clipMatrix, SkCanvas::SaveLayerFlags flags) { + this->push<SaveLayer>(0, bounds, paint, backdrop, clipMask, clipMatrix, flags); } -void RecordingCanvas::scale(float sx, float sy) { - if (sx == 1 && sy == 1) return; - - mState.scale(sx, sy); +void DisplayListData::concat(const SkMatrix& matrix) { + this->push<Concat>(0, matrix); } - -void RecordingCanvas::skew(float sx, float sy) { - mState.skew(sx, sy); +void DisplayListData::setMatrix(const SkMatrix& matrix) { + this->push<SetMatrix>(0, matrix); +} +void DisplayListData::translate(SkScalar dx, SkScalar dy) { + this->push<Translate>(0, dx, dy); } -void RecordingCanvas::translate(float dx, float dy) { - if (dx == 0 && dy == 0) return; - - mState.translate(dx, dy, 0); +void DisplayListData::clipPath(const SkPath& path, SkClipOp op, bool aa) { + this->push<ClipPath>(0, path, op, aa); +} +void DisplayListData::clipRect(const SkRect& rect, SkClipOp op, bool aa) { + this->push<ClipRect>(0, rect, op, aa); +} +void DisplayListData::clipRRect(const SkRRect& rrect, SkClipOp op, bool aa) { + this->push<ClipRRect>(0, rrect, op, aa); +} +void DisplayListData::clipRegion(const SkRegion& region, SkClipOp op) { + this->push<ClipRegion>(0, region, op); } -// Clip -bool RecordingCanvas::getClipBounds(SkRect* outRect) const { - *outRect = mState.getLocalClipBounds().toSkRect(); - return !(outRect->isEmpty()); +void DisplayListData::drawPaint(const SkPaint& paint) { + this->push<DrawPaint>(0, paint); } -bool RecordingCanvas::quickRejectRect(float left, float top, float right, float bottom) const { - return mState.quickRejectConservative(left, top, right, bottom); +void DisplayListData::drawPath(const SkPath& path, const SkPaint& paint) { + this->push<DrawPath>(0, path, paint); } -bool RecordingCanvas::quickRejectPath(const SkPath& path) const { - SkRect bounds = path.getBounds(); - return mState.quickRejectConservative(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); +void DisplayListData::drawRect(const SkRect& rect, const SkPaint& paint) { + this->push<DrawRect>(0, rect, paint); } -bool RecordingCanvas::clipRect(float left, float top, float right, float bottom, SkClipOp op) { - return mState.clipRect(left, top, right, bottom, op); +void DisplayListData::drawRegion(const SkRegion& region, const SkPaint& paint) { + this->push<DrawRegion>(0, region, paint); } -bool RecordingCanvas::clipPath(const SkPath* path, SkClipOp op) { - return mState.clipPath(path, op); +void DisplayListData::drawOval(const SkRect& oval, const SkPaint& paint) { + this->push<DrawOval>(0, oval, paint); +} +void DisplayListData::drawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint) { + this->push<DrawArc>(0, oval, startAngle, sweepAngle, useCenter, paint); +} +void DisplayListData::drawRRect(const SkRRect& rrect, const SkPaint& paint) { + this->push<DrawRRect>(0, rrect, paint); +} +void DisplayListData::drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { + this->push<DrawDRRect>(0, outer, inner, paint); } -// ---------------------------------------------------------------------------- -// android/graphics/Canvas draw operations -// ---------------------------------------------------------------------------- -void RecordingCanvas::drawColor(int color, SkBlendMode mode) { - addOp(alloc().create_trivial<ColorOp>(getRecordedClip(), color, mode)); +void DisplayListData::drawAnnotation(const SkRect& rect, const char* key, SkData* value) { + size_t bytes = strlen(key) + 1; + void* pod = this->push<DrawAnnotation>(bytes, rect, value); + copy_v(pod, key, bytes); +} +void DisplayListData::drawDrawable(SkDrawable* drawable, const SkMatrix* matrix) { + this->push<DrawDrawable>(0, drawable, matrix); +} +void DisplayListData::drawPicture(const SkPicture* picture, const SkMatrix* matrix, + const SkPaint* paint) { + this->push<DrawPicture>(0, picture, matrix, paint); +} +void DisplayListData::drawImage(sk_sp<const SkImage> image, SkScalar x, SkScalar y, + const SkPaint* paint, BitmapPalette palette) { + this->push<DrawImage>(0, std::move(image), x, y, paint, palette); +} +void DisplayListData::drawImageNine(sk_sp<const SkImage> image, const SkIRect& center, + const SkRect& dst, const SkPaint* paint) { + this->push<DrawImageNine>(0, std::move(image), center, dst, paint); +} +void DisplayListData::drawImageRect(sk_sp<const SkImage> image, const SkRect* src, + const SkRect& dst, const SkPaint* paint, + SkCanvas::SrcRectConstraint constraint, BitmapPalette palette) { + this->push<DrawImageRect>(0, std::move(image), src, dst, paint, constraint, palette); +} +void DisplayListData::drawImageLattice(sk_sp<const SkImage> image, const SkCanvas::Lattice& lattice, + const SkRect& dst, const SkPaint* paint, + BitmapPalette palette) { + int xs = lattice.fXCount, ys = lattice.fYCount; + int fs = lattice.fRectTypes ? (xs + 1) * (ys + 1) : 0; + size_t bytes = (xs + ys) * sizeof(int) + fs * sizeof(SkCanvas::Lattice::RectType) + + fs * sizeof(SkColor); + SkASSERT(lattice.fBounds); + void* pod = this->push<DrawImageLattice>(bytes, std::move(image), xs, ys, fs, *lattice.fBounds, + dst, paint, palette); + copy_v(pod, lattice.fXDivs, xs, lattice.fYDivs, ys, lattice.fColors, fs, lattice.fRectTypes, + fs); } -void RecordingCanvas::drawPaint(const SkPaint& paint) { - SkRect bounds; - if (getClipBounds(&bounds)) { - drawRect(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, paint); - } +void DisplayListData::drawText(const void* text, size_t bytes, SkScalar x, SkScalar y, + const SkPaint& paint) { + void* pod = this->push<DrawText>(bytes, bytes, x, y, paint); + copy_v(pod, (const char*)text, bytes); + mHasText = true; +} +void DisplayListData::drawPosText(const void* text, size_t bytes, const SkPoint pos[], + const SkPaint& paint) { + int n = paint.countText(text, bytes); + void* pod = this->push<DrawPosText>(n * sizeof(SkPoint) + bytes, bytes, paint, n); + copy_v(pod, pos, n, (const char*)text, bytes); + mHasText = true; +} +void DisplayListData::drawPosTextH(const void* text, size_t bytes, const SkScalar xs[], SkScalar y, + const SkPaint& paint) { + int n = paint.countText(text, bytes); + void* pod = this->push<DrawPosTextH>(n * sizeof(SkScalar) + bytes, bytes, y, paint, n); + copy_v(pod, xs, n, (const char*)text, bytes); + mHasText = true; +} +void DisplayListData::drawTextRSXform(const void* text, size_t bytes, const SkRSXform xforms[], + const SkRect* cull, const SkPaint& paint) { + int n = paint.countText(text, bytes); + void* pod = this->push<DrawTextRSXform>(bytes + n * sizeof(SkRSXform), bytes, n, cull, paint); + copy_v(pod, xforms, n, (const char*)text, bytes); + mHasText = true; +} +void DisplayListData::drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + this->push<DrawTextBlob>(0, blob, x, y, paint); + mHasText = true; } -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]); +void DisplayListData::drawPatch(const SkPoint points[12], const SkColor colors[4], + const SkPoint texs[4], SkBlendMode bmode, const SkPaint& paint) { + this->push<DrawPatch>(0, points, colors, texs, bmode, paint); +} +void DisplayListData::drawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint points[], + const SkPaint& paint) { + void* pod = this->push<DrawPoints>(count * sizeof(SkPoint), mode, count, paint); + copy_v(pod, points, count); +} +void DisplayListData::drawVertices(const SkVertices* vertices, const SkVertices::Bone bones[], + int boneCount, SkBlendMode mode, const SkPaint& paint) { + void* pod = this->push<DrawVertices>(boneCount * sizeof(SkVertices::Bone), vertices, boneCount, + mode, paint); + copy_v(pod, bones, boneCount); +} +void DisplayListData::drawAtlas(const SkImage* atlas, const SkRSXform xforms[], const SkRect texs[], + const SkColor colors[], int count, SkBlendMode xfermode, + const SkRect* cull, const SkPaint* paint) { + size_t bytes = count * (sizeof(SkRSXform) + sizeof(SkRect)); + if (colors) { + bytes += count * sizeof(SkColor); } - return unmappedBounds; + void* pod = + this->push<DrawAtlas>(bytes, atlas, count, xfermode, cull, paint, colors != nullptr); + copy_v(pod, xforms, count, texs, count, colors, colors ? count : 0); +} +void DisplayListData::drawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) { + this->push<DrawShadowRec>(0, path, rec); +} +void DisplayListData::drawVectorDrawable(VectorDrawableRoot* tree) { + this->push<DrawVectorDrawable>(0, tree); } -// Geometry -void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) { - if (CC_UNLIKELY(floatCount < 2 || paint.nothingToDraw())) return; - floatCount &= ~0x1; // round down to nearest two +typedef void (*draw_fn)(const void*, SkCanvas*, const SkMatrix&); +typedef void (*void_fn)(const void*); +typedef void (*color_transform_fn)(const void*, ColorTransform); + +// All ops implement draw(). +#define X(T) \ + [](const void* op, SkCanvas* c, const SkMatrix& original) { \ + ((const T*)op)->draw(c, original); \ + }, +static const draw_fn draw_fns[] = { +#include "DisplayListOps.in" +}; +#undef X + +// Most state ops (matrix, clip, save, restore) have a trivial destructor. +#define X(T) \ + !std::is_trivially_destructible<T>::value ? [](const void* op) { ((const T*)op)->~T(); } \ + : (void_fn) nullptr, + +static const void_fn dtor_fns[] = { +#include "DisplayListOps.in" +}; +#undef X + +void DisplayListData::draw(SkCanvas* canvas) const { + SkAutoCanvasRestore acr(canvas, false); + this->map(draw_fns, canvas, canvas->getTotalMatrix()); +} - addOp(alloc().create_trivial<PointsOp>( - calcBoundsOfPoints(points, floatCount), *mState.currentSnapshot()->transform, - getRecordedClip(), refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); +DisplayListData::~DisplayListData() { + this->reset(); } -void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) { - if (CC_UNLIKELY(floatCount < 4 || paint.nothingToDraw())) return; - floatCount &= ~0x3; // round down to nearest four +void DisplayListData::reset() { + this->map(dtor_fns); - addOp(alloc().create_trivial<LinesOp>( - calcBoundsOfPoints(points, floatCount), *mState.currentSnapshot()->transform, - getRecordedClip(), refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); + // Leave fBytes and fReserved alone. + fUsed = 0; } -void RecordingCanvas::drawRect(float left, float top, float right, float bottom, - const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; - - addOp(alloc().create_trivial<RectOp>(Rect(left, top, right, bottom), - *(mState.currentSnapshot()->transform), getRecordedClip(), - refPaint(&paint))); -} - -void RecordingCanvas::drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint) { - if (rects == nullptr) return; - - Vertex* rectData = (Vertex*)mDisplayList->allocator.create_trivial_array<Vertex>(vertexCount); - Vertex* vertex = rectData; - - float left = FLT_MAX; - float top = FLT_MAX; - float right = FLT_MIN; - float bottom = FLT_MIN; - for (int index = 0; index < vertexCount; index += 4) { - float l = rects[index + 0]; - float t = rects[index + 1]; - float r = rects[index + 2]; - float b = rects[index + 3]; - - Vertex::set(vertex++, l, t); - Vertex::set(vertex++, r, t); - Vertex::set(vertex++, l, b); - Vertex::set(vertex++, r, b); - - left = std::min(left, l); - top = std::min(top, t); - right = std::max(right, r); - bottom = std::max(bottom, b); - } - addOp(alloc().create_trivial<SimpleRectsOp>( - Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), - getRecordedClip(), refPaint(paint), rectData, vertexCount)); -} - -void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; - - 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(); +template <class T> +using has_paint_helper = decltype(std::declval<T>().paint); + +template <class T> +constexpr bool has_paint = std::experimental::is_detected_v<has_paint_helper, T>; + +template <class T> +using has_palette_helper = decltype(std::declval<T>().palette); + +template <class T> +constexpr bool has_palette = std::experimental::is_detected_v<has_palette_helper, T>; + +template <class T> +constexpr color_transform_fn colorTransformForOp() { + if + constexpr(has_paint<T> && has_palette<T>) { + // It's a bitmap + return [](const void* opRaw, ColorTransform transform) { + // TODO: We should be const. Or not. Or just use a different map + // Unclear, but this is the quick fix + const T* op = reinterpret_cast<const T*>(opRaw); + transformPaint(transform, const_cast<SkPaint*>(&(op->paint)), op->palette); + }; } - 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(); + else if + constexpr(has_paint<T>) { + return [](const void* opRaw, ColorTransform transform) { + // TODO: We should be const. Or not. Or just use a different map + // Unclear, but this is the quick fix + const T* op = reinterpret_cast<const T*>(opRaw); + transformPaint(transform, const_cast<SkPaint*>(&(op->paint))); + }; } + else { + return nullptr; } } -void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, - float ry, const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; +#define X(T) colorTransformForOp<T>(), +static const color_transform_fn color_transform_fns[] = { +#include "DisplayListOps.in" +}; +#undef X - if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) { - addOp(alloc().create_trivial<RoundRectOp>(Rect(left, top, right, bottom), - *(mState.currentSnapshot()->transform), - getRecordedClip(), refPaint(&paint), rx, ry)); - } else { - drawRect(left, top, right, bottom, paint); - } -} - -void RecordingCanvas::drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top, - CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom, - CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry, - CanvasPropertyPaint* paint) { - mDisplayList->ref(left); - mDisplayList->ref(top); - mDisplayList->ref(right); - mDisplayList->ref(bottom); - mDisplayList->ref(rx); - mDisplayList->ref(ry); - mDisplayList->ref(paint); - refBitmapsInShader(paint->value.getShader()); - addOp(alloc().create_trivial<RoundRectPropsOp>( - *(mState.currentSnapshot()->transform), getRecordedClip(), &paint->value, &left->value, - &top->value, &right->value, &bottom->value, &rx->value, &ry->value)); +void DisplayListData::applyColorTransform(ColorTransform transform) { + this->map(color_transform_fns, transform); } -void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { - // TODO: move to Canvas.h - if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return; +RecordingCanvas::RecordingCanvas() : INHERITED(1, 1), fDL(nullptr) {} - drawOval(x - radius, y - radius, x + radius, y + radius, paint); +void RecordingCanvas::reset(DisplayListData* dl, const SkIRect& bounds) { + this->resetCanvas(bounds.right(), bounds.bottom()); + fDL = dl; } -void RecordingCanvas::drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y, - CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) { - mDisplayList->ref(x); - mDisplayList->ref(y); - mDisplayList->ref(radius); - mDisplayList->ref(paint); - refBitmapsInShader(paint->value.getShader()); - addOp(alloc().create_trivial<CirclePropsOp>(*(mState.currentSnapshot()->transform), - getRecordedClip(), &paint->value, &x->value, - &y->value, &radius->value)); +sk_sp<SkSurface> RecordingCanvas::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) { + return nullptr; } -void RecordingCanvas::drawOval(float left, float top, float right, float bottom, - const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; - - addOp(alloc().create_trivial<OvalOp>(Rect(left, top, right, bottom), - *(mState.currentSnapshot()->transform), getRecordedClip(), - refPaint(&paint))); +void RecordingCanvas::onFlush() { + fDL->flush(); } -void RecordingCanvas::drawArc(float left, float top, float right, float bottom, float startAngle, - float sweepAngle, bool useCenter, const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; - - if (fabs(sweepAngle) >= 360.0f) { - drawOval(left, top, right, bottom, paint); - } else { - addOp(alloc().create_trivial<ArcOp>( - Rect(left, top, right, bottom), *(mState.currentSnapshot()->transform), - getRecordedClip(), refPaint(&paint), startAngle, sweepAngle, useCenter)); - } +void RecordingCanvas::willSave() { + fDL->save(); +} +SkCanvas::SaveLayerStrategy RecordingCanvas::getSaveLayerStrategy(const SaveLayerRec& rec) { + fDL->saveLayer(rec.fBounds, rec.fPaint, rec.fBackdrop, rec.fClipMask, rec.fClipMatrix, + rec.fSaveLayerFlags); + return SkCanvas::kNoLayer_SaveLayerStrategy; +} +void RecordingCanvas::willRestore() { + fDL->restore(); } -void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) { - if (CC_UNLIKELY(paint.nothingToDraw())) return; - - addOp(alloc().create_trivial<PathOp>(Rect(path.getBounds()), - *(mState.currentSnapshot()->transform), getRecordedClip(), - refPaint(&paint), refPath(&path))); +void RecordingCanvas::didConcat(const SkMatrix& matrix) { + fDL->concat(matrix); +} +void RecordingCanvas::didSetMatrix(const SkMatrix& matrix) { + fDL->setMatrix(matrix); +} +void RecordingCanvas::didTranslate(SkScalar dx, SkScalar dy) { + fDL->translate(dx, dy); } -void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { - mDisplayList->ref(tree); - mDisplayList->vectorDrawables.push_back(tree); - addOp(alloc().create_trivial<VectorDrawableOp>( - tree, Rect(tree->stagingProperties()->getBounds()), - *(mState.currentSnapshot()->transform), getRecordedClip())); -} - -// Bitmap-based -void RecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) { - save(SaveFlags::Matrix); - translate(left, top); - drawBitmap(bitmap, paint); - restore(); -} - -void RecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) { - if (matrix.isIdentity()) { - drawBitmap(bitmap, paint); - } else if (!(matrix.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) && - MathUtils::isPositive(matrix.getScaleX()) && - MathUtils::isPositive(matrix.getScaleY())) { - // SkMatrix::isScaleTranslate() not available in L - SkRect src; - SkRect dst; - bitmap.getBounds(&src); - matrix.mapRect(&dst, src); - drawBitmap(bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft, dst.fTop, - dst.fRight, dst.fBottom, paint); - } else { - save(SaveFlags::Matrix); - concat(matrix); - drawBitmap(bitmap, paint); - restore(); - } -} - -void RecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight, - float srcBottom, float dstLeft, float dstTop, float dstRight, - float dstBottom, const SkPaint* paint) { - if (srcLeft == 0 && srcTop == 0 && srcRight == bitmap.width() && srcBottom == bitmap.height() && - (srcBottom - srcTop == dstBottom - dstTop) && (srcRight - srcLeft == dstRight - dstLeft)) { - // transform simple rect to rect drawing case into position bitmap ops, since they merge - save(SaveFlags::Matrix); - translate(dstLeft, dstTop); - drawBitmap(bitmap, paint); - restore(); - } else { - addOp(alloc().create_trivial<BitmapRectOp>( - Rect(dstLeft, dstTop, dstRight, dstBottom), *(mState.currentSnapshot()->transform), - getRecordedClip(), refPaint(paint), refBitmap(bitmap), - Rect(srcLeft, srcTop, srcRight, srcBottom))); - } +void RecordingCanvas::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle style) { + fDL->clipRect(rect, op, style == kSoft_ClipEdgeStyle); + this->INHERITED::onClipRect(rect, op, style); +} +void RecordingCanvas::onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle style) { + fDL->clipRRect(rrect, op, style == kSoft_ClipEdgeStyle); + this->INHERITED::onClipRRect(rrect, op, style); +} +void RecordingCanvas::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle style) { + fDL->clipPath(path, op, style == kSoft_ClipEdgeStyle); + this->INHERITED::onClipPath(path, op, style); +} +void RecordingCanvas::onClipRegion(const SkRegion& region, SkClipOp op) { + fDL->clipRegion(region, op); + this->INHERITED::onClipRegion(region, op); } -void RecordingCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight, - const float* vertices, const int* colors, - const SkPaint* paint) { - int vertexCount = (meshWidth + 1) * (meshHeight + 1); - addOp(alloc().create_trivial<BitmapMeshOp>( - calcBoundsOfPoints(vertices, vertexCount * 2), *(mState.currentSnapshot()->transform), - getRecordedClip(), refPaint(paint), refBitmap(bitmap), meshWidth, meshHeight, - refBuffer<float>(vertices, vertexCount * 2), // 2 floats per vertex - refBuffer<int>(colors, vertexCount))); // 1 color per vertex +void RecordingCanvas::onDrawPaint(const SkPaint& paint) { + fDL->drawPaint(paint); +} +void RecordingCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { + fDL->drawPath(path, paint); +} +void RecordingCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) { + fDL->drawRect(rect, paint); +} +void RecordingCanvas::onDrawRegion(const SkRegion& region, const SkPaint& paint) { + fDL->drawRegion(region, paint); +} +void RecordingCanvas::onDrawOval(const SkRect& oval, const SkPaint& paint) { + fDL->drawOval(oval, paint); +} +void RecordingCanvas::onDrawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, + bool useCenter, const SkPaint& paint) { + fDL->drawArc(oval, startAngle, sweepAngle, useCenter, paint); +} +void RecordingCanvas::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) { + fDL->drawRRect(rrect, paint); +} +void RecordingCanvas::onDrawDRRect(const SkRRect& out, const SkRRect& in, const SkPaint& paint) { + fDL->drawDRRect(out, in, paint); } -void RecordingCanvas::drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& patch, - float dstLeft, float dstTop, float dstRight, float dstBottom, +void RecordingCanvas::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) { + fDL->drawDrawable(drawable, matrix); +} +void RecordingCanvas::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) { - addOp(alloc().create_trivial<PatchOp>(Rect(dstLeft, dstTop, dstRight, dstBottom), - *(mState.currentSnapshot()->transform), getRecordedClip(), - refPaint(paint), refBitmap(bitmap), refPatch(&patch))); -} - -double RecordingCanvas::drawAnimatedImage(AnimatedImageDrawable*) { - // Unimplemented - return 0; -} - -// Text -void RecordingCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int glyphCount, const SkPaint& paint, - float x, float y, float boundsLeft, float boundsTop, - float boundsRight, float boundsBottom, float totalAdvance) { - if (glyphCount <= 0 || paint.nothingToDraw()) return; - uint16_t* glyphs = (glyph_t*)alloc().alloc<glyph_t>(glyphCount * sizeof(glyph_t)); - float* positions = (float*)alloc().alloc<float>(2 * glyphCount * sizeof(float)); - glyphFunc(glyphs, positions); - - // TODO: either must account for text shadow in bounds, or record separate ops for text shadows - addOp(alloc().create_trivial<TextOp>(Rect(boundsLeft, boundsTop, boundsRight, boundsBottom), - *(mState.currentSnapshot()->transform), getRecordedClip(), - refPaint(&paint), glyphs, positions, glyphCount, x, y)); - drawTextDecorations(x, y, totalAdvance, paint); -} - -void RecordingCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, - const SkPaint& paint, const SkPath& path, size_t start, - size_t end) { - uint16_t glyphs[1]; - for (size_t i = start; i < end; i++) { - glyphs[0] = layout.getGlyphId(i); - float x = hOffset + layout.getX(i); - float y = vOffset + layout.getY(i); - if (paint.nothingToDraw()) return; - const uint16_t* tempGlyphs = refBuffer<glyph_t>(glyphs, 1); - addOp(alloc().create_trivial<TextOnPathOp>(*(mState.currentSnapshot()->transform), - getRecordedClip(), refPaint(&paint), tempGlyphs, - 1, refPath(&path), x, y)); - } -} - -void RecordingCanvas::drawBitmap(Bitmap& bitmap, const SkPaint* paint) { - addOp(alloc().create_trivial<BitmapOp>(Rect(bitmap.width(), bitmap.height()), - *(mState.currentSnapshot()->transform), - getRecordedClip(), refPaint(paint), refBitmap(bitmap))); -} - -void RecordingCanvas::drawRenderNode(RenderNode* renderNode) { - auto&& stagingProps = renderNode->stagingProperties(); - RenderNodeOp* op = alloc().create_trivial<RenderNodeOp>( - Rect(stagingProps.getWidth(), stagingProps.getHeight()), - *(mState.currentSnapshot()->transform), getRecordedClip(), renderNode); - int opIndex = addOp(op); - if (CC_LIKELY(opIndex >= 0)) { - int childIndex = mDisplayList->addChild(op); - - // update the chunk's child indices - DisplayList::Chunk& chunk = mDisplayList->chunks.back(); - chunk.endChildIndex = childIndex + 1; - - if (renderNode->stagingProperties().isProjectionReceiver()) { - // use staging property, since recording on UI thread - mDisplayList->projectionReceiveIndex = opIndex; - } - } + fDL->drawPicture(picture, matrix, paint); +} +void RecordingCanvas::onDrawAnnotation(const SkRect& rect, const char key[], SkData* val) { + fDL->drawAnnotation(rect, key, val); } -void RecordingCanvas::drawLayer(DeferredLayerUpdater* layerHandle) { - // We ref the DeferredLayerUpdater due to its thread-safe ref-counting semantics. - mDisplayList->ref(layerHandle); +void RecordingCanvas::onDrawText(const void* text, size_t bytes, SkScalar x, SkScalar y, + const SkPaint& paint) { + fDL->drawText(text, bytes, x, y, paint); +} +void RecordingCanvas::onDrawPosText(const void* text, size_t bytes, const SkPoint pos[], + const SkPaint& paint) { + fDL->drawPosText(text, bytes, pos, paint); +} +void RecordingCanvas::onDrawPosTextH(const void* text, size_t bytes, const SkScalar xs[], + SkScalar y, const SkPaint& paint) { + fDL->drawPosTextH(text, bytes, xs, y, paint); +} +void RecordingCanvas::onDrawTextRSXform(const void* text, size_t bytes, const SkRSXform xform[], + const SkRect* cull, const SkPaint& paint) { + fDL->drawTextRSXform(text, bytes, xform, cull, paint); +} +void RecordingCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, + const SkPaint& paint) { + fDL->drawTextBlob(blob, x, y, paint); +} - LOG_ALWAYS_FATAL_IF(layerHandle->getBackingLayerApi() != Layer::Api::OpenGL); - // Note that the backing layer has *not* yet been updated, so don't trust - // its width, height, transform, etc...! - addOp(alloc().create_trivial<TextureLayerOp>( - Rect(layerHandle->getWidth(), layerHandle->getHeight()), - *(mState.currentSnapshot()->transform), getRecordedClip(), layerHandle)); +void RecordingCanvas::onDrawBitmap(const SkBitmap& bm, SkScalar x, SkScalar y, + const SkPaint* paint) { + fDL->drawImage(SkImage::MakeFromBitmap(bm), x, y, paint, BitmapPalette::Unknown); +} +void RecordingCanvas::onDrawBitmapNine(const SkBitmap& bm, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) { + fDL->drawImageNine(SkImage::MakeFromBitmap(bm), center, dst, paint); +} +void RecordingCanvas::onDrawBitmapRect(const SkBitmap& bm, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + fDL->drawImageRect(SkImage::MakeFromBitmap(bm), src, dst, paint, constraint, + BitmapPalette::Unknown); +} +void RecordingCanvas::onDrawBitmapLattice(const SkBitmap& bm, const SkCanvas::Lattice& lattice, + const SkRect& dst, const SkPaint* paint) { + fDL->drawImageLattice(SkImage::MakeFromBitmap(bm), lattice, dst, paint, BitmapPalette::Unknown); } -void RecordingCanvas::callDrawGLFunction(Functor* functor, GlFunctorLifecycleListener* listener) { - mDisplayList->functors.push_back({functor, listener}); - mDisplayList->ref(listener); - addOp(alloc().create_trivial<FunctorOp>(*(mState.currentSnapshot()->transform), - getRecordedClip(), functor)); +void RecordingCanvas::drawImage(const sk_sp<SkImage>& image, SkScalar x, SkScalar y, + const SkPaint* paint, BitmapPalette palette) { + fDL->drawImage(image, x, y, paint, palette); } -int RecordingCanvas::addOp(RecordedOp* op) { - // skip op with empty clip - if (op->localClip && op->localClip->rect.isEmpty()) { - // NOTE: this rejection happens after op construction/content ref-ing, so content ref'd - // and held by renderthread isn't affected by clip rejection. - // Could rewind alloc here if desired, but callers would have to not touch op afterwards. - return -1; +void RecordingCanvas::drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, + const SkRect& dst, const SkPaint* paint, + SrcRectConstraint constraint, BitmapPalette palette) { + fDL->drawImageRect(image, &src, dst, paint, constraint, palette); +} + +void RecordingCanvas::drawImageLattice(const sk_sp<SkImage>& image, const Lattice& lattice, + const SkRect& dst, const SkPaint* paint, + BitmapPalette palette) { + if (!image || dst.isEmpty()) { + return; } - int insertIndex = mDisplayList->ops.size(); - mDisplayList->ops.push_back(op); - if (mDeferredBarrierType != DeferredBarrierType::None) { - // op is first in new chunk - mDisplayList->chunks.emplace_back(); - DisplayList::Chunk& newChunk = mDisplayList->chunks.back(); - newChunk.beginOpIndex = insertIndex; - newChunk.endOpIndex = insertIndex + 1; - newChunk.reorderChildren = (mDeferredBarrierType == DeferredBarrierType::OutOfOrder); - newChunk.reorderClip = mDeferredBarrierClip; + SkIRect bounds; + Lattice latticePlusBounds = lattice; + if (!latticePlusBounds.fBounds) { + bounds = SkIRect::MakeWH(image->width(), image->height()); + latticePlusBounds.fBounds = &bounds; + } - int nextChildIndex = mDisplayList->children.size(); - newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex; - mDeferredBarrierType = DeferredBarrierType::None; + if (SkLatticeIter::Valid(image->width(), image->height(), latticePlusBounds)) { + fDL->drawImageLattice(image, latticePlusBounds, dst, paint, palette); } else { - // standard case - append to existing chunk - mDisplayList->chunks.back().endOpIndex = insertIndex + 1; + fDL->drawImageRect(image, nullptr, dst, paint, SrcRectConstraint::kFast_SrcRectConstraint, + palette); } - return insertIndex; } -void RecordingCanvas::refBitmapsInShader(const SkShader* shader) { - if (!shader) return; +void RecordingCanvas::onDrawImage(const SkImage* img, SkScalar x, SkScalar y, + const SkPaint* paint) { + fDL->drawImage(sk_ref_sp(img), x, y, paint, BitmapPalette::Unknown); +} +void RecordingCanvas::onDrawImageNine(const SkImage* img, const SkIRect& center, const SkRect& dst, + const SkPaint* paint) { + fDL->drawImageNine(sk_ref_sp(img), center, dst, paint); +} +void RecordingCanvas::onDrawImageRect(const SkImage* img, const SkRect* src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint) { + fDL->drawImageRect(sk_ref_sp(img), src, dst, paint, constraint, BitmapPalette::Unknown); +} +void RecordingCanvas::onDrawImageLattice(const SkImage* img, const SkCanvas::Lattice& lattice, + const SkRect& dst, const SkPaint* paint) { + fDL->drawImageLattice(sk_ref_sp(img), lattice, dst, paint, BitmapPalette::Unknown); +} - // 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)) { - Bitmap* hwuiBitmap = static_cast<Bitmap*>(bitmap.pixelRef()); - refBitmap(*hwuiBitmap); - return; - } - SkShader::ComposeRec rec; - if (shader->asACompose(&rec)) { - refBitmapsInShader(rec.fShaderA); - refBitmapsInShader(rec.fShaderB); - return; - } +void RecordingCanvas::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], + const SkPoint texCoords[4], SkBlendMode bmode, + const SkPaint& paint) { + fDL->drawPatch(cubics, colors, texCoords, bmode, paint); +} +void RecordingCanvas::onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[], + const SkPaint& paint) { + fDL->drawPoints(mode, count, pts, paint); +} +void RecordingCanvas::onDrawVerticesObject(const SkVertices* vertices, + const SkVertices::Bone bones[], int boneCount, + SkBlendMode mode, const SkPaint& paint) { + fDL->drawVertices(vertices, bones, boneCount, mode, paint); +} +void RecordingCanvas::onDrawAtlas(const SkImage* atlas, const SkRSXform xforms[], + const SkRect texs[], const SkColor colors[], int count, + SkBlendMode bmode, const SkRect* cull, const SkPaint* paint) { + fDL->drawAtlas(atlas, xforms, texs, colors, count, bmode, cull, paint); +} +void RecordingCanvas::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) { + fDL->drawShadowRec(path, rec); +} + +void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { + fDL->drawVectorDrawable(tree); } }; // namespace uirenderer diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index e663402a80f3..099e0be433ea 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,307 +14,207 @@ * limitations under the License. */ -#ifndef ANDROID_HWUI_RECORDING_CANVAS_H -#define ANDROID_HWUI_RECORDING_CANVAS_H +#pragma once -#include "CanvasState.h" -#include "DisplayList.h" -#include "ResourceCache.h" -#include "SkiaCanvasProxy.h" -#include "Snapshot.h" +#include "CanvasTransform.h" #include "hwui/Bitmap.h" #include "hwui/Canvas.h" -#include "utils/LinearAllocator.h" #include "utils/Macros.h" - -#include <SkDrawFilter.h> -#include <SkPaint.h> -#include <SkTLazy.h> +#include "utils/TypeLogic.h" + +#include "SkCanvas.h" +#include "SkCanvasVirtualEnforcer.h" +#include "SkDrawable.h" +#include "SkNoDrawCanvas.h" +#include "SkPaint.h" +#include "SkPath.h" +#include "SkRect.h" +#include "SkTDArray.h" +#include "SkTemplates.h" #include <vector> namespace android { namespace uirenderer { -struct ClipBase; -class DeferredLayerUpdater; -struct RecordedOp; +enum class DisplayListOpType : uint8_t { +#define X(T) T, +#include "DisplayListOps.in" +#undef X +}; + +struct DisplayListOp { + const uint8_t type : 8; + const uint32_t skip : 24; +}; + +static_assert(sizeof(DisplayListOp) == 4); + +class RecordingCanvas; + +class DisplayListData final { +public: + DisplayListData() : mHasText(false) {} + ~DisplayListData(); + + void draw(SkCanvas* canvas) const; + + void reset(); + bool empty() const { return fUsed == 0; } + + void applyColorTransform(ColorTransform transform); -class ANDROID_API RecordingCanvas : public Canvas, public CanvasStateClient { - enum class DeferredBarrierType { - None, - InOrder, - OutOfOrder, - }; + bool hasText() const { return mHasText; } +private: + friend class RecordingCanvas; + + void flush(); + + void save(); + void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, const SkImage*, + const SkMatrix*, SkCanvas::SaveLayerFlags); + void restore(); + + void concat(const SkMatrix&); + void setMatrix(const SkMatrix&); + void translate(SkScalar, SkScalar); + void translateZ(SkScalar); + + void clipPath(const SkPath&, SkClipOp, bool aa); + void clipRect(const SkRect&, SkClipOp, bool aa); + void clipRRect(const SkRRect&, SkClipOp, bool aa); + void clipRegion(const SkRegion&, SkClipOp); + + void drawPaint(const SkPaint&); + void drawPath(const SkPath&, const SkPaint&); + void drawRect(const SkRect&, const SkPaint&); + void drawRegion(const SkRegion&, const SkPaint&); + void drawOval(const SkRect&, const SkPaint&); + void drawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&); + void drawRRect(const SkRRect&, const SkPaint&); + void drawDRRect(const SkRRect&, const SkRRect&, const SkPaint&); + + void drawAnnotation(const SkRect&, const char*, SkData*); + void drawDrawable(SkDrawable*, const SkMatrix*); + void drawPicture(const SkPicture*, const SkMatrix*, const SkPaint*); + + void drawText(const void*, size_t, SkScalar, SkScalar, const SkPaint&); + void drawPosText(const void*, size_t, const SkPoint[], const SkPaint&); + void drawPosTextH(const void*, size_t, const SkScalar[], SkScalar, const SkPaint&); + void drawTextRSXform(const void*, size_t, const SkRSXform[], const SkRect*, const SkPaint&); + void drawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&); + + void drawImage(sk_sp<const SkImage>, SkScalar, SkScalar, const SkPaint*, BitmapPalette palette); + void drawImageNine(sk_sp<const SkImage>, const SkIRect&, const SkRect&, const SkPaint*); + void drawImageRect(sk_sp<const SkImage>, const SkRect*, const SkRect&, const SkPaint*, + SkCanvas::SrcRectConstraint, BitmapPalette palette); + void drawImageLattice(sk_sp<const SkImage>, const SkCanvas::Lattice&, const SkRect&, + const SkPaint*, BitmapPalette); + + void drawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode, + const SkPaint&); + void drawPoints(SkCanvas::PointMode, size_t, const SkPoint[], const SkPaint&); + void drawVertices(const SkVertices*, const SkVertices::Bone bones[], int boneCount, SkBlendMode, + const SkPaint&); + void drawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int, + SkBlendMode, const SkRect*, const SkPaint*); + void drawShadowRec(const SkPath&, const SkDrawShadowRec&); + void drawVectorDrawable(VectorDrawableRoot* tree); + + template <typename T, typename... Args> + void* push(size_t, Args&&...); + + template <typename Fn, typename... Args> + void map(const Fn[], Args...) const; + + SkAutoTMalloc<uint8_t> fBytes; + size_t fUsed = 0; + size_t fReserved = 0; + + bool mHasText : 1; +}; + +class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> { public: - RecordingCanvas(size_t width, size_t height); - virtual ~RecordingCanvas(); - - virtual void resetRecording(int width, int height, RenderNode* node = nullptr) override; - virtual WARN_UNUSED_RESULT DisplayList* finishRecording() override; - // ---------------------------------------------------------------------------- - // MISC HWUI OPERATIONS - TODO: CATEGORIZE - // ---------------------------------------------------------------------------- - virtual void insertReorderBarrier(bool enableReorder) override; - - virtual void drawLayer(DeferredLayerUpdater* layerHandle) override; - virtual void drawRenderNode(RenderNode* renderNode) override; - virtual void callDrawGLFunction(Functor* functor, - GlFunctorLifecycleListener* listener) override; - - // ---------------------------------------------------------------------------- - // CanvasStateClient interface - // ---------------------------------------------------------------------------- - virtual void onViewportInitialized() override; - virtual void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) override; - virtual GLuint getTargetFbo() const override { return -1; } - - // ---------------------------------------------------------------------------- - // HWUI Canvas draw operations - // ---------------------------------------------------------------------------- - - virtual void drawRoundRect(CanvasPropertyPrimitive* left, CanvasPropertyPrimitive* top, - CanvasPropertyPrimitive* right, CanvasPropertyPrimitive* bottom, - CanvasPropertyPrimitive* rx, CanvasPropertyPrimitive* ry, - CanvasPropertyPaint* paint) override; - virtual void drawCircle(CanvasPropertyPrimitive* x, CanvasPropertyPrimitive* y, - CanvasPropertyPrimitive* radius, CanvasPropertyPaint* paint) override; - - // ---------------------------------------------------------------------------- - // android/graphics/Canvas interface - // ---------------------------------------------------------------------------- - virtual SkCanvas* asSkCanvas() override; - - virtual void setBitmap(const SkBitmap& bitmap) override { - LOG_ALWAYS_FATAL("RecordingCanvas is not backed by a bitmap."); - } - - virtual bool isOpaque() override { return false; } - virtual int width() override { return mState.getWidth(); } - virtual int height() override { return mState.getHeight(); } - - // ---------------------------------------------------------------------------- - // android/graphics/Canvas state operations - // ---------------------------------------------------------------------------- - // Save (layer) - virtual int getSaveCount() const override { return mState.getSaveCount(); } - virtual int save(SaveFlags::Flags flags) override; - virtual void restore() override; - virtual void restoreToCount(int saveCount) override; - - virtual int saveLayer(float left, float top, float right, float bottom, const SkPaint* paint, - SaveFlags::Flags flags) override; - virtual int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, - SaveFlags::Flags flags) override { - SkPaint paint; - paint.setAlpha(alpha); - return saveLayer(left, top, right, bottom, &paint, flags); - } - - // Matrix - virtual void getMatrix(SkMatrix* outMatrix) const override { mState.getMatrix(outMatrix); } - virtual void setMatrix(const SkMatrix& matrix) override { mState.setMatrix(matrix); } - - virtual void concat(const SkMatrix& matrix) override { mState.concatMatrix(matrix); } - virtual void rotate(float degrees) override; - virtual void scale(float sx, float sy) override; - virtual void skew(float sx, float sy) override; - virtual void translate(float dx, float dy) override; - - // Clip - virtual bool getClipBounds(SkRect* outRect) const override; - virtual bool quickRejectRect(float left, float top, float right, float bottom) const override; - virtual bool quickRejectPath(const SkPath& path) const override; - - virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override; - virtual bool clipPath(const SkPath* path, SkClipOp 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, SkBlendMode 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(const SkVertices*, SkBlendMode, const SkPaint& paint) - override { /* RecordingCanvas does not support drawVertices(); ignore */ - } - - virtual void drawVectorDrawable(VectorDrawableRoot* tree) override; - - // Bitmap-based - virtual void drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) override; - virtual void drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) override; - virtual void drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight, - float srcBottom, float dstLeft, float dstTop, float dstRight, - float dstBottom, const SkPaint* paint) override; - virtual void drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight, - const float* vertices, const int* colors, - const SkPaint* paint) override; - virtual void drawNinePatch(Bitmap& bitmap, const android::Res_png_9patch& chunk, float dstLeft, - float dstTop, float dstRight, float dstBottom, - const SkPaint* paint) override; - virtual double drawAnimatedImage(AnimatedImageDrawable*) override; - - // Text - virtual bool drawTextAbsolutePos() const override { return false; } - -protected: - virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& paint, float x, - float y, float boundsLeft, float boundsTop, float boundsRight, - float boundsBottom, float totalAdvance) override; - virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, - const SkPaint& paint, const SkPath& path, size_t start, - size_t end) override; + RecordingCanvas(); + void reset(DisplayListData*, const SkIRect& bounds); + + sk_sp<SkSurface> onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; + + void willSave() override; + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; + void willRestore() override; + + void onFlush() override; + + void didConcat(const SkMatrix&) override; + void didSetMatrix(const SkMatrix&) override; + void didTranslate(SkScalar, SkScalar) override; + + void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; + void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; + void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; + void onClipRegion(const SkRegion&, SkClipOp) override; + + void onDrawPaint(const SkPaint&) override; + void onDrawPath(const SkPath&, const SkPaint&) override; + void onDrawRect(const SkRect&, const SkPaint&) override; + void onDrawRegion(const SkRegion&, const SkPaint&) override; + void onDrawOval(const SkRect&, const SkPaint&) override; + void onDrawArc(const SkRect&, SkScalar, SkScalar, bool, const SkPaint&) override; + void onDrawRRect(const SkRRect&, const SkPaint&) override; + void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; + + void onDrawDrawable(SkDrawable*, const SkMatrix*) override; + void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override; + void onDrawAnnotation(const SkRect&, const char[], SkData*) override; + + void onDrawText(const void*, size_t, SkScalar x, SkScalar y, const SkPaint&) override; + void onDrawPosText(const void*, size_t, const SkPoint[], const SkPaint&) override; + void onDrawPosTextH(const void*, size_t, const SkScalar[], SkScalar, const SkPaint&) override; + + void onDrawTextRSXform(const void*, size_t, const SkRSXform[], const SkRect*, + const SkPaint&) override; + void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override; + + void onDrawBitmap(const SkBitmap&, SkScalar, SkScalar, const SkPaint*) override; + void onDrawBitmapLattice(const SkBitmap&, const Lattice&, const SkRect&, + const SkPaint*) override; + void onDrawBitmapNine(const SkBitmap&, const SkIRect&, const SkRect&, const SkPaint*) override; + void onDrawBitmapRect(const SkBitmap&, const SkRect*, const SkRect&, const SkPaint*, + SrcRectConstraint) override; + + void drawImage(const sk_sp<SkImage>& image, SkScalar left, SkScalar top, const SkPaint* paint, + BitmapPalette pallete); + + void drawImageRect(const sk_sp<SkImage>& image, const SkRect& src, const SkRect& dst, + const SkPaint* paint, SrcRectConstraint constraint, BitmapPalette palette); + void drawImageLattice(const sk_sp<SkImage>& image, const Lattice& lattice, const SkRect& dst, + const SkPaint* paint, BitmapPalette palette); + + void onDrawImage(const SkImage*, SkScalar, SkScalar, const SkPaint*) override; + void onDrawImageLattice(const SkImage*, const Lattice&, const SkRect&, const SkPaint*) override; + void onDrawImageNine(const SkImage*, const SkIRect&, const SkRect&, const SkPaint*) override; + void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*, + SrcRectConstraint) override; + + void onDrawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode, + const SkPaint&) override; + void onDrawPoints(PointMode, size_t count, const SkPoint pts[], const SkPaint&) override; + void onDrawVerticesObject(const SkVertices*, const SkVertices::Bone bones[], int boneCount, + SkBlendMode, const SkPaint&) override; + void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[], int, + SkBlendMode, const SkRect*, const SkPaint*) override; + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override; + + void drawVectorDrawable(VectorDrawableRoot* tree); private: - const ClipBase* getRecordedClip() { - return mState.writableSnapshot()->mutateClipArea().serializeClip(alloc()); - } - - void drawBitmap(Bitmap& bitmap, const SkPaint* paint); - void drawSimpleRects(const float* rects, int vertexCount, const SkPaint* paint); - - int addOp(RecordedOp* op); - // ---------------------------------------------------------------------------- - // lazy object copy - // ---------------------------------------------------------------------------- - LinearAllocator& alloc() { return mDisplayList->allocator; } - - void refBitmapsInShader(const SkShader* shader); - - template <class T> - inline const T* refBuffer(const T* srcBuffer, int32_t count) { - if (!srcBuffer) return nullptr; - - T* dstBuffer = (T*)mDisplayList->allocator.alloc<T>(count * sizeof(T)); - memcpy(dstBuffer, srcBuffer, count * sizeof(T)); - return dstBuffer; - } - - inline const SkPath* refPath(const SkPath* path) { - if (!path) return nullptr; - - // The points/verbs within the path are refcounted so this copy operation - // is inexpensive and maintains the generationID of the original path. - const SkPath* cachedPath = new SkPath(*path); - mDisplayList->pathResources.push_back(cachedPath); - return cachedPath; - } - - /** - * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in - * (with deduping based on paint hash / equality check) - */ - inline const SkPaint* refPaint(const SkPaint* paint) { - if (!paint) return nullptr; - - // If there is a draw filter apply it here and store the modified paint - // so that we don't need to modify the paint every time we access it. - SkTLazy<SkPaint> filteredPaint; - if (mDrawFilter.get()) { - filteredPaint.set(*paint); - mDrawFilter->filter(filteredPaint.get(), SkDrawFilter::kPaint_Type); - paint = filteredPaint.get(); - } - - // compute the hash key for the paint and check the cache. - const uint32_t key = paint->getHash(); - const SkPaint* cachedPaint = mPaintMap.valueFor(key); - // In the unlikely event that 2 unique paints have the same hash we do a - // object equality check to ensure we don't erroneously dedup them. - if (cachedPaint == nullptr || *cachedPaint != *paint) { - cachedPaint = new SkPaint(*paint); - mDisplayList->paints.emplace_back(cachedPaint); - // replaceValueFor() performs an add if the entry doesn't exist - mPaintMap.replaceValueFor(key, cachedPaint); - refBitmapsInShader(cachedPaint->getShader()); - } - - return cachedPaint; - } - - inline const SkRegion* refRegion(const SkRegion* region) { - if (!region) { - return region; - } - - const SkRegion* cachedRegion = mRegionMap.valueFor(region); - // TODO: Add generation ID to SkRegion - if (cachedRegion == nullptr) { - std::unique_ptr<const SkRegion> copy(new SkRegion(*region)); - cachedRegion = copy.get(); - mDisplayList->regions.push_back(std::move(copy)); - - // replaceValueFor() performs an add if the entry doesn't exist - mRegionMap.replaceValueFor(region, cachedRegion); - } - - return cachedRegion; - } - - inline Bitmap* refBitmap(Bitmap& 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. - - // this is required because sk_sp's ctor adopts the pointer, - // but does not increment the refcount, - bitmap.ref(); - mDisplayList->bitmapResources.emplace_back(&bitmap); - return &bitmap; - } - - inline const Res_png_9patch* refPatch(const Res_png_9patch* patch) { - mDisplayList->patchResources.push_back(patch); - mResourceCache.incrementRefcount(patch); - return patch; - } - - DefaultKeyedVector<uint32_t, const SkPaint*> mPaintMap; - DefaultKeyedVector<const SkPath*, const SkPath*> mPathMap; - DefaultKeyedVector<const SkRegion*, const SkRegion*> mRegionMap; - - CanvasState mState; - std::unique_ptr<SkiaCanvasProxy> mSkiaCanvasProxy; - ResourceCache& mResourceCache; - DeferredBarrierType mDeferredBarrierType = DeferredBarrierType::None; - const ClipBase* mDeferredBarrierClip = nullptr; - DisplayList* mDisplayList = nullptr; - sk_sp<SkDrawFilter> mDrawFilter; -}; // class RecordingCanvas + typedef SkCanvasVirtualEnforcer<SkNoDrawCanvas> INHERITED; -}; // namespace uirenderer -}; // namespace android + DisplayListData* fDL; +}; -#endif // ANDROID_HWUI_RECORDING_CANVAS_H +}; // namespace uirenderer +}; // namespace android
\ No newline at end of file diff --git a/libs/hwui/RenderBuffer.h b/libs/hwui/RenderBuffer.h deleted file mode 100644 index 191a66a6d741..000000000000 --- a/libs/hwui/RenderBuffer.h +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_RENDER_BUFFER_H -#define ANDROID_HWUI_RENDER_BUFFER_H - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -namespace android { -namespace uirenderer { - -/** - * Represents an OpenGL render buffer. Render buffers are attached - * to layers to perform stencil work. - */ -struct RenderBuffer { - /** - * Creates a new render buffer in the specified format and dimensions. - * The format must be one of the formats allowed by glRenderbufferStorage(). - */ - RenderBuffer(GLenum format, uint32_t width, uint32_t height) - : mFormat(format), mWidth(width), mHeight(height), mAllocated(false) { - glGenRenderbuffers(1, &mName); - } - - ~RenderBuffer() { - if (mName) { - glDeleteRenderbuffers(1, &mName); - } - } - - /** - * Returns the GL name of this render buffer. - */ - GLuint getName() const { return mName; } - - /** - * Returns the format of this render buffer. - */ - GLenum getFormat() const { return mFormat; } - - /** - * Binds this render buffer to the current GL context. - */ - void bind() const { glBindRenderbuffer(GL_RENDERBUFFER, mName); } - - /** - * Indicates whether this render buffer has allocated its - * storage. See allocate() and resize(). - */ - bool isAllocated() const { return mAllocated; } - - /** - * Allocates this render buffer's storage if needed. - * This method doesn't do anything if isAllocated() returns true. - */ - void allocate() { - if (!mAllocated) { - glRenderbufferStorage(GL_RENDERBUFFER, mFormat, mWidth, mHeight); - mAllocated = true; - } - } - - /** - * Resizes this render buffer. If the buffer was previously allocated, - * the storage is re-allocated wit the new specified dimensions. If the - * buffer wasn't previously allocated, the buffer remains unallocated. - */ - void resize(uint32_t width, uint32_t height) { - if (isAllocated() && (width != mWidth || height != mHeight)) { - glRenderbufferStorage(GL_RENDERBUFFER, mFormat, width, height); - } - - mWidth = width; - mHeight = height; - } - - /** - * Returns the width of the render buffer in pixels. - */ - uint32_t getWidth() const { return mWidth; } - - /** - * Returns the height of the render buffer in pixels. - */ - uint32_t getHeight() const { return mHeight; } - - /** - * Returns the size of this render buffer in bytes. - */ - uint32_t getSize() const { - // Round to the nearest byte - return (uint32_t)((mWidth * mHeight * formatSize(mFormat)) / 8.0f + 0.5f); - } - - /** - * Returns the number of bits per component in the specified format. - * The format must be one of the formats allowed by glRenderbufferStorage(). - */ - static uint32_t formatSize(GLenum format) { - switch (format) { - case GL_STENCIL_INDEX8: - return 8; - case GL_STENCIL_INDEX1_OES: - return 1; - case GL_STENCIL_INDEX4_OES: - return 4; - case GL_DEPTH_COMPONENT16: - case GL_RGBA4: - case GL_RGB565: - case GL_RGB5_A1: - return 16; - } - return 0; - } - - /** - * Indicates whether the specified format represents a stencil buffer. - */ - static bool isStencilBuffer(GLenum format) { - switch (format) { - case GL_STENCIL_INDEX8: - case GL_STENCIL_INDEX1_OES: - case GL_STENCIL_INDEX4_OES: - return true; - } - return false; - } - - /** - * Returns the name of the specified render buffer format. - */ - static const char* formatName(GLenum format) { - switch (format) { - case GL_STENCIL_INDEX8: - return "STENCIL_8"; - case GL_STENCIL_INDEX1_OES: - return "STENCIL_1"; - case GL_STENCIL_INDEX4_OES: - return "STENCIL_4"; - case GL_DEPTH_COMPONENT16: - return "DEPTH_16"; - case GL_RGBA4: - return "RGBA_4444"; - case GL_RGB565: - return "RGB_565"; - case GL_RGB5_A1: - return "RGBA_5551"; - } - return "Unknown"; - } - -private: - GLenum mFormat; - - uint32_t mWidth; - uint32_t mHeight; - - bool mAllocated; - - GLuint mName; -}; // struct RenderBuffer - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_RENDER_BUFFER_H diff --git a/libs/hwui/RenderBufferCache.cpp b/libs/hwui/RenderBufferCache.cpp deleted file mode 100644 index 98010d8da1bd..000000000000 --- a/libs/hwui/RenderBufferCache.cpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "RenderBufferCache.h" -#include "Debug.h" -#include "DeviceInfo.h" -#include "Properties.h" - -#include <utils/Log.h> - -#include <cstdlib> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -// Debug -#if DEBUG_RENDER_BUFFERS -#define RENDER_BUFFER_LOGD(...) ALOGD(__VA_ARGS__) -#else -#define RENDER_BUFFER_LOGD(...) -#endif - -static uint32_t calculateRboCacheSize() { - // TODO: Do we need to use extensions().has4BitStencil() here? - // The tuning guide recommends it, but all real devices are configured - // with a larger cache than necessary by 4x, so keep the 2x for now regardless - return DeviceInfo::multiplyByResolution(2); -} - -/////////////////////////////////////////////////////////////////////////////// -// Constructors/destructor -/////////////////////////////////////////////////////////////////////////////// - -RenderBufferCache::RenderBufferCache() : mSize(0), mMaxSize(calculateRboCacheSize()) {} - -RenderBufferCache::~RenderBufferCache() { - clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Size management -/////////////////////////////////////////////////////////////////////////////// - -uint32_t RenderBufferCache::getSize() { - return mSize; -} - -uint32_t RenderBufferCache::getMaxSize() { - return mMaxSize; -} - -/////////////////////////////////////////////////////////////////////////////// -// Caching -/////////////////////////////////////////////////////////////////////////////// - -int RenderBufferCache::RenderBufferEntry::compare(const RenderBufferCache::RenderBufferEntry& lhs, - const RenderBufferCache::RenderBufferEntry& rhs) { - int deltaInt = int(lhs.mWidth) - int(rhs.mWidth); - if (deltaInt != 0) return deltaInt; - - deltaInt = int(lhs.mHeight) - int(rhs.mHeight); - if (deltaInt != 0) return deltaInt; - - return int(lhs.mFormat) - int(rhs.mFormat); -} - -void RenderBufferCache::deleteBuffer(RenderBuffer* buffer) { - if (buffer) { - RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d)", - RenderBuffer::formatName(buffer->getFormat()), buffer->getWidth(), - buffer->getHeight()); - - mSize -= buffer->getSize(); - delete buffer; - } -} - -void RenderBufferCache::clear() { - for (auto entry : mCache) { - deleteBuffer(entry.mBuffer); - } - mCache.clear(); -} - -RenderBuffer* RenderBufferCache::get(GLenum format, const uint32_t width, const uint32_t height) { - RenderBuffer* buffer = nullptr; - - RenderBufferEntry entry(format, width, height); - auto iter = mCache.find(entry); - - if (iter != mCache.end()) { - entry = *iter; - mCache.erase(iter); - - buffer = entry.mBuffer; - mSize -= buffer->getSize(); - - RENDER_BUFFER_LOGD("Found %s render buffer (%dx%d)", RenderBuffer::formatName(format), - width, height); - } else { - buffer = new RenderBuffer(format, width, height); - - RENDER_BUFFER_LOGD("Created new %s render buffer (%dx%d)", RenderBuffer::formatName(format), - width, height); - } - - buffer->bind(); - buffer->allocate(); - - return buffer; -} - -bool RenderBufferCache::put(RenderBuffer* buffer) { - if (!buffer) return false; - - const uint32_t size = buffer->getSize(); - if (size < mMaxSize) { - while (mSize + size > mMaxSize) { - RenderBuffer* victim = mCache.begin()->mBuffer; - deleteBuffer(victim); - mCache.erase(mCache.begin()); - } - - RenderBufferEntry entry(buffer); - - mCache.insert(entry); - mSize += size; - - RENDER_BUFFER_LOGD("Added %s render buffer (%dx%d)", - RenderBuffer::formatName(buffer->getFormat()), buffer->getWidth(), - buffer->getHeight()); - - return true; - } else { - RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d) Size=%d, MaxSize=%d", - RenderBuffer::formatName(buffer->getFormat()), buffer->getWidth(), - buffer->getHeight(), size, mMaxSize); - delete buffer; - } - return false; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/RenderBufferCache.h b/libs/hwui/RenderBufferCache.h deleted file mode 100644 index c936a5283965..000000000000 --- a/libs/hwui/RenderBufferCache.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_RENDER_BUFFER_CACHE_H -#define ANDROID_HWUI_RENDER_BUFFER_CACHE_H - -#include <GLES2/gl2.h> - -#include "RenderBuffer.h" - -#include <set> - -namespace android { -namespace uirenderer { - -class RenderBufferCache { -public: - RenderBufferCache(); - ~RenderBufferCache(); - - /** - * Returns a buffer with the exact specified dimensions. If no suitable - * buffer can be found, a new one is created and returned. If creating a - * new buffer fails, NULL is returned. - * - * When a buffer is obtained from the cache, it is removed and the total - * size of the cache goes down. - * - * The returned buffer is always allocated and bound - * (see RenderBuffer::isAllocated()). - * - * @param format The desired render buffer format - * @param width The desired width of the buffer - * @param height The desired height of the buffer - */ - RenderBuffer* get(GLenum format, const uint32_t width, const uint32_t height); - - /** - * Adds the buffer to the cache. The buffer will not be added if there is - * not enough space available. Adding a buffer can cause other buffer to - * be removed from the cache. - * - * @param buffer The render buffer to add to the cache - * - * @return True if the buffer was added, false otherwise. - */ - bool put(RenderBuffer* buffer); - /** - * Clears the cache. This causes all layers to be deleted. - */ - void clear(); - - /** - * Returns the maximum size of the cache in bytes. - */ - uint32_t getMaxSize(); - /** - * Returns the current size of the cache in bytes. - */ - uint32_t getSize(); - -private: - struct RenderBufferEntry { - RenderBufferEntry() : mBuffer(nullptr), mWidth(0), mHeight(0) {} - - RenderBufferEntry(GLenum format, const uint32_t width, const uint32_t height) - : mBuffer(nullptr), mFormat(format), mWidth(width), mHeight(height) {} - - explicit RenderBufferEntry(RenderBuffer* buffer) - : mBuffer(buffer) - , mFormat(buffer->getFormat()) - , mWidth(buffer->getWidth()) - , mHeight(buffer->getHeight()) {} - - static int compare(const RenderBufferEntry& lhs, const RenderBufferEntry& rhs); - - bool operator==(const RenderBufferEntry& other) const { return compare(*this, other) == 0; } - - bool operator!=(const RenderBufferEntry& other) const { return compare(*this, other) != 0; } - - bool operator<(const RenderBufferEntry& other) const { - return RenderBufferEntry::compare(*this, other) < 0; - } - - RenderBuffer* mBuffer; - GLenum mFormat; - uint32_t mWidth; - uint32_t mHeight; - }; // struct RenderBufferEntry - - void deleteBuffer(RenderBuffer* buffer); - - std::multiset<RenderBufferEntry> mCache; - - uint32_t mSize; - uint32_t mMaxSize; -}; // class RenderBufferCache - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_RENDER_BUFFER_CACHE_H diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index d93a7578cfd7..d2a8f02cc6a7 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -16,10 +16,8 @@ #include "RenderNode.h" -#include "BakedOpRenderer.h" #include "DamageAccumulator.h" #include "Debug.h" -#include "RecordedOp.h" #include "TreeInfo.h" #include "VectorDrawable.h" #include "renderstate/RenderState.h" @@ -29,9 +27,6 @@ #include "utils/StringUtils.h" #include "utils/TraceUtils.h" -#include "protos/ProtoHelpers.h" -#include "protos/hwui.pb.h" - #include <SkPathOps.h> #include <algorithm> #include <sstream> @@ -101,77 +96,6 @@ void RenderNode::output(std::ostream& output, uint32_t level) { output << std::endl; } -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 (mStagingDisplayList) { @@ -188,11 +112,9 @@ void RenderNode::prepareTree(TreeInfo& info) { LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing"); MarkAndSweepRemoved observer(&info); - // The OpenGL renderer reserves the stencil buffer for overdraw debugging. Functors - // will need to be drawn in a layer. - bool functorsNeedLayer = Properties::debugOverdraw && !Properties::isSkiaEnabled(); - - prepareTreeImpl(observer, info, functorsNeedLayer); + const int before = info.disableForceDark; + prepareTreeImpl(observer, info, false); + LOG_ALWAYS_FATAL_IF(before != info.disableForceDark, "Mis-matched force dark"); } void RenderNode::addAnimator(const sp<BaseRenderNodeAnimator>& animator) { @@ -238,7 +160,7 @@ void RenderNode::pushLayerUpdate(TreeInfo& info) { CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) || CC_UNLIKELY(!properties().fitsOnLayer())) { if (CC_UNLIKELY(hasLayer())) { - renderthread::CanvasContext::destroyLayer(this); + this->setLayerSurface(nullptr); } return; } @@ -275,6 +197,11 @@ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool fu if (info.mode == TreeInfo::MODE_FULL) { pushStagingPropertiesChanges(info); } + + if (!mProperties.getAllowForceDark()) { + info.disableForceDark++; + } + uint32_t animatorDirtyMask = 0; if (CC_LIKELY(info.runAnimations)) { animatorDirtyMask = mAnimatorManager.animate(info); @@ -312,6 +239,9 @@ void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool fu } pushLayerUpdate(info); + if (!mProperties.getAllowForceDark()) { + info.disableForceDark--; + } info.damageAccumulator->popTransform(); } @@ -352,7 +282,45 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { mStagingDisplayList = nullptr; if (mDisplayList) { mDisplayList->syncContents(); + handleForceDark(info); + } +} + +void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { + if (CC_LIKELY(!info || info->disableForceDark)) { + return; + } + auto usage = usageHint(); + const auto& children = mDisplayList->mChildNodes; + if (mDisplayList->hasText()) { + usage = UsageHint::Foreground; + } + if (usage == UsageHint::Unknown) { + if (children.size() > 1) { + usage = UsageHint::Background; + } else if (children.size() == 1 && + children.front().getRenderNode()->usageHint() != + UsageHint::Background) { + usage = UsageHint::Background; + } + } + if (children.size() > 1) { + // Crude overlap check + SkRect drawn = SkRect::MakeEmpty(); + for (auto iter = children.rbegin(); iter != children.rend(); ++iter) { + const auto& child = iter->getRenderNode(); + // We use stagingProperties here because we haven't yet sync'd the children + SkRect bounds = SkRect::MakeXYWH(child->stagingProperties().getX(), child->stagingProperties().getY(), + child->stagingProperties().getWidth(), child->stagingProperties().getHeight()); + if (bounds.contains(drawn)) { + // This contains everything drawn after it, so make it a background + child->setUsageHint(UsageHint::Background); + } + drawn.join(bounds); + } } + mDisplayList->mDisplayList.applyColorTransform( + usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light); } void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) { @@ -379,7 +347,7 @@ void RenderNode::deleteDisplayList(TreeObserver& observer, TreeInfo* info) { void RenderNode::destroyHardwareResources(TreeInfo* info) { if (hasLayer()) { - renderthread::CanvasContext::destroyLayer(this); + this->setLayerSurface(nullptr); } setStagingDisplayList(nullptr); @@ -389,7 +357,7 @@ void RenderNode::destroyHardwareResources(TreeInfo* info) { void RenderNode::destroyLayers() { if (hasLayer()) { - renderthread::CanvasContext::destroyLayer(this); + this->setLayerSurface(nullptr); } if (mDisplayList) { mDisplayList->updateChildren([](RenderNode* child) { child->destroyLayers(); }); @@ -459,78 +427,6 @@ void RenderNode::applyViewPropertyTransforms(mat4& matrix, bool true3dTransform) } } -/** - * Organizes the DisplayList hierarchy to prepare for background projection reordering. - * - * This should be called before a call to defer() or drawDisplayList() - * - * Each DisplayList that serves as a 3d root builds its list of composited children, - * which are flagged to not draw in the standard draw loop. - */ -void RenderNode::computeOrdering() { - ATRACE_CALL(); - mProjectedNodes.clear(); - - // TODO: create temporary DDLOp and call computeOrderingImpl on top DisplayList so that - // transform properties are applied correctly to top level children - if (mDisplayList == nullptr) return; - for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) { - RenderNodeOp* childOp = mDisplayList->getChildren()[i]; - childOp->renderNode->computeOrderingImpl(childOp, &mProjectedNodes, &mat4::identity()); - } -} - -void RenderNode::computeOrderingImpl( - RenderNodeOp* opState, std::vector<RenderNodeOp*>* compositedChildrenOfProjectionSurface, - const mat4* transformFromProjectionSurface) { - mProjectedNodes.clear(); - 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->localMatrix); - - if (properties().getProjectBackwards()) { - // composited projectee, flag for out of order draw, save matrix, and store in proj surface - opState->skipInOrderDraw = true; - opState->transformFromCompositingAncestor = localTransformFromProjectionSurface; - compositedChildrenOfProjectionSurface->push_back(opState); - } else { - // standard in order draw - opState->skipInOrderDraw = false; - } - - if (mDisplayList->getChildren().size() > 0) { - const bool isProjectionReceiver = mDisplayList->projectionReceiveIndex >= 0; - bool haveAppliedPropertiesToProjection = false; - for (unsigned int i = 0; i < mDisplayList->getChildren().size(); i++) { - RenderNodeOp* childOp = mDisplayList->getChildren()[i]; - RenderNode* child = childOp->renderNode; - - std::vector<RenderNodeOp*>* projectionChildren = nullptr; - const mat4* projectionTransform = nullptr; - if (isProjectionReceiver && !child->properties().getProjectBackwards()) { - // if receiving projections, collect projecting descendant - - // 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. - projectionChildren = &mProjectedNodes; - projectionTransform = &mat4::identity(); - } else { - if (!haveAppliedPropertiesToProjection) { - applyViewPropertyTransforms(localTransformFromProjectionSurface); - haveAppliedPropertiesToProjection = true; - } - projectionChildren = compositedChildrenOfProjectionSurface; - projectionTransform = &localTransformFromProjectionSurface; - } - child->computeOrderingImpl(childOp, projectionChildren, projectionTransform); - } - } -} - const SkPath* RenderNode::getClippedOutline(const SkRect& clipRect) const { const SkPath* outlinePath = properties().getOutline().getPath(); const uint32_t outlineID = outlinePath->getGenerationID(); diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index 1469a156e2d8..be0b46b1c45f 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -28,6 +28,7 @@ #include <androidfw/ResourceTypes.h> #include "AnimatorManager.h" +#include "CanvasTransform.h" #include "Debug.h" #include "DisplayList.h" #include "Matrix.h" @@ -48,9 +49,6 @@ namespace android { namespace uirenderer { class CanvasState; -class DisplayListOp; -class FrameBuilder; -class OffscreenBuffer; class Rect; class SkiaShader; struct RenderNodeOp; @@ -76,7 +74,6 @@ class RenderNode; */ class RenderNode : public VirtualLightRefBase { friend class TestUtils; // allow TestUtils to access syncDisplayList / syncProperties - friend class FrameBuilder; public: enum DirtyPropertyMask { @@ -104,16 +101,13 @@ public: ANDROID_API void setStagingDisplayList(DisplayList* newData); - void computeOrdering(); - ANDROID_API void output(); ANDROID_API int getDebugSize(); - void copyTo(proto::RenderNode* node); bool isRenderable() const { return mDisplayList && !mDisplayList->isEmpty(); } bool hasProjectionReceiver() const { - return mDisplayList && mDisplayList->projectionReceiveIndex >= 0; + return mDisplayList && mDisplayList->containsProjectionReceiver(); } const char* getName() const { return mName.string(); } @@ -178,9 +172,6 @@ public: } const DisplayList* getDisplayList() const { return mDisplayList; } - OffscreenBuffer* getLayer() const { return mLayer; } - OffscreenBuffer** getLayerHandle() { return &mLayer; } // ugh... - void setLayer(OffscreenBuffer* layer) { mLayer = layer; } // Note: The position callbacks are relying on the listener using // the frameNumber to appropriately batch/synchronize these transactions. @@ -218,6 +209,10 @@ public: void output(std::ostream& output, uint32_t level); + void setUsageHint(UsageHint usageHint) { mUsageHint = usageHint; } + + UsageHint usageHint() const { return mUsageHint; } + private: void computeOrderingImpl(RenderNodeOp* opState, std::vector<RenderNodeOp*>* compositedChildrenOfProjectionSurface, @@ -225,6 +220,7 @@ private: void syncProperties(); void syncDisplayList(TreeObserver& observer, TreeInfo* info); + void handleForceDark(TreeInfo* info); void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer); void pushStagingPropertiesChanges(TreeInfo& info); @@ -256,10 +252,6 @@ private: 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. - OffscreenBuffer* mLayer = nullptr; - /** * Draw time state - these properties are only set and used during rendering */ @@ -277,6 +269,8 @@ private: sp<PositionListener> mPositionListener; + UsageHint mUsageHint = UsageHint::Unknown; + // METHODS & FIELDS ONLY USED BY THE SKIA RENDERER public: /** @@ -298,7 +292,7 @@ public: * Returns true if an offscreen layer from any renderPipeline is attached * to this node. */ - bool hasLayer() const { return mLayer || mSkiaLayer.get(); } + bool hasLayer() const { return mSkiaLayer.get(); } /** * Used by the RenderPipeline to attach an offscreen surface to the RenderNode. diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index e49574462e9e..ff9cf45cdc73 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -44,8 +44,8 @@ void LayerProperties::reset() { } bool LayerProperties::setColorFilter(SkColorFilter* filter) { - if (mColorFilter == filter) return false; - SkRefCnt_SafeAssign(mColorFilter, filter); + if (mColorFilter.get() == filter) return false; + mColorFilter = sk_ref_sp(filter); return true; } @@ -62,7 +62,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) { setOpaque(other.opaque()); setAlpha(other.alpha()); setXferMode(other.xferMode()); - setColorFilter(other.colorFilter()); + setColorFilter(other.getColorFilter()); return *this; } diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 6470d4c16130..04379ae68a0d 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -16,7 +16,6 @@ #pragma once -#include "Caches.h" #include "DeviceInfo.h" #include "Outline.h" #include "Rect.h" @@ -89,9 +88,7 @@ public: SkBlendMode xferMode() const { return mMode; } - bool setColorFilter(SkColorFilter* filter); - - SkColorFilter* colorFilter() const { return mColorFilter; } + SkColorFilter* getColorFilter() const { return mColorFilter.get(); } // Sets alpha, xfermode, and colorfilter from an SkPaint // paint may be NULL, in which case defaults will be set @@ -105,6 +102,7 @@ private: LayerProperties(); ~LayerProperties(); void reset(); + bool setColorFilter(SkColorFilter* filter); // Private since external users should go through properties().effectiveLayerType() LayerType type() const { return mType; } @@ -116,7 +114,7 @@ private: bool mOpaque; uint8_t mAlpha; SkBlendMode mMode; - SkColorFilter* mColorFilter = nullptr; + sk_sp<SkColorFilter> mColorFilter; }; /* @@ -328,9 +326,7 @@ public: bool isPivotExplicitlySet() const { return mPrimitiveFields.mPivotExplicitlySet; } - bool resetPivot() { - return RP_SET_AND_DIRTY(mPrimitiveFields.mPivotExplicitlySet, false); - } + bool resetPivot() { return RP_SET_AND_DIRTY(mPrimitiveFields.mPivotExplicitlySet, false); } bool setCameraDistance(float distance) { if (distance != getCameraDistance()) { @@ -510,17 +506,13 @@ public: getOutline().getAlpha() != 0.0f; } - SkColor getSpotShadowColor() const { - return mPrimitiveFields.mSpotShadowColor; - } + SkColor getSpotShadowColor() const { return mPrimitiveFields.mSpotShadowColor; } bool setSpotShadowColor(SkColor shadowColor) { return RP_SET(mPrimitiveFields.mSpotShadowColor, shadowColor); } - SkColor getAmbientShadowColor() const { - return mPrimitiveFields.mAmbientShadowColor; - } + SkColor getAmbientShadowColor() const { return mPrimitiveFields.mAmbientShadowColor; } bool setAmbientShadowColor(SkColor shadowColor) { return RP_SET(mPrimitiveFields.mAmbientShadowColor, shadowColor); @@ -543,6 +535,14 @@ public: return CC_UNLIKELY(promotedToLayer()) ? LayerType::RenderLayer : mLayerProperties.mType; } + bool setAllowForceDark(bool allow) { + return RP_SET(mPrimitiveFields.mAllowForceDark, allow); + } + + bool getAllowForceDark() const { + return mPrimitiveFields.mAllowForceDark; + } + private: // Rendering properties struct PrimitiveFields { @@ -562,6 +562,7 @@ private: bool mMatrixOrPivotDirty = false; bool mProjectBackwards = false; bool mProjectionReceiver = false; + bool mAllowForceDark = true; Rect mClipBounds; Outline mOutline; RevealClip mRevealClip; diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp deleted file mode 100644 index d60b99469368..000000000000 --- a/libs/hwui/ResourceCache.cpp +++ /dev/null @@ -1,154 +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 "ResourceCache.h" -#include "Caches.h" - -namespace android { - -using namespace uirenderer; -ANDROID_SINGLETON_STATIC_INSTANCE(ResourceCache); - -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Resource cache -/////////////////////////////////////////////////////////////////////////////// - -void ResourceCache::logCache() { - ALOGD("ResourceCache: cacheReport:"); - for (size_t i = 0; i < mCache->size(); ++i) { - ResourceReference* ref = mCache->valueAt(i); - ALOGD(" ResourceCache: mCache(%zu): resource, ref = 0x%p, 0x%p", i, mCache->keyAt(i), - mCache->valueAt(i)); - ALOGD(" ResourceCache: mCache(%zu): refCount, destroyed, type = %d, %d, %d", i, - ref->refCount, ref->destroyed, ref->resourceType); - } -} - -ResourceCache::ResourceCache() { - Mutex::Autolock _l(mLock); - mCache = new KeyedVector<const void*, ResourceReference*>(); -} - -ResourceCache::~ResourceCache() { - Mutex::Autolock _l(mLock); - delete mCache; -} - -void ResourceCache::lock() { - mLock.lock(); -} - -void ResourceCache::unlock() { - mLock.unlock(); -} - -void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType) { - Mutex::Autolock _l(mLock); - incrementRefcountLocked(resource, resourceType); -} - -void ResourceCache::incrementRefcount(const Res_png_9patch* patchResource) { - incrementRefcount((void*)patchResource, kNinePatch); -} - -void ResourceCache::incrementRefcountLocked(void* resource, ResourceType resourceType) { - ssize_t index = mCache->indexOfKey(resource); - ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : nullptr; - if (ref == nullptr || mCache->size() == 0) { - ref = new ResourceReference(resourceType); - mCache->add(resource, ref); - } - ref->refCount++; -} - -void ResourceCache::decrementRefcount(void* resource) { - Mutex::Autolock _l(mLock); - decrementRefcountLocked(resource); -} - -void ResourceCache::decrementRefcount(const Res_png_9patch* patchResource) { - decrementRefcount((void*)patchResource); -} - -void ResourceCache::decrementRefcountLocked(void* resource) { - ssize_t index = mCache->indexOfKey(resource); - ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : nullptr; - if (ref == nullptr) { - // Should not get here - shouldn't get a call to decrement if we're not yet tracking it - return; - } - ref->refCount--; - if (ref->refCount == 0) { - deleteResourceReferenceLocked(resource, ref); - } -} - -void ResourceCache::decrementRefcountLocked(const Res_png_9patch* patchResource) { - decrementRefcountLocked((void*)patchResource); -} - -void ResourceCache::destructor(Res_png_9patch* resource) { - Mutex::Autolock _l(mLock); - destructorLocked(resource); -} - -void ResourceCache::destructorLocked(Res_png_9patch* resource) { - ssize_t index = mCache->indexOfKey(resource); - ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : nullptr; - if (ref == nullptr) { - // If we're not tracking this resource, just delete it - if (Caches::hasInstance()) { - Caches::getInstance().patchCache.removeDeferred(resource); - } else { - // A Res_png_9patch is actually an array of byte that's larger - // than sizeof(Res_png_9patch). It must be freed as an array. - delete[](int8_t*) resource; - } - return; - } - ref->destroyed = true; - if (ref->refCount == 0) { - deleteResourceReferenceLocked(resource, ref); - } -} - -/** - * This method should only be called while the mLock mutex is held (that mutex is grabbed - * by the various destructor() and recycle() methods which call this method). - */ -void ResourceCache::deleteResourceReferenceLocked(const void* resource, ResourceReference* ref) { - if (ref->destroyed) { - switch (ref->resourceType) { - case kNinePatch: { - if (Caches::hasInstance()) { - Caches::getInstance().patchCache.removeDeferred((Res_png_9patch*)resource); - } else { - // A Res_png_9patch is actually an array of byte that's larger - // than sizeof(Res_png_9patch). It must be freed as an array. - int8_t* patch = (int8_t*)resource; - delete[] patch; - } - } break; - } - } - mCache->removeItem(resource); - delete ref; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h deleted file mode 100644 index fd3f9fd05d58..000000000000 --- a/libs/hwui/ResourceCache.h +++ /dev/null @@ -1,103 +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_RESOURCE_CACHE_H -#define ANDROID_HWUI_RESOURCE_CACHE_H - -#include <cutils/compiler.h> - -#include <SkBitmap.h> -#include <SkPixelRef.h> - -#include <utils/KeyedVector.h> -#include <utils/Singleton.h> - -#include <androidfw/ResourceTypes.h> - -namespace android { -namespace uirenderer { - -class Layer; - -/** - * Type of Resource being cached - */ -enum ResourceType { - kNinePatch, -}; - -class ResourceReference { -public: - explicit ResourceReference(ResourceType type) { - refCount = 0; - destroyed = false; - resourceType = type; - } - - int refCount; - bool destroyed; - ResourceType resourceType; -}; - -class ANDROID_API ResourceCache : public Singleton<ResourceCache> { - ResourceCache(); - ~ResourceCache(); - - friend class Singleton<ResourceCache>; - -public: - /** - * When using these two methods, make sure to only invoke the *Locked() - * variants of increment/decrementRefcount(), recyle() and destructor() - */ - void lock(); - void unlock(); - - void incrementRefcount(const Res_png_9patch* resource); - - void decrementRefcount(const Res_png_9patch* resource); - - void decrementRefcountLocked(const Res_png_9patch* resource); - - void destructor(Res_png_9patch* resource); - - void destructorLocked(Res_png_9patch* resource); - -private: - void deleteResourceReferenceLocked(const void* resource, ResourceReference* ref); - - void incrementRefcount(void* resource, ResourceType resourceType); - void incrementRefcountLocked(void* resource, ResourceType resourceType); - - void decrementRefcount(void* resource); - void decrementRefcountLocked(void* resource); - - void logCache(); - - /** - * Used to increment, decrement, and destroy. Incrementing is generally accessed on the UI - * thread, but destroying resources may be called from the GC thread, the finalizer thread, - * or a reference queue finalization thread. - */ - mutable Mutex mLock; - - KeyedVector<const void*, ResourceReference*>* mCache; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_RESOURCE_CACHE_H diff --git a/libs/hwui/ShadowTessellator.cpp b/libs/hwui/ShadowTessellator.cpp deleted file mode 100644 index d0155ee473f7..000000000000 --- a/libs/hwui/ShadowTessellator.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <math.h> -#include <utils/Log.h> -#include <utils/MathUtils.h> -#include <utils/Trace.h> - -#include "AmbientShadow.h" -#include "Properties.h" -#include "ShadowTessellator.h" -#include "SpotShadow.h" -#include "Vector.h" - -namespace android { -namespace uirenderer { - -void ShadowTessellator::tessellateAmbientShadow(bool isCasterOpaque, const Vector3* casterPolygon, - int casterVertexCount, const Vector3& centroid3d, - const Rect& casterBounds, const Rect& localClip, - float maxZ, VertexBuffer& shadowVertexBuffer) { - ATRACE_CALL(); - - // A bunch of parameters to tweak the shadow. - // TODO: Allow some of these changable by debug settings or APIs. - float heightFactor = 1.0f / 128; - const float geomFactor = 64; - - if (CC_UNLIKELY(Properties::overrideAmbientRatio > 0.0f)) { - heightFactor *= Properties::overrideAmbientRatio; - } - - Rect ambientShadowBounds(casterBounds); - ambientShadowBounds.outset(maxZ * geomFactor * heightFactor); - - if (!localClip.intersects(ambientShadowBounds)) { -#if DEBUG_SHADOW - ALOGD("Ambient shadow is out of clip rect!"); -#endif - return; - } - - AmbientShadow::createAmbientShadow(isCasterOpaque, casterPolygon, casterVertexCount, centroid3d, - heightFactor, geomFactor, shadowVertexBuffer); -} - -void ShadowTessellator::tessellateSpotShadow(bool isCasterOpaque, const Vector3* casterPolygon, - int casterVertexCount, const Vector3& casterCentroid, - const mat4& receiverTransform, - const Vector3& lightCenter, int lightRadius, - const Rect& casterBounds, const Rect& localClip, - VertexBuffer& shadowVertexBuffer) { - ATRACE_CALL(); - - Vector3 adjustedLightCenter(lightCenter); - if (CC_UNLIKELY(Properties::overrideLightPosY > 0)) { - adjustedLightCenter.y = -Properties::overrideLightPosY; // negated since this shifts up - } - if (CC_UNLIKELY(Properties::overrideLightPosZ > 0)) { - adjustedLightCenter.z = Properties::overrideLightPosZ; - } - -#if DEBUG_SHADOW - ALOGD("light center %f %f %f %d", adjustedLightCenter.x, adjustedLightCenter.y, - adjustedLightCenter.z, lightRadius); -#endif - if (isnan(adjustedLightCenter.x) || isnan(adjustedLightCenter.y) || - isnan(adjustedLightCenter.z)) { - return; - } - - // light position (because it's in local space) needs to compensate for receiver transform - // TODO: should apply to light orientation, not just position - Matrix4 reverseReceiverTransform; - reverseReceiverTransform.loadInverse(receiverTransform); - reverseReceiverTransform.mapPoint3d(adjustedLightCenter); - - if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) { - lightRadius = Properties::overrideLightRadius; - } - - // Now light and caster are both in local space, we will check whether - // the shadow is within the clip area. - Rect lightRect = Rect(adjustedLightCenter.x - lightRadius, adjustedLightCenter.y - lightRadius, - adjustedLightCenter.x + lightRadius, adjustedLightCenter.y + lightRadius); - lightRect.unionWith(localClip); - if (!lightRect.intersects(casterBounds)) { -#if DEBUG_SHADOW - ALOGD("Spot shadow is out of clip rect!"); -#endif - return; - } - - SpotShadow::createSpotShadow(isCasterOpaque, adjustedLightCenter, lightRadius, casterPolygon, - casterVertexCount, casterCentroid, shadowVertexBuffer); - -#if DEBUG_SHADOW - if (shadowVertexBuffer.getVertexCount() <= 0) { - ALOGD("Spot shadow generation failed %d", shadowVertexBuffer.getVertexCount()); - } -#endif -} - -/** - * Calculate the centroid of a 2d polygon. - * - * @param poly The polygon, which is represented in a Vector2 array. - * @param polyLength The length of the polygon in terms of number of vertices. - * @return the centroid of the polygon. - */ -Vector2 ShadowTessellator::centroid2d(const Vector2* poly, int polyLength) { - double sumx = 0; - double sumy = 0; - int p1 = polyLength - 1; - double area = 0; - for (int p2 = 0; p2 < polyLength; p2++) { - double x1 = poly[p1].x; - double y1 = poly[p1].y; - double x2 = poly[p2].x; - double y2 = poly[p2].y; - double a = (x1 * y2 - x2 * y1); - sumx += (x1 + x2) * a; - sumy += (y1 + y2) * a; - area += a; - p1 = p2; - } - - Vector2 centroid = poly[0]; - if (area != 0) { - centroid = (Vector2){static_cast<float>(sumx / (3 * area)), - static_cast<float>(sumy / (3 * area))}; - } else { - ALOGW("Area is 0 while computing centroid!"); - } - return centroid; -} - -// Make sure p1 -> p2 is going CW around the poly. -Vector2 ShadowTessellator::calculateNormal(const Vector2& p1, const Vector2& p2) { - Vector2 result = p2 - p1; - if (result.x != 0 || result.y != 0) { - result.normalize(); - // Calculate the normal , which is CCW 90 rotate to the delta. - float tempy = result.y; - result.y = result.x; - result.x = -tempy; - } - return result; -} - -int ShadowTessellator::getExtraVertexNumber(const Vector2& vector1, const Vector2& vector2, - float divisor) { - // When there is no distance difference, there is no need for extra vertices. - if (vector1.lengthSquared() == 0 || vector2.lengthSquared() == 0) { - return 0; - } - // The formula is : - // extraNumber = floor(acos(dot(n1, n2)) / (M_PI / EXTRA_VERTEX_PER_PI)) - // The value ranges for each step are: - // dot( ) --- [-1, 1] - // acos( ) --- [0, M_PI] - // floor(...) --- [0, EXTRA_VERTEX_PER_PI] - float dotProduct = vector1.dot(vector2); - // make sure that dotProduct value is in acsof input range [-1, 1] - dotProduct = MathUtils::clamp(dotProduct, -1.0f, 1.0f); - // TODO: Use look up table for the dotProduct to extraVerticesNumber - // computation, if needed. - float angle = acosf(dotProduct); - return (int)floor(angle / divisor); -} - -void ShadowTessellator::checkOverflow(int used, int total, const char* bufferName) { - LOG_ALWAYS_FATAL_IF(used > total, "Error: %s overflow!!! used %d, total %d", bufferName, used, - total); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/ShadowTessellator.h b/libs/hwui/ShadowTessellator.h deleted file mode 100644 index 79f46f9e9a06..000000000000 --- a/libs/hwui/ShadowTessellator.h +++ /dev/null @@ -1,94 +0,0 @@ - -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef ANDROID_HWUI_SHADOW_TESSELLATOR_H -#define ANDROID_HWUI_SHADOW_TESSELLATOR_H - -#include <SkPath.h> - -#include "Debug.h" -#include "Matrix.h" - -namespace android { -namespace uirenderer { - -class VertexBuffer; - -// All SHADOW_* are used to define all the geometry property of shadows. -// Use a simplified example to illustrate the geometry setup here. -// Assuming we use 6 rays and only 1 layer, Then we will have 2 hexagons, which -// are 0 to 5 and 6 to 11. The area between them will be the penumbra area, and -// the area inside the 2nd hexagon is the umbra. -// Ambient shadow is using only 1 layer for opaque caster, otherwise, spot -// shadow and ambient shadow are using 2 layers. -// Triange strip indices for penumbra area: (0, 6, 1, 7, 2, 8, 3, 9, 4, 10, 5, 11, 0, 6) -// 0 -// -// 5 6 1 -// 11 7 -// -// 10 8 -// 4 9 2 -// -// 3 - -// The total number of rays starting from the centroid of shadow area, in order -// to generate the shadow geometry. -#define SHADOW_RAY_COUNT 128 - -// The total number of all the vertices representing the shadow. -// For the case we only have 1 layer, then we will just fill only 2/3 of it. -#define SHADOW_VERTEX_COUNT (3 * SHADOW_RAY_COUNT) - -// The total number of indices used for drawing the shadow geometry as triangle strips. -// Depending on the mode we are drawing, we can have 1 layer or 2 layers. -// Therefore, we only build the longer index buffer. -#define TWO_POLY_RING_SHADOW_INDEX_COUNT (4 * (SHADOW_RAY_COUNT + 1)) -#define ONE_POLY_RING_SHADOW_INDEX_COUNT (2 * (SHADOW_RAY_COUNT + 1)) - -#define MAX_SHADOW_INDEX_COUNT TWO_POLY_RING_SHADOW_INDEX_COUNT - -#define SHADOW_MIN_CASTER_Z 0.001f - -#define MINIMAL_DELTA_THETA (M_PI / 180 / 1000) - -class ShadowTessellator { -public: - static void tessellateAmbientShadow(bool isCasterOpaque, const Vector3* casterPolygon, - int casterVertexCount, const Vector3& centroid3d, - const Rect& casterBounds, const Rect& localClip, float maxZ, - VertexBuffer& shadowVertexBuffer); - - static void tessellateSpotShadow(bool isCasterOpaque, const Vector3* casterPolygon, - int casterVertexCount, const Vector3& casterCentroid, - const mat4& receiverTransform, const Vector3& lightCenter, - int lightRadius, const Rect& casterBounds, - const Rect& localClip, VertexBuffer& shadowVertexBuffer); - - static Vector2 centroid2d(const Vector2* poly, int polyLength); - - static Vector2 calculateNormal(const Vector2& p1, const Vector2& p2); - - static int getExtraVertexNumber(const Vector2& vector1, const Vector2& vector2, float divisor); - - static void checkOverflow(int used, int total, const char* bufferName); -}; // ShadowTessellator - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_SHADOW_TESSELLATOR_H diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 40b811d813fd..9c707bab95f1 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -21,6 +21,7 @@ #include "VectorDrawable.h" #include "hwui/Bitmap.h" #include "hwui/MinikinUtils.h" +#include "hwui/PaintFilter.h" #include "pipeline/skia/AnimatedDrawables.h" #include <SkAnimatedImage.h> @@ -28,7 +29,6 @@ #include <SkColorFilter.h> #include <SkColorSpaceXformCanvas.h> #include <SkDeque.h> -#include <SkDrawFilter.h> #include <SkDrawable.h> #include <SkGraphics.h> #include <SkImage.h> @@ -40,6 +40,8 @@ #include <SkTextBlob.h> #include <memory> +#include <optional> +#include <utility> namespace android { @@ -62,13 +64,7 @@ SkiaCanvas::SkiaCanvas(const SkBitmap& bitmap) { mCanvasOwned = std::unique_ptr<SkCanvas>(new SkCanvas(bitmap, SkCanvas::ColorBehavior::kLegacy)); if (cs.get() == nullptr || cs->isSRGB()) { - if (!uirenderer::Properties::isSkiaEnabled()) { - mCanvasWrapper = - SkCreateColorSpaceXformCanvas(mCanvasOwned.get(), SkColorSpace::MakeSRGB()); - mCanvas = mCanvasWrapper.get(); - } else { - mCanvas = mCanvasOwned.get(); - } + mCanvas = mCanvasOwned.get(); } else { /** The wrapper is needed if we are drawing into a non-sRGB destination, since * we need to transform all colors (not just bitmaps via filters) into the @@ -101,8 +97,6 @@ void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { std::unique_ptr<SkCanvas> newCanvasWrapper; if (cs.get() != nullptr && !cs->isSRGB()) { newCanvasWrapper = SkCreateColorSpaceXformCanvas(newCanvas.get(), std::move(cs)); - } else if (!uirenderer::Properties::isSkiaEnabled()) { - newCanvasWrapper = SkCreateColorSpaceXformCanvas(newCanvas.get(), SkColorSpace::MakeSRGB()); } // deletes the previously owned canvas (if any) @@ -219,7 +213,7 @@ public: Clip(const SkRRect& rrect, SkClipOp op, const SkMatrix& m) : mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {} Clip(const SkPath& path, SkClipOp op, const SkMatrix& m) - : mType(Type::Path), mOp(op), mMatrix(m), mPath(&path) {} + : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {} void apply(SkCanvas* canvas) const { canvas->setMatrix(mMatrix); @@ -231,7 +225,7 @@ public: canvas->clipRRect(mRRect, mOp); break; case Type::Path: - canvas->clipPath(*mPath.get(), mOp); + canvas->clipPath(mPath.value(), mOp); break; } } @@ -248,7 +242,7 @@ private: SkMatrix mMatrix; // These are logically a union (tracked separately due to non-POD path). - SkTLazy<SkPath> mPath; + std::optional<SkPath> mPath; SkRRect mRRect; }; @@ -408,12 +402,12 @@ bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) { // Canvas state operations: Filters // ---------------------------------------------------------------------------- -SkDrawFilter* SkiaCanvas::getDrawFilter() { - return mCanvas->getDrawFilter(); +PaintFilter* SkiaCanvas::getPaintFilter() { + return mPaintFilter.get(); } -void SkiaCanvas::setDrawFilter(SkDrawFilter* drawFilter) { - mCanvas->setDrawFilter(drawFilter); +void SkiaCanvas::setPaintFilter(sk_sp<PaintFilter> paintFilter) { + mPaintFilter = std::move(paintFilter); } // ---------------------------------------------------------------------------- @@ -447,8 +441,15 @@ void SkiaCanvas::drawColor(int color, SkBlendMode mode) { mCanvas->drawColor(color, mode); } +SkiaCanvas::PaintCoW&& SkiaCanvas::filterPaint(PaintCoW&& paint) const { + if (mPaintFilter) { + mPaintFilter->filter(&paint.writeable()); + } + return std::move(paint); +} + void SkiaCanvas::drawPaint(const SkPaint& paint) { - mCanvas->drawPaint(paint); + mCanvas->drawPaint(*filterPaint(paint)); } // ---------------------------------------------------------------------------- @@ -465,53 +466,58 @@ void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint pts[i].set(points[0], points[1]); points += 2; } - mCanvas->drawPoints(mode, count, pts.get(), paint); + mCanvas->drawPoints(mode, count, pts.get(), *filterPaint(paint)); } void SkiaCanvas::drawPoint(float x, float y, const SkPaint& paint) { - mCanvas->drawPoint(x, y, paint); + mCanvas->drawPoint(x, y, *filterPaint(paint)); } void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint) { - this->drawPoints(points, count, paint, SkCanvas::kPoints_PointMode); + this->drawPoints(points, count, *filterPaint(paint), SkCanvas::kPoints_PointMode); } void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY, const SkPaint& paint) { - mCanvas->drawLine(startX, startY, stopX, stopY, paint); + mCanvas->drawLine(startX, startY, stopX, stopY, *filterPaint(paint)); } void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) { if (CC_UNLIKELY(count < 4 || paint.nothingToDraw())) return; - this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode); + this->drawPoints(points, count, *filterPaint(paint), SkCanvas::kLines_PointMode); } void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; - mCanvas->drawRect({left, top, right, bottom}, paint); + mCanvas->drawRect({left, top, right, bottom}, *filterPaint(paint)); } void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; - mCanvas->drawRegion(region, paint); + mCanvas->drawRegion(region, *filterPaint(paint)); } void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry, const SkPaint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); - mCanvas->drawRoundRect(rect, rx, ry, paint); + mCanvas->drawRoundRect(rect, rx, ry, *filterPaint(paint)); +} + +void SkiaCanvas::drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) { + mCanvas->drawDRRect(outer, inner, *filterPaint(paint)); } void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) { if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return; - mCanvas->drawCircle(x, y, radius, paint); + mCanvas->drawCircle(x, y, radius, *filterPaint(paint)); } void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) { if (CC_UNLIKELY(paint.nothingToDraw())) return; SkRect oval = SkRect::MakeLTRB(left, top, right, bottom); - mCanvas->drawOval(oval, paint); + mCanvas->drawOval(oval, *filterPaint(paint)); } void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle, @@ -519,9 +525,9 @@ void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float if (CC_UNLIKELY(paint.nothingToDraw())) return; SkRect arc = SkRect::MakeLTRB(left, top, right, bottom); if (fabs(sweepAngle) >= 360.0f) { - mCanvas->drawOval(arc, paint); + mCanvas->drawOval(arc, *filterPaint(paint)); } else { - mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint); + mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, *filterPaint(paint)); } } @@ -530,19 +536,19 @@ void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) { if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) { return; } - mCanvas->drawPath(path, paint); + mCanvas->drawPath(path, *filterPaint(paint)); } void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) { - mCanvas->drawVertices(vertices, mode, paint); + mCanvas->drawVertices(vertices, mode, *filterPaint(paint)); } // ---------------------------------------------------------------------------- // Canvas draw operations: Bitmaps // ---------------------------------------------------------------------------- -const SkPaint* SkiaCanvas::addFilter(const SkPaint* origPaint, SkPaint* tmpPaint, - sk_sp<SkColorFilter> colorSpaceFilter) { +SkiaCanvas::PaintCoW&& SkiaCanvas::filterBitmap(PaintCoW&& paint, + sk_sp<SkColorFilter> colorSpaceFilter) const { /* We don't apply the colorSpace filter if this canvas is already wrapped with * a SkColorSpaceXformCanvas since it already takes care of converting the * contents of the bitmap into the appropriate colorspace. The mCanvasWrapper @@ -550,39 +556,31 @@ const SkPaint* SkiaCanvas::addFilter(const SkPaint* origPaint, SkPaint* tmpPaint * to have a non-sRGB colorspace. */ if (!mCanvasWrapper && colorSpaceFilter) { - if (origPaint) { - *tmpPaint = *origPaint; - } - - if (tmpPaint->getColorFilter()) { - tmpPaint->setColorFilter( - SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorSpaceFilter)); - LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter()); + SkPaint& tmpPaint = paint.writeable(); + if (tmpPaint.getColorFilter()) { + tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter(tmpPaint.refColorFilter(), + std::move(colorSpaceFilter))); + LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter()); } else { - tmpPaint->setColorFilter(colorSpaceFilter); + tmpPaint.setColorFilter(std::move(colorSpaceFilter)); } - - return tmpPaint; - } else { - return origPaint; } + return filterPaint(std::move(paint)); } void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) { - SkPaint tmpPaint; sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - mCanvas->drawImage(image, left, top, addFilter(paint, &tmpPaint, colorFilter)); + mCanvas->drawImage(image, left, top, filterBitmap(paint, std::move(colorFilter))); } void SkiaCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) { SkAutoCanvasRestore acr(mCanvas, true); mCanvas->concat(matrix); - SkPaint tmpPaint; sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - mCanvas->drawImage(image, 0, 0, addFilter(paint, &tmpPaint, colorFilter)); + mCanvas->drawImage(image, 0, 0, filterBitmap(paint, std::move(colorFilter))); } void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight, @@ -591,10 +589,9 @@ void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float s SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - SkPaint tmpPaint; sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - mCanvas->drawImageRect(image, srcRect, dstRect, addFilter(paint, &tmpPaint, colorFilter), + mCanvas->drawImageRect(image, srcRect, dstRect, filterBitmap(paint, std::move(colorFilter)), SkCanvas::kFast_SrcRectConstraint); } @@ -673,21 +670,20 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight, #endif // cons-up a shader for the bitmap - SkPaint tmpPaint; - if (paint) { - tmpPaint = *paint; - } + PaintCoW paintCoW(paint); + SkPaint& tmpPaint = paintCoW.writeable(); sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); sk_sp<SkShader> shader = image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode); if (colorFilter) { - shader = shader->makeWithColorFilter(colorFilter); + shader = shader->makeWithColorFilter(std::move(colorFilter)); } - tmpPaint.setShader(shader); + tmpPaint.setShader(std::move(shader)); - mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, tmpPaint); + mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, + *filterPaint(std::move(paintCoW))); } void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, float dstLeft, @@ -714,10 +710,10 @@ void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, floa lattice.fBounds = nullptr; SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - SkPaint tmpPaint; sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter)); + mCanvas->drawImageLattice(image.get(), lattice, dst, + filterBitmap(paint, std::move(colorFilter))); } double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) { @@ -736,16 +732,15 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& p float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) { if (count <= 0 || paint.nothingToDraw()) return; - // Set align to left for drawing, as we don't want individual - // glyphs centered or right-aligned; the offset above takes - // care of all alignment. SkPaint paintCopy(paint); - paintCopy.setTextAlign(SkPaint::kLeft_Align); + if (mPaintFilter) { + mPaintFilter->filter(&paintCopy); + } SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); // Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and // older. - if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 - && paintCopy.getStyle() == SkPaint::kStroke_Style) { + if (!mCanvasOwned && sApiLevel <= 27 && paintCopy.getStrokeWidth() <= 0 && + paintCopy.getStyle() == SkPaint::kStroke_Style) { paintCopy.setStyle(SkPaint::kFill_Style); } @@ -764,11 +759,10 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& p void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset, const SkPaint& paint, const SkPath& path, size_t start, size_t end) { - // Set align to left for drawing, as we don't want individual - // glyphs centered or right-aligned; the offsets take care of - // that portion of the alignment. SkPaint paintCopy(paint); - paintCopy.setTextAlign(SkPaint::kLeft_Align); + if (mPaintFilter) { + mPaintFilter->filter(&paintCopy); + } SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding); const int N = end - start; diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h index 3efc22a03cdf..3a877cf84010 100644 --- a/libs/hwui/SkiaCanvas.h +++ b/libs/hwui/SkiaCanvas.h @@ -22,7 +22,9 @@ #include "hwui/Canvas.h" #include <SkCanvas.h> -#include <SkTLazy.h> + +#include <cassert> +#include <optional> namespace android { @@ -87,8 +89,8 @@ public: virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override; virtual bool clipPath(const SkPath* path, SkClipOp op) override; - virtual SkDrawFilter* getDrawFilter() override; - virtual void setDrawFilter(SkDrawFilter* drawFilter) override; + virtual PaintFilter* getPaintFilter() override; + virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) override; virtual SkCanvasState* captureCanvasState() const override; @@ -105,6 +107,10 @@ public: 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 drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, + 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; @@ -158,6 +164,46 @@ protected: const SkPaint& paint, const SkPath& path, size_t start, size_t end) override; + /** This class acts as a copy on write SkPaint. + * + * Initially this will be the SkPaint passed to the contructor. + * The first time writable() is called this will become a copy of the + * initial SkPaint (or a default SkPaint if nullptr). + */ + struct PaintCoW { + PaintCoW(const SkPaint& that) : mPtr(&that) {} + PaintCoW(const SkPaint* ptr) : mPtr(ptr) {} + PaintCoW(const PaintCoW&) = delete; + PaintCoW(PaintCoW&&) = delete; + PaintCoW& operator=(const PaintCoW&) = delete; + PaintCoW& operator=(PaintCoW&&) = delete; + SkPaint& writeable() { + if (!mStorage) { + if (!mPtr) { + mStorage.emplace(); + } else { + mStorage.emplace(*mPtr); + } + mPtr = &*mStorage; + } + return *mStorage; + } + operator const SkPaint*() const { return mPtr; } + const SkPaint* operator->() const { assert(mPtr); return mPtr; } + const SkPaint& operator*() const { assert(mPtr); return *mPtr; } + explicit operator bool() { return mPtr != nullptr; } + private: + const SkPaint* mPtr; + std::optional<SkPaint> mStorage; + }; + + /** Filters the paint using the current paint filter. + * + * @param paint the paint to filter. Will be initialized with the default + * SkPaint before filtering if filtering is required. + */ + PaintCoW&& filterPaint(PaintCoW&& paint) const; + private: struct SaveRec { int saveCount; @@ -174,8 +220,15 @@ private: void drawPoints(const float* points, int count, const SkPaint& paint, SkCanvas::PointMode mode); - const SkPaint* addFilter(const SkPaint* origPaint, SkPaint* tmpPaint, - sk_sp<SkColorFilter> colorSpaceFilter); + /** Filters the paint for bitmap drawing. + * + * After filtering the paint for bitmap drawing, + * also calls filterPaint on the paint. + * + * @param paint the paint to filter. Will be initialized with the default + * SkPaint before filtering if filtering is required. + */ + PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter) const; class Clip; @@ -185,6 +238,7 @@ private: // unless it is the same as mCanvasOwned.get() std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves. std::vector<Clip> mClipStack; // tracks persistent clips. + sk_sp<PaintFilter> mPaintFilter; }; } // namespace android diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp deleted file mode 100644 index fc009d871620..000000000000 --- a/libs/hwui/SkiaCanvasProxy.cpp +++ /dev/null @@ -1,486 +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 "SkiaCanvasProxy.h" - -#include <memory> - -#include <log/log.h> - -#include <SkLatticeIter.h> -#include <SkPaint.h> -#include <SkPatchUtils.h> -#include <SkPath.h> -#include <SkPixelRef.h> -#include <SkRRect.h> -#include <SkRSXform.h> -#include <SkRect.h> -#include <SkSurface.h> -#include <SkTextBlobRunIterator.h> -#include <SkVertices.h> -#include "hwui/Bitmap.h" - -namespace android { -namespace uirenderer { - -SkiaCanvasProxy::SkiaCanvasProxy(Canvas* canvas, bool filterHwuiCalls) - : INHERITED(canvas->width(), canvas->height()) - , mCanvas(canvas) - , mFilterHwuiCalls(filterHwuiCalls) {} - -void SkiaCanvasProxy::onDrawPaint(const SkPaint& paint) { - mCanvas->drawPaint(paint); -} - -void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPoint pts[], - const SkPaint& paint) { - if (!pts || count == 0) { - return; - } - - // convert the SkPoints into 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; - - switch (pointMode) { - case kPoints_PointMode: { - mCanvas->drawPoints(floatArray, floatCount, paint); - break; - } - case kLines_PointMode: { - mCanvas->drawLines(floatArray, floatCount, paint); - break; - } - case kPolygon_PointMode: { - SkPaint strokedPaint(paint); - strokedPaint.setStyle(SkPaint::kStroke_Style); - - SkPath path; - for (size_t i = 0; i < count - 1; i++) { - path.moveTo(pts[i]); - path.lineTo(pts[i + 1]); - this->drawPath(path, strokedPaint); - path.rewind(); - } - break; - } - default: - LOG_ALWAYS_FATAL("Unknown point type"); - } -} - -void SkiaCanvasProxy::onDrawOval(const SkRect& rect, const SkPaint& paint) { - mCanvas->drawOval(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint); -} - -void SkiaCanvasProxy::onDrawRect(const SkRect& rect, const SkPaint& paint) { - mCanvas->drawRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint); -} - -void SkiaCanvasProxy::onDrawRRect(const SkRRect& roundRect, const SkPaint& paint) { - if (!roundRect.isComplex()) { - const SkRect& rect = roundRect.rect(); - SkVector radii = roundRect.getSimpleRadii(); - mCanvas->drawRoundRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, radii.fX, radii.fY, - paint); - } else { - SkPath path; - path.addRRect(roundRect); - mCanvas->drawPath(path, paint); - } -} - -void SkiaCanvasProxy::onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, - bool useCenter, const SkPaint& paint) { - mCanvas->drawArc(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, startAngle, sweepAngle, - useCenter, paint); -} - -void SkiaCanvasProxy::onDrawPath(const SkPath& path, const SkPaint& paint) { - mCanvas->drawPath(path, paint); -} - -void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, - const SkPaint* paint) { - sk_sp<Bitmap> hwuiBitmap = Bitmap::createFrom(bitmap.info(), *bitmap.pixelRef()); - // HWUI doesn't support extractSubset(), so convert any subsetted bitmap into - // a drawBitmapRect(); pass through an un-subsetted bitmap. - if (hwuiBitmap && bitmap.dimensions() != hwuiBitmap->info().dimensions()) { - SkIPoint origin = bitmap.pixelRefOrigin(); - mCanvas->drawBitmap( - *hwuiBitmap, 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(*hwuiBitmap, left, top, paint); - } -} - -void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& skBitmap, const SkRect* srcPtr, - const SkRect& dst, const SkPaint* paint, SrcRectConstraint) { - SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(skBitmap.width(), skBitmap.height()); - // TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src? - Bitmap* bitmap = reinterpret_cast<Bitmap*>(skBitmap.pixelRef()); - mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft, dst.fTop, - dst.fRight, dst.fBottom, paint); -} - -void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, - const SkRect& dst, const SkPaint*) { - // TODO make nine-patch drawing a method on Canvas.h - SkDEBUGFAIL("SkiaCanvasProxy::onDrawBitmapNine is not yet supported"); -} - -void SkiaCanvasProxy::onDrawImage(const SkImage* image, SkScalar left, SkScalar top, - const SkPaint* paint) { - SkBitmap skiaBitmap; - SkPixmap pixmap; - if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) { - onDrawBitmap(skiaBitmap, left, top, paint); - } -} - -void SkiaCanvasProxy::onDrawImageRect(const SkImage* image, const SkRect* srcPtr, const SkRect& dst, - const SkPaint* paint, SrcRectConstraint constraint) { - SkBitmap skiaBitmap; - SkPixmap pixmap; - if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) { - sk_sp<Bitmap> bitmap = Bitmap::createFrom(skiaBitmap.info(), *skiaBitmap.pixelRef()); - SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(image->width(), image->height()); - mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft, - dst.fTop, dst.fRight, dst.fBottom, paint); - } -} - -void SkiaCanvasProxy::onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, - const SkPaint*) { - SkDEBUGFAIL("SkiaCanvasProxy::onDrawImageNine is not yet supported"); -} - -void SkiaCanvasProxy::onDrawImageLattice(const SkImage* image, const Lattice& lattice, - const SkRect& dst, const SkPaint* paint) { - SkLatticeIter iter(lattice, dst); - SkRect srcR, dstR; - while (iter.next(&srcR, &dstR)) { - onDrawImageRect(image, &srcR, dstR, paint, SkCanvas::kFast_SrcRectConstraint); - } -} - -void SkiaCanvasProxy::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode, - const SkPaint& paint) { - if (mFilterHwuiCalls) { - return; - } - mCanvas->drawVertices(vertices, bmode, paint); -} - -sk_sp<SkSurface> SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) { - SkDEBUGFAIL("SkiaCanvasProxy::onNewSurface is not supported"); - return NULL; -} - -void SkiaCanvasProxy::willSave() { - mCanvas->save(android::SaveFlags::MatrixClip); -} - -static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) { - SaveFlags::Flags saveFlags = 0; - - if (!(layerFlags & SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag)) { - saveFlags |= SaveFlags::ClipToLayer; - } - - return saveFlags; -} - -SkCanvas::SaveLayerStrategy SkiaCanvasProxy::getSaveLayerStrategy( - const SaveLayerRec& saveLayerRec) { - SkRect 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, saveLayerRec.fPaint, - saveFlags(saveLayerRec.fSaveLayerFlags)); - return SkCanvas::kNoLayer_SaveLayerStrategy; -} - -void SkiaCanvasProxy::willRestore() { - mCanvas->restore(); -} - -void SkiaCanvasProxy::didConcat(const SkMatrix& matrix) { - mCanvas->concat(matrix); -} - -void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) { - mCanvas->setMatrix(matrix); -} - -void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, - const SkPaint& paint) { - SkPath path; - path.addRRect(outer); - path.addRRect(inner); - path.setFillType(SkPath::kEvenOdd_FillType); - this->drawPath(path, paint); -} - -/** - * Utility class that converts the incoming text & paint from the given encoding - * into glyphIDs. - */ -class GlyphIDConverter { -public: - GlyphIDConverter(const void* text, size_t byteLength, const SkPaint& origPaint) { - paint = origPaint; - if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) { - glyphIDs = (uint16_t*)text; - count = byteLength >> 1; - } else { - // 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); - } - } - - SkPaint paint; - uint16_t* glyphIDs; - int count; - -private: - std::unique_ptr<uint16_t[]> storage; -}; - -void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, - const SkPaint& origPaint) { - // convert to glyphIDs if necessary - GlyphIDConverter glyphs(text, byteLength, origPaint); - - // compute the glyph positions - std::unique_ptr<SkScalar[]> glyphWidths(new SkScalar[glyphs.count]); - glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get()); - - // compute conservative bounds - // NOTE: We could call the faster paint.getFontBounds for a less accurate, - // but even more conservative bounds if this is too slow. - SkRect bounds; - glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds); - - // adjust for non-left alignment - if (glyphs.paint.getTextAlign() != SkPaint::kLeft_Align) { - SkScalar stop = 0; - for (int i = 0; i < glyphs.count; i++) { - stop += glyphWidths[i]; - } - if (glyphs.paint.getTextAlign() == SkPaint::kCenter_Align) { - stop = SkScalarHalf(stop); - } - if (glyphs.paint.isVerticalText()) { - y -= stop; - } else { - x -= stop; - } - } - - // setup the first glyph position and adjust bounds if needed - int xBaseline = 0; - int yBaseline = 0; - if (mCanvas->drawTextAbsolutePos()) { - bounds.offset(x, y); - xBaseline = x; - yBaseline = y; - } - - static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint is no longer two floats"); - auto glyphFunc = [&](uint16_t* text, float* positions) { - memcpy(text, glyphs.glyphIDs, glyphs.count * sizeof(uint16_t)); - size_t posIndex = 0; - // setup the first glyph position - positions[posIndex++] = xBaseline; - positions[posIndex++] = yBaseline; - // setup the remaining glyph positions - if (glyphs.paint.isVerticalText()) { - float yPosition = yBaseline; - for (int i = 1; i < glyphs.count; i++) { - positions[posIndex++] = xBaseline; - yPosition += glyphWidths[i - 1]; - positions[posIndex++] = yPosition; - } - } else { - float xPosition = xBaseline; - for (int i = 1; i < glyphs.count; i++) { - xPosition += glyphWidths[i - 1]; - positions[posIndex++] = xPosition; - positions[posIndex++] = yBaseline; - } - } - }; - mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop, - bounds.fRight, bounds.fBottom, 0); -} - -void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], - const SkPaint& origPaint) { - // convert to glyphIDs if necessary - GlyphIDConverter glyphs(text, byteLength, origPaint); - - // convert to relative positions if necessary - int x, y; - if (mCanvas->drawTextAbsolutePos()) { - x = 0; - y = 0; - } else { - x = pos[0].fX; - y = pos[0].fY; - } - - // Compute conservative bounds. If the content has already been processed - // by Minikin then it had already computed these bounds. Unfortunately, - // there is no way to capture those bounds as part of the Skia drawPosText - // API so we need to do that computation again here. - SkRect bounds = SkRect::MakeEmpty(); - for (int i = 0; i < glyphs.count; i++) { - SkRect glyphBounds = SkRect::MakeEmpty(); - glyphs.paint.measureText(&glyphs.glyphIDs[i], sizeof(uint16_t), &glyphBounds); - glyphBounds.offset(pos[i].fX, pos[i].fY); - bounds.join(glyphBounds); - } - - static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint is no longer two floats"); - auto glyphFunc = [&](uint16_t* text, float* positions) { - memcpy(text, glyphs.glyphIDs, glyphs.count * sizeof(uint16_t)); - if (mCanvas->drawTextAbsolutePos()) { - memcpy(positions, pos, 2 * glyphs.count * sizeof(float)); - } else { - for (int i = 0, posIndex = 0; i < glyphs.count; i++) { - positions[posIndex++] = pos[i].fX - x; - positions[posIndex++] = pos[i].fY - y; - } - } - }; - mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop, - bounds.fRight, bounds.fBottom, 0); -} - -void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], - SkScalar constY, const SkPaint& paint) { - const size_t pointCount = byteLength >> 1; - 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.get(), paint); -} - -void SkiaCanvasProxy::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, - const SkMatrix* matrix, const SkPaint& origPaint) { - SkDEBUGFAIL("SkiaCanvasProxy::onDrawTextOnPath is not supported"); -} - -void SkiaCanvasProxy::onDrawTextRSXform(const void* text, size_t byteLength, - const SkRSXform xform[], const SkRect* cullRect, - const SkPaint& paint) { - GlyphIDConverter glyphs(text, byteLength, paint); // Just get count - SkMatrix localM, currM, origM; - mCanvas->getMatrix(&currM); - origM = currM; - for (int i = 0; i < glyphs.count; i++) { - localM.setRSXform(*xform++); - currM.setConcat(origM, localM); - mCanvas->setMatrix(currM); - this->onDrawText((char*)text + (byteLength / glyphs.count * i), byteLength / glyphs.count, - 0, 0, paint); - } - mCanvas->setMatrix(origM); -} - -void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, - const SkPaint& paint) { - SkPaint runPaint = paint; - - SkTextBlobRunIterator it(blob); - for (; !it.done(); it.next()) { - size_t textLen = it.glyphCount() * sizeof(uint16_t); - const SkPoint& offset = it.offset(); - // applyFontToPaint() always overwrites the exact same attributes, - // so it is safe to not re-seed the paint for this reason. - it.applyFontToPaint(&runPaint); - - switch (it.positioning()) { - case SkTextBlob::kDefault_Positioning: - this->drawText(it.glyphs(), textLen, x + offset.x(), y + offset.y(), runPaint); - break; - case SkTextBlob::kHorizontal_Positioning: { - std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]); - for (size_t i = 0; i < it.glyphCount(); i++) { - pts[i].set(x + offset.x() + it.pos()[i], y + offset.y()); - } - this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint); - break; - } - case SkTextBlob::kFull_Positioning: { - std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]); - for (size_t i = 0; i < it.glyphCount(); i++) { - const size_t xIndex = i * 2; - const size_t yIndex = xIndex + 1; - pts[i].set(x + offset.x() + it.pos()[xIndex], - y + offset.y() + it.pos()[yIndex]); - } - this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint); - break; - } - default: - SK_ABORT("unhandled positioning mode"); - } - } -} - -void SkiaCanvasProxy::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], - const SkPoint texCoords[4], SkBlendMode bmode, - const SkPaint& paint) { - if (mFilterHwuiCalls) { - return; - } - SkMatrix matrix; - mCanvas->getMatrix(&matrix); - SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &matrix); - - mCanvas->drawVertices( - SkPatchUtils::MakeVertices(cubics, colors, texCoords, lod.width(), lod.height()).get(), - bmode, paint); -} - -void SkiaCanvasProxy::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle) { - mCanvas->clipRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, op); -} - -void SkiaCanvasProxy::onClipRRect(const SkRRect& roundRect, SkClipOp op, ClipEdgeStyle) { - SkPath path; - path.addRRect(roundRect); - mCanvas->clipPath(&path, op); -} - -void SkiaCanvasProxy::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle) { - mCanvas->clipPath(&path, op); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h deleted file mode 100644 index 360d5a08b974..000000000000 --- a/libs/hwui/SkiaCanvasProxy.h +++ /dev/null @@ -1,112 +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. - */ - -#ifndef SkiaCanvasProxy_DEFINED -#define SkiaCanvasProxy_DEFINED - -#include <SkCanvas.h> -#include <cutils/compiler.h> - -#include "hwui/Canvas.h" - -namespace android { -namespace uirenderer { - -/** - * This class serves as a proxy between Skia's SkCanvas and Android Framework's - * Canvas. The class does not maintain any draw-related state and will pass - * through most requests directly to the Canvas provided in the constructor. - * - * Upon construction it is expected that the provided Canvas has already been - * prepared for recording and will continue to be in the recording state while - * this proxy class is being used. - * - * If filterHwuiCalls is true, the proxy silently ignores away draw calls that - * aren't supported by HWUI. - */ -class ANDROID_API SkiaCanvasProxy : public SkCanvas { -public: - explicit SkiaCanvasProxy(Canvas* canvas, bool filterHwuiCalls = false); - virtual ~SkiaCanvasProxy() {} - -protected: - virtual sk_sp<SkSurface> onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; - - virtual void willSave() override; - virtual SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; - virtual void willRestore() override; - - virtual void didConcat(const SkMatrix&) override; - virtual void didSetMatrix(const SkMatrix&) override; - - virtual void onDrawPaint(const SkPaint& paint) override; - virtual void onDrawPoints(PointMode, size_t count, const SkPoint pts[], - const SkPaint&) override; - virtual void onDrawOval(const SkRect&, const SkPaint&) override; - virtual void onDrawRect(const SkRect&, const SkPaint&) override; - virtual void onDrawRRect(const SkRRect&, const SkPaint&) override; - virtual void onDrawPath(const SkPath& path, const SkPaint&) override; - virtual void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, - const SkPaint&) override; - 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, SrcRectConstraint) override; - virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, - const SkPaint*) override; - virtual void onDrawImage(const SkImage*, SkScalar dx, SkScalar dy, const SkPaint*); - virtual void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*, - SrcRectConstraint); - virtual void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, - const SkPaint*); - virtual void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst, - const SkPaint*); - virtual void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override; - - virtual void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; - - virtual void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, - const SkPaint&) override; - virtual void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], - const SkPaint&) override; - virtual void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], - SkScalar constY, const SkPaint&) override; - virtual void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, - const SkMatrix* matrix, const SkPaint&) override; - virtual void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform[], - const SkRect* cullRect, const SkPaint& paint); - virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, - const SkPaint& paint) override; - - virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], - const SkPoint texCoords[4], SkBlendMode, - const SkPaint& paint) override; - - virtual void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; - virtual void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; - virtual void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; - -private: - Canvas* mCanvas; - bool mFilterHwuiCalls; - - typedef SkCanvas INHERITED; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // SkiaCanvasProxy_DEFINED diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp deleted file mode 100644 index df746554f306..000000000000 --- a/libs/hwui/SkiaShader.cpp +++ /dev/null @@ -1,360 +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 "SkiaShader.h" - -#include "Caches.h" -#include "Extensions.h" -#include "Matrix.h" -#include "Texture.h" -#include "hwui/Bitmap.h" - -#include <SkMatrix.h> -#include <utils/Log.h> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Support -/////////////////////////////////////////////////////////////////////////////// - -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. - */ -static inline bool isPowerOfTwo(unsigned int n) { - return !(n & (n - 1)); -} - -static inline void bindUniformColor(int slot, FloatColor color) { - glUniform4fv(slot, 1, reinterpret_cast<const float*>(&color)); -} - -static inline void bindTexture(Caches* caches, Texture* texture, GLenum wrapS, GLenum wrapT) { - caches->textureState().bindTexture(texture->target(), texture->id()); - texture->setWrapST(wrapS, wrapT); -} - -/** - * Compute the matrix to transform to screen space. - * @param screenSpace Output param for the computed matrix. - * @param unitMatrix The unit matrix for gradient shaders, as returned by SkShader::asAGradient, - * or identity. - * @param localMatrix Local matrix, as returned by SkShader::getLocalMatrix(). - * @param modelViewMatrix Model view matrix, as supplied by the OpenGLRenderer. - */ -static void computeScreenSpaceMatrix(mat4& screenSpace, const SkMatrix& unitMatrix, - const SkMatrix& localMatrix, const mat4& modelViewMatrix) { - mat4 shaderMatrix; - // uses implicit construction - shaderMatrix.loadInverse(localMatrix); - // again, uses implicit construction - screenSpace.loadMultiply(unitMatrix, shaderMatrix); - screenSpace.multiply(modelViewMatrix); -} - -/////////////////////////////////////////////////////////////////////////////// -// Gradient shader matrix helpers -/////////////////////////////////////////////////////////////////////////////// - -static void toLinearUnitMatrix(const SkPoint pts[2], SkMatrix* matrix) { - SkVector vec = pts[1] - pts[0]; - const float mag = vec.length(); - const float inv = mag ? 1.0f / mag : 0; - - vec.scale(inv); - matrix->setSinCos(-vec.fY, vec.fX, pts[0].fX, pts[0].fY); - matrix->postTranslate(-pts[0].fX, -pts[0].fY); - matrix->postScale(inv, inv); -} - -static void toCircularUnitMatrix(const float x, const float y, const float radius, - SkMatrix* matrix) { - const float inv = 1.0f / radius; - matrix->setTranslate(-x, -y); - matrix->postScale(inv, inv); -} - -static void toSweepUnitMatrix(const float x, const float y, SkMatrix* matrix) { - matrix->setTranslate(-x, -y); -} - -/////////////////////////////////////////////////////////////////////////////// -// Common gradient code -/////////////////////////////////////////////////////////////////////////////// - -static bool isSimpleGradient(const SkShader::GradientInfo& gradInfo) { - return gradInfo.fColorCount == 2 && gradInfo.fTileMode == SkShader::kClamp_TileMode; -} - -/////////////////////////////////////////////////////////////////////////////// -// Store / apply -/////////////////////////////////////////////////////////////////////////////// - -bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 modelViewMatrix, - GLuint* textureUnit, ProgramDescription* description, - SkiaShaderData::GradientShaderData* outData) { - SkShader::GradientInfo gradInfo; - gradInfo.fColorCount = 0; - gradInfo.fColors = nullptr; - gradInfo.fColorOffsets = nullptr; - - SkMatrix unitMatrix; - switch (shader.asAGradient(&gradInfo)) { - case SkShader::kLinear_GradientType: - description->gradientType = ProgramDescription::kGradientLinear; - - toLinearUnitMatrix(gradInfo.fPoint, &unitMatrix); - break; - case SkShader::kRadial_GradientType: - description->gradientType = ProgramDescription::kGradientCircular; - - toCircularUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY, gradInfo.fRadius[0], - &unitMatrix); - break; - case SkShader::kSweep_GradientType: - description->gradientType = ProgramDescription::kGradientSweep; - - toSweepUnitMatrix(gradInfo.fPoint[0].fX, gradInfo.fPoint[0].fY, &unitMatrix); - break; - default: - // Do nothing. This shader is unsupported. - return false; - } - description->hasGradient = true; - description->isSimpleGradient = isSimpleGradient(gradInfo); - - computeScreenSpaceMatrix(outData->screenSpace, unitMatrix, shader.getLocalMatrix(), - modelViewMatrix); - - // re-query shader to get full color / offset data - std::unique_ptr<SkColor[]> colorStorage(new SkColor[gradInfo.fColorCount]); - std::unique_ptr<SkScalar[]> colorOffsets(new SkScalar[gradInfo.fColorCount]); - gradInfo.fColors = &colorStorage[0]; - gradInfo.fColorOffsets = &colorOffsets[0]; - shader.asAGradient(&gradInfo); - - if (CC_UNLIKELY(!description->isSimpleGradient)) { - outData->gradientSampler = (*textureUnit)++; - -#ifndef SK_SCALAR_IS_FLOAT -#error Need to convert gradInfo.fColorOffsets to float! -#endif - outData->gradientTexture = caches.gradientCache.get( - gradInfo.fColors, gradInfo.fColorOffsets, gradInfo.fColorCount); - outData->wrapST = gTileModes[gradInfo.fTileMode]; - } else { - outData->gradientSampler = 0; - outData->gradientTexture = nullptr; - - outData->startColor.set(gradInfo.fColors[0]); - outData->endColor.set(gradInfo.fColors[1]); - } - - return true; -} - -void applyGradient(Caches& caches, const SkiaShaderData::GradientShaderData& data, - const GLsizei width, const GLsizei height) { - if (CC_UNLIKELY(data.gradientTexture)) { - caches.textureState().activateTexture(data.gradientSampler); - bindTexture(&caches, data.gradientTexture, data.wrapST, data.wrapST); - glUniform1i(caches.program().getUniform("gradientSampler"), data.gradientSampler); - } else { - bindUniformColor(caches.program().getUniform("startColor"), data.startColor); - bindUniformColor(caches.program().getUniform("endColor"), data.endColor); - } - - glUniform2f(caches.program().getUniform("screenSize"), 1.0f / width, 1.0f / height); - glUniformMatrix4fv(caches.program().getUniform("screenSpace"), 1, GL_FALSE, - &data.screenSpace.data[0]); -} - -bool tryStoreBitmap(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, - GLuint* textureUnit, ProgramDescription* description, - SkiaShaderData::BitmapShaderData* outData) { - SkBitmap bitmap; - SkShader::TileMode xy[2]; - if (!shader.isABitmap(&bitmap, nullptr, xy)) { - return false; - } - - // TODO: create hwui-owned BitmapShader. - Bitmap* hwuiBitmap = static_cast<Bitmap*>(bitmap.pixelRef()); - outData->bitmapTexture = caches.textureCache.get(hwuiBitmap); - if (!outData->bitmapTexture) return false; - - outData->bitmapSampler = (*textureUnit)++; - - const float width = outData->bitmapTexture->width(); - const float height = outData->bitmapTexture->height(); - - Texture* texture = outData->bitmapTexture; - - description->hasBitmap = true; - description->hasLinearTexture = texture->isLinear(); - description->hasColorSpaceConversion = texture->hasColorSpaceConversion(); - description->transferFunction = texture->getTransferFunctionType(); - description->hasTranslucentConversion = texture->blend; - description->isShaderBitmapExternal = hwuiBitmap->isHardware(); - // gralloc doesn't support non-clamp modes - if (hwuiBitmap->isHardware() || - (!caches.extensions().hasNPot() && (!isPowerOfTwo(width) || !isPowerOfTwo(height)) && - (xy[0] != SkShader::kClamp_TileMode || xy[1] != SkShader::kClamp_TileMode))) { - // need non-clamp mode, but it's not supported for this draw, - // so enable custom shader logic to mimic - description->useShaderBasedWrap = true; - description->bitmapWrapS = gTileModes[xy[0]]; - description->bitmapWrapT = gTileModes[xy[1]]; - - outData->wrapS = GL_CLAMP_TO_EDGE; - outData->wrapT = GL_CLAMP_TO_EDGE; - } else { - outData->wrapS = gTileModes[xy[0]]; - outData->wrapT = gTileModes[xy[1]]; - } - - computeScreenSpaceMatrix(outData->textureTransform, SkMatrix::I(), shader.getLocalMatrix(), - modelViewMatrix); - outData->textureDimension[0] = 1.0f / width; - outData->textureDimension[1] = 1.0f / height; - - return true; -} - -void applyBitmap(Caches& caches, const SkiaShaderData::BitmapShaderData& data) { - caches.textureState().activateTexture(data.bitmapSampler); - bindTexture(&caches, data.bitmapTexture, data.wrapS, data.wrapT); - data.bitmapTexture->setFilter(GL_LINEAR); - - glUniform1i(caches.program().getUniform("bitmapSampler"), data.bitmapSampler); - glUniformMatrix4fv(caches.program().getUniform("textureTransform"), 1, GL_FALSE, - &data.textureTransform.data[0]); - glUniform2fv(caches.program().getUniform("textureDimension"), 1, &data.textureDimension[0]); -} - -SkiaShaderType getComposeSubType(const SkShader& shader) { - // First check for a gradient shader. - switch (shader.asAGradient(nullptr)) { - case SkShader::kNone_GradientType: - // Not a gradient shader. Fall through to check for other types. - break; - case SkShader::kLinear_GradientType: - case SkShader::kRadial_GradientType: - case SkShader::kSweep_GradientType: - return kGradient_SkiaShaderType; - default: - // This is a Skia gradient that has no SkiaShader equivalent. Return None to skip. - return kNone_SkiaShaderType; - } - - // The shader is not a gradient. Check for a bitmap shader. - if (shader.isABitmap()) { - return kBitmap_SkiaShaderType; - } - return kNone_SkiaShaderType; -} - -void storeCompose(Caches& caches, const SkShader& bitmapShader, const SkShader& gradientShader, - const Matrix4& modelViewMatrix, GLuint* textureUnit, - ProgramDescription* description, SkiaShaderData* outData) { - LOG_ALWAYS_FATAL_IF(!tryStoreBitmap(caches, bitmapShader, modelViewMatrix, textureUnit, - description, &outData->bitmapData), - "failed storing bitmap shader data"); - LOG_ALWAYS_FATAL_IF(!tryStoreGradient(caches, gradientShader, modelViewMatrix, textureUnit, - description, &outData->gradientData), - "failing storing gradient shader data"); -} - -bool tryStoreCompose(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, - GLuint* textureUnit, ProgramDescription* description, - SkiaShaderData* outData) { - SkShader::ComposeRec rec; - if (!shader.asACompose(&rec)) return false; - - const SkiaShaderType shaderAType = getComposeSubType(*rec.fShaderA); - const SkiaShaderType shaderBType = getComposeSubType(*rec.fShaderB); - - // check that type enum values are the 2 flags that compose the kCompose value - if ((shaderAType & shaderBType) != 0) return false; - if ((shaderAType | shaderBType) != kCompose_SkiaShaderType) return false; - - mat4 transform; - computeScreenSpaceMatrix(transform, SkMatrix::I(), shader.getLocalMatrix(), modelViewMatrix); - if (shaderAType == kBitmap_SkiaShaderType) { - description->isBitmapFirst = true; - storeCompose(caches, *rec.fShaderA, *rec.fShaderB, transform, textureUnit, description, - outData); - } else { - description->isBitmapFirst = false; - storeCompose(caches, *rec.fShaderB, *rec.fShaderA, transform, textureUnit, description, - outData); - } - description->shadersMode = rec.fBlendMode; - return true; -} - -void SkiaShader::store(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, - GLuint* textureUnit, ProgramDescription* description, - SkiaShaderData* outData) { - if (tryStoreGradient(caches, shader, modelViewMatrix, textureUnit, description, - &outData->gradientData)) { - outData->skiaShaderType = kGradient_SkiaShaderType; - return; - } - - if (tryStoreBitmap(caches, shader, modelViewMatrix, textureUnit, description, - &outData->bitmapData)) { - outData->skiaShaderType = kBitmap_SkiaShaderType; - return; - } - - if (tryStoreCompose(caches, shader, modelViewMatrix, textureUnit, description, outData)) { - outData->skiaShaderType = kCompose_SkiaShaderType; - return; - } - - // Unknown/unsupported type, so explicitly ignore shader - outData->skiaShaderType = kNone_SkiaShaderType; -} - -void SkiaShader::apply(Caches& caches, const SkiaShaderData& data, const GLsizei width, - const GLsizei height) { - if (!data.skiaShaderType) return; - - if (data.skiaShaderType & kGradient_SkiaShaderType) { - applyGradient(caches, data.gradientData, width, height); - } - if (data.skiaShaderType & kBitmap_SkiaShaderType) { - applyBitmap(caches, data.bitmapData); - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h deleted file mode 100644 index e8e92bdc1d76..000000000000 --- a/libs/hwui/SkiaShader.h +++ /dev/null @@ -1,86 +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_SKIA_SHADER_H -#define ANDROID_HWUI_SKIA_SHADER_H - -#include "FloatColor.h" -#include "Matrix.h" - -#include <GLES2/gl2.h> -#include <SkShader.h> -#include <cutils/compiler.h> - -namespace android { -namespace uirenderer { - -class Caches; -class Extensions; -class Texture; -struct ProgramDescription; - -/** - * Type of Skia shader in use. - * - * Note that kBitmap | kGradient = kCompose, since Compose implies - * both its component types are in use simultaneously. No other - * composition of multiple types is supported. - */ -enum SkiaShaderType { - kNone_SkiaShaderType = 0, - kBitmap_SkiaShaderType = 1, - kGradient_SkiaShaderType = 2, - kCompose_SkiaShaderType = kBitmap_SkiaShaderType | kGradient_SkiaShaderType, -}; - -struct SkiaShaderData { - SkiaShaderType skiaShaderType; - struct BitmapShaderData { - Texture* bitmapTexture; - GLuint bitmapSampler; - GLenum wrapS; - GLenum wrapT; - - Matrix4 textureTransform; - float textureDimension[2]; - } bitmapData; - struct GradientShaderData { - Matrix4 screenSpace; - - // simple gradient - FloatColor startColor; - FloatColor endColor; - - // complex gradient - Texture* gradientTexture; - GLuint gradientSampler; - GLenum wrapST; - } gradientData; -}; - -class SkiaShader { -public: - static void store(Caches& caches, const SkShader& shader, const Matrix4& modelViewMatrix, - GLuint* textureUnit, ProgramDescription* description, - SkiaShaderData* outData); - static void apply(Caches& caches, const SkiaShaderData& data, const GLsizei width, - const GLsizei height); -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_SKIA_SHADER_H diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp deleted file mode 100644 index f1a1bef7c94e..000000000000 --- a/libs/hwui/Snapshot.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (C) 2012 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 "Snapshot.h" - -#include "hwui/Canvas.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Constructors -/////////////////////////////////////////////////////////////////////////////// - -Snapshot::Snapshot() - : flags(0) - , previous(nullptr) - , layer(nullptr) - , fbo(0) - , alpha(1.0f) - , roundRectClipState(nullptr) - , projectionPathMask(nullptr) - , mClipArea(&mClipAreaRoot) { - transform = &mTransformRoot; - mRelativeLightCenter.x = mRelativeLightCenter.y = mRelativeLightCenter.z = 0; -} - -/** - * Copies the specified snapshot/ The specified snapshot is stored as - * the previous snapshot. - */ -Snapshot::Snapshot(Snapshot* s, int saveFlags) - : flags(0) - , previous(s) - , layer(s->layer) - , fbo(s->fbo) - , alpha(s->alpha) - , roundRectClipState(s->roundRectClipState) - , projectionPathMask(s->projectionPathMask) - , mClipArea(nullptr) - , mViewportData(s->mViewportData) - , mRelativeLightCenter(s->mRelativeLightCenter) { - if (saveFlags & SaveFlags::Matrix) { - mTransformRoot = *s->transform; - transform = &mTransformRoot; - } else { - transform = s->transform; - } - - if (saveFlags & SaveFlags::Clip) { - mClipAreaRoot = s->getClipArea(); - mClipArea = &mClipAreaRoot; - } else { - mClipArea = s->mClipArea; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Clipping -/////////////////////////////////////////////////////////////////////////////// - -void Snapshot::clip(const Rect& localClip, SkClipOp op) { - flags |= Snapshot::kFlagClipSet; - mClipArea->clipRectWithTransform(localClip, transform, static_cast<SkRegion::Op>(op)); -} - -void Snapshot::clipPath(const SkPath& path, SkClipOp op) { - flags |= Snapshot::kFlagClipSet; - mClipArea->clipPathWithTransform(path, transform, static_cast<SkRegion::Op>(op)); -} - -void Snapshot::setClip(float left, float top, float right, float bottom) { - flags |= Snapshot::kFlagClipSet; - mClipArea->setClip(left, top, right, bottom); -} - -bool Snapshot::hasPerspectiveTransform() const { - return transform->isPerspective(); -} - -const Rect& Snapshot::getLocalClip() { - mat4 inverse; - inverse.loadInverse(*transform); - - mLocalClip.set(mClipArea->getClipRect()); - inverse.mapRect(mLocalClip); - - return mLocalClip; -} - -void Snapshot::resetClip(float left, float top, float right, float bottom) { - // TODO: This is incorrect, when we start rendering into a new layer, - // we may have to modify the previous snapshot's clip rect and clip - // region if the previous restore() call did not restore the clip - mClipArea = &mClipAreaRoot; - setClip(left, top, right, bottom); -} - -/////////////////////////////////////////////////////////////////////////////// -// Clipping round rect -/////////////////////////////////////////////////////////////////////////////// - -void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds, float radius, - bool highPriority) { - if (bounds.isEmpty()) { - mClipArea->setEmpty(); - return; - } - - if (roundRectClipState && roundRectClipState->highPriority) { - // ignore, don't replace, already have a high priority clip - return; - } - - RoundRectClipState* state = new (allocator) RoundRectClipState; - - state->highPriority = highPriority; - - // store the inverse drawing matrix - Matrix4 roundRectDrawingMatrix = getOrthoMatrix(); - roundRectDrawingMatrix.multiply(*transform); - state->matrix.loadInverse(roundRectDrawingMatrix); - - // compute area under rounded corners - only draws overlapping these rects need to be clipped - for (int i = 0; i < 4; i++) { - state->dangerRects[i] = bounds; - } - state->dangerRects[0].bottom = state->dangerRects[1].bottom = bounds.top + radius; - state->dangerRects[0].right = state->dangerRects[2].right = bounds.left + radius; - state->dangerRects[1].left = state->dangerRects[3].left = bounds.right - radius; - state->dangerRects[2].top = state->dangerRects[3].top = bounds.bottom - radius; - for (int i = 0; i < 4; i++) { - transform->mapRect(state->dangerRects[i]); - - // round danger rects out as though they are AA geometry (since they essentially are) - state->dangerRects[i].snapGeometryToPixelBoundaries(true); - } - - // store RR area - state->innerRect = bounds; - state->innerRect.inset(radius); - state->radius = radius; - - // store as immutable so, for this frame, pointer uniquely identifies this bundle of shader info - roundRectClipState = state; -} - -void Snapshot::setProjectionPathMask(const SkPath* path) { - projectionPathMask = path; -} - -static Snapshot* getClipRoot(Snapshot* target) { - while (target->previous && target->previous->previous) { - target = target->previous; - } - return target; -} - -const ClipBase* Snapshot::serializeIntersectedClip(LinearAllocator& allocator, - const ClipBase* recordedClip, - const Matrix4& recordedClipTransform) { - auto target = this; - if (CC_UNLIKELY(recordedClip && recordedClip->intersectWithRoot)) { - // Clip must be intersected with root, instead of current clip. - target = getClipRoot(this); - } - - return target->mClipArea->serializeIntersectedClip(allocator, recordedClip, - recordedClipTransform); -} - -void Snapshot::applyClip(const ClipBase* recordedClip, const Matrix4& transform) { - if (CC_UNLIKELY(recordedClip && recordedClip->intersectWithRoot)) { - // current clip is being replaced, but must intersect with clip root - *mClipArea = *(getClipRoot(this)->mClipArea); - } - mClipArea->applyClip(recordedClip, transform); -} - -/////////////////////////////////////////////////////////////////////////////// -// Queries -/////////////////////////////////////////////////////////////////////////////// - -void Snapshot::dump() const { - ALOGD("Snapshot %p, flags %x, prev %p, height %d, hasComplexClip %d", this, flags, previous, - getViewportHeight(), !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()); - - ALOGD(" Transform (at %p):", transform); - transform->dump(); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h deleted file mode 100644 index 655f819ca41b..000000000000 --- a/libs/hwui/Snapshot.h +++ /dev/null @@ -1,279 +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. - */ - -#pragma once - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -#include <ui/Region.h> -#include <utils/LinearAllocator.h> -#include <utils/RefBase.h> - -#include <SkClipOp.h> -#include <SkRegion.h> - -#include "ClipArea.h" -#include "Layer.h" -#include "Matrix.h" -#include "Outline.h" -#include "Rect.h" -#include "utils/Macros.h" - -namespace android { -namespace uirenderer { - -/** - * Temporary structure holding information for a single outline clip. - * - * These structures are treated as immutable once created, and only exist for a single frame, which - * is why they may only be allocated with a LinearAllocator. - */ -class RoundRectClipState { -public: - static void* operator new(size_t size) = delete; - static void* operator new(size_t size, LinearAllocator& allocator) { - return allocator.alloc<RoundRectClipState>(size); - } - - bool areaRequiresRoundRectClip(const Rect& rect) const { - return rect.intersects(dangerRects[0]) || rect.intersects(dangerRects[1]) || - rect.intersects(dangerRects[2]) || rect.intersects(dangerRects[3]); - } - - bool highPriority; - Matrix4 matrix; - Rect dangerRects[4]; - Rect innerRect; - float radius; -}; - -/** - * A snapshot holds information about the current state of the rendering - * surface. A snapshot is usually created whenever the user calls save() - * and discarded when the user calls restore(). Once a snapshot is created, - * it can hold information for deferred rendering. - * - * Each snapshot has a link to a previous snapshot, indicating the previous - * state of the renderer. - */ -class Snapshot { -public: - Snapshot(); - Snapshot(Snapshot* s, int saveFlags); - - /** - * Various flags set on ::flags. - */ - enum Flags { - /** - * Indicates that the clip region was modified. When this - * snapshot is restored so must the clip. - */ - kFlagClipSet = 0x1, - /** - * Indicates that this snapshot was created when saving - * a new layer. - */ - kFlagIsLayer = 0x2, - /** - * Indicates that this snapshot is a special type of layer - * backed by an FBO. This flag only makes sense when the - * flag kFlagIsLayer is also set. - * - * Viewport has been modified to fit the new Fbo, and must be - * restored when this snapshot is restored. - */ - kFlagIsFboLayer = 0x4, - }; - - /** - * Modifies the current clip with the new clip rectangle and - * the specified operation. The specified rectangle is transformed - * by this snapshot's trasnformation. - */ - void clip(const Rect& localClip, SkClipOp op); - - /** - * Modifies the current clip with the new clip rectangle and - * the specified operation. The specified rectangle is considered - * already transformed. - */ - void clipTransformed(const Rect& r, SkClipOp op = SkClipOp::kIntersect); - - /** - * Modifies the current clip with the specified path and operation. - */ - void clipPath(const SkPath& path, SkClipOp op); - - /** - * Sets the current clip. - */ - void setClip(float left, float top, float right, float bottom); - - /** - * Returns the current clip in local coordinates. The clip rect is - * transformed by the inverse transform matrix. - */ - ANDROID_API const Rect& getLocalClip(); - - /** - * Returns the current clip in render target coordinates. - */ - const Rect& getRenderTargetClip() const { return mClipArea->getClipRect(); } - - /* - * Accessor functions so that the clip area can stay private - */ - bool clipIsEmpty() const { return mClipArea->isEmpty(); } - const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); } - bool clipIsSimple() const { return mClipArea->isSimple(); } - const ClipArea& getClipArea() const { return *mClipArea; } - ClipArea& mutateClipArea() { return *mClipArea; } - - WARN_UNUSED_RESULT const ClipBase* serializeIntersectedClip( - LinearAllocator& allocator, const ClipBase* recordedClip, - const Matrix4& recordedClipTransform); - void applyClip(const ClipBase* clip, const Matrix4& transform); - - /** - * Resets the clip to the specified rect. - */ - void resetClip(float left, float top, float right, float bottom); - - void initializeViewport(int width, int height) { - mViewportData.initialize(width, height); - mClipAreaRoot.setViewportDimensions(width, height); - } - - int getViewportWidth() const { return mViewportData.mWidth; } - int getViewportHeight() const { return mViewportData.mHeight; } - const Matrix4& getOrthoMatrix() const { return mViewportData.mOrthoMatrix; } - - const Vector3& getRelativeLightCenter() const { return mRelativeLightCenter; } - void setRelativeLightCenter(const Vector3& lightCenter) { mRelativeLightCenter = lightCenter; } - - /** - * Sets (and replaces) the current clipping outline - * - * If the current round rect clip is high priority, the incoming clip is ignored. - */ - void setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds, float radius, - bool highPriority); - - /** - * Sets (and replaces) the current projection mask - */ - void setProjectionPathMask(const SkPath* path); - - /** - * Indicates whether the current transform has perspective components. - */ - bool hasPerspectiveTransform() const; - - /** - * Dirty flags. - */ - int flags; - - /** - * Previous snapshot. - */ - Snapshot* previous; - - /** - * A pointer to the currently active layer. - * - * This snapshot does not own the layer, this pointer must not be freed. - */ - Layer* layer; - - /** - * Target FBO used for rendering. Set to 0 when rendering directly - * into the framebuffer. - */ - GLuint fbo; - - /** - * Local transformation. Holds the current translation, scale and - * rotation values. - * - * This is a reference to a matrix owned by this snapshot or another - * snapshot. This pointer must not be freed. See ::mTransformRoot. - */ - mat4* transform; - - /** - * Current alpha value. This value is 1 by default, but may be set by a DisplayList which - * has translucent rendering in a non-overlapping View. This value will be used by - * the renderer to set the alpha in the current color being used for ensuing drawing - * operations. The value is inherited by child snapshots because the same value should - * be applied to descendants of the current DisplayList (for example, a TextView contains - * the base alpha value which should be applied to the child DisplayLists used for drawing - * the actual text). - */ - float alpha; - - /** - * Current clipping round rect. - * - * Points to data not owned by the snapshot, and may only be replaced by subsequent RR clips, - * never modified. - */ - const RoundRectClipState* roundRectClipState; - - /** - * Current projection masking path - used exclusively to mask projected, tessellated circles. - */ - const SkPath* projectionPathMask; - - void dump() const; - -private: - struct ViewportData { - ViewportData() : mWidth(0), mHeight(0) {} - void initialize(int width, int height) { - mWidth = width; - mHeight = height; - mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1); - } - - /* - * Width and height of current viewport. - * - * The viewport is always defined to be (0, 0, width, height). - */ - int mWidth; - int mHeight; - /** - * Contains the current orthographic, projection matrix. - */ - mat4 mOrthoMatrix; - }; - - mat4 mTransformRoot; - - ClipArea mClipAreaRoot; - ClipArea* mClipArea; - Rect mLocalClip; - - ViewportData mViewportData; - Vector3 mRelativeLightCenter; - -}; // class Snapshot - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/SpotShadow.cpp b/libs/hwui/SpotShadow.cpp deleted file mode 100644 index e371ac8da1e5..000000000000 --- a/libs/hwui/SpotShadow.cpp +++ /dev/null @@ -1,1120 +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. - */ - -// The highest z value can't be higher than (CASTER_Z_CAP_RATIO * light.z) -#define CASTER_Z_CAP_RATIO 0.95f - -// When there is no umbra, then just fake the umbra using -// centroid * (1 - FAKE_UMBRA_SIZE_RATIO) + outline * FAKE_UMBRA_SIZE_RATIO -#define FAKE_UMBRA_SIZE_RATIO 0.05f - -// When the polygon is about 90 vertices, the penumbra + umbra can reach 270 rays. -// That is consider pretty fine tessllated polygon so far. -// This is just to prevent using too much some memory when edge slicing is not -// needed any more. -#define FINE_TESSELLATED_POLYGON_RAY_NUMBER 270 -/** - * Extra vertices for the corner for smoother corner. - * Only for outer loop. - * Note that we use such extra memory to avoid an extra loop. - */ -// For half circle, we could add EXTRA_VERTEX_PER_PI vertices. -// Set to 1 if we don't want to have any. -#define SPOT_EXTRA_CORNER_VERTEX_PER_PI 18 - -// For the whole polygon, the sum of all the deltas b/t normals is 2 * M_PI, -// therefore, the maximum number of extra vertices will be twice bigger. -#define SPOT_MAX_EXTRA_CORNER_VERTEX_NUMBER (2 * SPOT_EXTRA_CORNER_VERTEX_PER_PI) - -// For each RADIANS_DIVISOR, we would allocate one more vertex b/t the normals. -#define SPOT_CORNER_RADIANS_DIVISOR (M_PI / SPOT_EXTRA_CORNER_VERTEX_PER_PI) - -#define PENUMBRA_ALPHA 0.0f -#define UMBRA_ALPHA 1.0f - -#include "SpotShadow.h" - -#include "ShadowTessellator.h" -#include "Vertex.h" -#include "VertexBuffer.h" -#include "utils/MathUtils.h" - -#include <math.h> -#include <stdlib.h> -#include <utils/Log.h> -#include <algorithm> - -// 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. -namespace android { -namespace uirenderer { - -static const float EPSILON = 1e-7; - -/** - * For each polygon's vertex, the light center will project it to the receiver - * as one of the outline vertex. - * For each outline vertex, we need to store the position and normal. - * Normal here is defined against the edge by the current vertex and the next vertex. - */ -struct OutlineData { - Vector2 position; - Vector2 normal; - float radius; -}; - -/** - * For each vertex, we need to keep track of its angle, whether it is penumbra or - * umbra, and its corresponding vertex index. - */ -struct SpotShadow::VertexAngleData { - // The angle to the vertex from the centroid. - float mAngle; - // True is the vertex comes from penumbra, otherwise it comes from umbra. - bool mIsPenumbra; - // The index of the vertex described by this data. - int mVertexIndex; - void set(float angle, bool isPenumbra, int index) { - mAngle = angle; - mIsPenumbra = isPenumbra; - mVertexIndex = index; - } -}; - -/** - * Calculate the angle between and x and a y coordinate. - * The atan2 range from -PI to PI. - */ -static float angle(const Vector2& point, const Vector2& center) { - return atan2(point.y - center.y, point.x - center.x); -} - -/** - * Calculate the intersection of a ray with the line segment defined by two points. - * - * Returns a negative value in error conditions. - - * @param rayOrigin The start of the ray - * @param dx The x vector of the ray - * @param dy The y vector of the ray - * @param p1 The first point defining the line segment - * @param p2 The second point defining the line segment - * @return The distance along the ray if it intersects with the line segment, negative if otherwise - */ -static float rayIntersectPoints(const Vector2& rayOrigin, float dx, float dy, const Vector2& p1, - const Vector2& p2) { - // The math below is derived from solving this formula, basically the - // intersection point should stay on both the ray and the edge of (p1, p2). - // solve([p1x+t*(p2x-p1x)=dx*t2+px,p1y+t*(p2y-p1y)=dy*t2+py],[t,t2]); - - float divisor = (dx * (p1.y - p2.y) + dy * p2.x - dy * p1.x); - if (divisor == 0) return -1.0f; // error, invalid divisor - -#if DEBUG_SHADOW - float interpVal = (dx * (p1.y - rayOrigin.y) + dy * rayOrigin.x - dy * p1.x) / divisor; - if (interpVal < 0 || interpVal > 1) { - ALOGW("rayIntersectPoints is hitting outside the segment %f", interpVal); - } -#endif - - float distance = (p1.x * (rayOrigin.y - p2.y) + p2.x * (p1.y - rayOrigin.y) + - rayOrigin.x * (p2.y - p1.y)) / - divisor; - - return distance; // may be negative in error cases -} - -/** - * Sort points by their X coordinates - * - * @param points the points as a Vector2 array. - * @param pointsLength the number of vertices of the polygon. - */ -void SpotShadow::xsort(Vector2* points, int pointsLength) { - auto cmp = [](const Vector2& a, const Vector2& b) -> bool { return a.x < b.x; }; - std::sort(points, points + pointsLength, cmp); -} - -/** - * compute the convex hull of a collection of Points - * - * @param points the points as a Vector2 array. - * @param pointsLength the number of vertices of the polygon. - * @param retPoly pre allocated array of floats to put the vertices - * @return the number of points in the polygon 0 if no intersection - */ -int SpotShadow::hull(Vector2* points, int pointsLength, Vector2* retPoly) { - xsort(points, pointsLength); - int n = pointsLength; - Vector2 lUpper[n]; - lUpper[0] = points[0]; - lUpper[1] = points[1]; - - int lUpperSize = 2; - - for (int i = 2; i < n; i++) { - lUpper[lUpperSize] = points[i]; - lUpperSize++; - - while (lUpperSize > 2 && - !ccw(lUpper[lUpperSize - 3].x, lUpper[lUpperSize - 3].y, lUpper[lUpperSize - 2].x, - lUpper[lUpperSize - 2].y, lUpper[lUpperSize - 1].x, lUpper[lUpperSize - 1].y)) { - // Remove the middle point of the three last - lUpper[lUpperSize - 2].x = lUpper[lUpperSize - 1].x; - lUpper[lUpperSize - 2].y = lUpper[lUpperSize - 1].y; - lUpperSize--; - } - } - - Vector2 lLower[n]; - lLower[0] = points[n - 1]; - lLower[1] = points[n - 2]; - - int lLowerSize = 2; - - for (int i = n - 3; i >= 0; i--) { - lLower[lLowerSize] = points[i]; - lLowerSize++; - - while (lLowerSize > 2 && - !ccw(lLower[lLowerSize - 3].x, lLower[lLowerSize - 3].y, lLower[lLowerSize - 2].x, - lLower[lLowerSize - 2].y, lLower[lLowerSize - 1].x, lLower[lLowerSize - 1].y)) { - // Remove the middle point of the three last - lLower[lLowerSize - 2] = lLower[lLowerSize - 1]; - lLowerSize--; - } - } - - // output points in CW ordering - const int total = lUpperSize + lLowerSize - 2; - int outIndex = total - 1; - for (int i = 0; i < lUpperSize; i++) { - retPoly[outIndex] = lUpper[i]; - outIndex--; - } - - for (int i = 1; i < lLowerSize - 1; i++) { - retPoly[outIndex] = lLower[i]; - outIndex--; - } - // TODO: Add test harness which verify that all the points are inside the hull. - return total; -} - -/** - * Test whether the 3 points form a counter clockwise turn. - * - * @return true if a right hand turn - */ -bool SpotShadow::ccw(float ax, float ay, float bx, float by, float cx, float cy) { - return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax) > EPSILON; -} - -/** - * Sort points about a center point - * - * @param poly The in and out polyogon as a Vector2 array. - * @param polyLength The number of vertices of the polygon. - * @param center the center ctr[0] = x , ctr[1] = y to sort around. - */ -void SpotShadow::sort(Vector2* poly, int polyLength, const Vector2& center) { - quicksortCirc(poly, 0, polyLength - 1, center); -} - -/** - * Swap points pointed to by i and j - */ -void SpotShadow::swap(Vector2* points, int i, int j) { - Vector2 temp = points[i]; - points[i] = points[j]; - points[j] = temp; -} - -/** - * quick sort implementation about the center. - */ -void SpotShadow::quicksortCirc(Vector2* points, int low, int high, const Vector2& center) { - int i = low, j = high; - int p = low + (high - low) / 2; - float pivot = angle(points[p], center); - while (i <= j) { - while (angle(points[i], center) > pivot) { - i++; - } - while (angle(points[j], center) < pivot) { - j--; - } - - if (i <= j) { - swap(points, i, j); - i++; - j--; - } - } - if (low < j) quicksortCirc(points, low, j, center); - if (i < high) quicksortCirc(points, i, high, center); -} - -/** - * Test whether a point is inside the polygon. - * - * @param testPoint the point to test - * @param poly the polygon - * @return true if the testPoint is inside the poly. - */ -bool SpotShadow::testPointInsidePolygon(const Vector2 testPoint, const Vector2* poly, int len) { - bool c = false; - float testx = testPoint.x; - float testy = testPoint.y; - for (int i = 0, j = len - 1; i < len; j = i++) { - float startX = poly[j].x; - float startY = poly[j].y; - float endX = poly[i].x; - float endY = poly[i].y; - - if (((endY > testy) != (startY > testy)) && - (testx < (startX - endX) * (testy - endY) / (startY - endY) + endX)) { - c = !c; - } - } - return c; -} - -/** - * Reverse the polygon - * - * @param polygon the polygon as a Vector2 array - * @param len the number of points of the polygon - */ -void SpotShadow::reverse(Vector2* polygon, int len) { - int n = len / 2; - for (int i = 0; i < n; i++) { - Vector2 tmp = polygon[i]; - int k = len - 1 - i; - polygon[i] = polygon[k]; - polygon[k] = tmp; - } -} - -/** - * Compute a horizontal circular polygon about point (x , y , height) of radius - * (size) - * - * @param points number of the points of the output polygon. - * @param lightCenter the center of the light. - * @param size the light size. - * @param ret result polygon. - */ -void SpotShadow::computeLightPolygon(int points, const Vector3& lightCenter, float size, - Vector3* ret) { - // TODO: Caching all the sin / cos values and store them in a look up table. - for (int i = 0; i < points; i++) { - float angle = 2 * i * M_PI / points; - ret[i].x = cosf(angle) * size + lightCenter.x; - ret[i].y = sinf(angle) * size + lightCenter.y; - ret[i].z = lightCenter.z; - } -} - -/** - * From light center, project one vertex to the z=0 surface and get the outline. - * - * @param outline The result which is the outline position. - * @param lightCenter The center of light. - * @param polyVertex The input polygon's vertex. - * - * @return float The ratio of (polygon.z / light.z - polygon.z) - */ -float SpotShadow::projectCasterToOutline(Vector2& outline, const Vector3& lightCenter, - const Vector3& polyVertex) { - float lightToPolyZ = lightCenter.z - polyVertex.z; - float ratioZ = CASTER_Z_CAP_RATIO; - if (lightToPolyZ != 0) { - // If any caster's vertex is almost above the light, we just keep it as 95% - // of the height of the light. - ratioZ = MathUtils::clamp(polyVertex.z / lightToPolyZ, 0.0f, CASTER_Z_CAP_RATIO); - } - - outline.x = polyVertex.x - ratioZ * (lightCenter.x - polyVertex.x); - outline.y = polyVertex.y - ratioZ * (lightCenter.y - polyVertex.y); - return ratioZ; -} - -/** - * Generate the shadow spot light of shape lightPoly and a object poly - * - * @param isCasterOpaque whether the caster is opaque - * @param lightCenter the center of the light - * @param lightSize the radius of the light - * @param poly x,y,z vertexes of a convex polygon that occludes the light source - * @param polyLength number of vertexes of the occluding polygon - * @param shadowTriangleStrip return an (x,y,alpha) triangle strip representing the shadow. Return - * empty strip if error. - */ -void SpotShadow::createSpotShadow(bool isCasterOpaque, const Vector3& lightCenter, float lightSize, - const Vector3* poly, int polyLength, const Vector3& polyCentroid, - VertexBuffer& shadowTriangleStrip) { - if (CC_UNLIKELY(lightCenter.z <= 0)) { - ALOGW("Relative Light Z is not positive. No spot shadow!"); - return; - } - if (CC_UNLIKELY(polyLength < 3)) { -#if DEBUG_SHADOW - ALOGW("Invalid polygon length. No spot shadow!"); -#endif - return; - } - OutlineData outlineData[polyLength]; - Vector2 outlineCentroid; - // Calculate the projected outline for each polygon's vertices from the light center. - // - // O Light - // / - // / - // . Polygon vertex - // / - // / - // O Outline vertices - // - // Ratio = (Poly - Outline) / (Light - Poly) - // Outline.x = Poly.x - Ratio * (Light.x - Poly.x) - // Outline's radius / Light's radius = Ratio - - // Compute the last outline vertex to make sure we can get the normal and outline - // in one single loop. - projectCasterToOutline(outlineData[polyLength - 1].position, lightCenter, poly[polyLength - 1]); - - // Take the outline's polygon, calculate the normal for each outline edge. - int currentNormalIndex = polyLength - 1; - int nextNormalIndex = 0; - - for (int i = 0; i < polyLength; i++) { - float ratioZ = projectCasterToOutline(outlineData[i].position, lightCenter, poly[i]); - outlineData[i].radius = ratioZ * lightSize; - - outlineData[currentNormalIndex].normal = ShadowTessellator::calculateNormal( - outlineData[currentNormalIndex].position, outlineData[nextNormalIndex].position); - currentNormalIndex = (currentNormalIndex + 1) % polyLength; - nextNormalIndex++; - } - - projectCasterToOutline(outlineCentroid, lightCenter, polyCentroid); - - int penumbraIndex = 0; - // Then each polygon's vertex produce at minmal 2 penumbra vertices. - // Since the size can be dynamic here, we keep track of the size and update - // the real size at the end. - int allocatedPenumbraLength = 2 * polyLength + SPOT_MAX_EXTRA_CORNER_VERTEX_NUMBER; - Vector2 penumbra[allocatedPenumbraLength]; - int totalExtraCornerSliceNumber = 0; - - Vector2 umbra[polyLength]; - - // When centroid is covered by all circles from outline, then we consider - // the umbra is invalid, and we will tune down the shadow strength. - bool hasValidUmbra = true; - // We need the minimal of RaitoVI to decrease the spot shadow strength accordingly. - float minRaitoVI = FLT_MAX; - - for (int i = 0; i < polyLength; i++) { - // Generate all the penumbra's vertices only using the (outline vertex + normal * radius) - // There is no guarantee that the penumbra is still convex, but for - // each outline vertex, it will connect to all its corresponding penumbra vertices as - // triangle fans. And for neighber penumbra vertex, it will be a trapezoid. - // - // Penumbra Vertices marked as Pi - // Outline Vertices marked as Vi - // (P3) - // (P2) | ' (P4) - // (P1)' | | ' - // ' | | ' - // (P0) ------------------------------------------------(P5) - // | (V0) |(V1) - // | | - // | | - // | | - // | | - // | | - // | | - // | | - // | | - // (V3)-----------------------------------(V2) - int preNormalIndex = (i + polyLength - 1) % polyLength; - - const Vector2& previousNormal = outlineData[preNormalIndex].normal; - const Vector2& currentNormal = outlineData[i].normal; - - // Depending on how roundness we want for each corner, we can subdivide - // further here and/or introduce some heuristic to decide how much the - // subdivision should be. - int currentExtraSliceNumber = ShadowTessellator::getExtraVertexNumber( - previousNormal, currentNormal, SPOT_CORNER_RADIANS_DIVISOR); - - int currentCornerSliceNumber = 1 + currentExtraSliceNumber; - totalExtraCornerSliceNumber += currentExtraSliceNumber; -#if DEBUG_SHADOW - ALOGD("currentExtraSliceNumber should be %d", currentExtraSliceNumber); - ALOGD("currentCornerSliceNumber should be %d", currentCornerSliceNumber); - ALOGD("totalCornerSliceNumber is %d", totalExtraCornerSliceNumber); -#endif - if (CC_UNLIKELY(totalExtraCornerSliceNumber > SPOT_MAX_EXTRA_CORNER_VERTEX_NUMBER)) { - currentCornerSliceNumber = 1; - } - for (int k = 0; k <= currentCornerSliceNumber; k++) { - Vector2 avgNormal = - (previousNormal * (currentCornerSliceNumber - k) + currentNormal * k) / - currentCornerSliceNumber; - avgNormal.normalize(); - penumbra[penumbraIndex++] = outlineData[i].position + avgNormal * outlineData[i].radius; - } - - // Compute the umbra by the intersection from the outline's centroid! - // - // (V) ------------------------------------ - // | ' | - // | ' | - // | ' (I) | - // | ' | - // | ' (C) | - // | | - // | | - // | | - // | | - // ------------------------------------ - // - // Connect a line b/t the outline vertex (V) and the centroid (C), it will - // intersect with the outline vertex's circle at point (I). - // Now, ratioVI = VI / VC, ratioIC = IC / VC - // Then the intersetion point can be computed as Ixy = Vxy * ratioIC + Cxy * ratioVI; - // - // When all of the outline circles cover the the outline centroid, (like I is - // on the other side of C), there is no real umbra any more, so we just fake - // a small area around the centroid as the umbra, and tune down the spot - // shadow's umbra strength to simulate the effect the whole shadow will - // become lighter in this case. - // The ratio can be simulated by using the inverse of maximum of ratioVI for - // all (V). - float distOutline = (outlineData[i].position - outlineCentroid).length(); - if (CC_UNLIKELY(distOutline == 0)) { - // If the outline has 0 area, then there is no spot shadow anyway. - ALOGW("Outline has 0 area, no spot shadow!"); - return; - } - - float ratioVI = outlineData[i].radius / distOutline; - minRaitoVI = std::min(minRaitoVI, ratioVI); - if (ratioVI >= (1 - FAKE_UMBRA_SIZE_RATIO)) { - ratioVI = (1 - FAKE_UMBRA_SIZE_RATIO); - } - // When we know we don't have valid umbra, don't bother to compute the - // values below. But we can't skip the loop yet since we want to know the - // maximum ratio. - float ratioIC = 1 - ratioVI; - umbra[i] = outlineData[i].position * ratioIC + outlineCentroid * ratioVI; - } - - hasValidUmbra = (minRaitoVI <= 1.0); - float shadowStrengthScale = 1.0; - if (!hasValidUmbra) { -#if DEBUG_SHADOW - ALOGW("The object is too close to the light or too small, no real umbra!"); -#endif - for (int i = 0; i < polyLength; i++) { - umbra[i] = outlineData[i].position * FAKE_UMBRA_SIZE_RATIO + - outlineCentroid * (1 - FAKE_UMBRA_SIZE_RATIO); - } - shadowStrengthScale = 1.0 / minRaitoVI; - } - - int penumbraLength = penumbraIndex; - int umbraLength = polyLength; - -#if DEBUG_SHADOW - ALOGD("penumbraLength is %d , allocatedPenumbraLength %d", penumbraLength, - allocatedPenumbraLength); - dumpPolygon(poly, polyLength, "input poly"); - dumpPolygon(penumbra, penumbraLength, "penumbra"); - dumpPolygon(umbra, umbraLength, "umbra"); - ALOGD("hasValidUmbra is %d and shadowStrengthScale is %f", hasValidUmbra, shadowStrengthScale); -#endif - - // The penumbra and umbra needs to be in convex shape to keep consistency - // and quality. - // Since we are still shooting rays to penumbra, it needs to be convex. - // Umbra can be represented as a fan from the centroid, but visually umbra - // looks nicer when it is convex. - Vector2 finalUmbra[umbraLength]; - Vector2 finalPenumbra[penumbraLength]; - int finalUmbraLength = hull(umbra, umbraLength, finalUmbra); - int finalPenumbraLength = hull(penumbra, penumbraLength, finalPenumbra); - - generateTriangleStrip(isCasterOpaque, shadowStrengthScale, finalPenumbra, finalPenumbraLength, - finalUmbra, finalUmbraLength, poly, polyLength, shadowTriangleStrip, - outlineCentroid); -} - -/** - * This is only for experimental purpose. - * After intersections are calculated, we could smooth the polygon if needed. - * So far, we don't think it is more appealing yet. - * - * @param level The level of smoothness. - * @param rays The total number of rays. - * @param rayDist (In and Out) The distance for each ray. - * - */ -void SpotShadow::smoothPolygon(int level, int rays, float* rayDist) { - for (int k = 0; k < level; k++) { - for (int i = 0; i < rays; i++) { - float p1 = rayDist[(rays - 1 + i) % rays]; - float p2 = rayDist[i]; - float p3 = rayDist[(i + 1) % rays]; - rayDist[i] = (p1 + p2 * 2 + p3) / 4; - } - } -} - -// Index pair is meant for storing the tessellation information for the penumbra -// area. One index must come from exterior tangent of the circles, the other one -// must come from the interior tangent of the circles. -struct IndexPair { - int outerIndex; - int innerIndex; -}; - -// For one penumbra vertex, find the cloest umbra vertex and return its index. -inline int getClosestUmbraIndex(const Vector2& pivot, const Vector2* polygon, int polygonLength) { - float minLengthSquared = FLT_MAX; - int resultIndex = -1; - bool hasDecreased = false; - // Starting with some negative offset, assuming both umbra and penumbra are starting - // at the same angle, this can help to find the result faster. - // Normally, loop 3 times, we can find the closest point. - int offset = polygonLength - 2; - for (int i = 0; i < polygonLength; i++) { - int currentIndex = (i + offset) % polygonLength; - float currentLengthSquared = (pivot - polygon[currentIndex]).lengthSquared(); - if (currentLengthSquared < minLengthSquared) { - if (minLengthSquared != FLT_MAX) { - hasDecreased = true; - } - minLengthSquared = currentLengthSquared; - resultIndex = currentIndex; - } else if (currentLengthSquared > minLengthSquared && hasDecreased) { - // Early break b/c we have found the closet one and now the length - // is increasing again. - break; - } - } - if (resultIndex == -1) { - ALOGE("resultIndex is -1, the polygon must be invalid!"); - resultIndex = 0; - } - return resultIndex; -} - -// Allow some epsilon here since the later ray intersection did allow for some small -// floating point error, when the intersection point is slightly outside the segment. -inline bool sameDirections(bool isPositiveCross, float a, float b) { - if (isPositiveCross) { - return a >= -EPSILON && b >= -EPSILON; - } else { - return a <= EPSILON && b <= EPSILON; - } -} - -// Find the right polygon edge to shoot the ray at. -inline int findPolyIndex(bool isPositiveCross, int startPolyIndex, const Vector2& umbraDir, - const Vector2* polyToCentroid, int polyLength) { - // Make sure we loop with a bound. - for (int i = 0; i < polyLength; i++) { - int currentIndex = (i + startPolyIndex) % polyLength; - const Vector2& currentToCentroid = polyToCentroid[currentIndex]; - const Vector2& nextToCentroid = polyToCentroid[(currentIndex + 1) % polyLength]; - - float currentCrossUmbra = currentToCentroid.cross(umbraDir); - float umbraCrossNext = umbraDir.cross(nextToCentroid); - if (sameDirections(isPositiveCross, currentCrossUmbra, umbraCrossNext)) { -#if DEBUG_SHADOW - ALOGD("findPolyIndex loop %d times , index %d", i, currentIndex); -#endif - return currentIndex; - } - } - LOG_ALWAYS_FATAL("Can't find the right polygon's edge from startPolyIndex %d", startPolyIndex); - return -1; -} - -// Generate the index pair for penumbra / umbra vertices, and more penumbra vertices -// if needed. -inline void genNewPenumbraAndPairWithUmbra(const Vector2* penumbra, int penumbraLength, - const Vector2* umbra, int umbraLength, - Vector2* newPenumbra, int& newPenumbraIndex, - IndexPair* verticesPair, int& verticesPairIndex) { - // In order to keep everything in just one loop, we need to pre-compute the - // closest umbra vertex for the last penumbra vertex. - int previousClosestUmbraIndex = - getClosestUmbraIndex(penumbra[penumbraLength - 1], umbra, umbraLength); - for (int i = 0; i < penumbraLength; i++) { - const Vector2& currentPenumbraVertex = penumbra[i]; - // For current penumbra vertex, starting from previousClosestUmbraIndex, - // then check the next one until the distance increase. - // The last one before the increase is the umbra vertex we need to pair with. - float currentLengthSquared = - (currentPenumbraVertex - umbra[previousClosestUmbraIndex]).lengthSquared(); - int currentClosestUmbraIndex = previousClosestUmbraIndex; - int indexDelta = 0; - for (int j = 1; j < umbraLength; j++) { - int newUmbraIndex = (previousClosestUmbraIndex + j) % umbraLength; - float newLengthSquared = (currentPenumbraVertex - umbra[newUmbraIndex]).lengthSquared(); - if (newLengthSquared > currentLengthSquared) { - // currentClosestUmbraIndex is the umbra vertex's index which has - // currently found smallest distance, so we can simply break here. - break; - } else { - currentLengthSquared = newLengthSquared; - indexDelta++; - currentClosestUmbraIndex = newUmbraIndex; - } - } - - if (indexDelta > 1) { - // For those umbra don't have penumbra, generate new penumbra vertices by - // interpolation. - // - // Assuming Pi for penumbra vertices, and Ui for umbra vertices. - // In the case like below P1 paired with U1 and P2 paired with U5. - // U2 to U4 are unpaired umbra vertices. - // - // P1 P2 - // | | - // U1 U2 U3 U4 U5 - // - // We will need to generate 3 more penumbra vertices P1.1, P1.2, P1.3 - // to pair with U2 to U4. - // - // P1 P1.1 P1.2 P1.3 P2 - // | | | | | - // U1 U2 U3 U4 U5 - // - // That distance ratio b/t Ui to U1 and Ui to U5 decides its paired penumbra - // vertex's location. - int newPenumbraNumber = indexDelta - 1; - - float accumulatedDeltaLength[indexDelta]; - float totalDeltaLength = 0; - - // To save time, cache the previous umbra vertex info outside the loop - // and update each loop. - Vector2 previousClosestUmbra = umbra[previousClosestUmbraIndex]; - Vector2 skippedUmbra; - // Use umbra data to precompute the length b/t unpaired umbra vertices, - // and its ratio against the total length. - for (int k = 0; k < indexDelta; k++) { - int skippedUmbraIndex = (previousClosestUmbraIndex + k + 1) % umbraLength; - skippedUmbra = umbra[skippedUmbraIndex]; - float currentDeltaLength = (skippedUmbra - previousClosestUmbra).length(); - - totalDeltaLength += currentDeltaLength; - accumulatedDeltaLength[k] = totalDeltaLength; - - previousClosestUmbra = skippedUmbra; - } - - const Vector2& previousPenumbra = penumbra[(i + penumbraLength - 1) % penumbraLength]; - // Then for each unpaired umbra vertex, create a new penumbra by the ratio, - // and pair them togehter. - for (int k = 0; k < newPenumbraNumber; k++) { - float weightForCurrentPenumbra = 1.0f; - if (totalDeltaLength != 0.0f) { - weightForCurrentPenumbra = accumulatedDeltaLength[k] / totalDeltaLength; - } - float weightForPreviousPenumbra = 1.0f - weightForCurrentPenumbra; - - Vector2 interpolatedPenumbra = currentPenumbraVertex * weightForCurrentPenumbra + - previousPenumbra * weightForPreviousPenumbra; - - int skippedUmbraIndex = (previousClosestUmbraIndex + k + 1) % umbraLength; - verticesPair[verticesPairIndex].outerIndex = newPenumbraIndex; - verticesPair[verticesPairIndex].innerIndex = skippedUmbraIndex; - verticesPairIndex++; - newPenumbra[newPenumbraIndex++] = interpolatedPenumbra; - } - } - verticesPair[verticesPairIndex].outerIndex = newPenumbraIndex; - verticesPair[verticesPairIndex].innerIndex = currentClosestUmbraIndex; - verticesPairIndex++; - newPenumbra[newPenumbraIndex++] = currentPenumbraVertex; - - previousClosestUmbraIndex = currentClosestUmbraIndex; - } -} - -// Precompute all the polygon's vector, return true if the reference cross product is positive. -inline bool genPolyToCentroid(const Vector2* poly2d, int polyLength, const Vector2& centroid, - Vector2* polyToCentroid) { - for (int j = 0; j < polyLength; j++) { - polyToCentroid[j] = poly2d[j] - centroid; - // Normalize these vectors such that we can use epsilon comparison after - // computing their cross products with another normalized vector. - polyToCentroid[j].normalize(); - } - float refCrossProduct = 0; - for (int j = 0; j < polyLength; j++) { - refCrossProduct = polyToCentroid[j].cross(polyToCentroid[(j + 1) % polyLength]); - if (refCrossProduct != 0) { - break; - } - } - - return refCrossProduct > 0; -} - -// For one umbra vertex, shoot an ray from centroid to it. -// If the ray hit the polygon first, then return the intersection point as the -// closer vertex. -inline Vector2 getCloserVertex(const Vector2& umbraVertex, const Vector2& centroid, - const Vector2* poly2d, int polyLength, const Vector2* polyToCentroid, - bool isPositiveCross, int& previousPolyIndex) { - Vector2 umbraToCentroid = umbraVertex - centroid; - float distanceToUmbra = umbraToCentroid.length(); - umbraToCentroid = umbraToCentroid / distanceToUmbra; - - // previousPolyIndex is updated for each item such that we can minimize the - // looping inside findPolyIndex(); - previousPolyIndex = findPolyIndex(isPositiveCross, previousPolyIndex, umbraToCentroid, - polyToCentroid, polyLength); - - float dx = umbraToCentroid.x; - float dy = umbraToCentroid.y; - float distanceToIntersectPoly = - rayIntersectPoints(centroid, dx, dy, poly2d[previousPolyIndex], - poly2d[(previousPolyIndex + 1) % polyLength]); - if (distanceToIntersectPoly < 0) { - distanceToIntersectPoly = 0; - } - - // Pick the closer one as the occluded area vertex. - Vector2 closerVertex; - if (distanceToIntersectPoly < distanceToUmbra) { - closerVertex.x = centroid.x + dx * distanceToIntersectPoly; - closerVertex.y = centroid.y + dy * distanceToIntersectPoly; - } else { - closerVertex = umbraVertex; - } - - return closerVertex; -} - -/** - * Generate a triangle strip given two convex polygon -**/ -void SpotShadow::generateTriangleStrip(bool isCasterOpaque, float shadowStrengthScale, - Vector2* penumbra, int penumbraLength, Vector2* umbra, - int umbraLength, const Vector3* poly, int polyLength, - VertexBuffer& shadowTriangleStrip, const Vector2& centroid) { - bool hasOccludedUmbraArea = false; - Vector2 poly2d[polyLength]; - - if (isCasterOpaque) { - for (int i = 0; i < polyLength; i++) { - poly2d[i].x = poly[i].x; - poly2d[i].y = poly[i].y; - } - // Make sure the centroid is inside the umbra, otherwise, fall back to the - // approach as if there is no occluded umbra area. - if (testPointInsidePolygon(centroid, poly2d, polyLength)) { - hasOccludedUmbraArea = true; - } - } - - // For each penumbra vertex, find its corresponding closest umbra vertex index. - // - // Penumbra Vertices marked as Pi - // Umbra Vertices marked as Ui - // (P3) - // (P2) | ' (P4) - // (P1)' | | ' - // ' | | ' - // (P0) ------------------------------------------------(P5) - // | (U0) |(U1) - // | | - // | |(U2) (P5.1) - // | | - // | | - // | | - // | | - // | | - // | | - // (U4)-----------------------------------(U3) (P6) - // - // At least, like P0, P1, P2, they will find the matching umbra as U0. - // If we jump over some umbra vertex without matching penumbra vertex, then - // we will generate some new penumbra vertex by interpolation. Like P6 is - // matching U3, but U2 is not matched with any penumbra vertex. - // So interpolate P5.1 out and match U2. - // In this way, every umbra vertex will have a matching penumbra vertex. - // - // The total pair number can be as high as umbraLength + penumbraLength. - const int maxNewPenumbraLength = umbraLength + penumbraLength; - IndexPair verticesPair[maxNewPenumbraLength]; - int verticesPairIndex = 0; - - // Cache all the existing penumbra vertices and newly interpolated vertices into a - // a new array. - Vector2 newPenumbra[maxNewPenumbraLength]; - int newPenumbraIndex = 0; - - // For each penumbra vertex, find its closet umbra vertex by comparing the - // neighbor umbra vertices. - genNewPenumbraAndPairWithUmbra(penumbra, penumbraLength, umbra, umbraLength, newPenumbra, - newPenumbraIndex, verticesPair, verticesPairIndex); - ShadowTessellator::checkOverflow(verticesPairIndex, maxNewPenumbraLength, "Spot pair"); - ShadowTessellator::checkOverflow(newPenumbraIndex, maxNewPenumbraLength, "Spot new penumbra"); -#if DEBUG_SHADOW - for (int i = 0; i < umbraLength; i++) { - ALOGD("umbra i %d, [%f, %f]", i, umbra[i].x, umbra[i].y); - } - for (int i = 0; i < newPenumbraIndex; i++) { - ALOGD("new penumbra i %d, [%f, %f]", i, newPenumbra[i].x, newPenumbra[i].y); - } - for (int i = 0; i < verticesPairIndex; i++) { - ALOGD("index i %d, [%d, %d]", i, verticesPair[i].outerIndex, verticesPair[i].innerIndex); - } -#endif - - // For the size of vertex buffer, we need 3 rings, one has newPenumbraSize, - // one has umbraLength, the last one has at most umbraLength. - // - // For the size of index buffer, the umbra area needs (2 * umbraLength + 2). - // The penumbra one can vary a bit, but it is bounded by (2 * verticesPairIndex + 2). - // And 2 more for jumping between penumbra to umbra. - const int newPenumbraLength = newPenumbraIndex; - const int totalVertexCount = newPenumbraLength + umbraLength * 2; - const int totalIndexCount = 2 * umbraLength + 2 * verticesPairIndex + 6; - AlphaVertex* shadowVertices = shadowTriangleStrip.alloc<AlphaVertex>(totalVertexCount); - uint16_t* indexBuffer = shadowTriangleStrip.allocIndices<uint16_t>(totalIndexCount); - int vertexBufferIndex = 0; - int indexBufferIndex = 0; - - // Fill the IB and VB for the penumbra area. - for (int i = 0; i < newPenumbraLength; i++) { - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], newPenumbra[i].x, newPenumbra[i].y, - PENUMBRA_ALPHA); - } - // Since the umbra can be a faked one when the occluder is too high, the umbra should be lighter - // in this case. - float scaledUmbraAlpha = UMBRA_ALPHA * shadowStrengthScale; - - for (int i = 0; i < umbraLength; i++) { - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], umbra[i].x, umbra[i].y, - scaledUmbraAlpha); - } - - for (int i = 0; i < verticesPairIndex; i++) { - indexBuffer[indexBufferIndex++] = verticesPair[i].outerIndex; - // All umbra index need to be offseted by newPenumbraSize. - indexBuffer[indexBufferIndex++] = verticesPair[i].innerIndex + newPenumbraLength; - } - indexBuffer[indexBufferIndex++] = verticesPair[0].outerIndex; - indexBuffer[indexBufferIndex++] = verticesPair[0].innerIndex + newPenumbraLength; - - // Now fill the IB and VB for the umbra area. - // First duplicated the index from previous strip and the first one for the - // degenerated triangles. - indexBuffer[indexBufferIndex] = indexBuffer[indexBufferIndex - 1]; - indexBufferIndex++; - indexBuffer[indexBufferIndex++] = newPenumbraLength + 0; - // Save the first VB index for umbra area in order to close the loop. - int savedStartIndex = vertexBufferIndex; - - if (hasOccludedUmbraArea) { - // Precompute all the polygon's vector, and the reference cross product, - // in order to find the right polygon edge for the ray to intersect. - Vector2 polyToCentroid[polyLength]; - bool isPositiveCross = genPolyToCentroid(poly2d, polyLength, centroid, polyToCentroid); - - // Because both the umbra and polygon are going in the same direction, - // we can save the previous polygon index to make sure we have less polygon - // vertex to compute for each ray. - int previousPolyIndex = 0; - for (int i = 0; i < umbraLength; i++) { - // Shoot a ray from centroid to each umbra vertices and pick the one with - // shorter distance to the centroid, b/t the umbra vertex or the intersection point. - Vector2 closerVertex = - getCloserVertex(umbra[i], centroid, poly2d, polyLength, polyToCentroid, - isPositiveCross, previousPolyIndex); - - // We already stored the umbra vertices, just need to add the occlued umbra's ones. - indexBuffer[indexBufferIndex++] = newPenumbraLength + i; - indexBuffer[indexBufferIndex++] = vertexBufferIndex; - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], closerVertex.x, closerVertex.y, - scaledUmbraAlpha); - } - } else { - // If there is no occluded umbra at all, then draw the triangle fan - // starting from the centroid to all umbra vertices. - int lastCentroidIndex = vertexBufferIndex; - AlphaVertex::set(&shadowVertices[vertexBufferIndex++], centroid.x, centroid.y, - scaledUmbraAlpha); - for (int i = 0; i < umbraLength; i++) { - indexBuffer[indexBufferIndex++] = newPenumbraLength + i; - indexBuffer[indexBufferIndex++] = lastCentroidIndex; - } - } - // Closing the umbra area triangle's loop here. - indexBuffer[indexBufferIndex++] = newPenumbraLength; - indexBuffer[indexBufferIndex++] = savedStartIndex; - - // At the end, update the real index and vertex buffer size. - shadowTriangleStrip.updateVertexCount(vertexBufferIndex); - shadowTriangleStrip.updateIndexCount(indexBufferIndex); - ShadowTessellator::checkOverflow(vertexBufferIndex, totalVertexCount, "Spot Vertex Buffer"); - ShadowTessellator::checkOverflow(indexBufferIndex, totalIndexCount, "Spot Index Buffer"); - - shadowTriangleStrip.setMeshFeatureFlags(VertexBuffer::kAlpha | VertexBuffer::kIndices); - shadowTriangleStrip.computeBounds<AlphaVertex>(); -} - -#if DEBUG_SHADOW - -#define TEST_POINT_NUMBER 128 -/** - * Calculate the bounds for generating random test points. - */ -void SpotShadow::updateBound(const Vector2 inVector, Vector2& lowerBound, Vector2& upperBound) { - if (inVector.x < lowerBound.x) { - lowerBound.x = inVector.x; - } - - if (inVector.y < lowerBound.y) { - lowerBound.y = inVector.y; - } - - if (inVector.x > upperBound.x) { - upperBound.x = inVector.x; - } - - if (inVector.y > upperBound.y) { - upperBound.y = inVector.y; - } -} - -/** - * For debug purpose, when things go wrong, dump the whole polygon data. - */ -void SpotShadow::dumpPolygon(const Vector2* poly, int polyLength, const char* polyName) { - for (int i = 0; i < polyLength; i++) { - ALOGD("polygon %s i %d x %f y %f", polyName, i, poly[i].x, poly[i].y); - } -} - -/** - * For debug purpose, when things go wrong, dump the whole polygon data. - */ -void SpotShadow::dumpPolygon(const Vector3* poly, int polyLength, const char* polyName) { - for (int i = 0; i < polyLength; i++) { - ALOGD("polygon %s i %d x %f y %f z %f", polyName, i, poly[i].x, poly[i].y, poly[i].z); - } -} - -/** - * Test whether the polygon is convex. - */ -bool SpotShadow::testConvex(const Vector2* polygon, int polygonLength, const char* name) { - bool isConvex = true; - for (int i = 0; i < polygonLength; i++) { - Vector2 start = polygon[i]; - Vector2 middle = polygon[(i + 1) % polygonLength]; - Vector2 end = polygon[(i + 2) % polygonLength]; - - float delta = (float(middle.x) - start.x) * (float(end.y) - start.y) - - (float(middle.y) - start.y) * (float(end.x) - start.x); - bool isCCWOrCoLinear = (delta >= EPSILON); - - if (isCCWOrCoLinear) { - ALOGW("(Error Type 2): polygon (%s) is not a convex b/c start (x %f, y %f)," - "middle (x %f, y %f) and end (x %f, y %f) , delta is %f !!!", - name, start.x, start.y, middle.x, middle.y, end.x, end.y, delta); - isConvex = false; - break; - } - } - return isConvex; -} - -/** - * Test whether or not the polygon (intersection) is within the 2 input polygons. - * Using Marte Carlo method, we generate a random point, and if it is inside the - * intersection, then it must be inside both source polygons. - */ -void SpotShadow::testIntersection(const Vector2* poly1, int poly1Length, const Vector2* poly2, - int poly2Length, const Vector2* intersection, - int intersectionLength) { - // Find the min and max of x and y. - Vector2 lowerBound = {FLT_MAX, FLT_MAX}; - Vector2 upperBound = {-FLT_MAX, -FLT_MAX}; - for (int i = 0; i < poly1Length; i++) { - updateBound(poly1[i], lowerBound, upperBound); - } - for (int i = 0; i < poly2Length; i++) { - updateBound(poly2[i], lowerBound, upperBound); - } - - bool dumpPoly = false; - for (int k = 0; k < TEST_POINT_NUMBER; k++) { - // Generate a random point between minX, minY and maxX, maxY. - float randomX = rand() / float(RAND_MAX); - float randomY = rand() / float(RAND_MAX); - - Vector2 testPoint; - testPoint.x = lowerBound.x + randomX * (upperBound.x - lowerBound.x); - testPoint.y = lowerBound.y + randomY * (upperBound.y - lowerBound.y); - - // If the random point is in both poly 1 and 2, then it must be intersection. - if (testPointInsidePolygon(testPoint, intersection, intersectionLength)) { - if (!testPointInsidePolygon(testPoint, poly1, poly1Length)) { - dumpPoly = true; - ALOGW("(Error Type 1): one point (%f, %f) in the intersection is" - " not in the poly1", - testPoint.x, testPoint.y); - } - - if (!testPointInsidePolygon(testPoint, poly2, poly2Length)) { - dumpPoly = true; - ALOGW("(Error Type 1): one point (%f, %f) in the intersection is" - " not in the poly2", - testPoint.x, testPoint.y); - } - } - } - - if (dumpPoly) { - dumpPolygon(intersection, intersectionLength, "intersection"); - for (int i = 1; i < intersectionLength; i++) { - Vector2 delta = intersection[i] - intersection[i - 1]; - ALOGD("Intersetion i, %d Vs i-1 is delta %f", i, delta.lengthSquared()); - } - - dumpPolygon(poly1, poly1Length, "poly 1"); - dumpPolygon(poly2, poly2Length, "poly 2"); - } -} -#endif - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/SpotShadow.h b/libs/hwui/SpotShadow.h deleted file mode 100644 index 8476be70318b..000000000000 --- a/libs/hwui/SpotShadow.h +++ /dev/null @@ -1,79 +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. - */ - -#ifndef ANDROID_HWUI_SPOT_SHADOW_H -#define ANDROID_HWUI_SPOT_SHADOW_H - -#include "Debug.h" -#include "Vector.h" - -namespace android { -namespace uirenderer { - -class VertexBuffer; - -class SpotShadow { -public: - static void createSpotShadow(bool isCasterOpaque, const Vector3& lightCenter, float lightSize, - const Vector3* poly, int polyLength, const Vector3& polyCentroid, - VertexBuffer& retstrips); - -private: - struct VertexAngleData; - - static float projectCasterToOutline(Vector2& outline, const Vector3& lightCenter, - const Vector3& polyVertex); - - static void computeLightPolygon(int points, const Vector3& lightCenter, float size, - Vector3* ret); - - static void smoothPolygon(int level, int rays, float* rayDist); - static float rayIntersectPoly(const Vector2* poly, int polyLength, const Vector2& point, - float dx, float dy); - - static void xsort(Vector2* points, int pointsLength); - static int hull(Vector2* points, int pointsLength, Vector2* retPoly); - static bool ccw(float ax, float ay, float bx, float by, float cx, float cy); - static void sort(Vector2* poly, int polyLength, const Vector2& center); - - static void swap(Vector2* points, int i, int j); - static void quicksortCirc(Vector2* points, int low, int high, const Vector2& center); - static void quicksortX(Vector2* points, int low, int high); - - static bool testPointInsidePolygon(const Vector2 testPoint, const Vector2* poly, int len); - static void reverse(Vector2* polygon, int len); - - static void generateTriangleStrip(bool isCasterOpaque, float shadowStrengthScale, - Vector2* penumbra, int penumbraLength, Vector2* umbra, - int umbraLength, const Vector3* poly, int polyLength, - VertexBuffer& retstrips, const Vector2& centroid); - -#if DEBUG_SHADOW - static bool testConvex(const Vector2* polygon, int polygonLength, const char* name); - static void testIntersection(const Vector2* poly1, int poly1Length, const Vector2* poly2, - int poly2Length, const Vector2* intersection, - int intersectionLength); - static void updateBound(const Vector2 inVector, Vector2& lowerBound, Vector2& upperBound); - static void dumpPolygon(const Vector2* poly, int polyLength, const char* polyName); - static void dumpPolygon(const Vector3* poly, int polyLength, const char* polyName); -#endif - -}; // SpotShadow - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_SPOT_SHADOW_H diff --git a/libs/hwui/TessellationCache.cpp b/libs/hwui/TessellationCache.cpp deleted file mode 100644 index c7d93da718e7..000000000000 --- a/libs/hwui/TessellationCache.cpp +++ /dev/null @@ -1,444 +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 <utils/JenkinsHash.h> -#include <utils/Trace.h> - -#include "Caches.h" -#include "PathTessellator.h" -#include "ShadowTessellator.h" -#include "TessellationCache.h" - -#include "thread/Signal.h" -#include "thread/Task.h" -#include "thread/TaskProcessor.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Cache entries -/////////////////////////////////////////////////////////////////////////////// - -TessellationCache::Description::Description() - : type(Type::None) - , scaleX(1.0f) - , scaleY(1.0f) - , aa(false) - , cap(SkPaint::kDefault_Cap) - , style(SkPaint::kFill_Style) - , strokeWidth(1.0f) { - // Shape bits should be set to zeroes, because they are used for hash calculation. - memset(&shape, 0, sizeof(Shape)); -} - -TessellationCache::Description::Description(Type type, const Matrix4& transform, - const SkPaint& paint) - : type(type) - , aa(paint.isAntiAlias()) - , cap(paint.getStrokeCap()) - , style(paint.getStyle()) - , strokeWidth(paint.getStrokeWidth()) { - PathTessellator::extractTessellationScales(transform, &scaleX, &scaleY); - // Shape bits should be set to zeroes, because they are used for hash calculation. - memset(&shape, 0, sizeof(Shape)); -} - -bool TessellationCache::Description::operator==(const TessellationCache::Description& rhs) const { - if (type != rhs.type) return false; - if (scaleX != rhs.scaleX) return false; - if (scaleY != rhs.scaleY) return false; - if (aa != rhs.aa) return false; - if (cap != rhs.cap) return false; - if (style != rhs.style) return false; - if (strokeWidth != rhs.strokeWidth) return false; - if (type == Type::None) return true; - const Shape::RoundRect& lRect = shape.roundRect; - const Shape::RoundRect& rRect = rhs.shape.roundRect; - - if (lRect.width != rRect.width) return false; - if (lRect.height != rRect.height) return false; - if (lRect.rx != rRect.rx) return false; - return lRect.ry == rRect.ry; -} - -hash_t TessellationCache::Description::hash() const { - uint32_t hash = JenkinsHashMix(0, static_cast<int>(type)); - hash = JenkinsHashMix(hash, aa); - hash = JenkinsHashMix(hash, cap); - hash = JenkinsHashMix(hash, style); - hash = JenkinsHashMix(hash, android::hash_type(strokeWidth)); - hash = JenkinsHashMix(hash, android::hash_type(scaleX)); - hash = JenkinsHashMix(hash, android::hash_type(scaleY)); - hash = JenkinsHashMixBytes(hash, (uint8_t*)&shape, sizeof(Shape)); - return JenkinsHashWhiten(hash); -} - -void TessellationCache::Description::setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const { - matrix->loadScale(scaleX, scaleY, 1.0f); - paint->setAntiAlias(aa); - paint->setStrokeCap(cap); - paint->setStyle(style); - paint->setStrokeWidth(strokeWidth); -} - -TessellationCache::ShadowDescription::ShadowDescription() : nodeKey(nullptr) { - memset(&matrixData, 0, sizeof(matrixData)); -} - -TessellationCache::ShadowDescription::ShadowDescription(const SkPath* nodeKey, - const Matrix4* drawTransform) - : nodeKey(nodeKey) { - memcpy(&matrixData, drawTransform->data, sizeof(matrixData)); -} - -bool TessellationCache::ShadowDescription::operator==( - const TessellationCache::ShadowDescription& rhs) const { - return nodeKey == rhs.nodeKey && memcmp(&matrixData, &rhs.matrixData, sizeof(matrixData)) == 0; -} - -hash_t TessellationCache::ShadowDescription::hash() const { - uint32_t hash = JenkinsHashMixBytes(0, (uint8_t*)&nodeKey, sizeof(const void*)); - hash = JenkinsHashMixBytes(hash, (uint8_t*)&matrixData, sizeof(matrixData)); - return JenkinsHashWhiten(hash); -} - -/////////////////////////////////////////////////////////////////////////////// -// General purpose tessellation task processing -/////////////////////////////////////////////////////////////////////////////// - -class TessellationCache::TessellationTask : public Task<VertexBuffer*> { -public: - TessellationTask(Tessellator tessellator, const Description& description) - : tessellator(tessellator), description(description) {} - - ~TessellationTask() {} - - Tessellator tessellator; - Description description; -}; - -class TessellationCache::TessellationProcessor : public TaskProcessor<VertexBuffer*> { -public: - explicit TessellationProcessor(Caches& caches) : TaskProcessor<VertexBuffer*>(&caches.tasks) {} - ~TessellationProcessor() {} - - virtual void onProcess(const sp<Task<VertexBuffer*> >& task) override { - TessellationTask* t = static_cast<TessellationTask*>(task.get()); - ATRACE_NAME("shape tessellation"); - VertexBuffer* buffer = t->tessellator(t->description); - t->setResult(buffer); - } -}; - -class TessellationCache::Buffer { -public: - explicit Buffer(const sp<Task<VertexBuffer*> >& task) : mTask(task), mBuffer(nullptr) {} - - ~Buffer() { - mTask.clear(); - delete mBuffer; - } - - unsigned int getSize() { - blockOnPrecache(); - return mBuffer->getSize(); - } - - const VertexBuffer* getVertexBuffer() { - blockOnPrecache(); - return mBuffer; - } - -private: - void blockOnPrecache() { - if (mTask != nullptr) { - mBuffer = mTask->getResult(); - LOG_ALWAYS_FATAL_IF(mBuffer == nullptr, "Failed to precache"); - mTask.clear(); - } - } - sp<Task<VertexBuffer*> > mTask; - VertexBuffer* mBuffer; -}; - -/////////////////////////////////////////////////////////////////////////////// -// Shadow tessellation task processing -/////////////////////////////////////////////////////////////////////////////// - -static void mapPointFakeZ(Vector3& point, const mat4* transformXY, const mat4* transformZ) { - // map z coordinate with true 3d matrix - point.z = transformZ->mapZ(point); - - // map x,y coordinates with draw/Skia matrix - transformXY->mapPoint(point.x, point.y); -} - -static void reverseVertexArray(Vertex* polygon, int len) { - int n = len / 2; - for (int i = 0; i < n; i++) { - Vertex tmp = polygon[i]; - int k = len - 1 - i; - polygon[i] = polygon[k]; - polygon[k] = tmp; - } -} - -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) { - // tessellate caster outline into a 2d polygon - 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.front(), casterVertices2d.size()); - - if (casterVertices2d.size() == 0) return; - - // map 2d caster poly into 3d - const int casterVertexCount = casterVertices2d.size(); - Vector3 casterPolygon[casterVertexCount]; - float minZ = FLT_MAX; - float maxZ = -FLT_MAX; - for (int i = 0; i < casterVertexCount; i++) { - const Vertex& point2d = casterVertices2d[i]; - casterPolygon[i] = (Vector3){point2d.x, point2d.y, 0}; - mapPointFakeZ(casterPolygon[i], casterTransformXY, casterTransformZ); - minZ = std::min(minZ, casterPolygon[i].z); - maxZ = std::max(maxZ, casterPolygon[i].z); - } - - // map the centroid of the caster into 3d - Vector2 centroid = ShadowTessellator::centroid2d( - reinterpret_cast<const Vector2*>(&casterVertices2d.front()), casterVertexCount); - Vector3 centroid3d = {centroid.x, centroid.y, 0}; - mapPointFakeZ(centroid3d, casterTransformXY, casterTransformZ); - - // if the caster intersects the z=0 plane, lift it in Z so it doesn't - if (minZ < SHADOW_MIN_CASTER_Z) { - float casterLift = SHADOW_MIN_CASTER_Z - minZ; - for (int i = 0; i < casterVertexCount; i++) { - casterPolygon[i].z += casterLift; - } - centroid3d.z += casterLift; - } - - // Check whether we want to draw the shadow at all by checking the caster's bounds against clip. - // We only have ortho projection, so we can just ignore the Z in caster for - // simple rejection calculation. - Rect casterBounds(casterPerimeter->getBounds()); - casterTransformXY->mapRect(casterBounds); - - // actual tessellation of both shadows - ShadowTessellator::tessellateAmbientShadow(isCasterOpaque, casterPolygon, casterVertexCount, - centroid3d, casterBounds, *localClip, maxZ, - ambientBuffer); - - ShadowTessellator::tessellateSpotShadow(isCasterOpaque, casterPolygon, casterVertexCount, - centroid3d, *drawTransform, lightCenter, lightRadius, - casterBounds, *localClip, spotBuffer); -} - -class ShadowProcessor : public TaskProcessor<TessellationCache::vertexBuffer_pair_t> { -public: - explicit ShadowProcessor(Caches& caches) - : TaskProcessor<TessellationCache::vertexBuffer_pair_t>(&caches.tasks) {} - ~ShadowProcessor() {} - - virtual void onProcess(const sp<Task<TessellationCache::vertexBuffer_pair_t> >& task) override { - TessellationCache::ShadowTask* t = static_cast<TessellationCache::ShadowTask*>(task.get()); - ATRACE_NAME("shadow tessellation"); - - tessellateShadows(&t->drawTransform, &t->localClip, t->opaque, &t->casterPerimeter, - &t->transformXY, &t->transformZ, t->lightCenter, t->lightRadius, - t->ambientBuffer, t->spotBuffer); - - t->setResult(TessellationCache::vertexBuffer_pair_t(&t->ambientBuffer, &t->spotBuffer)); - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// Cache constructor/destructor -/////////////////////////////////////////////////////////////////////////////// - -TessellationCache::TessellationCache() - : mMaxSize(MB(1)) - , mCache(LruCache<Description, Buffer*>::kUnlimitedCapacity) - , mShadowCache( - LruCache<ShadowDescription, Task<vertexBuffer_pair_t*>*>::kUnlimitedCapacity) { - mCache.setOnEntryRemovedListener(&mBufferRemovedListener); - mShadowCache.setOnEntryRemovedListener(&mBufferPairRemovedListener); - mDebugEnabled = Properties::debugLevel & kDebugCaches; -} - -TessellationCache::~TessellationCache() { - mCache.clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Size management -/////////////////////////////////////////////////////////////////////////////// - -uint32_t TessellationCache::getSize() { - LruCache<Description, Buffer*>::Iterator iter(mCache); - uint32_t size = 0; - while (iter.next()) { - size += iter.value()->getSize(); - } - return size; -} - -uint32_t TessellationCache::getMaxSize() { - return mMaxSize; -} - -/////////////////////////////////////////////////////////////////////////////// -// Caching -/////////////////////////////////////////////////////////////////////////////// - -void TessellationCache::trim() { - uint32_t size = getSize(); - while (size > mMaxSize) { - size -= mCache.peekOldestValue()->getSize(); - mCache.removeOldest(); - } - mShadowCache.clear(); -} - -void TessellationCache::clear() { - mCache.clear(); - mShadowCache.clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Callbacks -/////////////////////////////////////////////////////////////////////////////// - -void TessellationCache::BufferRemovedListener::operator()(Description& description, - Buffer*& buffer) { - delete buffer; -} - -/////////////////////////////////////////////////////////////////////////////// -// Shadows -/////////////////////////////////////////////////////////////////////////////// - -void TessellationCache::precacheShadows(const Matrix4* drawTransform, const Rect& localClip, - bool opaque, const SkPath* casterPerimeter, - const Matrix4* transformXY, const Matrix4* transformZ, - const Vector3& lightCenter, float lightRadius) { - ShadowDescription key(casterPerimeter, drawTransform); - - if (mShadowCache.get(key)) return; - sp<ShadowTask> task = new ShadowTask(drawTransform, localClip, opaque, casterPerimeter, - transformXY, transformZ, lightCenter, lightRadius); - if (mShadowProcessor == nullptr) { - mShadowProcessor = new ShadowProcessor(Caches::getInstance()); - } - mShadowProcessor->add(task); - task->incStrong(nullptr); // not using sp<>s, so manually ref while in the cache - mShadowCache.put(key, task.get()); -} - -sp<TessellationCache::ShadowTask> TessellationCache::getShadowTask( - const Matrix4* drawTransform, const Rect& localClip, bool opaque, - const SkPath* casterPerimeter, const Matrix4* transformXY, const Matrix4* transformZ, - const Vector3& lightCenter, float lightRadius) { - ShadowDescription key(casterPerimeter, drawTransform); - ShadowTask* task = static_cast<ShadowTask*>(mShadowCache.get(key)); - if (!task) { - precacheShadows(drawTransform, localClip, opaque, casterPerimeter, transformXY, transformZ, - lightCenter, lightRadius); - task = static_cast<ShadowTask*>(mShadowCache.get(key)); - } - LOG_ALWAYS_FATAL_IF(task == nullptr, "shadow not precached"); - return task; -} - -/////////////////////////////////////////////////////////////////////////////// -// Tessellation precaching -/////////////////////////////////////////////////////////////////////////////// - -TessellationCache::Buffer* TessellationCache::getOrCreateBuffer(const Description& entry, - Tessellator tessellator) { - Buffer* buffer = mCache.get(entry); - if (!buffer) { - // not cached, enqueue a task to fill the buffer - sp<TessellationTask> task = new TessellationTask(tessellator, entry); - buffer = new Buffer(task); - - if (mProcessor == nullptr) { - mProcessor = new TessellationProcessor(Caches::getInstance()); - } - mProcessor->add(task); - bool inserted = mCache.put(entry, buffer); - // Note to the static analyzer that this insert should always succeed. - LOG_ALWAYS_FATAL_IF(!inserted, "buffers shouldn't spontaneously appear in the cache"); - } - return buffer; -} - -static VertexBuffer* tessellatePath(const TessellationCache::Description& description, - const SkPath& path) { - Matrix4 matrix; - SkPaint paint; - description.setupMatrixAndPaint(&matrix, &paint); - VertexBuffer* buffer = new VertexBuffer(); - PathTessellator::tessellatePath(path, &paint, matrix, *buffer); - return buffer; -} - -/////////////////////////////////////////////////////////////////////////////// -// RoundRect -/////////////////////////////////////////////////////////////////////////////// - -static VertexBuffer* tessellateRoundRect(const TessellationCache::Description& description) { - SkRect rect = - SkRect::MakeWH(description.shape.roundRect.width, description.shape.roundRect.height); - float rx = description.shape.roundRect.rx; - float ry = description.shape.roundRect.ry; - if (description.style == SkPaint::kStrokeAndFill_Style) { - float outset = description.strokeWidth / 2; - rect.outset(outset, outset); - rx += outset; - ry += outset; - } - SkPath path; - path.addRoundRect(rect, rx, ry); - return tessellatePath(description, path); -} - -TessellationCache::Buffer* TessellationCache::getRoundRectBuffer(const Matrix4& transform, - const SkPaint& paint, float width, - float height, float rx, float ry) { - Description entry(Description::Type::RoundRect, transform, paint); - entry.shape.roundRect.width = width; - entry.shape.roundRect.height = height; - entry.shape.roundRect.rx = rx; - entry.shape.roundRect.ry = ry; - return getOrCreateBuffer(entry, &tessellateRoundRect); -} -const VertexBuffer* TessellationCache::getRoundRect(const Matrix4& transform, const SkPaint& paint, - float width, float height, float rx, float ry) { - return getRoundRectBuffer(transform, paint, width, height, rx, ry)->getVertexBuffer(); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/TessellationCache.h b/libs/hwui/TessellationCache.h deleted file mode 100644 index a0f0ed4653e0..000000000000 --- a/libs/hwui/TessellationCache.h +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include "Debug.h" -#include "Matrix.h" -#include "Rect.h" -#include "Vector.h" -#include "VertexBuffer.h" -#include "thread/TaskProcessor.h" -#include "utils/Macros.h" -#include "utils/Pair.h" - -#include <SkPaint.h> -#include <SkPath.h> - -#include <utils/LruCache.h> -#include <utils/Mutex.h> -#include <utils/StrongPointer.h> - -class SkBitmap; -class SkCanvas; -struct SkRect; - -namespace android { -namespace uirenderer { - -class Caches; -class VertexBuffer; - -/////////////////////////////////////////////////////////////////////////////// -// Classes -/////////////////////////////////////////////////////////////////////////////// - -class TessellationCache { -public: - typedef Pair<VertexBuffer*, VertexBuffer*> vertexBuffer_pair_t; - - struct Description { - HASHABLE_TYPE(Description); - enum class Type { - None, - RoundRect, - }; - - Type type; - float scaleX; - float scaleY; - bool aa; - SkPaint::Cap cap; - SkPaint::Style style; - float strokeWidth; - union Shape { - struct RoundRect { - float width; - float height; - float rx; - float ry; - } roundRect; - } shape; - - Description(); - Description(Type type, const Matrix4& transform, const SkPaint& paint); - void setupMatrixAndPaint(Matrix4* matrix, SkPaint* paint) const; - }; - - struct ShadowDescription { - HASHABLE_TYPE(ShadowDescription); - const SkPath* nodeKey; - float matrixData[16]; - - ShadowDescription(); - ShadowDescription(const SkPath* nodeKey, const Matrix4* drawTransform); - }; - - class ShadowTask : public Task<vertexBuffer_pair_t> { - public: - ShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque, - const SkPath* casterPerimeter, const Matrix4* transformXY, - const Matrix4* transformZ, const Vector3& lightCenter, float lightRadius) - : drawTransform(*drawTransform) - , localClip(localClip) - , opaque(opaque) - , casterPerimeter(*casterPerimeter) - , transformXY(*transformXY) - , transformZ(*transformZ) - , lightCenter(lightCenter) - , lightRadius(lightRadius) {} - - /* Note - we deep copy all task parameters, because *even though* pointers into Allocator - * controlled objects (like the SkPath and Matrix4s) should be safe for the entire frame, - * certain Allocators are destroyed before trim() is called to flush incomplete tasks. - * - * These deep copies could be avoided, long term, by canceling or flushing outstanding - * tasks before tearing down single-frame LinearAllocators. - */ - const Matrix4 drawTransform; - const Rect localClip; - bool opaque; - const SkPath casterPerimeter; - const Matrix4 transformXY; - const Matrix4 transformZ; - const Vector3 lightCenter; - const float lightRadius; - VertexBuffer ambientBuffer; - VertexBuffer spotBuffer; - }; - - TessellationCache(); - ~TessellationCache(); - - /** - * Clears the cache. This causes all TessellationBuffers to be deleted. - */ - void clear(); - /** - * Returns the maximum size of the cache in bytes. - */ - uint32_t getMaxSize(); - /** - * Returns the current size of the cache in bytes. - */ - uint32_t getSize(); - - /** - * Trims the contents of the cache, removing items until it's under its - * specified limit. - * - * Trimming is used for caches that support pre-caching from a worker - * thread. During pre-caching the maximum limit of the cache can be - * exceeded for the duration of the frame. It is therefore required to - * trim the cache at the end of the frame to keep the total amount of - * memory used under control. - * - * Also removes transient Shadow VertexBuffers, which aren't cached between frames. - */ - void trim(); - - // TODO: precache/get for Oval, Lines, Points, etc. - - void precacheRoundRect(const Matrix4& transform, const SkPaint& paint, float width, - float height, float rx, float ry) { - getRoundRectBuffer(transform, paint, width, height, rx, ry); - } - const VertexBuffer* getRoundRect(const Matrix4& transform, const SkPaint& paint, float width, - float height, float rx, float ry); - - sp<ShadowTask> getShadowTask(const Matrix4* drawTransform, const Rect& localClip, bool opaque, - const SkPath* casterPerimeter, const Matrix4* transformXY, - const Matrix4* transformZ, const Vector3& lightCenter, - float lightRadius); - -private: - class Buffer; - class TessellationTask; - class TessellationProcessor; - - typedef VertexBuffer* (*Tessellator)(const Description&); - - void precacheShadows(const Matrix4* drawTransform, const Rect& localClip, bool opaque, - const SkPath* casterPerimeter, const Matrix4* transformXY, - const Matrix4* transformZ, const Vector3& lightCenter, float lightRadius); - - Buffer* getRectBuffer(const Matrix4& transform, const SkPaint& paint, float width, - float height); - Buffer* getRoundRectBuffer(const Matrix4& transform, const SkPaint& paint, float width, - float height, float rx, float ry); - - Buffer* getOrCreateBuffer(const Description& entry, Tessellator tessellator); - - const uint32_t mMaxSize; - - bool mDebugEnabled; - - mutable Mutex mLock; - - /////////////////////////////////////////////////////////////////////////////// - // General tessellation caching - /////////////////////////////////////////////////////////////////////////////// - sp<TaskProcessor<VertexBuffer*> > mProcessor; - LruCache<Description, Buffer*> mCache; - class BufferRemovedListener : public OnEntryRemoved<Description, Buffer*> { - void operator()(Description& description, Buffer*& buffer) override; - }; - BufferRemovedListener mBufferRemovedListener; - - /////////////////////////////////////////////////////////////////////////////// - // Shadow tessellation caching - /////////////////////////////////////////////////////////////////////////////// - sp<TaskProcessor<vertexBuffer_pair_t> > mShadowProcessor; - - // holds a pointer, and implicit strong ref to each shadow task of the frame - LruCache<ShadowDescription, Task<vertexBuffer_pair_t>*> mShadowCache; - class BufferPairRemovedListener - : public OnEntryRemoved<ShadowDescription, Task<vertexBuffer_pair_t>*> { - void operator()(ShadowDescription& description, - Task<vertexBuffer_pair_t>*& bufferPairTask) override { - bufferPairTask->decStrong(nullptr); - } - }; - BufferPairRemovedListener mBufferPairRemovedListener; - -}; // 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 deleted file mode 100644 index c892ceb3e14d..000000000000 --- a/libs/hwui/TextDropShadowCache.cpp +++ /dev/null @@ -1,209 +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 <utils/JenkinsHash.h> - -#include "Caches.h" -#include "Debug.h" -#include "FontRenderer.h" -#include "Properties.h" -#include "TextDropShadowCache.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Cache support -/////////////////////////////////////////////////////////////////////////////// - -hash_t ShadowText::hash() const { - uint32_t hash = JenkinsHashMix(0, glyphCount); - hash = JenkinsHashMix(hash, android::hash_type(radius)); - hash = JenkinsHashMix(hash, android::hash_type(textSize)); - hash = JenkinsHashMix(hash, android::hash_type(typeface)); - hash = JenkinsHashMix(hash, flags); - hash = JenkinsHashMix(hash, android::hash_type(italicStyle)); - hash = JenkinsHashMix(hash, android::hash_type(scaleX)); - if (glyphs) { - hash = JenkinsHashMixShorts(hash, reinterpret_cast<const uint16_t*>(glyphs), glyphCount); - } - if (positions) { - for (uint32_t i = 0; i < glyphCount * 2; i++) { - hash = JenkinsHashMix(hash, android::hash_type(positions[i])); - } - } - return JenkinsHashWhiten(hash); -} - -int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { - int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount); - if (deltaInt != 0) return deltaInt; - - deltaInt = lhs.flags - rhs.flags; - if (deltaInt != 0) return deltaInt; - - if (lhs.radius < rhs.radius) return -1; - if (lhs.radius > rhs.radius) return +1; - - if (lhs.typeface < rhs.typeface) return -1; - if (lhs.typeface > rhs.typeface) return +1; - - if (lhs.textSize < rhs.textSize) return -1; - if (lhs.textSize > rhs.textSize) return +1; - - if (lhs.italicStyle < rhs.italicStyle) return -1; - if (lhs.italicStyle > rhs.italicStyle) return +1; - - if (lhs.scaleX < rhs.scaleX) return -1; - if (lhs.scaleX > rhs.scaleX) return +1; - - if (lhs.glyphs != rhs.glyphs) { - if (!lhs.glyphs) return -1; - if (!rhs.glyphs) return +1; - - deltaInt = memcmp(lhs.glyphs, rhs.glyphs, lhs.glyphCount * sizeof(glyph_t)); - if (deltaInt != 0) return deltaInt; - } - - if (lhs.positions != rhs.positions) { - if (!lhs.positions) return -1; - if (!rhs.positions) return +1; - - return memcmp(lhs.positions, rhs.positions, lhs.glyphCount * sizeof(float) * 2); - } - - return 0; -} - -/////////////////////////////////////////////////////////////////////////////// -// Constructors/destructor -/////////////////////////////////////////////////////////////////////////////// - -TextDropShadowCache::TextDropShadowCache() - : TextDropShadowCache(DeviceInfo::multiplyByResolution(2)) {} - -TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize) - : mCache(LruCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity) - , mSize(0) - , mMaxSize(maxByteSize) { - mCache.setOnEntryRemovedListener(this); - mDebugEnabled = Properties::debugLevel & kDebugMoreCaches; -} - -TextDropShadowCache::~TextDropShadowCache() { - mCache.clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Size management -/////////////////////////////////////////////////////////////////////////////// - -uint32_t TextDropShadowCache::getSize() { - return mSize; -} - -uint32_t TextDropShadowCache::getMaxSize() { - return mMaxSize; -} - -/////////////////////////////////////////////////////////////////////////////// -// Callbacks -/////////////////////////////////////////////////////////////////////////////// - -void TextDropShadowCache::operator()(ShadowText&, ShadowTexture*& texture) { - if (texture) { - mSize -= texture->objectSize(); - - if (mDebugEnabled) { - ALOGD("Shadow texture deleted, size = %d", texture->bitmapSize); - } - - texture->deleteTexture(); - delete texture; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Caching -/////////////////////////////////////////////////////////////////////////////// - -void TextDropShadowCache::clear() { - mCache.clear(); -} - -ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, - float radius, const float* positions) { - ShadowText entry(paint, radius, numGlyphs, glyphs, positions); - ShadowTexture* texture = mCache.get(entry); - - if (!texture) { - SkPaint paintCopy(*paint); - paintCopy.setTextAlign(SkPaint::kLeft_Align); - FontRenderer::DropShadow shadow = - mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs, radius, positions); - - if (!shadow.image) { - return nullptr; - } - - Caches& caches = Caches::getInstance(); - - texture = new ShadowTexture(caches); - texture->left = shadow.penX; - texture->top = shadow.penY; - texture->generation = 0; - texture->blend = true; - - const uint32_t size = shadow.width * shadow.height; - - // Don't even try to cache a bitmap that's bigger than the cache - if (size < mMaxSize) { - while (mSize + size > mMaxSize) { - LOG_ALWAYS_FATAL_IF(!mCache.removeOldest(), - "Failed to remove oldest from cache. mSize = %" PRIu32 - ", mCache.size() = %zu", - mSize, mCache.size()); - } - } - - // Textures are Alpha8 - 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); - - if (size < mMaxSize) { - if (mDebugEnabled) { - ALOGD("Shadow texture created, size = %d", texture->bitmapSize); - } - - entry.copyTextLocally(); - - mSize += texture->objectSize(); - mCache.put(entry, texture); - } else { - texture->cleanup = true; - } - - // Cleanup shadow - free(shadow.image); - } - - return texture; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h deleted file mode 100644 index 86a012970f17..000000000000 --- a/libs/hwui/TextDropShadowCache.h +++ /dev/null @@ -1,164 +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_TEXT_DROP_SHADOW_CACHE_H -#define ANDROID_HWUI_TEXT_DROP_SHADOW_CACHE_H - -#include <GLES2/gl2.h> - -#include <SkPaint.h> - -#include <utils/LruCache.h> -#include <utils/String16.h> - -#include "Texture.h" -#include "font/Font.h" - -namespace android { -namespace uirenderer { - -class Caches; -class FontRenderer; - -struct ShadowText { - ShadowText() - : glyphCount(0) - , radius(0.0f) - , textSize(0.0f) - , typeface(nullptr) - , flags(0) - , italicStyle(0.0f) - , scaleX(0) - , glyphs(nullptr) - , positions(nullptr) {} - - // len is the number of bytes in text - ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const glyph_t* srcGlyphs, - const float* positions) - : glyphCount(glyphCount) - , radius(radius) - , textSize(paint->getTextSize()) - , typeface(paint->getTypeface()) - , flags(paint->isFakeBoldText() ? Font::kFakeBold : 0) - , italicStyle(paint->getTextSkewX()) - , scaleX(paint->getTextScaleX()) - , glyphs(srcGlyphs) - , positions(positions) {} - - ~ShadowText() {} - - hash_t hash() const; - - static int compare(const ShadowText& lhs, const ShadowText& rhs); - - bool operator==(const ShadowText& other) const { return compare(*this, other) == 0; } - - bool operator!=(const ShadowText& other) const { return compare(*this, other) != 0; } - - void copyTextLocally() { - str.setTo(reinterpret_cast<const char16_t*>(glyphs), glyphCount); - glyphs = reinterpret_cast<const glyph_t*>(str.string()); - if (positions != nullptr) { - positionsCopy.clear(); - positionsCopy.appendArray(positions, glyphCount * 2); - positions = positionsCopy.array(); - } - } - - uint32_t glyphCount; - float radius; - float textSize; - SkTypeface* typeface; - uint32_t flags; - float italicStyle; - float scaleX; - const glyph_t* glyphs; - const float* positions; - - // Not directly used to compute the cache key - String16 str; - Vector<float> positionsCopy; - -}; // struct ShadowText - -// Caching support - -inline int strictly_order_type(const ShadowText& lhs, const ShadowText& rhs) { - return ShadowText::compare(lhs, rhs) < 0; -} - -inline int compare_type(const ShadowText& lhs, const ShadowText& rhs) { - return ShadowText::compare(lhs, rhs); -} - -inline hash_t hash_type(const ShadowText& entry) { - return entry.hash(); -} - -/** - * Alpha texture used to represent a shadow. - */ -struct ShadowTexture : public Texture { - explicit ShadowTexture(Caches& caches) : Texture(caches) {} - - float left; - float top; -}; // struct ShadowTexture - -class TextDropShadowCache : public OnEntryRemoved<ShadowText, ShadowTexture*> { -public: - TextDropShadowCache(); - explicit TextDropShadowCache(uint32_t maxByteSize); - ~TextDropShadowCache(); - - /** - * Used as a callback when an entry is removed from the cache. - * Do not invoke directly. - */ - void operator()(ShadowText& text, ShadowTexture*& texture) override; - - ShadowTexture* get(const SkPaint* paint, const glyph_t* text, int numGlyphs, float radius, - const float* positions); - - /** - * Clears the cache. This causes all textures to be deleted. - */ - void clear(); - - void setFontRenderer(FontRenderer& fontRenderer) { mRenderer = &fontRenderer; } - - /** - * Returns the maximum size of the cache in bytes. - */ - uint32_t getMaxSize(); - /** - * Returns the current size of the cache in bytes. - */ - uint32_t getSize(); - -private: - LruCache<ShadowText, ShadowTexture*> mCache; - - uint32_t mSize; - const uint32_t mMaxSize; - FontRenderer* mRenderer = nullptr; - bool mDebugEnabled; -}; // class TextDropShadowCache - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_TEXT_DROP_SHADOW_CACHE_H diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp deleted file mode 100644 index 1e90eebe3bb8..000000000000 --- a/libs/hwui/Texture.cpp +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Texture.h" -#include "Caches.h" -#include "utils/GLUtils.h" -#include "utils/MathUtils.h" -#include "utils/TraceUtils.h" - -#include <utils/Log.h> - -#include <math/mat4.h> - -#include <SkCanvas.h> - -namespace android { -namespace uirenderer { - -// Number of bytes used by a texture in the given format -static int bytesPerPixel(GLint glFormat) { - switch (glFormat) { - // The wrapped-texture case, usually means a SurfaceTexture - case 0: - return 0; - case GL_LUMINANCE: - case GL_ALPHA: - return 1; - case GL_SRGB8: - case GL_RGB: - return 3; - case GL_SRGB8_ALPHA8: - case GL_RGBA: - return 4; - case GL_RGBA16F: - return 8; - default: - LOG_ALWAYS_FATAL("UNKNOWN FORMAT 0x%x", glFormat); - } -} - -void Texture::setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture, bool force) { - if (force || wrapS != mWrapS || wrapT != mWrapT) { - mWrapS = wrapS; - mWrapT = wrapT; - - if (bindTexture) { - mCaches.textureState().bindTexture(mTarget, mId); - } - - glTexParameteri(mTarget, GL_TEXTURE_WRAP_S, wrapS); - glTexParameteri(mTarget, GL_TEXTURE_WRAP_T, wrapT); - } -} - -void Texture::setFilterMinMag(GLenum min, GLenum mag, bool bindTexture, bool force) { - if (force || min != mMinFilter || mag != mMagFilter) { - mMinFilter = min; - mMagFilter = mag; - - if (bindTexture) { - mCaches.textureState().bindTexture(mTarget, mId); - } - - if (mipMap && min == GL_LINEAR) min = GL_LINEAR_MIPMAP_LINEAR; - - glTexParameteri(mTarget, GL_TEXTURE_MIN_FILTER, min); - glTexParameteri(mTarget, GL_TEXTURE_MAG_FILTER, mag); - } -} - -void Texture::deleteTexture() { - mCaches.textureState().deleteTexture(mId); - mId = 0; - mTarget = GL_NONE; - if (mEglImageHandle != EGL_NO_IMAGE_KHR) { - EGLDisplay eglDisplayHandle = eglGetCurrentDisplay(); - eglDestroyImageKHR(eglDisplayHandle, mEglImageHandle); - mEglImageHandle = EGL_NO_IMAGE_KHR; - } -} - -bool Texture::updateLayout(uint32_t width, uint32_t height, GLint internalFormat, GLint format, - GLenum target) { - if (mWidth == width && mHeight == height && mFormat == format && - mInternalFormat == internalFormat && mTarget == target) { - return false; - } - mWidth = width; - mHeight = height; - mFormat = format; - mInternalFormat = internalFormat; - mTarget = target; - notifySizeChanged(mWidth * mHeight * bytesPerPixel(internalFormat)); - return true; -} - -void Texture::resetCachedParams() { - mWrapS = GL_REPEAT; - mWrapT = GL_REPEAT; - mMinFilter = GL_NEAREST_MIPMAP_LINEAR; - mMagFilter = GL_LINEAR; -} - -void Texture::upload(GLint internalFormat, uint32_t width, uint32_t height, GLenum format, - GLenum type, const void* pixels) { - GL_CHECKPOINT(MODERATE); - - // We don't have color space information, we assume the data is gamma encoded - mIsLinear = false; - - bool needsAlloc = updateLayout(width, height, internalFormat, format, GL_TEXTURE_2D); - if (!mId) { - glGenTextures(1, &mId); - needsAlloc = true; - resetCachedParams(); - } - mCaches.textureState().bindTexture(GL_TEXTURE_2D, mId); - if (needsAlloc) { - glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, pixels); - } else if (pixels) { - glTexSubImage2D(GL_TEXTURE_2D, 0, internalFormat, mWidth, mHeight, 0, format, type, pixels); - } - GL_CHECKPOINT(MODERATE); -} - -void Texture::uploadHardwareBitmapToTexture(GraphicBuffer* buffer) { - EGLDisplay eglDisplayHandle = eglGetCurrentDisplay(); - if (mEglImageHandle != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(eglDisplayHandle, mEglImageHandle); - mEglImageHandle = EGL_NO_IMAGE_KHR; - } - mEglImageHandle = eglCreateImageKHR(eglDisplayHandle, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, - buffer->getNativeBuffer(), 0); - glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, mEglImageHandle); -} - -static void uploadToTexture(bool resize, GLint internalFormat, 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, internalFormat, 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, internalFormat, width, height, 0, format, type, temp); - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, temp); - } - - free(temp); - } -} - -void Texture::colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType, bool needSRGB, - GLint* outInternalFormat, GLint* outFormat, - GLint* outType) { - switch (colorType) { - case kAlpha_8_SkColorType: - *outFormat = GL_ALPHA; - *outInternalFormat = GL_ALPHA; - *outType = GL_UNSIGNED_BYTE; - break; - case kRGB_565_SkColorType: - if (needSRGB) { - // We would ideally use a GL_RGB/GL_SRGB8 texture but the - // intermediate Skia bitmap needs to be ARGB_8888 - *outFormat = GL_RGBA; - *outInternalFormat = caches.rgbaInternalFormat(); - *outType = GL_UNSIGNED_BYTE; - } else { - *outFormat = GL_RGB; - *outInternalFormat = GL_RGB; - *outType = GL_UNSIGNED_SHORT_5_6_5; - } - break; - // ARGB_4444 is upconverted to RGBA_8888 - case kARGB_4444_SkColorType: - case kN32_SkColorType: - *outFormat = GL_RGBA; - *outInternalFormat = caches.rgbaInternalFormat(needSRGB); - *outType = GL_UNSIGNED_BYTE; - break; - case kGray_8_SkColorType: - *outFormat = GL_LUMINANCE; - *outInternalFormat = GL_LUMINANCE; - *outType = GL_UNSIGNED_BYTE; - break; - case kRGBA_F16_SkColorType: - if (caches.extensions().getMajorGlVersion() >= 3) { - // This format is always linear - *outFormat = GL_RGBA; - *outInternalFormat = GL_RGBA16F; - *outType = GL_HALF_FLOAT; - } else { - *outFormat = GL_RGBA; - *outInternalFormat = caches.rgbaInternalFormat(true); - *outType = GL_UNSIGNED_BYTE; - } - break; - default: - LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType); - break; - } -} - -SkBitmap Texture::uploadToN32(const SkBitmap& bitmap, bool hasLinearBlending, - sk_sp<SkColorSpace> sRGB) { - SkBitmap rgbaBitmap; - rgbaBitmap.allocPixels(SkImageInfo::MakeN32(bitmap.width(), bitmap.height(), - bitmap.info().alphaType(), - hasLinearBlending ? sRGB : nullptr)); - rgbaBitmap.eraseColor(0); - - if (bitmap.colorType() == kRGBA_F16_SkColorType) { - // Drawing RGBA_F16 onto ARGB_8888 is not supported - bitmap.readPixels(rgbaBitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()), - rgbaBitmap.getPixels(), rgbaBitmap.rowBytes(), 0, 0); - } else { - SkCanvas canvas(rgbaBitmap); - canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr); - } - - return rgbaBitmap; -} - -bool Texture::hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBlending) { - return info.colorType() == kARGB_4444_SkColorType || - (info.colorType() == kRGB_565_SkColorType && hasLinearBlending && - info.colorSpace()->isSRGB()) || - (info.colorType() == kRGBA_F16_SkColorType && - Caches::getInstance().extensions().getMajorGlVersion() < 3); -} - -void Texture::upload(Bitmap& bitmap) { - ATRACE_FORMAT("Upload %ux%u Texture", bitmap.width(), bitmap.height()); - - // We could also enable mipmapping if both bitmap dimensions are powers - // of 2 but we'd have to deal with size changes. Let's keep this simple - const bool canMipMap = mCaches.extensions().hasNPot(); - - // If the texture had mipmap enabled but not anymore, - // force a glTexImage2D to discard the mipmap levels - bool needsAlloc = canMipMap && mipMap && !bitmap.hasHardwareMipMap(); - bool setDefaultParams = false; - - if (!mId) { - glGenTextures(1, &mId); - needsAlloc = true; - setDefaultParams = true; - } - - bool hasLinearBlending = mCaches.extensions().hasLinearBlending(); - bool needSRGB = transferFunctionCloseToSRGB(bitmap.info().colorSpace()); - - GLint internalFormat, format, type; - colorTypeToGlFormatAndType(mCaches, bitmap.colorType(), needSRGB && hasLinearBlending, - &internalFormat, &format, &type); - - // Some devices don't support GL_RGBA16F, so we need to compare the color type - // and internal GL format to decide what to do with 16 bit bitmaps - bool rgba16fNeedsConversion = - bitmap.colorType() == kRGBA_F16_SkColorType && internalFormat != GL_RGBA16F; - - // RGBA16F is always linear extended sRGB - if (internalFormat == GL_RGBA16F) { - mIsLinear = true; - } - - mConnector.reset(); - - // Alpha masks don't have color profiles - // If an RGBA16F bitmap needs conversion, we know the target will be sRGB - if (!mIsLinear && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) { - SkColorSpace* colorSpace = bitmap.info().colorSpace(); - // If the bitmap is sRGB we don't need conversion - if (colorSpace != nullptr && !colorSpace->isSRGB()) { - SkMatrix44 xyzMatrix(SkMatrix44::kUninitialized_Constructor); - if (!colorSpace->toXYZD50(&xyzMatrix)) { - ALOGW("Incompatible color space!"); - } else { - SkColorSpaceTransferFn fn; - if (!colorSpace->isNumericalTransferFn(&fn)) { - ALOGW("Incompatible color space, no numerical transfer function!"); - } else { - float data[16]; - xyzMatrix.asColMajorf(data); - - ColorSpace::TransferParameters p = {fn.fG, fn.fA, fn.fB, fn.fC, - fn.fD, fn.fE, fn.fF}; - ColorSpace src("Unnamed", mat4f((const float*)&data[0]).upperLeft(), p); - mConnector.reset(new ColorSpaceConnector(src, ColorSpace::sRGB())); - - // A non-sRGB color space might have a transfer function close enough to sRGB - // that we can save shader instructions by using an sRGB sampler - // This is only possible if we have hardware support for sRGB textures - if (needSRGB && internalFormat == GL_RGBA && mCaches.extensions().hasSRGB() && - !bitmap.isHardware()) { - internalFormat = GL_SRGB8_ALPHA8; - } - } - } - } - } - - GLenum target = bitmap.isHardware() ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D; - needsAlloc |= updateLayout(bitmap.width(), bitmap.height(), internalFormat, format, target); - - blend = !bitmap.isOpaque(); - mCaches.textureState().bindTexture(mTarget, mId); - - // TODO: Handle sRGB gray bitmaps - if (CC_UNLIKELY(hasUnsupportedColorType(bitmap.info(), hasLinearBlending))) { - SkBitmap skBitmap; - bitmap.getSkBitmap(&skBitmap); - sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB(); - SkBitmap rgbaBitmap = uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB)); - uploadToTexture(needsAlloc, internalFormat, format, type, rgbaBitmap.rowBytesAsPixels(), - rgbaBitmap.bytesPerPixel(), rgbaBitmap.width(), rgbaBitmap.height(), - rgbaBitmap.getPixels()); - } else if (bitmap.isHardware()) { - uploadHardwareBitmapToTexture(bitmap.graphicBuffer()); - } else { - uploadToTexture(needsAlloc, internalFormat, format, type, bitmap.rowBytesAsPixels(), - bitmap.info().bytesPerPixel(), bitmap.width(), bitmap.height(), - bitmap.pixels()); - } - - if (canMipMap) { - mipMap = bitmap.hasHardwareMipMap(); - if (mipMap) { - glGenerateMipmap(GL_TEXTURE_2D); - } - } - - if (setDefaultParams) { - setFilter(GL_NEAREST); - setWrap(GL_CLAMP_TO_EDGE); - } -} - -void Texture::wrap(GLuint id, uint32_t width, uint32_t height, GLint internalFormat, GLint format, - GLenum target) { - mId = id; - mWidth = width; - mHeight = height; - mFormat = format; - mInternalFormat = internalFormat; - mTarget = target; - mConnector.reset(); - // We're wrapping an existing texture, so don't double count this memory - notifySizeChanged(0); -} - -TransferFunctionType Texture::getTransferFunctionType() const { - if (mConnector.get() != nullptr && mInternalFormat != GL_SRGB8_ALPHA8) { - const ColorSpace::TransferParameters& p = mConnector->getSource().getTransferParameters(); - if (MathUtils::isZero(p.e) && MathUtils::isZero(p.f)) { - if (MathUtils::areEqual(p.a, 1.0f) && MathUtils::isZero(p.b) && - MathUtils::isZero(p.c) && MathUtils::isZero(p.d)) { - if (MathUtils::areEqual(p.g, 1.0f)) { - return TransferFunctionType::None; - } - return TransferFunctionType::Gamma; - } - return TransferFunctionType::Limited; - } - return TransferFunctionType::Full; - } - return TransferFunctionType::None; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h deleted file mode 100644 index 5b7e4e261f30..000000000000 --- a/libs/hwui/Texture.h +++ /dev/null @@ -1,228 +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_TEXTURE_H -#define ANDROID_HWUI_TEXTURE_H - -#include "GpuMemoryTracker.h" -#include "hwui/Bitmap.h" -#include "utils/Color.h" - -#include <memory> - -#include <math/mat3.h> - -#include <ui/ColorSpace.h> - -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <GLES2/gl2.h> -#include <GLES3/gl3.h> -#include <SkBitmap.h> - -namespace android { - -class GraphicBuffer; - -namespace uirenderer { - -class Caches; -class UvMapper; -class Layer; - -/** - * Represents an OpenGL texture. - */ -class Texture : public GpuMemoryTracker { -public: - static SkBitmap uploadToN32(const SkBitmap& bitmap, bool hasLinearBlending, - sk_sp<SkColorSpace> sRGB); - static bool hasUnsupportedColorType(const SkImageInfo& info, bool hasLinearBlending); - static void colorTypeToGlFormatAndType(const Caches& caches, SkColorType colorType, - bool needSRGB, GLint* outInternalFormat, - GLint* outFormat, GLint* outType); - - explicit Texture(Caches& caches) : GpuMemoryTracker(GpuObjectType::Texture), mCaches(caches) {} - - virtual ~Texture() {} - - inline void setWrap(GLenum wrap, bool bindTexture = false, bool force = false) { - setWrapST(wrap, wrap, bindTexture, force); - } - - virtual void setWrapST(GLenum wrapS, GLenum wrapT, bool bindTexture = false, - bool force = false); - - inline void setFilter(GLenum filter, bool bindTexture = false, bool force = false) { - setFilterMinMag(filter, filter, bindTexture, force); - } - - virtual void setFilterMinMag(GLenum min, GLenum mag, bool bindTexture = false, - bool force = false); - - /** - * Convenience method to call glDeleteTextures() on this texture's id. - */ - void deleteTexture(); - - /** - * 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. - */ - void resize(uint32_t width, uint32_t height, GLint internalFormat, GLint format) { - upload(internalFormat, width, height, format, - internalFormat == GL_RGBA16F ? GL_HALF_FLOAT : GL_UNSIGNED_BYTE, nullptr); - } - - /** - * Updates this Texture with the contents of the provided Bitmap, - * 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 Bitmap. - */ - void upload(Bitmap& source); - - /** - * Basically glTexImage2D/glTexSubImage2D. - */ - 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 internalFormat, GLint format, - GLenum target); - - GLuint id() const { return mId; } - - uint32_t width() const { return mWidth; } - - uint32_t height() const { return mHeight; } - - GLint format() const { return mFormat; } - - GLint internalFormat() const { return mInternalFormat; } - - GLenum target() const { return mTarget; } - - /** - * Returns nullptr if this texture does not require color space conversion - * to sRGB, or a valid pointer to a ColorSpaceConnector if a conversion - * is required. - */ - constexpr const ColorSpaceConnector* getColorSpaceConnector() const { return mConnector.get(); } - - constexpr bool hasColorSpaceConversion() const { return mConnector.get() != nullptr; } - - TransferFunctionType getTransferFunctionType() const; - - /** - * Returns true if this texture uses a linear encoding format. - */ - constexpr bool isLinear() const { return mIsLinear; } - - /** - * Generation of the backing bitmap, - */ - uint32_t generation = 0; - /** - * Indicates whether the texture requires blending. - */ - bool blend = false; - /** - * Indicates whether this texture should be cleaned up after use. - */ - bool cleanup = false; - /** - * Optional, size of the original bitmap. - */ - uint32_t bitmapSize = 0; - /** - * Indicates whether this texture will use trilinear filtering. - */ - bool mipMap = false; - - /** - * Optional, pointer to a texture coordinates mapper. - */ - const UvMapper* uvMapper = nullptr; - - /** - * Whether or not the Texture is marked in use and thus not evictable for - * the current frame. This is reset at the start of a new frame. - */ - void* isInUse = nullptr; - -private: - // TODO: Temporarily grant private access to GlLayer, remove once - // GlLayer can be de-tangled from being a dual-purpose render target - // and external texture wrapper - friend class GlLayer; - - // Returns true if the texture layout (size, format, etc.) changed, false if it was the same - bool updateLayout(uint32_t width, uint32_t height, GLint internalFormat, GLint format, - GLenum target); - void uploadHardwareBitmapToTexture(GraphicBuffer* buffer); - void resetCachedParams(); - - GLuint mId = 0; - uint32_t mWidth = 0; - uint32_t mHeight = 0; - GLint mFormat = 0; - GLint mInternalFormat = 0; - GLenum mTarget = GL_NONE; - EGLImageKHR mEglImageHandle = EGL_NO_IMAGE_KHR; - - /* See GLES spec section 3.8.14 - * "In the initial state, the value assigned to TEXTURE_MIN_FILTER is - * NEAREST_MIPMAP_LINEAR and the value for TEXTURE_MAG_FILTER is LINEAR. - * s, t, and r wrap modes are all set to REPEAT." - */ - GLenum mWrapS = GL_REPEAT; - GLenum mWrapT = GL_REPEAT; - GLenum mMinFilter = GL_NEAREST_MIPMAP_LINEAR; - GLenum mMagFilter = GL_LINEAR; - - // Indicates whether the content of the texture is in linear space - bool mIsLinear = false; - - Caches& mCaches; - - std::unique_ptr<ColorSpaceConnector> mConnector; -}; // struct Texture - -class AutoTexture { -public: - explicit AutoTexture(Texture* texture) : texture(texture) {} - ~AutoTexture() { - if (texture && texture->cleanup) { - texture->deleteTexture(); - delete texture; - } - } - - Texture* const texture; -}; // class AutoTexture - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_TEXTURE_H diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp deleted file mode 100644 index 9d365fb29ebe..000000000000 --- a/libs/hwui/TextureCache.cpp +++ /dev/null @@ -1,227 +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 <GLES2/gl2.h> - -#include <utils/Mutex.h> - -#include "Caches.h" -#include "DeviceInfo.h" -#include "Properties.h" -#include "Texture.h" -#include "TextureCache.h" -#include "hwui/Bitmap.h" -#include "utils/TraceUtils.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Constructors/destructor -/////////////////////////////////////////////////////////////////////////////// - -TextureCache::TextureCache() - : mCache(LruCache<uint32_t, Texture*>::kUnlimitedCapacity) - , mSize(0) - , mMaxSize(DeviceInfo::multiplyByResolution(4 * 6)) // 6 screen-sized RGBA_8888 bitmaps - , mFlushRate(.4f) { - mCache.setOnEntryRemovedListener(this); - mMaxTextureSize = DeviceInfo::get()->maxTextureSize(); - mDebugEnabled = Properties::debugLevel & kDebugCaches; -} - -TextureCache::~TextureCache() { - this->clear(); -} - -/////////////////////////////////////////////////////////////////////////////// -// Size management -/////////////////////////////////////////////////////////////////////////////// - -uint32_t TextureCache::getSize() { - return mSize; -} - -uint32_t TextureCache::getMaxSize() { - return mMaxSize; -} - -/////////////////////////////////////////////////////////////////////////////// -// Callbacks -/////////////////////////////////////////////////////////////////////////////// - -void TextureCache::operator()(uint32_t&, Texture*& texture) { - // This will be called already locked - if (texture) { - mSize -= texture->bitmapSize; - TEXTURE_LOGD("TextureCache::callback: name, removed size, mSize = %d, %d, %d", texture->id, - texture->bitmapSize, mSize); - if (mDebugEnabled) { - ALOGD("Texture deleted, size = %d", texture->bitmapSize); - } - texture->deleteTexture(); - delete texture; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Caching -/////////////////////////////////////////////////////////////////////////////// - -void TextureCache::resetMarkInUse(void* ownerToken) { - LruCache<uint32_t, Texture*>::Iterator iter(mCache); - while (iter.next()) { - if (iter.value()->isInUse == ownerToken) { - iter.value()->isInUse = nullptr; - } - } -} - -bool TextureCache::canMakeTextureFromBitmap(Bitmap* bitmap) { - if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) { - ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)", bitmap->width(), - bitmap->height(), mMaxTextureSize, mMaxTextureSize); - return false; - } - return true; -} - -Texture* TextureCache::createTexture(Bitmap* bitmap) { - Texture* texture = new Texture(Caches::getInstance()); - texture->bitmapSize = bitmap->rowBytes() * bitmap->height(); - texture->generation = bitmap->getGenerationID(); - texture->upload(*bitmap); - return texture; -} - -// Returns a prepared Texture* that either is already in the cache or can fit -// in the cache (and is thus added to the cache) -Texture* TextureCache::getCachedTexture(Bitmap* bitmap) { - if (bitmap->isHardware()) { - auto textureIterator = mHardwareTextures.find(bitmap->getStableID()); - if (textureIterator == mHardwareTextures.end()) { - Texture* texture = createTexture(bitmap); - mHardwareTextures.insert( - std::make_pair(bitmap->getStableID(), std::unique_ptr<Texture>(texture))); - if (mDebugEnabled) { - ALOGD("Texture created for hw bitmap size = %d", texture->bitmapSize); - } - return texture; - } - return textureIterator->second.get(); - } - - Texture* texture = mCache.get(bitmap->getStableID()); - - if (!texture) { - if (!canMakeTextureFromBitmap(bitmap)) { - return nullptr; - } - - const uint32_t size = bitmap->rowBytes() * bitmap->height(); - bool canCache = size < mMaxSize; - // Don't even try to cache a bitmap that's bigger than the cache - while (canCache && mSize + size > mMaxSize) { - Texture* oldest = mCache.peekOldestValue(); - if (oldest && !oldest->isInUse) { - mCache.removeOldest(); - } else { - canCache = false; - } - } - - if (canCache) { - texture = createTexture(bitmap); - mSize += size; - TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d", - bitmap, texture->id, size, mSize); - if (mDebugEnabled) { - ALOGD("Texture created, size = %d", size); - } - mCache.put(bitmap->getStableID(), texture); - } - } 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 - texture->upload(*bitmap); - texture->generation = bitmap->getGenerationID(); - } - - return texture; -} - -bool TextureCache::prefetchAndMarkInUse(void* ownerToken, Bitmap* bitmap) { - Texture* texture = getCachedTexture(bitmap); - if (texture) { - texture->isInUse = ownerToken; - } - return texture; -} - -bool TextureCache::prefetch(Bitmap* bitmap) { - return getCachedTexture(bitmap); -} - -Texture* TextureCache::get(Bitmap* bitmap) { - Texture* texture = getCachedTexture(bitmap); - - if (!texture) { - if (!canMakeTextureFromBitmap(bitmap)) { - return nullptr; - } - texture = createTexture(bitmap); - texture->cleanup = true; - } - - return texture; -} - -bool TextureCache::destroyTexture(uint32_t pixelRefStableID) { - auto hardwareIter = mHardwareTextures.find(pixelRefStableID); - if (hardwareIter != mHardwareTextures.end()) { - hardwareIter->second->deleteTexture(); - mHardwareTextures.erase(hardwareIter); - return true; - } - return mCache.remove(pixelRefStableID); -} - -void TextureCache::clear() { - mCache.clear(); - for (auto& iter : mHardwareTextures) { - iter.second->deleteTexture(); - } - mHardwareTextures.clear(); - TEXTURE_LOGD("TextureCache:clear(), mSize = %d", mSize); -} - -void TextureCache::flush() { - if (mFlushRate >= 1.0f || mCache.size() == 0) return; - if (mFlushRate <= 0.0f) { - clear(); - return; - } - - uint32_t targetSize = uint32_t(mSize * mFlushRate); - TEXTURE_LOGD("TextureCache::flush: target size: %d", targetSize); - - while (mSize > targetSize) { - mCache.removeOldest(); - } -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h deleted file mode 100644 index 19e7bea99669..000000000000 --- a/libs/hwui/TextureCache.h +++ /dev/null @@ -1,144 +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_TEXTURE_CACHE_H -#define ANDROID_HWUI_TEXTURE_CACHE_H - -#include <SkBitmap.h> - -#include <cutils/compiler.h> - -#include <utils/LruCache.h> -#include <utils/Mutex.h> - -#include "Debug.h" - -#include <unordered_map> -#include <vector> - -namespace android { - -class Bitmap; - -namespace uirenderer { - -class Texture; - -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -// Debug -#if DEBUG_TEXTURES -#define TEXTURE_LOGD(...) ALOGD(__VA_ARGS__) -#else -#define TEXTURE_LOGD(...) -#endif - -/////////////////////////////////////////////////////////////////////////////// -// Classes -/////////////////////////////////////////////////////////////////////////////// - -/** - * A simple LRU texture cache. The cache has a maximum size expressed in bytes. - * Any texture added to the cache causing the cache to grow beyond the maximum - * allowed size will also cause the oldest texture to be kicked out. - */ -class TextureCache : public OnEntryRemoved<uint32_t, Texture*> { -public: - TextureCache(); - ~TextureCache(); - - /** - * Used as a callback when an entry is removed from the cache. - * Do not invoke directly. - */ - void operator()(uint32_t&, Texture*& texture) override; - - /** - * Resets all Textures to not be marked as in use - */ - void resetMarkInUse(void* ownerToken); - - /** - * Attempts to precache the SkBitmap. Returns true if a Texture was successfully - * acquired for the bitmap, false otherwise. If a Texture was acquired it is - * marked as in use. - */ - bool prefetchAndMarkInUse(void* ownerToken, Bitmap* bitmap); - - /** - * Attempts to precache the SkBitmap. Returns true if a Texture was successfully - * acquired for the bitmap, false otherwise. Does not mark the Texture - * as in use and won't update currently in-use Textures. - */ - bool prefetch(Bitmap* bitmap); - - /** - * Returns the texture associated with the specified bitmap from within the cache. - * If the texture cannot be found in the cache, a new texture is generated. - */ - Texture* get(Bitmap* bitmap); - - /** - * Removes the texture associated with the specified pixelRef. Must be called from RenderThread - * Returns true if a texture was destroyed, false if no texture with that id was found - */ - bool destroyTexture(uint32_t pixelRefStableID); - - /** - * Clears the cache. This causes all textures to be deleted. - */ - void clear(); - - /** - * Returns the maximum size of the cache in bytes. - */ - uint32_t getMaxSize(); - /** - * Returns the current size of the cache in bytes. - */ - uint32_t getSize(); - - /** - * Partially flushes the cache. The amount of memory freed by a flush - * is defined by the flush rate. - */ - void flush(); - -private: - bool canMakeTextureFromBitmap(Bitmap* bitmap); - - Texture* getCachedTexture(Bitmap* bitmap); - Texture* createTexture(Bitmap* bitmap); - - LruCache<uint32_t, Texture*> mCache; - - uint32_t mSize; - const uint32_t mMaxSize; - GLint mMaxTextureSize; - - const float mFlushRate; - - bool mDebugEnabled; - - std::unordered_map<uint32_t, std::unique_ptr<Texture>> mHardwareTextures; -}; // class TextureCache - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_TEXTURE_CACHE_H diff --git a/libs/hwui/TreeInfo.cpp b/libs/hwui/TreeInfo.cpp new file mode 100644 index 000000000000..808a12a311e2 --- /dev/null +++ b/libs/hwui/TreeInfo.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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 "TreeInfo.h" + +#include "renderthread/CanvasContext.h" + +namespace android::uirenderer { + +TreeInfo::TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext) + : mode(mode) + , prepareTextures(mode == MODE_FULL) + , canvasContext(canvasContext) + , disableForceDark(canvasContext.useForceDark() ? 0 : 1) {} + +} // namespace android::uirenderer diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h index f2766d6a5b6e..a0d960527ca6 100644 --- a/libs/hwui/TreeInfo.h +++ b/libs/hwui/TreeInfo.h @@ -16,6 +16,7 @@ #pragma once +#include "Properties.h" #include "utils/Macros.h" #include <utils/Timers.h> @@ -39,7 +40,7 @@ public: virtual void onError(const std::string& message) = 0; protected: - virtual ~ErrorHandler() {} + virtual ~ErrorHandler() = default; }; class TreeObserver { @@ -51,7 +52,7 @@ public: virtual void onMaybeRemovedFromTree(RenderNode* node) = 0; protected: - virtual ~TreeObserver() {} + virtual ~TreeObserver() = default; }; // This would be a struct, but we want to PREVENT_COPY_AND_ASSIGN @@ -70,8 +71,7 @@ public: MODE_RT_ONLY, }; - TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext) - : mode(mode), prepareTextures(mode == MODE_FULL), canvasContext(canvasContext) {} + TreeInfo(TraversalMode mode, renderthread::CanvasContext& canvasContext); TraversalMode mode; // TODO: Remove this? Currently this is used to signal to stop preparing @@ -93,6 +93,8 @@ public: bool updateWindowPositions = false; + int disableForceDark; + struct Out { bool hasFunctors = false; // This is only updated if evaluateAnimations is true diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp index 18358e25fd5b..6cf04bf5f811 100644 --- a/libs/hwui/VectorDrawable.cpp +++ b/libs/hwui/VectorDrawable.cpp @@ -458,30 +458,22 @@ void Tree::drawStaging(Canvas* outCanvas) { mStagingCache.dirty = false; } - SkPaint tmpPaint; - SkPaint* paint = updatePaint(&tmpPaint, &mStagingProperties); + SkPaint paint; + getPaintFor(&paint, mStagingProperties); outCanvas->drawBitmap(*mStagingCache.bitmap, 0, 0, mStagingCache.bitmap->width(), mStagingCache.bitmap->height(), mStagingProperties.getBounds().left(), mStagingProperties.getBounds().top(), mStagingProperties.getBounds().right(), - mStagingProperties.getBounds().bottom(), paint); -} - -SkPaint* Tree::getPaint() { - return updatePaint(&mPaint, &mProperties); + mStagingProperties.getBounds().bottom(), &paint); } -// Update the given paint with alpha and color filter. Return nullptr if no color filter is -// specified and root alpha is 1. Otherwise, return updated paint. -SkPaint* Tree::updatePaint(SkPaint* outPaint, TreeProperties* prop) { - if (prop->getRootAlpha() == 1.0f && prop->getColorFilter() == nullptr) { - return nullptr; - } else { - outPaint->setColorFilter(sk_ref_sp(prop->getColorFilter())); - outPaint->setFilterQuality(kLow_SkFilterQuality); - outPaint->setAlpha(prop->getRootAlpha() * 255); - return outPaint; +void Tree::getPaintFor(SkPaint* outPaint, const TreeProperties &prop) const { + // HWUI always draws VD with bilinear filtering. + outPaint->setFilterQuality(kLow_SkFilterQuality); + if (prop.getColorFilter() != nullptr) { + outPaint->setColorFilter(sk_ref_sp(prop.getColorFilter())); } + outPaint->setAlpha(prop.getRootAlpha() * 255); } Bitmap& Tree::getBitmapUpdateIfDirty() { @@ -554,12 +546,16 @@ void Tree::Cache::clear() { mAtlasKey = INVALID_ATLAS_KEY; } -void Tree::draw(SkCanvas* canvas, const SkRect& bounds) { +void Tree::draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& inPaint) { + // Update the paint for any animatable properties + SkPaint paint = inPaint; + paint.setAlpha(mProperties.getRootAlpha() * 255); + SkRect src; sk_sp<SkSurface> vdSurface = mCache.getSurface(&src); if (vdSurface) { - canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, - bounds, getPaint(), SkCanvas::kFast_SrcRectConstraint); + canvas->drawImageRect(vdSurface->makeImageSnapshot().get(), src, bounds, &paint, + SkCanvas::kFast_SrcRectConstraint); } else { // Handle the case when VectorDrawableAtlas has been destroyed, because of memory pressure. // We render the VD into a temporary standalone buffer and mark the frame as dirty. Next @@ -570,8 +566,8 @@ void Tree::draw(SkCanvas* canvas, const SkRect& bounds) { int scaledWidth = SkScalarCeilToInt(mProperties.getScaledWidth()); int scaledHeight = SkScalarCeilToInt(mProperties.getScaledHeight()); - canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), - bounds, getPaint(), SkCanvas::kFast_SrcRectConstraint); + canvas->drawBitmapRect(skiaBitmap, SkRect::MakeWH(scaledWidth, scaledHeight), bounds, + &paint, SkCanvas::kFast_SrcRectConstraint); mCache.clear(); markDirty(); } @@ -621,6 +617,80 @@ void Tree::onPropertyChanged(TreeProperties* prop) { } } +class MinMaxAverage { +public: + void add(float sample) { + if (mCount == 0) { + mMin = sample; + mMax = sample; + } else { + mMin = std::min(mMin, sample); + mMax = std::max(mMax, sample); + } + mTotal += sample; + mCount++; + } + + float average() { return mTotal / mCount; } + + float min() { return mMin; } + + float max() { return mMax; } + + float delta() { return mMax - mMin; } + +private: + float mMin = 0.0f; + float mMax = 0.0f; + float mTotal = 0.0f; + int mCount = 0; +}; + +BitmapPalette Tree::computePalette() { + // TODO Cache this and share the code with Bitmap.cpp + + ATRACE_CALL(); + + // TODO: This calculation of converting to HSV & tracking min/max is probably overkill + // Experiment with something simpler since we just want to figure out if it's "color-ful" + // and then the average perceptual lightness. + + MinMaxAverage hue, saturation, value; + int sampledCount = 0; + + // Sample a grid of 100 pixels to get an overall estimation of the colors in play + mRootNode->forEachFillColor([&](SkColor color) { + if (SkColorGetA(color) < 75) { + return; + } + sampledCount++; + float hsv[3]; + SkColorToHSV(color, hsv); + hue.add(hsv[0]); + saturation.add(hsv[1]); + value.add(hsv[2]); + }); + + if (sampledCount == 0) { + ALOGV("VectorDrawable is mostly translucent"); + return BitmapPalette::Unknown; + } + + ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = " + "%f]; value [min = %f, max = %f, avg = %f]", + sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(), + saturation.average(), value.min(), value.max(), value.average()); + + if (hue.delta() <= 20 && saturation.delta() <= .1f) { + if (value.average() >= .5f) { + return BitmapPalette::Light; + } else { + return BitmapPalette::Dark; + } + } + return BitmapPalette::Unknown; +} + }; // namespace VectorDrawable }; // namespace uirenderer diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h index da52a9503377..9c0bb161380c 100644 --- a/libs/hwui/VectorDrawable.h +++ b/libs/hwui/VectorDrawable.h @@ -59,12 +59,6 @@ namespace VectorDrawable { onPropertyChanged(); \ retVal; \ }) -#define UPDATE_SKPROP(field, value) \ - ({ \ - bool retVal = ((field) != (value)); \ - if ((field) != (value)) SkRefCnt_SafeAssign((field), (value)); \ - retVal; \ - }) /* A VectorDrawable is composed of a tree of nodes. * Each node can be a group node, or a path. @@ -126,6 +120,8 @@ public: virtual void syncProperties() = 0; virtual void setAntiAlias(bool aa) = 0; + virtual void forEachFillColor(const std::function<void(SkColor)>& func) const { } + protected: std::string mName; PropertyChangedListener* mPropertyChangedListener = nullptr; @@ -223,29 +219,28 @@ public: int fillType = 0; /* non-zero or kWinding_FillType in Skia */ }; explicit FullPathProperties(Node* mNode) : Properties(mNode), mTrimDirty(false) {} - ~FullPathProperties() { - SkSafeUnref(fillGradient); - SkSafeUnref(strokeGradient); - } + ~FullPathProperties() {} void syncProperties(const FullPathProperties& prop) { mPrimitiveFields = prop.mPrimitiveFields; mTrimDirty = true; - UPDATE_SKPROP(fillGradient, prop.fillGradient); - UPDATE_SKPROP(strokeGradient, prop.strokeGradient); + fillGradient = prop.fillGradient; + strokeGradient = prop.strokeGradient; onPropertyChanged(); } void setFillGradient(SkShader* gradient) { - if (UPDATE_SKPROP(fillGradient, gradient)) { + if (fillGradient.get() != gradient) { + fillGradient = sk_ref_sp(gradient); onPropertyChanged(); } } void setStrokeGradient(SkShader* gradient) { - if (UPDATE_SKPROP(strokeGradient, gradient)) { + if (strokeGradient.get() != gradient) { + strokeGradient = sk_ref_sp(gradient); onPropertyChanged(); } } - SkShader* getFillGradient() const { return fillGradient; } - SkShader* getStrokeGradient() const { return strokeGradient; } + SkShader* getFillGradient() const { return fillGradient.get(); } + SkShader* getStrokeGradient() const { return strokeGradient.get(); } float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; } void setStrokeWidth(float strokeWidth) { VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth); @@ -325,8 +320,8 @@ public: count, }; PrimitiveFields mPrimitiveFields; - SkShader* fillGradient = nullptr; - SkShader* strokeGradient = nullptr; + sk_sp<SkShader> fillGradient; + sk_sp<SkShader> strokeGradient; }; // Called from UI thread @@ -356,6 +351,9 @@ public: } } virtual void setAntiAlias(bool aa) { mAntiAlias = aa; } + void forEachFillColor(const std::function<void(SkColor)>& func) const override { + func(mStagingProperties.getFillColor()); + } protected: const SkPath& getUpdatedPath(bool useStagingData, SkPath* tempStagingPath) override; @@ -487,6 +485,12 @@ public: } } + void forEachFillColor(const std::function<void(SkColor)>& func) const override { + for (auto& child : mChildren) { + child->forEachFillColor(func); + } + } + private: GroupProperties mProperties = GroupProperties(this); GroupProperties mStagingProperties = GroupProperties(this); @@ -502,8 +506,8 @@ public: // Copy properties from the tree and use the give node as the root node Tree(const Tree* copy, Group* rootNode) : Tree(rootNode) { - mStagingProperties.syncAnimatableProperties(*copy->stagingProperties()); - mStagingProperties.syncNonAnimatableProperties(*copy->stagingProperties()); + mStagingProperties.syncAnimatableProperties(copy->stagingProperties()); + mStagingProperties.syncNonAnimatableProperties(copy->stagingProperties()); } // Draws the VD onto a bitmap cache, then the bitmap cache will be rendered onto the input // canvas. Returns the number of pixels needed for the bitmap cache. @@ -513,7 +517,6 @@ public: Bitmap& getBitmapUpdateIfDirty(); void setAllowCaching(bool allowCaching) { mAllowCaching = allowCaching; } - SkPaint* getPaint(); void syncProperties() { if (mStagingProperties.mNonAnimatablePropertiesDirty) { mCache.dirty |= (mProperties.mNonAnimatableProperties.viewportWidth != @@ -550,8 +553,7 @@ public: SkRect bounds; int scaledWidth = 0; int scaledHeight = 0; - SkColorFilter* colorFilter = nullptr; - ~NonAnimatableProperties() { SkSafeUnref(colorFilter); } + sk_sp<SkColorFilter> colorFilter; } mNonAnimatableProperties; bool mNonAnimatablePropertiesDirty = true; @@ -561,8 +563,7 @@ public: void syncNonAnimatableProperties(const TreeProperties& prop) { // Copy over the data that can only be changed in UI thread if (mNonAnimatableProperties.colorFilter != prop.mNonAnimatableProperties.colorFilter) { - SkRefCnt_SafeAssign(mNonAnimatableProperties.colorFilter, - prop.mNonAnimatableProperties.colorFilter); + mNonAnimatableProperties.colorFilter = prop.mNonAnimatableProperties.colorFilter; } mNonAnimatableProperties = prop.mNonAnimatableProperties; } @@ -599,12 +600,13 @@ public: } } void setColorFilter(SkColorFilter* filter) { - if (UPDATE_SKPROP(mNonAnimatableProperties.colorFilter, filter)) { + if (mNonAnimatableProperties.colorFilter.get() != filter) { + mNonAnimatableProperties.colorFilter = sk_ref_sp(filter); mNonAnimatablePropertiesDirty = true; mTree->onPropertyChanged(this); } } - SkColorFilter* getColorFilter() const { return mNonAnimatableProperties.colorFilter; } + SkColorFilter* getColorFilter() const { return mNonAnimatableProperties.colorFilter.get(); } float getViewportWidth() const { return mNonAnimatableProperties.viewportWidth; } float getViewportHeight() const { return mNonAnimatableProperties.viewportHeight; } @@ -626,7 +628,7 @@ public: }; void onPropertyChanged(TreeProperties* prop); TreeProperties* mutateStagingProperties() { return &mStagingProperties; } - const TreeProperties* stagingProperties() const { return &mStagingProperties; } + const TreeProperties& stagingProperties() const { return mStagingProperties; } // This should only be called from animations on RT TreeProperties* mutateProperties() { return &mProperties; } @@ -644,7 +646,10 @@ public: * Draws VD cache into a canvas. This should always be called from RT and it works with Skia * pipelines only. */ - void draw(SkCanvas* canvas, const SkRect& bounds); + void draw(SkCanvas* canvas, const SkRect& bounds, const SkPaint& paint); + + void getPaintFor(SkPaint* outPaint, const TreeProperties &props) const; + BitmapPalette computePalette(); /** * Draws VD into a GPU backed surface. @@ -688,7 +693,6 @@ private: skiapipeline::AtlasKey mAtlasKey = INVALID_ATLAS_KEY; }; - SkPaint* updatePaint(SkPaint* outPaint, TreeProperties* prop); bool allocateBitmapIfNeeded(Cache& cache, int width, int height); bool canReuseBitmap(Bitmap*, int width, int height); void updateBitmapCache(Bitmap& outCache, bool useStagingData); @@ -704,8 +708,6 @@ private: TreeProperties mProperties = TreeProperties(this); TreeProperties mStagingProperties = TreeProperties(this); - SkPaint mPaint; - Cache mStagingCache; Cache mCache; @@ -718,6 +720,8 @@ private: } // namespace VectorDrawable typedef VectorDrawable::Path::Data PathData; +typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; + } // namespace uirenderer } // namespace android diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h index 186d0a88a33e..f0912777e3d8 100644 --- a/libs/hwui/Vertex.h +++ b/libs/hwui/Vertex.h @@ -19,7 +19,6 @@ #include "Vector.h" -#include "FloatColor.h" #include "utils/Macros.h" namespace android { @@ -74,46 +73,6 @@ struct TextureVertex { REQUIRE_COMPATIBLE_LAYOUT(TextureVertex); -/** - * Simple structure to describe a vertex with a position, texture UV and an - * sRGB color with alpha. The color is stored pre-multiplied in linear space. - */ -struct ColorTextureVertex { - float x, y; - float u, v; - float r, g, b, a; // pre-multiplied linear - - static inline void set(ColorTextureVertex* vertex, float x, float y, float u, float v, - uint32_t color) { - FloatColor c; - c.set(color); - *vertex = {x, y, u, v, c.r, c.g, c.b, c.a}; - } -}; // struct ColorTextureVertex - -REQUIRE_COMPATIBLE_LAYOUT(ColorTextureVertex); - -/** - * Simple structure to describe a vertex with a position and an alpha value. - */ -struct AlphaVertex { - float x, y; - float alpha; - - static inline void set(AlphaVertex* vertex, float x, float y, float alpha) { - *vertex = {x, y, alpha}; - } - - static inline void copyWithOffset(AlphaVertex* vertex, const AlphaVertex& src, float x, - float y) { - AlphaVertex::set(vertex, src.x + x, src.y + y, src.alpha); - } - - static inline void setColor(AlphaVertex* vertex, float alpha) { vertex[0].alpha = alpha; } -}; // struct AlphaVertex - -REQUIRE_COMPATIBLE_LAYOUT(AlphaVertex); - }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/VkLayer.cpp b/libs/hwui/VkLayer.cpp deleted file mode 100644 index 30fba7ae7d9b..000000000000 --- a/libs/hwui/VkLayer.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2017 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 "VkLayer.h" - -#include "renderstate/RenderState.h" - -#include <SkCanvas.h> -#include <SkSurface.h> - -namespace android { -namespace uirenderer { - -void VkLayer::updateTexture() { - sk_sp<SkSurface> surface; - SkImageInfo info = SkImageInfo::MakeS32(mWidth, mHeight, kPremul_SkAlphaType); - surface = SkSurface::MakeRenderTarget(mRenderState.getGrContext(), SkBudgeted::kNo, info); - surface->getCanvas()->clear(SK_ColorBLUE); - mImage = surface->makeImageSnapshot(); -} - -void VkLayer::onVkContextDestroyed() { - mImage = nullptr; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/VkLayer.h b/libs/hwui/VkLayer.h deleted file mode 100644 index e9664d04b7a5..000000000000 --- a/libs/hwui/VkLayer.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2017 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 "Layer.h" - -#include <SkImage.h> - -namespace android { -namespace uirenderer { -/** - * A layer has dimensions and is backed by a VkImage. - */ -class VkLayer : public Layer { -public: - VkLayer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight, - sk_sp<SkColorFilter> colorFilter, int alpha, SkBlendMode mode, bool blend) - : Layer(renderState, Api::Vulkan, colorFilter, alpha, mode) - , mWidth(layerWidth) - , mHeight(layerHeight) - , mBlend(blend) {} - - virtual ~VkLayer() {} - - uint32_t getWidth() const override { return mWidth; } - - uint32_t getHeight() const override { return mHeight; } - - void setSize(uint32_t width, uint32_t height) override { - mWidth = width; - mHeight = height; - } - - void setBlend(bool blend) override { mBlend = blend; } - - bool isBlend() const override { return mBlend; } - - sk_sp<SkImage> getImage() { return mImage; } - - void updateTexture(); - - // If we've destroyed the vulkan context (VkInstance, VkDevice, etc.), we must make sure to - // destroy any VkImages that were made with that context. - void onVkContextDestroyed(); - -private: - int mWidth; - int mHeight; - bool mBlend; - - sk_sp<SkImage> mImage; - -}; // struct VkLayer - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/debug/wrap_gles.h b/libs/hwui/debug/wrap_gles.h index 27a29aa0a6b2..3da6e802d178 100644 --- a/libs/hwui/debug/wrap_gles.h +++ b/libs/hwui/debug/wrap_gles.h @@ -28,6 +28,9 @@ #include <GLES3/gl31.h> #include <GLES3/gl32.h> +// constant used by the NULL GPU implementation as well as HWUI's unit tests +constexpr int NULL_GPU_MAX_TEXTURE_SIZE = 2048; + // Generate stubs that route all the calls to our function table #include "gles_redefine.h" diff --git a/libs/hwui/font/CacheTexture.cpp b/libs/hwui/font/CacheTexture.cpp deleted file mode 100644 index 5dd8bb8119e9..000000000000 --- a/libs/hwui/font/CacheTexture.cpp +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright (C) 2012 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 <SkGlyph.h> - -#include "../Caches.h" -#include "../Debug.h" -#include "../Extensions.h" -#include "../PixelBuffer.h" -#include "CacheTexture.h" -#include "FontUtil.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// CacheBlock -/////////////////////////////////////////////////////////////////////////////// - -/** - * Insert new block into existing linked list of blocks. Blocks are sorted in increasing-width - * order, except for the final block (the remainder space at the right, since we fill from the - * left). - */ -CacheBlock* CacheBlock::insertBlock(CacheBlock* head, CacheBlock* newBlock) { -#if DEBUG_FONT_RENDERER - ALOGD("insertBlock: this, x, y, w, h = %p, %d, %d, %d, %d", newBlock, newBlock->mX, - newBlock->mY, newBlock->mWidth, newBlock->mHeight); -#endif - - CacheBlock* currBlock = head; - CacheBlock* prevBlock = nullptr; - - while (currBlock && currBlock->mY != TEXTURE_BORDER_SIZE) { - if (newBlock->mWidth < currBlock->mWidth) { - newBlock->mNext = currBlock; - newBlock->mPrev = prevBlock; - currBlock->mPrev = newBlock; - - if (prevBlock) { - prevBlock->mNext = newBlock; - return head; - } else { - return newBlock; - } - } - - prevBlock = currBlock; - currBlock = currBlock->mNext; - } - - // new block larger than all others - insert at end (but before the remainder space, if there) - newBlock->mNext = currBlock; - newBlock->mPrev = prevBlock; - - if (currBlock) { - currBlock->mPrev = newBlock; - } - - if (prevBlock) { - prevBlock->mNext = newBlock; - return head; - } else { - return newBlock; - } -} - -CacheBlock* CacheBlock::removeBlock(CacheBlock* head, CacheBlock* blockToRemove) { -#if DEBUG_FONT_RENDERER - ALOGD("removeBlock: this, x, y, w, h = %p, %d, %d, %d, %d", blockToRemove, blockToRemove->mX, - blockToRemove->mY, blockToRemove->mWidth, blockToRemove->mHeight); -#endif - - CacheBlock* newHead = head; - CacheBlock* nextBlock = blockToRemove->mNext; - CacheBlock* prevBlock = blockToRemove->mPrev; - - if (prevBlock) { - // If this doesn't hold, we have a use-after-free below. - LOG_ALWAYS_FATAL_IF(head == blockToRemove, - "removeBlock: head should not have a previous block"); - prevBlock->mNext = nextBlock; - } else { - newHead = nextBlock; - } - - if (nextBlock) { - nextBlock->mPrev = prevBlock; - } - - delete blockToRemove; - - return newHead; -} - -/////////////////////////////////////////////////////////////////////////////// -// CacheTexture -/////////////////////////////////////////////////////////////////////////////// - -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.blend = true; - - mCacheBlocks = - new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, - getWidth() - TEXTURE_BORDER_SIZE, getHeight() - TEXTURE_BORDER_SIZE); - - // OpenGL ES 3.0+ lets us specify the row length for unpack operations such - // as glTexSubImage2D(). This allows us to upload a sub-rectangle of a texture. - // With OpenGL ES 2.0 we have to upload entire stripes instead. - mHasUnpackRowLength = mCaches.extensions().hasUnpackRowLength(); -} - -CacheTexture::~CacheTexture() { - releaseMesh(); - releasePixelBuffer(); - reset(); -} - -void CacheTexture::reset() { - // Delete existing cache blocks - while (mCacheBlocks != nullptr) { - CacheBlock* tmpBlock = mCacheBlocks; - mCacheBlocks = mCacheBlocks->mNext; - delete tmpBlock; - } - mNumGlyphs = 0; - mCurrentQuad = 0; -} - -void CacheTexture::init() { - // reset, then create a new remainder space to start again - reset(); - mCacheBlocks = - new CacheBlock(TEXTURE_BORDER_SIZE, TEXTURE_BORDER_SIZE, - getWidth() - TEXTURE_BORDER_SIZE, getHeight() - TEXTURE_BORDER_SIZE); -} - -void CacheTexture::releaseMesh() { - delete[] mMesh; -} - -void CacheTexture::releasePixelBuffer() { - if (mPixelBuffer) { - delete mPixelBuffer; - mPixelBuffer = nullptr; - } - mTexture.deleteTexture(); - mDirty = false; - mCurrentQuad = 0; -} - -void CacheTexture::setLinearFiltering(bool linearFiltering) { - mTexture.setFilter(linearFiltering ? GL_LINEAR : GL_NEAREST); -} - -void CacheTexture::allocateMesh() { - if (!mMesh) { - mMesh = new TextureVertex[mMaxQuadCount * 4]; - } -} - -void CacheTexture::allocatePixelBuffer() { - if (!mPixelBuffer) { - mPixelBuffer = PixelBuffer::create(mFormat, getWidth(), getHeight()); - } - - GLint internalFormat = mFormat; - if (mFormat == GL_RGBA) { - internalFormat = mCaches.rgbaInternalFormat(); - } - - mTexture.resize(mWidth, mHeight, internalFormat, mFormat); - mTexture.setFilter(getLinearFiltering() ? GL_LINEAR : GL_NEAREST); - mTexture.setWrap(GL_CLAMP_TO_EDGE); -} - -bool CacheTexture::upload() { - const Rect& dirtyRect = mDirtyRect; - - // align the x direction to 32 and y direction to 4 for better performance - uint32_t x = (((uint32_t)dirtyRect.left) & (~0x1F)); - uint32_t y = (((uint32_t)dirtyRect.top) & (~0x3)); - uint32_t r = ((((uint32_t)dirtyRect.right) + 0x1F) & (~0x1F)) - x; - uint32_t b = ((((uint32_t)dirtyRect.bottom) + 0x3) & (~0x3)) - y; - uint32_t width = (r > getWidth() ? getWidth() : r); - uint32_t height = (b > getHeight() ? getHeight() : b); - - // The unpack row length only needs to be specified when a new - // texture is bound - if (mHasUnpackRowLength) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, getWidth()); - } else { - x = 0; - width = getWidth(); - } - - mPixelBuffer->upload(x, y, width, height); - setDirty(false); - - return mHasUnpackRowLength; -} - -void CacheTexture::setDirty(bool dirty) { - mDirty = dirty; - if (!dirty) { - mDirtyRect.setEmpty(); - } -} - -bool CacheTexture::fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) { - switch (glyph.fMaskFormat) { - case SkMask::kA8_Format: - case SkMask::kBW_Format: - if (mFormat != GL_ALPHA) { -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: texture format %x is inappropriate for monochromatic glyphs", - mFormat); -#endif - return false; - } - break; - case SkMask::kARGB32_Format: - if (mFormat != GL_RGBA) { -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: texture format %x is inappropriate for colour glyphs", mFormat); -#endif - return false; - } - break; - default: -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: unknown glyph format %x encountered", glyph.fMaskFormat); -#endif - return false; - } - - if (glyph.fHeight + TEXTURE_BORDER_SIZE * 2 > getHeight()) { - return false; - } - - uint16_t glyphW = glyph.fWidth + TEXTURE_BORDER_SIZE; - uint16_t glyphH = glyph.fHeight + TEXTURE_BORDER_SIZE; - - // roundedUpW equals glyphW to the next multiple of CACHE_BLOCK_ROUNDING_SIZE. - // This columns for glyphs that are close but not necessarily exactly the same size. It trades - // off the loss of a few pixels for some glyphs against the ability to store more glyphs - // of varying sizes in one block. - uint16_t roundedUpW = (glyphW + CACHE_BLOCK_ROUNDING_SIZE - 1) & -CACHE_BLOCK_ROUNDING_SIZE; - - CacheBlock* cacheBlock = mCacheBlocks; - while (cacheBlock) { - // Store glyph in this block iff: it fits the block's remaining space and: - // it's the remainder space (mY == 0) or there's only enough height for this one glyph - // or it's within ROUNDING_SIZE of the block width - if (roundedUpW <= cacheBlock->mWidth && glyphH <= cacheBlock->mHeight && - (cacheBlock->mY == TEXTURE_BORDER_SIZE || - (cacheBlock->mWidth - roundedUpW < CACHE_BLOCK_ROUNDING_SIZE))) { - if (cacheBlock->mHeight - glyphH < glyphH) { - // Only enough space for this glyph - don't bother rounding up the width - roundedUpW = glyphW; - } - - *retOriginX = cacheBlock->mX; - *retOriginY = cacheBlock->mY; - - // If this is the remainder space, create a new cache block for this column. Otherwise, - // adjust the info about this column. - if (cacheBlock->mY == TEXTURE_BORDER_SIZE) { - uint16_t oldX = cacheBlock->mX; - // Adjust remainder space dimensions - cacheBlock->mWidth -= roundedUpW; - cacheBlock->mX += roundedUpW; - - if (getHeight() - glyphH >= glyphH) { - // There's enough height left over to create a new CacheBlock - CacheBlock* newBlock = - new CacheBlock(oldX, glyphH + TEXTURE_BORDER_SIZE, roundedUpW, - getHeight() - glyphH - TEXTURE_BORDER_SIZE); -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: Created new block: this, x, y, w, h = %p, %d, %d, %d, %d", - newBlock, newBlock->mX, newBlock->mY, newBlock->mWidth, - newBlock->mHeight); -#endif - mCacheBlocks = CacheBlock::insertBlock(mCacheBlocks, newBlock); - } - } else { - // Insert into current column and adjust column dimensions - cacheBlock->mY += glyphH; - cacheBlock->mHeight -= glyphH; -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: Added to existing block: this, x, y, w, h = %p, %d, %d, %d, %d", - cacheBlock, cacheBlock->mX, cacheBlock->mY, cacheBlock->mWidth, - cacheBlock->mHeight); -#endif - } - - if (cacheBlock->mHeight < std::min(glyphH, glyphW)) { - // If remaining space in this block is too small to be useful, remove it - mCacheBlocks = CacheBlock::removeBlock(mCacheBlocks, cacheBlock); - } - - mDirty = true; - const Rect r(*retOriginX - TEXTURE_BORDER_SIZE, *retOriginY - TEXTURE_BORDER_SIZE, - *retOriginX + glyphW, *retOriginY + glyphH); - mDirtyRect.unionWith(r); - mNumGlyphs++; - -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: current block list:"); - mCacheBlocks->output(); -#endif - - return true; - } - cacheBlock = cacheBlock->mNext; - } -#if DEBUG_FONT_RENDERER - ALOGD("fitBitmap: returning false for glyph of size %d, %d", glyphW, glyphH); -#endif - return false; -} - -uint32_t CacheTexture::calculateFreeMemory() const { - CacheBlock* cacheBlock = mCacheBlocks; - uint32_t free = 0; - // currently only two formats are supported: GL_ALPHA or GL_RGBA; - uint32_t bpp = mFormat == GL_RGBA ? 4 : 1; - while (cacheBlock) { - free += bpp * cacheBlock->mWidth * cacheBlock->mHeight; - cacheBlock = cacheBlock->mNext; - } - return free; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/font/CacheTexture.h b/libs/hwui/font/CacheTexture.h deleted file mode 100644 index 654378eeb47e..000000000000 --- a/libs/hwui/font/CacheTexture.h +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2012 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_CACHE_TEXTURE_H -#define ANDROID_HWUI_CACHE_TEXTURE_H - -#include "PixelBuffer.h" -#include "Rect.h" -#include "Texture.h" -#include "Vertex.h" - -#include <GLES3/gl3.h> -#include <SkGlyph.h> -#include <utils/Log.h> - -namespace android { -namespace uirenderer { - -class Caches; - -/** - * CacheBlock is a node in a linked list of current free space areas in a CacheTexture. - * Using CacheBlocks enables us to pack the cache from top to bottom as well as left to right. - * When we add a glyph to the cache, we see if it fits within one of the existing columns that - * have already been started (this is the case if the glyph fits vertically as well as - * horizontally, and if its width is sufficiently close to the column width to avoid - * sub-optimal packing of small glyphs into wide columns). If there is no column in which the - * glyph fits, we check the final node, which is the remaining space in the cache, creating - * a new column as appropriate. - * - * As columns fill up, we remove their CacheBlock from the list to avoid having to check - * small blocks in the future. - */ -struct CacheBlock { - uint16_t mX; - uint16_t mY; - uint16_t mWidth; - uint16_t mHeight; - CacheBlock* mNext; - CacheBlock* mPrev; - - CacheBlock(uint16_t x, uint16_t y, uint16_t width, uint16_t height) - : mX(x), mY(y), mWidth(width), mHeight(height), mNext(nullptr), mPrev(nullptr) {} - - static CacheBlock* insertBlock(CacheBlock* head, CacheBlock* newBlock); - static CacheBlock* removeBlock(CacheBlock* head, CacheBlock* blockToRemove); - - void output() { - CacheBlock* currBlock = this; - while (currBlock) { - ALOGD("Block: this, x, y, w, h = %p, %d, %d, %d, %d", currBlock, currBlock->mX, - currBlock->mY, currBlock->mWidth, currBlock->mHeight); - currBlock = currBlock->mNext; - } - } -}; - -class CacheTexture { -public: - CacheTexture(uint16_t width, uint16_t height, GLenum format, uint32_t maxQuadCount); - ~CacheTexture(); - - void reset(); - void init(); - - void releaseMesh(); - void releasePixelBuffer(); - - void allocatePixelBuffer(); - void allocateMesh(); - - // Returns true if glPixelStorei(GL_UNPACK_ROW_LENGTH) must be reset - // This method will also call setDirty(false) - bool upload(); - - bool fitBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY); - - inline uint16_t getWidth() const { return mWidth; } - - inline uint16_t getHeight() const { return mHeight; } - - inline GLenum getFormat() const { return mFormat; } - - inline uint32_t getOffset(uint16_t x, uint16_t y) const { - return (y * getWidth() + x) * PixelBuffer::formatSize(mFormat); - } - - inline const Rect* getDirtyRect() const { return &mDirtyRect; } - - inline PixelBuffer* getPixelBuffer() const { return mPixelBuffer; } - - Texture& getTexture() { - allocatePixelBuffer(); - return mTexture; - } - - GLuint getTextureId() { - allocatePixelBuffer(); - return mTexture.id(); - } - - inline bool isDirty() const { return mDirty; } - - inline bool getLinearFiltering() const { return mLinearFiltering; } - - /** - * This method assumes that the proper texture unit is active. - */ - void setLinearFiltering(bool linearFiltering); - - inline uint16_t getGlyphCount() const { return mNumGlyphs; } - - TextureVertex* mesh() const { return mMesh; } - - uint32_t meshElementCount() const { return mCurrentQuad * 6; } - - uint16_t* indices() const { return (uint16_t*)nullptr; } - - void resetMesh() { mCurrentQuad = 0; } - - inline void addQuad(float x1, float y1, float u1, float v1, float x2, float y2, float u2, - float v2, float x3, float y3, float u3, float v3, float x4, float y4, - float u4, float v4) { - TextureVertex* mesh = mMesh + mCurrentQuad * 4; - TextureVertex::set(mesh++, x2, y2, u2, v2); - TextureVertex::set(mesh++, x3, y3, u3, v3); - TextureVertex::set(mesh++, x1, y1, u1, v1); - TextureVertex::set(mesh++, x4, y4, u4, v4); - mCurrentQuad++; - } - - bool canDraw() const { return mCurrentQuad > 0; } - - bool endOfMesh() const { return mCurrentQuad == mMaxQuadCount; } - - uint32_t calculateFreeMemory() const; - -private: - void setDirty(bool dirty); - - PixelBuffer* mPixelBuffer = nullptr; - Texture mTexture; - uint32_t mWidth, mHeight; - GLenum mFormat; - bool mLinearFiltering = false; - bool mDirty = false; - uint16_t mNumGlyphs = 0; - TextureVertex* mMesh = nullptr; - uint32_t mCurrentQuad = 0; - uint32_t mMaxQuadCount; - Caches& mCaches; - CacheBlock* mCacheBlocks; - bool mHasUnpackRowLength; - Rect mDirtyRect; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_CACHE_TEXTURE_H diff --git a/libs/hwui/font/CachedGlyphInfo.h b/libs/hwui/font/CachedGlyphInfo.h deleted file mode 100644 index 93bb823db512..000000000000 --- a/libs/hwui/font/CachedGlyphInfo.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2012 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_CACHED_GLYPH_INFO_H -#define ANDROID_HWUI_CACHED_GLYPH_INFO_H - -namespace android { -namespace uirenderer { - -class CacheTexture; - -struct CachedGlyphInfo { - // Has the cache been invalidated? - bool mIsValid; - // Location of the cached glyph in the bitmap - // in case we need to resize the texture or - // render to bitmap - uint32_t mStartX; - uint32_t mStartY; - uint32_t mBitmapWidth; - uint32_t mBitmapHeight; - // Also cache texture coords for the quad - float mBitmapMinU; - float mBitmapMinV; - float mBitmapMaxU; - float mBitmapMaxV; - // Minimize how much we call freetype - uint32_t mGlyphIndex; - float mAdvanceX; - float mAdvanceY; - // Values below contain a glyph's origin in the bitmap - int32_t mBitmapLeft; - int32_t mBitmapTop; - // Auto-kerning; represents a 2.6 fixed-point value with range [-1, 1]. - int8_t mLsbDelta; - int8_t mRsbDelta; - CacheTexture* mCacheTexture; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_CACHED_GLYPH_INFO_H diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp deleted file mode 100644 index 41a24a809e89..000000000000 --- a/libs/hwui/font/Font.cpp +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright (C) 2012 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/compiler.h> - -#include <utils/JenkinsHash.h> -#include <utils/Trace.h> - -#include <SkGlyph.h> -#include <SkGlyphCache.h> -#include <SkSurfaceProps.h> -#include <SkUtils.h> - -#include "../Debug.h" -#include "../FontRenderer.h" -#include "../PixelBuffer.h" -#include "../Properties.h" -#include "Font.h" -#include "FontUtil.h" - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Font -/////////////////////////////////////////////////////////////////////////////// - -Font::Font(FontRenderer* state, const Font::FontDescription& desc) - : mState(state), mDescription(desc) {} - -Font::FontDescription::FontDescription(const SkPaint* paint, const SkMatrix& rasterMatrix) - : mLookupTransform(rasterMatrix) { - mFontId = SkTypeface::UniqueID(paint->getTypeface()); - mFontSize = paint->getTextSize(); - mFlags = 0; - if (paint->isFakeBoldText()) { - mFlags |= Font::kFakeBold; - } - mItalicStyle = paint->getTextSkewX(); - mScaleX = paint->getTextScaleX(); - mStyle = paint->getStyle(); - mStrokeWidth = paint->getStrokeWidth(); - mAntiAliasing = paint->isAntiAlias(); - mHinting = paint->getHinting(); - if (!mLookupTransform.invert(&mInverseLookupTransform)) { - ALOGW("Could not query the inverse lookup transform for this font"); - } -} - -Font::~Font() { - for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { - delete mCachedGlyphs.valueAt(i); - } -} - -hash_t Font::FontDescription::hash() const { - uint32_t hash = JenkinsHashMix(0, mFontId); - hash = JenkinsHashMix(hash, android::hash_type(mFontSize)); - hash = JenkinsHashMix(hash, android::hash_type(mFlags)); - hash = JenkinsHashMix(hash, android::hash_type(mItalicStyle)); - hash = JenkinsHashMix(hash, android::hash_type(mScaleX)); - hash = JenkinsHashMix(hash, android::hash_type(mStyle)); - hash = JenkinsHashMix(hash, android::hash_type(mStrokeWidth)); - hash = JenkinsHashMix(hash, int(mAntiAliasing)); - hash = JenkinsHashMix(hash, android::hash_type(mHinting)); - hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleX])); - hash = JenkinsHashMix(hash, android::hash_type(mLookupTransform[SkMatrix::kMScaleY])); - return JenkinsHashWhiten(hash); -} - -int Font::FontDescription::compare(const Font::FontDescription& lhs, - const Font::FontDescription& rhs) { - int deltaInt = int(lhs.mFontId) - int(rhs.mFontId); - if (deltaInt != 0) return deltaInt; - - if (lhs.mFontSize < rhs.mFontSize) return -1; - if (lhs.mFontSize > rhs.mFontSize) return +1; - - if (lhs.mItalicStyle < rhs.mItalicStyle) return -1; - if (lhs.mItalicStyle > rhs.mItalicStyle) return +1; - - deltaInt = int(lhs.mFlags) - int(rhs.mFlags); - if (deltaInt != 0) return deltaInt; - - if (lhs.mScaleX < rhs.mScaleX) return -1; - if (lhs.mScaleX > rhs.mScaleX) return +1; - - deltaInt = int(lhs.mStyle) - int(rhs.mStyle); - if (deltaInt != 0) return deltaInt; - - if (lhs.mStrokeWidth < rhs.mStrokeWidth) return -1; - if (lhs.mStrokeWidth > rhs.mStrokeWidth) return +1; - - deltaInt = int(lhs.mAntiAliasing) - int(rhs.mAntiAliasing); - if (deltaInt != 0) return deltaInt; - - deltaInt = int(lhs.mHinting) - int(rhs.mHinting); - if (deltaInt != 0) return deltaInt; - - if (lhs.mLookupTransform[SkMatrix::kMScaleX] < rhs.mLookupTransform[SkMatrix::kMScaleX]) - return -1; - if (lhs.mLookupTransform[SkMatrix::kMScaleX] > rhs.mLookupTransform[SkMatrix::kMScaleX]) - return +1; - - if (lhs.mLookupTransform[SkMatrix::kMScaleY] < rhs.mLookupTransform[SkMatrix::kMScaleY]) - return -1; - if (lhs.mLookupTransform[SkMatrix::kMScaleY] > rhs.mLookupTransform[SkMatrix::kMScaleY]) - return +1; - - return 0; -} - -void Font::invalidateTextureCache(CacheTexture* cacheTexture) { - for (uint32_t i = 0; i < mCachedGlyphs.size(); i++) { - CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueAt(i); - if (!cacheTexture || cachedGlyph->mCacheTexture == cacheTexture) { - cachedGlyph->mIsValid = false; - } - } -} - -void Font::measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, - uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos) { - int width = (int)glyph->mBitmapWidth; - int height = (int)glyph->mBitmapHeight; - - int nPenX = x + glyph->mBitmapLeft; - int nPenY = y + glyph->mBitmapTop; - - if (bounds->bottom > nPenY) { - bounds->bottom = nPenY; - } - if (bounds->left > nPenX) { - bounds->left = nPenX; - } - if (bounds->right < nPenX + width) { - bounds->right = nPenX + width; - } - if (bounds->top < nPenY + height) { - bounds->top = nPenY + height; - } -} - -void Font::drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW, - uint32_t bitmapH, Rect* bounds, const float* pos) { - float width = (float)glyph->mBitmapWidth; - float height = (float)glyph->mBitmapHeight; - - float nPenX = x + glyph->mBitmapLeft; - float nPenY = y + glyph->mBitmapTop + height; - - float u1 = glyph->mBitmapMinU; - float u2 = glyph->mBitmapMaxU; - float v1 = glyph->mBitmapMinV; - float v2 = glyph->mBitmapMaxV; - - mState->appendMeshQuad(nPenX, nPenY, u1, v2, nPenX + width, nPenY, u2, v2, nPenX + width, - nPenY - height, u2, v1, nPenX, nPenY - height, u1, v1, - glyph->mCacheTexture); -} - -void Font::drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, - uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, - const float* pos) { - float width = (float)glyph->mBitmapWidth; - float height = (float)glyph->mBitmapHeight; - - SkPoint p[4]; - p[0].iset(glyph->mBitmapLeft, glyph->mBitmapTop + height); - p[1].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop + height); - p[2].iset(glyph->mBitmapLeft + width, glyph->mBitmapTop); - p[3].iset(glyph->mBitmapLeft, glyph->mBitmapTop); - - mDescription.mInverseLookupTransform.mapPoints(p, 4); - - p[0].offset(x, y); - p[1].offset(x, y); - p[2].offset(x, y); - p[3].offset(x, y); - - float u1 = glyph->mBitmapMinU; - float u2 = glyph->mBitmapMaxU; - float v1 = glyph->mBitmapMinV; - float v2 = glyph->mBitmapMaxV; - - mState->appendRotatedMeshQuad(p[0].x(), p[0].y(), u1, v2, p[1].x(), p[1].y(), u2, v2, p[2].x(), - p[2].y(), u2, v1, p[3].x(), p[3].y(), u1, v1, - glyph->mCacheTexture); -} - -void Font::drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, - uint32_t bitmapWidth, uint32_t bitmapHeight, Rect* bounds, - const float* pos) { - int dstX = x + glyph->mBitmapLeft; - int dstY = y + glyph->mBitmapTop; - - CacheTexture* cacheTexture = glyph->mCacheTexture; - PixelBuffer* pixelBuffer = cacheTexture->getPixelBuffer(); - - uint32_t formatSize = PixelBuffer::formatSize(pixelBuffer->getFormat()); - uint32_t alpha_channel_offset = PixelBuffer::formatAlphaOffset(pixelBuffer->getFormat()); - uint32_t cacheWidth = cacheTexture->getWidth(); - uint32_t srcStride = formatSize * cacheWidth; - uint32_t startY = glyph->mStartY * srcStride; - uint32_t endY = startY + (glyph->mBitmapHeight * srcStride); - - const uint8_t* cacheBuffer = pixelBuffer->map(); - - for (uint32_t cacheY = startY, bitmapY = dstY * bitmapWidth; cacheY < endY; - cacheY += srcStride, bitmapY += bitmapWidth) { - for (uint32_t i = 0; i < glyph->mBitmapWidth; ++i) { - uint8_t* dst = &(bitmap[bitmapY + dstX + i]); - const uint8_t& src = - cacheBuffer[cacheY + (glyph->mStartX + i) * formatSize + alpha_channel_offset]; - // Add alpha values to a max of 255, full opacity. This is done to handle - // fonts/strings where glyphs overlap. - *dst = std::min(*dst + src, 255); - } - } -} - -void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset, - SkPathMeasure& measure, SkPoint* position, SkVector* tangent) { - const float halfWidth = glyph->mBitmapWidth * 0.5f; - const float height = glyph->mBitmapHeight; - - vOffset += glyph->mBitmapTop + height; - - SkPoint destination[4]; - bool ok = measure.getPosTan(x + hOffset + glyph->mBitmapLeft + halfWidth, position, tangent); - if (!ok) { - ALOGW("The path for drawTextOnPath is empty or null"); - } - - // Move along the tangent and offset by the normal - destination[0].set(-tangent->fX * halfWidth - tangent->fY * vOffset, - -tangent->fY * halfWidth + tangent->fX * vOffset); - destination[1].set(tangent->fX * halfWidth - tangent->fY * vOffset, - tangent->fY * halfWidth + tangent->fX * vOffset); - destination[2].set(destination[1].fX + tangent->fY * height, - destination[1].fY - tangent->fX * height); - destination[3].set(destination[0].fX + tangent->fY * height, - destination[0].fY - tangent->fX * height); - - const float u1 = glyph->mBitmapMinU; - const float u2 = glyph->mBitmapMaxU; - const float v1 = glyph->mBitmapMinV; - const float v2 = glyph->mBitmapMaxV; - - mState->appendRotatedMeshQuad( - position->x() + destination[0].x(), position->y() + destination[0].y(), u1, v2, - position->x() + destination[1].x(), position->y() + destination[1].y(), u2, v2, - position->x() + destination[2].x(), position->y() + destination[2].y(), u2, v1, - position->x() + destination[3].x(), position->y() + destination[3].y(), u1, v1, - glyph->mCacheTexture); -} - -CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bool precaching) { - CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(textUnit); - if (cachedGlyph) { - // Is the glyph still in texture cache? - if (!cachedGlyph->mIsValid) { - 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); - } - } else { - cachedGlyph = cacheGlyph(paint, textUnit, precaching); - } - - return cachedGlyph; -} - -void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y, - const float* positions) { - render(paint, glyphs, numGlyphs, x, y, FRAMEBUFFER, nullptr, 0, 0, nullptr, positions); -} - -void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkPath* path, - float hOffset, float vOffset) { - if (numGlyphs == 0 || glyphs == nullptr) { - return; - } - - int glyphsCount = 0; - int prevRsbDelta = 0; - - float penX = 0.0f; - - SkPoint position; - SkVector tangent; - - SkPathMeasure measure(*path, false); - float pathLength = SkScalarToFloat(measure.getLength()); - - if (paint->getTextAlign() != SkPaint::kLeft_Align) { - float textWidth = SkScalarToFloat(paint->measureText(glyphs, numGlyphs * 2)); - float pathOffset = pathLength; - if (paint->getTextAlign() == SkPaint::kCenter_Align) { - textWidth *= 0.5f; - pathOffset *= 0.5f; - } - penX += pathOffset - textWidth; - } - - while (glyphsCount < numGlyphs && penX < pathLength) { - glyph_t glyph = *(glyphs++); - - if (IS_END_OF_STRING(glyph)) { - break; - } - - CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); - penX += AUTO_KERN(prevRsbDelta, cachedGlyph->mLsbDelta); - prevRsbDelta = cachedGlyph->mRsbDelta; - - if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) { - drawCachedGlyph(cachedGlyph, penX, hOffset, vOffset, measure, &position, &tangent); - } - - penX += cachedGlyph->mAdvanceX; - - glyphsCount++; - } -} - -void Font::measure(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, Rect* bounds, - const float* positions) { - if (bounds == nullptr) { - ALOGE("No return rectangle provided to measure text"); - return; - } - bounds->set(1e6, -1e6, -1e6, 1e6); - render(paint, glyphs, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions); -} - -void Font::precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs) { - if (numGlyphs == 0 || glyphs == nullptr) { - return; - } - - int glyphsCount = 0; - while (glyphsCount < numGlyphs) { - glyph_t glyph = *(glyphs++); - - // Reached the end of the string - if (IS_END_OF_STRING(glyph)) { - break; - } - - getCachedGlyph(paint, glyph, true); - glyphsCount++; - } -} - -void Font::render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y, - RenderMode mode, uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, - Rect* bounds, const float* positions) { - if (numGlyphs == 0 || glyphs == nullptr) { - return; - } - - static RenderGlyph gRenderGlyph[] = {&android::uirenderer::Font::drawCachedGlyph, - &android::uirenderer::Font::drawCachedGlyphTransformed, - &android::uirenderer::Font::drawCachedGlyphBitmap, - &android::uirenderer::Font::drawCachedGlyphBitmap, - &android::uirenderer::Font::measureCachedGlyph, - &android::uirenderer::Font::measureCachedGlyph}; - RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform]; - - int glyphsCount = 0; - - while (glyphsCount < numGlyphs) { - glyph_t glyph = *(glyphs++); - - // Reached the end of the string - if (IS_END_OF_STRING(glyph)) { - break; - } - - CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph); - - // If it's still not valid, we couldn't cache it, so we shouldn't - // draw garbage; also skip empty glyphs (spaces) - if (cachedGlyph->mIsValid && cachedGlyph->mCacheTexture) { - int penX = x + (int)roundf(positions[(glyphsCount << 1)]); - int penY = y + (int)roundf(positions[(glyphsCount << 1) + 1]); -#ifdef BUGREPORT_FONT_CACHE_USAGE - mState->historyTracker().glyphRendered(cachedGlyph, penX, penY); -#endif - (*this.*render)(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH, bounds, positions); - } else { -#ifdef BUGREPORT_FONT_CACHE_USAGE - mState->historyTracker().glyphRendered(cachedGlyph, -1, -1); -#endif - } - - glyphsCount++; - } -} - -void Font::updateGlyphCache(const SkPaint* paint, const SkGlyph& skiaGlyph, - SkGlyphCache* skiaGlyphCache, CachedGlyphInfo* glyph, bool precaching) { - glyph->mAdvanceX = skiaGlyph.fAdvanceX; - glyph->mAdvanceY = skiaGlyph.fAdvanceY; - glyph->mBitmapLeft = skiaGlyph.fLeft; - glyph->mBitmapTop = skiaGlyph.fTop; - glyph->mLsbDelta = skiaGlyph.fLsbDelta; - glyph->mRsbDelta = skiaGlyph.fRsbDelta; - - uint32_t startX = 0; - uint32_t startY = 0; - - // Get the bitmap for the glyph - if (!skiaGlyph.fImage) { - skiaGlyphCache->findImage(skiaGlyph); - } - mState->cacheBitmap(skiaGlyph, glyph, &startX, &startY, precaching); - - if (!glyph->mIsValid) { - return; - } - - uint32_t endX = startX + skiaGlyph.fWidth; - uint32_t endY = startY + skiaGlyph.fHeight; - - glyph->mStartX = startX; - glyph->mStartY = startY; - glyph->mBitmapWidth = skiaGlyph.fWidth; - glyph->mBitmapHeight = skiaGlyph.fHeight; - - bool empty = skiaGlyph.fWidth == 0 || skiaGlyph.fHeight == 0; - if (!empty) { - uint32_t cacheWidth = glyph->mCacheTexture->getWidth(); - uint32_t cacheHeight = glyph->mCacheTexture->getHeight(); - - glyph->mBitmapMinU = startX / (float)cacheWidth; - glyph->mBitmapMinV = startY / (float)cacheHeight; - glyph->mBitmapMaxU = endX / (float)cacheWidth; - glyph->mBitmapMaxV = endY / (float)cacheHeight; - - mState->setTextureDirty(); - } -} - -CachedGlyphInfo* Font::cacheGlyph(const SkPaint* paint, glyph_t glyph, bool precaching) { - CachedGlyphInfo* newGlyph = new CachedGlyphInfo(); - mCachedGlyphs.add(glyph, newGlyph); - - 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; - - updateGlyphCache(paint, skiaGlyph, autoCache.getCache(), newGlyph, precaching); - - return newGlyph; -} - -Font* Font::create(FontRenderer* state, const SkPaint* paint, const SkMatrix& matrix) { - FontDescription description(paint, matrix); - Font* font = state->mActiveFonts.get(description); - - if (!font) { - font = new Font(state, description); - state->mActiveFonts.put(description, font); - } - font->mIdentityTransform = matrix.isIdentity(); - - return font; -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h deleted file mode 100644 index 85221bc069f1..000000000000 --- a/libs/hwui/font/Font.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2012 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_FONT_H -#define ANDROID_HWUI_FONT_H - -#include <vector> - -#include <utils/KeyedVector.h> - -#include <SkPaint.h> -#include <SkPathMeasure.h> -#include <SkScalar.h> -#include <SkTypeface.h> - -#include "../Matrix.h" -#include "../Rect.h" -#include "FontUtil.h" - -class SkGlyphCache; - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Font -/////////////////////////////////////////////////////////////////////////////// - -struct CachedGlyphInfo; -class CacheTexture; -class FontRenderer; - -/** - * Represents a font, defined by a Skia font id and a font size. A font is used - * to generate glyphs and cache them in the FontState. - */ -class Font { -public: - enum Style { kFakeBold = 1 }; - - struct FontDescription { - FontDescription(const SkPaint* paint, const SkMatrix& matrix); - - static int compare(const FontDescription& lhs, const FontDescription& rhs); - - hash_t hash() const; - - bool operator==(const FontDescription& other) const { return compare(*this, other) == 0; } - - bool operator!=(const FontDescription& other) const { return compare(*this, other) != 0; } - - SkFontID mFontId; - float mFontSize; - int mFlags; - float mItalicStyle; - float mScaleX; - uint8_t mStyle; - float mStrokeWidth; - bool mAntiAliasing; - uint8_t mHinting; - SkMatrix mLookupTransform; - SkMatrix mInverseLookupTransform; - }; - - ~Font(); - - void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y, - const float* positions); - - void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, const SkPath* path, - float hOffset, float vOffset); - - const Font::FontDescription& getDescription() const { return mDescription; } - - /** - * Creates a new font associated with the specified font state. - */ - static Font* create(FontRenderer* state, const SkPaint* paint, const SkMatrix& matrix); - -private: - friend class FontRenderer; - - Font(FontRenderer* state, const Font::FontDescription& desc); - - typedef void (Font::*RenderGlyph)(CachedGlyphInfo*, int, int, uint8_t*, uint32_t, uint32_t, - Rect*, const float*); - - enum RenderMode { - FRAMEBUFFER, - BITMAP, - MEASURE, - }; - - void precache(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs); - - void render(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, int x, int y, - RenderMode mode, uint8_t* bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, - const float* positions); - - void measure(const SkPaint* paint, const glyph_t* glyphs, int numGlyphs, Rect* bounds, - const float* positions); - - void invalidateTextureCache(CacheTexture* cacheTexture = nullptr); - - CachedGlyphInfo* cacheGlyph(const SkPaint* paint, glyph_t glyph, bool precaching); - void updateGlyphCache(const SkPaint* paint, const SkGlyph& skiaGlyph, - SkGlyphCache* skiaGlyphCache, CachedGlyphInfo* glyph, bool precaching); - - void measureCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW, - uint32_t bitmapH, Rect* bounds, const float* pos); - void drawCachedGlyph(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, uint32_t bitmapW, - uint32_t bitmapH, Rect* bounds, const float* pos); - void drawCachedGlyphTransformed(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, - uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, - const float* pos); - void drawCachedGlyphBitmap(CachedGlyphInfo* glyph, int x, int y, uint8_t* bitmap, - uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* pos); - void drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float vOffset, - SkPathMeasure& measure, SkPoint* position, SkVector* tangent); - - CachedGlyphInfo* getCachedGlyph(const SkPaint* paint, glyph_t textUnit, - bool precaching = false); - - FontRenderer* mState; - FontDescription mDescription; - - // Cache of glyphs - DefaultKeyedVector<glyph_t, CachedGlyphInfo*> mCachedGlyphs; - - bool mIdentityTransform; -}; - -inline int strictly_order_type(const Font::FontDescription& lhs, const Font::FontDescription& rhs) { - return Font::FontDescription::compare(lhs, rhs) < 0; -} - -inline int compare_type(const Font::FontDescription& lhs, const Font::FontDescription& rhs) { - return Font::FontDescription::compare(lhs, rhs); -} - -inline hash_t hash_type(const Font::FontDescription& entry) { - return entry.hash(); -} - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_FONT_H diff --git a/libs/hwui/font/FontCacheHistoryTracker.cpp b/libs/hwui/font/FontCacheHistoryTracker.cpp deleted file mode 100644 index 5b61c494524a..000000000000 --- a/libs/hwui/font/FontCacheHistoryTracker.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 "FontCacheHistoryTracker.h" - -#include "CacheTexture.h" -#include "CachedGlyphInfo.h" - -namespace android { -namespace uirenderer { - -void FontCacheHistoryTracker::dumpCachedGlyph(String8& log, const CachedGlyph& glyph) { - log.appendFormat("glyph (texture %p, position: (%d, %d), size: %dx%d, gen: %d)", glyph.texture, - glyph.startX, glyph.startY, glyph.bitmapW, glyph.bitmapH, glyph.generation); -} - -void FontCacheHistoryTracker::dumpRenderEntry(String8& log, const RenderEntry& entry) { - if (entry.penX == -1 && entry.penY == -1) { - log.appendFormat(" glyph skipped in gen: %d\n", entry.glyph.generation); - } else { - log.appendFormat(" rendered "); - dumpCachedGlyph(log, entry.glyph); - log.appendFormat(" at (%d, %d)\n", entry.penX, entry.penY); - } -} - -void FontCacheHistoryTracker::dumpUploadEntry(String8& log, const CachedGlyph& glyph) { - if (glyph.bitmapW == 0 && glyph.bitmapH == 0) { - log.appendFormat(" cleared cachetexture %p in gen %d\n", glyph.texture, - glyph.generation); - } else { - log.appendFormat(" uploaded "); - dumpCachedGlyph(log, glyph); - log.appendFormat("\n"); - } -} - -void FontCacheHistoryTracker::dump(String8& log) const { - log.appendFormat("FontCacheHistory: \n"); - log.appendFormat(" Upload history: \n"); - for (size_t i = 0; i < mUploadHistory.size(); i++) { - dumpUploadEntry(log, mUploadHistory[i]); - } - log.appendFormat(" Render history: \n"); - for (size_t i = 0; i < mRenderHistory.size(); i++) { - dumpRenderEntry(log, mRenderHistory[i]); - } -} - -void FontCacheHistoryTracker::glyphRendered(CachedGlyphInfo* glyphInfo, int penX, int penY) { - RenderEntry& entry = mRenderHistory.next(); - entry.glyph.generation = generation; - entry.glyph.texture = glyphInfo->mCacheTexture; - entry.glyph.startX = glyphInfo->mStartX; - entry.glyph.startY = glyphInfo->mStartY; - entry.glyph.bitmapW = glyphInfo->mBitmapWidth; - entry.glyph.bitmapH = glyphInfo->mBitmapHeight; - entry.penX = penX; - entry.penY = penY; -} - -void FontCacheHistoryTracker::glyphUploaded(CacheTexture* texture, uint32_t x, uint32_t y, - uint16_t glyphW, uint16_t glyphH) { - CachedGlyph& glyph = mUploadHistory.next(); - glyph.generation = generation; - glyph.texture = texture; - glyph.startX = x; - glyph.startY = y; - glyph.bitmapW = glyphW; - glyph.bitmapH = glyphH; -} - -void FontCacheHistoryTracker::glyphsCleared(CacheTexture* texture) { - CachedGlyph& glyph = mUploadHistory.next(); - glyph.generation = generation; - glyph.texture = texture; - glyph.startX = 0; - glyph.startY = 0; - glyph.bitmapW = 0; - glyph.bitmapH = 0; -} - -void FontCacheHistoryTracker::frameCompleted() { - generation++; -} -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/font/FontCacheHistoryTracker.h b/libs/hwui/font/FontCacheHistoryTracker.h deleted file mode 100644 index 4b9ceccc09c9..000000000000 --- a/libs/hwui/font/FontCacheHistoryTracker.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include "../utils/RingBuffer.h" - -#include <utils/String8.h> - -namespace android { -namespace uirenderer { - -class CacheTexture; -struct CachedGlyphInfo; - -// Tracks glyph uploads and recent rendered/skipped glyphs, so it can give an idea -// what a missing character is: skipped glyph, wrong coordinates in cache texture etc. -class FontCacheHistoryTracker { -public: - void glyphRendered(CachedGlyphInfo*, int penX, int penY); - void glyphUploaded(CacheTexture*, uint32_t x, uint32_t y, uint16_t glyphW, uint16_t glyphH); - void glyphsCleared(CacheTexture*); - void frameCompleted(); - - void dump(String8& log) const; - -private: - struct CachedGlyph { - void* texture; - uint16_t generation; - uint16_t startX; - uint16_t startY; - uint16_t bitmapW; - uint16_t bitmapH; - }; - - struct RenderEntry { - CachedGlyph glyph; - int penX; - int penY; - }; - - static void dumpCachedGlyph(String8& log, const CachedGlyph& glyph); - static void dumpRenderEntry(String8& log, const RenderEntry& entry); - static void dumpUploadEntry(String8& log, const CachedGlyph& glyph); - - RingBuffer<RenderEntry, 300> mRenderHistory; - RingBuffer<CachedGlyph, 120> mUploadHistory; - uint16_t generation = 0; -}; - -}; // namespace uirenderer -}; // namespace android
\ No newline at end of file diff --git a/libs/hwui/font/FontUtil.h b/libs/hwui/font/FontUtil.h deleted file mode 100644 index 596e153db6a9..000000000000 --- a/libs/hwui/font/FontUtil.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2012 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_FONT_UTIL_H -#define ANDROID_HWUI_FONT_UTIL_H - -#include <SkUtils.h> - -#include "Properties.h" - -/////////////////////////////////////////////////////////////////////////////// -// Defines -/////////////////////////////////////////////////////////////////////////////// - -#ifdef TEXTURE_BORDER_SIZE -#if TEXTURE_BORDER_SIZE != 1 -#error TEXTURE_BORDER_SIZE other than 1 is not currently supported -#endif -#else -#define TEXTURE_BORDER_SIZE 1 -#endif - -#define CACHE_BLOCK_ROUNDING_SIZE 4 - -typedef uint16_t glyph_t; -#define GET_METRICS(cache, glyph) cache->getGlyphIDMetrics(glyph) -#define IS_END_OF_STRING(glyph) false - -// prev, next are assumed to be signed x.6 fixed-point numbers with range -// [-1, 1]. Result is an integral float. -#define AUTO_KERN(prev, next) static_cast<float>(((next) - (prev) + 32) >> 6) - -#endif // ANDROID_HWUI_FONT_UTIL_H diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index 007961a6bedb..8d4e7e09b458 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -21,7 +21,8 @@ #include <SkPicture.h> #include <SkRefCnt.h> -#include <SkTLazy.h> + +#include <optional> namespace android { @@ -126,13 +127,13 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { // Only called on the RenderThread. void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { - SkTLazy<SkPaint> lazyPaint; + std::optional<SkPaint> lazyPaint; SkAutoCanvasRestore acr(canvas, false); if (mProperties.mAlpha != SK_AlphaOPAQUE || mProperties.mColorFilter.get()) { - lazyPaint.init(); - lazyPaint.get()->setAlpha(mProperties.mAlpha); - lazyPaint.get()->setColorFilter(mProperties.mColorFilter); - lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality); + lazyPaint.emplace(); + lazyPaint->setAlpha(mProperties.mAlpha); + lazyPaint->setColorFilter(mProperties.mColorFilter); + lazyPaint->setFilterQuality(kLow_SkFilterQuality); } if (mProperties.mMirrored) { canvas->save(); @@ -147,8 +148,8 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { if (drawDirectly) { // The image is not animating, and never was. Draw directly from // mSkAnimatedImage. - if (lazyPaint.isValid()) { - canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get()); + if (lazyPaint) { + canvas->saveLayer(mSkAnimatedImage->getBounds(), &*lazyPaint); } std::unique_lock lock{mImageLock}; @@ -193,7 +194,7 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { if (!drawDirectly) { // No other thread will modify mCurrentSnap so this should be safe to // use without locking. - canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint.getMaybeNull()); + canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint ? &*lazyPaint : nullptr); } if (finalFrame) { diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h index 115c45ae4bcb..f0aa35acf71b 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.h +++ b/libs/hwui/hwui/AnimatedImageDrawable.h @@ -106,9 +106,7 @@ public: Snapshot decodeNextFrame(); Snapshot reset(); - size_t byteSize() const { - return sizeof(this) + mBytesUsed; - } + size_t byteSize() const { return sizeof(*this) + mBytesUsed; } protected: virtual void onDraw(SkCanvas* canvas) override; diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index e7ffbee521ff..75a6e722dd8a 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -15,11 +15,11 @@ */ #include "Bitmap.h" -#include "Caches.h" -#include "renderthread/EglManager.h" +#include "HardwareBitmapUploader.h" +#include "Properties.h" #include "renderthread/RenderProxy.h" -#include "renderthread/RenderThread.h" #include "utils/Color.h" +#include <utils/Trace.h> #include <sys/mman.h> @@ -34,17 +34,17 @@ #include <SkImagePriv.h> #include <SkToSRGBColorFilter.h> +#include <SkHighContrastFilter.h> +#include <limits> + namespace android { +// returns true if rowBytes * height can be represented by a positive int32_t value +// and places that value in size. static bool computeAllocationSize(size_t rowBytes, int height, size_t* size) { - int32_t rowBytes32 = SkToS32(rowBytes); - int64_t bigSize = (int64_t)height * rowBytes32; - if (rowBytes32 < 0 || !sk_64_isS32(bigSize)) { - return false; // allocation will be too large - } - - *size = sk_64_asS32(bigSize); - return true; + return 0 <= height && height <= std::numeric_limits<size_t>::max() && + !__builtin_mul_overflow(rowBytes, (size_t)height, size) && + *size <= std::numeric_limits<int32_t>::max(); } typedef sk_sp<Bitmap> (*AllocPixelRef)(size_t allocSize, const SkImageInfo& info, size_t rowBytes); @@ -85,7 +85,7 @@ static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& info, si } sk_sp<Bitmap> Bitmap::allocateHardwareBitmap(SkBitmap& bitmap) { - return uirenderer::renderthread::RenderProxy::allocateHardwareBitmap(bitmap); + return uirenderer::HardwareBitmapUploader::allocateHardwareBitmap(bitmap); } sk_sp<Bitmap> Bitmap::allocateHeapBitmap(SkBitmap* bitmap) { @@ -134,14 +134,15 @@ sk_sp<Bitmap> Bitmap::createFrom(const SkImageInfo& info, SkPixelRef& pixelRef) } sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer) { - PixelFormat format = graphicBuffer->getPixelFormat(); - if (!graphicBuffer.get() || - (format != PIXEL_FORMAT_RGBA_8888 && format != PIXEL_FORMAT_RGBA_FP16)) { - return nullptr; - } + return createFrom(graphicBuffer, SkColorSpace::MakeSRGB()); +} + +sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer, sk_sp<SkColorSpace> colorSpace) { + // As we will be effectively texture-sampling the buffer (using either EGL or Vulkan), we can + // view the colorspace as RGBA8888. SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(), kRGBA_8888_SkColorType, kPremul_SkAlphaType, - SkColorSpace::MakeSRGB()); + colorSpace); return sk_sp<Bitmap>(new Bitmap(graphicBuffer.get(), info)); } @@ -197,18 +198,18 @@ Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info mPixelStorage.ashmem.size = mappedSize; } -Bitmap::Bitmap(GraphicBuffer* buffer, const SkImageInfo& info) +Bitmap::Bitmap(GraphicBuffer* buffer, const SkImageInfo& info, BitmapPalette palette) : SkPixelRef(info.width(), info.height(), nullptr, bytesPerPixel(buffer->getPixelFormat()) * buffer->getStride()) , mInfo(validateAlpha(info)) - , mPixelStorageType(PixelStorageType::Hardware) { + , mPixelStorageType(PixelStorageType::Hardware) + , mPalette(palette) + , mPaletteGenerationId(getGenerationID()) { mPixelStorage.hardware.buffer = buffer; buffer->incStrong(buffer); setImmutable(); // HW bitmaps are always immutable - if (uirenderer::Properties::isSkiaEnabled()) { - mImage = SkImage::MakeFromAHardwareBuffer(reinterpret_cast<AHardwareBuffer*>(buffer), - mInfo.alphaType(), mInfo.refColorSpace()); - } + mImage = SkImage::MakeFromAHardwareBuffer(reinterpret_cast<AHardwareBuffer*>(buffer), + mInfo.alphaType(), mInfo.refColorSpace()); } Bitmap::~Bitmap() { @@ -231,8 +232,6 @@ Bitmap::~Bitmap() { mPixelStorage.hardware.buffer = nullptr; break; } - - android::uirenderer::renderthread::RenderProxy::onBitmapDestroyed(getStableID()); } bool Bitmap::hasHardwareMipMap() const { @@ -287,16 +286,10 @@ void Bitmap::setAlphaType(SkAlphaType alphaType) { } void Bitmap::getSkBitmap(SkBitmap* outBitmap) { - outBitmap->setHasHardwareMipMap(mHasHardwareMipMap); if (isHardware()) { - if (uirenderer::Properties::isSkiaEnabled()) { - outBitmap->allocPixels(SkImageInfo::Make(info().width(), info().height(), - info().colorType(), info().alphaType(), - nullptr)); - } else { - outBitmap->allocPixels(info()); - } - uirenderer::renderthread::RenderProxy::copyGraphicBufferInto(graphicBuffer(), outBitmap); + outBitmap->allocPixels(SkImageInfo::Make(info().width(), info().height(), + info().colorType(), info().alphaType(), nullptr)); + uirenderer::renderthread::RenderProxy::copyHWBitmapInto(this, outBitmap); if (mInfo.colorSpace()) { sk_sp<SkPixelRef> pixelRef = sk_ref_sp(outBitmap->pixelRef()); outBitmap->setInfo(mInfo); @@ -323,21 +316,102 @@ GraphicBuffer* Bitmap::graphicBuffer() { sk_sp<SkImage> Bitmap::makeImage(sk_sp<SkColorFilter>* outputColorFilter) { sk_sp<SkImage> image = mImage; if (!image) { - SkASSERT(!(isHardware() && uirenderer::Properties::isSkiaEnabled())); + SkASSERT(!isHardware()); SkBitmap skiaBitmap; skiaBitmap.setInfo(info(), rowBytes()); skiaBitmap.setPixelRef(sk_ref_sp(this), 0, 0); - skiaBitmap.setHasHardwareMipMap(mHasHardwareMipMap); // Note we don't cache in this case, because the raster image holds a pointer to this Bitmap // internally and ~Bitmap won't be invoked. // TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here. image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode); } - if (uirenderer::Properties::isSkiaEnabled() && image->colorSpace() != nullptr && - !image->colorSpace()->isSRGB()) { + if (image->colorSpace() != nullptr && !image->colorSpace()->isSRGB()) { *outputColorFilter = SkToSRGBColorFilter::Make(image->refColorSpace()); } return image; } +class MinMaxAverage { +public: + void add(float sample) { + if (mCount == 0) { + mMin = sample; + mMax = sample; + } else { + mMin = std::min(mMin, sample); + mMax = std::max(mMax, sample); + } + mTotal += sample; + mCount++; + } + + float average() { return mTotal / mCount; } + + float min() { return mMin; } + + float max() { return mMax; } + + float delta() { return mMax - mMin; } + +private: + float mMin = 0.0f; + float mMax = 0.0f; + float mTotal = 0.0f; + int mCount = 0; +}; + +BitmapPalette Bitmap::computePalette(const SkImageInfo& info, const void* addr, size_t rowBytes) { + ATRACE_CALL(); + + SkPixmap pixmap{info, addr, rowBytes}; + + // TODO: This calculation of converting to HSV & tracking min/max is probably overkill + // Experiment with something simpler since we just want to figure out if it's "color-ful" + // and then the average perceptual lightness. + + MinMaxAverage hue, saturation, value; + int sampledCount = 0; + + // Sample a grid of 100 pixels to get an overall estimation of the colors in play + const int x_step = std::max(1, pixmap.width() / 10); + const int y_step = std::max(1, pixmap.height() / 10); + for (int x = 0; x < pixmap.width(); x += x_step) { + for (int y = 0; y < pixmap.height(); y += y_step) { + SkColor color = pixmap.getColor(x, y); + if (!info.isOpaque() && SkColorGetA(color) < 75) { + continue; + } + + sampledCount++; + float hsv[3]; + SkColorToHSV(color, hsv); + hue.add(hsv[0]); + saturation.add(hsv[1]); + value.add(hsv[2]); + } + } + + // TODO: Tune the coverage threshold + if (sampledCount < 5) { + ALOGV("Not enough samples, only found %d for image sized %dx%d, format = %d, alpha = %d", + sampledCount, info.width(), info.height(), (int)info.colorType(), + (int)info.alphaType()); + return BitmapPalette::Unknown; + } + + ALOGV("samples = %d, hue [min = %f, max = %f, avg = %f]; saturation [min = %f, max = %f, avg = " + "%f]", + sampledCount, hue.min(), hue.max(), hue.average(), saturation.min(), saturation.max(), + saturation.average()); + + if (hue.delta() <= 20 && saturation.delta() <= .1f) { + if (value.average() >= .5f) { + return BitmapPalette::Light; + } else { + return BitmapPalette::Dark; + } + } + return BitmapPalette::Unknown; +} + } // namespace android diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 4f06656e8e6d..238c764cdea6 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -34,6 +34,14 @@ enum class PixelStorageType { Hardware, }; +// TODO: Find a better home for this. It's here because hwui/Bitmap is exported and CanvasTransform +// isn't, but cleanup should be done +enum class BitmapPalette { + Unknown, + Light, + Dark, +}; + namespace uirenderer { namespace renderthread { class RenderThread; @@ -56,6 +64,8 @@ public: size_t rowBytes); static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer); + static sk_sp<Bitmap> createFrom(sp<GraphicBuffer> graphicBuffer, + sk_sp<SkColorSpace> colorSpace); static sk_sp<Bitmap> createFrom(const SkImageInfo&, SkPixelRef&); @@ -63,11 +73,10 @@ public: Bitmap(void* address, void* context, FreeFunc freeFunc, const SkImageInfo& info, size_t rowBytes); Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes); - Bitmap(GraphicBuffer* buffer, const SkImageInfo& info); + Bitmap(GraphicBuffer* buffer, const SkImageInfo& info, + BitmapPalette palette = BitmapPalette::Unknown); - int rowBytesAsPixels() const { - return rowBytes() >> mInfo.shiftPerPixel(); - } + int rowBytesAsPixels() const { return rowBytes() >> mInfo.shiftPerPixel(); } void reconfigure(const SkImageInfo& info, size_t rowBytes); void reconfigure(const SkImageInfo& info); @@ -105,6 +114,20 @@ public: */ sk_sp<SkImage> makeImage(sk_sp<SkColorFilter>* outputColorFilter); + static BitmapPalette computePalette(const SkImageInfo& info, const void* addr, size_t rowBytes); + + static BitmapPalette computePalette(const SkBitmap& bitmap) { + return computePalette(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes()); + } + + BitmapPalette palette() { + if (!isHardware() && mPaletteGenerationId != getGenerationID()) { + mPalette = computePalette(info(), pixels(), rowBytes()); + mPaletteGenerationId = getGenerationID(); + } + return mPalette; + } + private: virtual ~Bitmap(); void* getStorage() const; @@ -113,6 +136,9 @@ private: const PixelStorageType mPixelStorageType; + BitmapPalette mPalette = BitmapPalette::Unknown; + uint32_t mPaletteGenerationId = -1; + bool mHasHardwareMipMap = false; union { diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp index 20543df85068..e2ea2bc37375 100644 --- a/libs/hwui/hwui/Canvas.cpp +++ b/libs/hwui/hwui/Canvas.cpp @@ -19,20 +19,16 @@ #include "MinikinUtils.h" #include "Paint.h" #include "Properties.h" -#include "RecordingCanvas.h" #include "RenderNode.h" #include "Typeface.h" #include "pipeline/skia/SkiaRecordingCanvas.h" -#include <SkDrawFilter.h> +#include "hwui/PaintFilter.h" namespace android { Canvas* Canvas::create_recording_canvas(int width, int height, uirenderer::RenderNode* renderNode) { - if (uirenderer::Properties::isSkiaEnabled()) { - return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height); - } - return new uirenderer::RecordingCanvas(width, height); + return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height); } static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness, @@ -44,10 +40,10 @@ static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkSca void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { uint32_t flags; - SkDrawFilter* drawFilter = getDrawFilter(); - if (drawFilter) { + PaintFilter* paintFilter = getPaintFilter(); + if (paintFilter) { SkPaint paintCopy(paint); - drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type); + paintFilter->filter(&paintCopy); flags = paintCopy.getFlags(); } else { flags = paint.getFlags(); @@ -156,15 +152,14 @@ private: float totalAdvance; }; -void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount, float x, - float y, minikin::Bidi bidiFlags, const Paint& origPaint, - const Typeface* typeface, minikin::MeasuredText* mt) { +void Canvas::drawText(const uint16_t* text, int textSize, int start, int count, int contextStart, + int contextCount, float x, float y, minikin::Bidi bidiFlags, + const Paint& origPaint, const Typeface* typeface, minikin::MeasuredText* mt) { // minikin may modify the original paint Paint paint(origPaint); - minikin::Layout layout = - MinikinUtils::doLayout(&paint, bidiFlags, typeface, text, start, count, contextCount, - mt); + minikin::Layout layout = MinikinUtils::doLayout(&paint, bidiFlags, typeface, text, textSize, + start, count, contextStart, contextCount, mt); x += MinikinUtils::xOffsetForTextAlign(&paint, layout); @@ -183,6 +178,41 @@ void Canvas::drawText(const uint16_t* text, int start, int count, int contextCou MinikinUtils::forFontRun(layout, &paint, f); } +void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight, + float outerBottom, float outerRx, float outerRy, float innerLeft, + float innerTop, float innerRight, float innerBottom, float innerRx, + float innerRy, const SkPaint& paint) { + if (CC_UNLIKELY(paint.nothingToDraw())) return; + SkRect outer = SkRect::MakeLTRB(outerLeft, outerTop, outerRight, outerBottom); + SkRect inner = SkRect::MakeLTRB(innerLeft, innerTop, innerRight, innerBottom); + + SkRRect outerRRect; + outerRRect.setRectXY(outer, outerRx, outerRy); + + SkRRect innerRRect; + innerRRect.setRectXY(inner, innerRx, innerRy); + drawDoubleRoundRect(outerRRect, innerRRect, paint); +} + +void Canvas::drawDoubleRoundRectRadii(float outerLeft, float outerTop, float outerRight, + float outerBottom, const float* outerRadii, float innerLeft, + float innerTop, float innerRight, float innerBottom, + const float* innerRadii, const SkPaint& paint) { + static_assert(sizeof(SkVector) == sizeof(float) * 2); + if (CC_UNLIKELY(paint.nothingToDraw())) return; + SkRect outer = SkRect::MakeLTRB(outerLeft, outerTop, outerRight, outerBottom); + SkRect inner = SkRect::MakeLTRB(innerLeft, innerTop, innerRight, innerBottom); + + SkRRect outerRRect; + const SkVector* outerSkVector = reinterpret_cast<const SkVector*>(outerRadii); + outerRRect.setRectRadii(outer, outerSkVector); + + SkRRect innerRRect; + const SkVector* innerSkVector = reinterpret_cast<const SkVector*>(innerRadii); + innerRRect.setRectRadii(inner, innerSkVector); + drawDoubleRoundRect(outerRRect, innerRRect, paint); +} + class DrawTextOnPathFunctor { public: DrawTextOnPathFunctor(const minikin::Layout& layout, Canvas* canvas, float hOffset, @@ -212,7 +242,10 @@ void Canvas::drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiF const Typeface* typeface) { Paint paintCopy(paint); minikin::Layout layout = - MinikinUtils::doLayout(&paintCopy, bidiFlags, typeface, text, 0, count, count, nullptr); + MinikinUtils::doLayout(&paintCopy, bidiFlags, typeface, text, count, // text buffer + 0, count, // draw range + 0, count, // context range + nullptr); hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path); // Set align to left for drawing, as we don't want individual diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index f341cf96120d..e99742bc2eba 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -39,13 +39,22 @@ enum class Bidi : uint8_t; } namespace android { +class PaintFilter; namespace uirenderer { class CanvasPropertyPaint; class CanvasPropertyPrimitive; class DeferredLayerUpdater; -class DisplayList; class RenderNode; + +namespace skiapipeline { +class SkiaDisplayList; +} + +/** + * Data structure that holds the list of commands used in display list stream + */ +using DisplayList = skiapipeline::SkiaDisplayList; } namespace SaveFlags { @@ -65,7 +74,6 @@ typedef uint32_t Flags; } // namespace SaveFlags namespace uirenderer { -class SkiaCanvasProxy; namespace VectorDrawable { class Tree; }; @@ -205,8 +213,8 @@ public: virtual bool clipPath(const SkPath* path, SkClipOp op) = 0; // filters - virtual SkDrawFilter* getDrawFilter() = 0; - virtual void setDrawFilter(SkDrawFilter* drawFilter) = 0; + virtual PaintFilter* getPaintFilter() = 0; + virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) = 0; // WebView only virtual SkCanvasState* captureCanvasState() const { return nullptr; } @@ -228,6 +236,8 @@ public: 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 drawDoubleRoundRect(const SkRRect& outer, const SkRRect& inner, + const SkPaint& paint) = 0; virtual void drawCircle(float x, float y, float radius, const SkPaint& paint) = 0; virtual void drawOval(float left, float top, float right, float bottom, const SkPaint& paint) = 0; @@ -268,14 +278,24 @@ public: * Converts utf16 text to glyphs, calculating position and boundary, * and delegating the final draw to virtual drawGlyphs method. */ - void drawText(const uint16_t* text, int start, int count, int contextCount, float x, float y, - minikin::Bidi bidiFlags, const Paint& origPaint, const Typeface* typeface, - minikin::MeasuredText* mt); + void drawText(const uint16_t* text, int textSize, int start, int count, int contextStart, + int contextCount, float x, float y, minikin::Bidi bidiFlags, + const Paint& origPaint, const Typeface* typeface, minikin::MeasuredText* mt); void drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiFlags, const SkPath& path, float hOffset, float vOffset, const Paint& paint, const Typeface* typeface); + void drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight, + float outerBottom, float outerRx, float outerRy, float innerLeft, + float innerTop, float innerRight, float innerBottom, float innerRx, + float innerRy, const SkPaint& paint); + + void drawDoubleRoundRectRadii(float outerLeft, float outerTop, float outerRight, + float outerBottom, const float* outerRadii, float innerLeft, + float innerTop, float innerRight, float innerBottom, + const float* innerRadii, const SkPaint& paint); + static int GetApiLevel() { return sApiLevel; } protected: @@ -296,7 +316,6 @@ protected: friend class DrawTextFunctor; friend class DrawTextOnPathFunctor; - friend class uirenderer::SkiaCanvasProxy; }; }; // namespace android diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp index 5d33860bab6b..769fce498a70 100644 --- a/libs/hwui/hwui/MinikinSkia.cpp +++ b/libs/hwui/hwui/MinikinSkia.cpp @@ -22,16 +22,23 @@ #include <SkTypeface.h> #include <log/log.h> +#include <minikin/Font.h> +#include <minikin/MinikinExtent.h> +#include <minikin/MinikinPaint.h> +#include <minikin/MinikinRect.h> + namespace android { MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize, - int ttcIndex, const std::vector<minikin::FontVariation>& axes) + std::string_view filePath, int ttcIndex, + const std::vector<minikin::FontVariation>& axes) : minikin::MinikinFont(typeface->uniqueID()) , mTypeface(std::move(typeface)) , mFontData(fontData) , mFontSize(fontSize) , mTtcIndex(ttcIndex) - , mAxes(axes) {} + , mAxes(axes) + , mFilePath(filePath) {} static void MinikinFontSkia_SetSkiaPaint(const minikin::MinikinFont* font, SkPaint* skPaint, const minikin::MinikinPaint& paint, @@ -77,11 +84,10 @@ void MinikinFontSkia::GetFontExtent(minikin::MinikinExtent* extent, const minikin::FontFakery& fakery) const { SkPaint skPaint; MinikinFontSkia_SetSkiaPaint(this, &skPaint, paint, fakery); - SkPaint::FontMetrics metrics; + SkFontMetrics metrics; skPaint.getFontMetrics(&metrics); extent->ascent = metrics.fAscent; extent->descent = metrics.fDescent; - extent->line_gap = metrics.fLeading; } SkTypeface* MinikinFontSkia::GetSkTypeface() const { @@ -127,25 +133,24 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation( sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), params)); - return std::make_shared<MinikinFontSkia>(std::move(face), mFontData, mFontSize, ttcIndex, - variations); + return std::make_shared<MinikinFontSkia>(std::move(face), mFontData, mFontSize, mFilePath, + ttcIndex, variations); } uint32_t MinikinFontSkia::packPaintFlags(const SkPaint* paint) { uint32_t flags = paint->getFlags(); - SkPaint::Hinting hinting = paint->getHinting(); + unsigned hinting = static_cast<unsigned>(paint->getHinting()); // select only flags that might affect text layout flags &= (SkPaint::kAntiAlias_Flag | SkPaint::kFakeBoldText_Flag | SkPaint::kLinearText_Flag | - SkPaint::kSubpixelText_Flag | SkPaint::kDevKernText_Flag | - SkPaint::kEmbeddedBitmapText_Flag | SkPaint::kAutoHinting_Flag | - SkPaint::kVerticalText_Flag); + SkPaint::kSubpixelText_Flag | SkPaint::kEmbeddedBitmapText_Flag | + SkPaint::kAutoHinting_Flag); flags |= (hinting << 16); return flags; } void MinikinFontSkia::unpackPaintFlags(SkPaint* paint, uint32_t paintFlags) { paint->setFlags(paintFlags & SkPaint::kAllFlags); - paint->setHinting(static_cast<SkPaint::Hinting>(paintFlags >> 16)); + paint->setHinting(static_cast<SkFontHinting>(paintFlags >> 16)); } void MinikinFontSkia::populateSkPaint(SkPaint* paint, const MinikinFont* font, diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h index d1565986304f..55576b7bfa4e 100644 --- a/libs/hwui/hwui/MinikinSkia.h +++ b/libs/hwui/hwui/MinikinSkia.h @@ -28,8 +28,9 @@ namespace android { class ANDROID_API MinikinFontSkia : public minikin::MinikinFont { public: - explicit MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize, - int ttcIndex, const std::vector<minikin::FontVariation>& axes); + MinikinFontSkia(sk_sp<SkTypeface> typeface, const void* fontData, size_t fontSize, + std::string_view filePath, int ttcIndex, + const std::vector<minikin::FontVariation>& axes); float GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint, const minikin::FontFakery& fakery) const override; @@ -48,6 +49,7 @@ public: const void* GetFontData() const; size_t GetFontSize() const; int GetFontIndex() const; + const std::string& getFilePath() const { return mFilePath; } const std::vector<minikin::FontVariation>& GetAxes() const; std::shared_ptr<minikin::MinikinFont> createFontWithVariation( const std::vector<minikin::FontVariation>&) const; @@ -68,6 +70,7 @@ private: size_t mFontSize; int mTtcIndex; std::vector<minikin::FontVariation> mAxes; + std::string mFilePath; }; } // namespace android diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 5b69bb78101f..ba240feb4f41 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -48,23 +48,25 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, } minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFlags, - const Typeface* typeface, const uint16_t* buf, size_t start, - size_t count, size_t bufSize, minikin::MeasuredText* mt) { + const Typeface* typeface, const uint16_t* buf, + size_t bufSize, size_t start, size_t count, + size_t contextStart, size_t contextCount, + minikin::MeasuredText* mt) { minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); - minikin::Layout layout; const minikin::U16StringPiece textBuf(buf, bufSize); const minikin::Range range(start, start + count); + const minikin::Range contextRange(contextStart, contextStart + contextCount); const minikin::HyphenEdit hyphenEdit = static_cast<minikin::HyphenEdit>(paint->getHyphenEdit()); const minikin::StartHyphenEdit startHyphen = minikin::startHyphenEdit(hyphenEdit); const minikin::EndHyphenEdit endHyphen = minikin::endHyphenEdit(hyphenEdit); if (mt == nullptr) { - layout.doLayout(textBuf,range, bidiFlags, minikinPaint, startHyphen, endHyphen); + return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags, + minikinPaint, startHyphen, endHyphen); } else { - mt->buildLayout(textBuf, range, minikinPaint, bidiFlags, startHyphen, endHyphen, &layout); + return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen); } - return layout; } float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, @@ -78,8 +80,7 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const minikin::EndHyphenEdit endHyphen = minikin::endHyphenEdit(hyphenEdit); return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen, - endHyphen, advances, nullptr /* extent */, - nullptr /* layout pieces */); + endHyphen, advances); } bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) { diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h index 77dfbb21f7b5..d27d54454ea0 100644 --- a/libs/hwui/hwui/MinikinUtils.h +++ b/libs/hwui/hwui/MinikinUtils.h @@ -25,11 +25,11 @@ #define _ANDROID_GRAPHICS_MINIKIN_UTILS_H_ #include <cutils/compiler.h> +#include <log/log.h> #include <minikin/Layout.h> #include "MinikinSkia.h" #include "Paint.h" #include "Typeface.h" -#include <log/log.h> namespace minikin { class MeasuredText; @@ -44,7 +44,8 @@ public: ANDROID_API static minikin::Layout doLayout(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, - size_t start, size_t count, size_t bufSize, + size_t bufSize, size_t start, size_t count, + size_t contextStart, size_t contextCount, minikin::MeasuredText* mt); ANDROID_API static float measureText(const Paint* paint, minikin::Bidi bidiFlags, diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 002f75906c35..c1a3b6d1143b 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -25,6 +25,7 @@ #include <string> #include <minikin/FontFamily.h> +#include <minikin/FamilyVariant.h> namespace android { @@ -73,9 +74,9 @@ public: uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; } - void setFamilyVariant(minikin::FontFamily::Variant variant) { mFamilyVariant = variant; } + void setFamilyVariant(minikin::FamilyVariant variant) { mFamilyVariant = variant; } - minikin::FontFamily::Variant getFamilyVariant() const { return mFamilyVariant; } + minikin::FamilyVariant getFamilyVariant() const { return mFamilyVariant; } void setHyphenEdit(uint32_t hyphen) { mHyphenEdit = hyphen; } @@ -85,18 +86,27 @@ public: const Typeface* getAndroidTypeface() const { return mTypeface; } + enum Align { + kLeft_Align, + kCenter_Align, + kRight_Align, + }; + Align getTextAlign() const { return mAlign; } + void setTextAlign(Align align) { mAlign = align; } + private: float mLetterSpacing = 0; float mWordSpacing = 0; std::string mFontFeatureSettings; uint32_t mMinikinLocaleListId; - minikin::FontFamily::Variant mFamilyVariant; + minikin::FamilyVariant mFamilyVariant; uint32_t mHyphenEdit = 0; // The native Typeface object has the same lifetime of the Java Typeface // object. The Java Paint object holds a strong reference to the Java Typeface // object. Thus, following pointer can never be a dangling pointer. Note that // nullptr is valid: it means the default typeface. const Typeface* mTypeface = nullptr; + Align mAlign = kLeft_Align; }; } // namespace android diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h new file mode 100644 index 000000000000..bf5627eac229 --- /dev/null +++ b/libs/hwui/hwui/PaintFilter.h @@ -0,0 +1,19 @@ +#ifndef ANDROID_GRAPHICS_PAINT_FILTER_H_ +#define ANDROID_GRAPHICS_PAINT_FILTER_H_ + +class SkPaint; + +namespace android { + +class PaintFilter : public SkRefCnt { +public: + /** + * Called with the paint that will be used to draw. + * The implementation may modify the paint as they wish. + */ + virtual void filter(SkPaint*) = 0; +}; + +} // namespace android + +#endif diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp index ae9c475d09d4..bdbf5cacaaf0 100644 --- a/libs/hwui/hwui/PaintImpl.cpp +++ b/libs/hwui/hwui/PaintImpl.cpp @@ -24,7 +24,7 @@ Paint::Paint() , mWordSpacing(0) , mFontFeatureSettings() , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FontFamily::Variant::DEFAULT) {} + , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {} Paint::Paint(const Paint& paint) : SkPaint(paint) @@ -34,7 +34,8 @@ Paint::Paint(const Paint& paint) , mMinikinLocaleListId(paint.mMinikinLocaleListId) , mFamilyVariant(paint.mFamilyVariant) , mHyphenEdit(paint.mHyphenEdit) - , mTypeface(paint.mTypeface) {} + , mTypeface(paint.mTypeface) + , mAlign(paint.mAlign) {} Paint::Paint(const SkPaint& paint) : SkPaint(paint) @@ -42,7 +43,7 @@ Paint::Paint(const SkPaint& paint) , mWordSpacing(0) , mFontFeatureSettings() , mMinikinLocaleListId(0) - , mFamilyVariant(minikin::FontFamily::Variant::DEFAULT) {} + , mFamilyVariant(minikin::FamilyVariant::DEFAULT) {} Paint::~Paint() {} @@ -55,6 +56,7 @@ Paint& Paint::operator=(const Paint& other) { mFamilyVariant = other.mFamilyVariant; mHyphenEdit = other.mHyphenEdit; mTypeface = other.mTypeface; + mAlign = other.mAlign; return *this; } @@ -64,6 +66,6 @@ bool operator==(const Paint& a, const Paint& b) { a.mFontFeatureSettings == b.mFontFeatureSettings && a.mMinikinLocaleListId == b.mMinikinLocaleListId && a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit && - a.mTypeface == b.mTypeface; + a.mTypeface == b.mTypeface && a.mAlign == b.mAlign; } } // namespace android diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index dca9ef5559a6..c4d8aa6c8fad 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -132,8 +132,10 @@ Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::Font bool italicFromFont; const minikin::FontStyle defaultStyle; - const minikin::MinikinFont* mf = families.empty() ? nullptr - : families[0]->getClosestMatch(defaultStyle).font->typeface().get(); + const minikin::MinikinFont* mf = + families.empty() + ? nullptr + : families[0]->getClosestMatch(defaultStyle).font->typeface().get(); if (mf != nullptr) { SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface(); const SkFontStyle& style = skTypeface->fontStyle(); @@ -176,12 +178,13 @@ void Typeface::setRobotoTypefaceForTest() { struct stat st = {}; LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont); void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0); - std::unique_ptr<SkMemoryStream> fontData(new SkMemoryStream(data, st.st_size)); - sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(fontData.release()); + std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size)); + sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData)); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont); std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>( - std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>()); + std::move(typeface), data, st.st_size, kRobotoFont, 0, + std::vector<minikin::FontVariation>()); std::vector<minikin::Font> fonts; fonts.push_back(minikin::Font::Builder(font).build()); diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h index 1f83d1a201b0..e4ba13da709c 100644 --- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h +++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h @@ -94,11 +94,6 @@ protected: mOutput << mIdent << "drawPosTextH" << std::endl; } - void onDrawTextOnPath(const void*, size_t, const SkPath&, const SkMatrix*, - const SkPaint&) override { - mOutput << mIdent << "drawTextOnPath" << std::endl; - } - void onDrawTextRSXform(const void*, size_t, const SkRSXform[], const SkRect*, const SkPaint&) override { mOutput << mIdent << "drawTextRSXform" << std::endl; @@ -143,7 +138,7 @@ protected: renderNodeDrawable->getRenderNode()->output(mOutput, mLevel + 1); return; } - auto glFunctorDrawable = getGLFunctorDrawable(drawable); + auto glFunctorDrawable = getFunctorDrawable(drawable); if (nullptr != glFunctorDrawable) { mOutput << std::string(mLevel * 2, ' ') << "drawGLFunctorDrawable" << std::endl; return; @@ -162,10 +157,10 @@ private: return nullptr; } - GLFunctorDrawable* getGLFunctorDrawable(SkDrawable* drawable) { + FunctorDrawable* getFunctorDrawable(SkDrawable* drawable) { for (auto& child : mDisplayList.mChildFunctors) { - if (drawable == &child) { - return &child; + if (drawable == child) { + return child; } } return nullptr; diff --git a/libs/hwui/pipeline/skia/FunctorDrawable.h b/libs/hwui/pipeline/skia/FunctorDrawable.h new file mode 100644 index 000000000000..162d13762e1a --- /dev/null +++ b/libs/hwui/pipeline/skia/FunctorDrawable.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 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 "GlFunctorLifecycleListener.h" + +#include <SkCanvas.h> +#include <SkDrawable.h> + +#include <utils/Functor.h> + +namespace android { +namespace uirenderer { + +namespace skiapipeline { + +/** + * This drawable wraps a functor enabling it to be recorded into a list + * of Skia drawing commands. + */ +class FunctorDrawable : public SkDrawable { +public: + FunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas) + : mFunctor(functor), mListener(listener), mBounds(canvas->getLocalClipBounds()) {} + virtual ~FunctorDrawable() {} + + virtual void syncFunctor() const = 0; + +protected: + virtual SkRect onGetBounds() override { return mBounds; } + + Functor* mFunctor; + sp<GlFunctorLifecycleListener> mListener; + const SkRect mBounds; +}; + +}; // namespace skiapipeline +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 3684bc1e6a1f..90d5e715f8cd 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -25,7 +25,6 @@ #include "GrBackendSurface.h" #include "GrRenderTarget.h" #include "GrRenderTargetContext.h" -#include "GrGLTypes.h" namespace android { namespace uirenderer { @@ -63,15 +62,13 @@ static bool GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSiz return false; } - GrBackendRenderTarget backendTarget = renderTarget->getBackendRenderTarget(); - const GrGLFramebufferInfo* fboInfo = backendTarget.getGLFramebufferInfo(); - - if (!fboInfo) { + GrGLFramebufferInfo fboInfo; + if (!renderTarget->getBackendRenderTarget().getGLFramebufferInfo(&fboInfo)) { ALOGW("Unable to extract renderTarget info from canvas; aborting GLFunctor draw"); return false; } - *outFboID = fboInfo->fFBOID; + *outFboID = fboInfo.fFBOID; *outFboSize = SkISize::Make(renderTargetContext->width(), renderTargetContext->height()); return true; } @@ -82,11 +79,6 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { return; } - if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { - canvas->clear(SK_ColorRED); - return; - } - GLuint fboID = 0; SkISize fboSize; if (!GetFboDetails(canvas, &fboID, &fboSize)) { @@ -109,14 +101,15 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { surfaceInfo); tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT); - GrBackendObject backendObject; - if (!tmpSurface->getRenderTargetHandle(&backendObject, SkSurface::kFlushWrite_BackendHandleAccess)) { + GrGLFramebufferInfo fboInfo; + if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess) + .getGLFramebufferInfo(&fboInfo)) { ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor"); return; } fboSize = SkISize::Make(surfaceInfo.width(), surfaceInfo.height()); - fboID = (GLuint)backendObject; + fboID = fboInfo.fFBOID; // update the matrix and clip that we pass to the WebView to match the coordinates of // the offscreen layer diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.h b/libs/hwui/pipeline/skia/GLFunctorDrawable.h index af57d7d33c2c..dd6ef25f30c5 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.h +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.h @@ -16,39 +16,29 @@ #pragma once -#include <SkCanvas.h> -#include <SkDrawable.h> +#include "FunctorDrawable.h" -#include <utils/Functor.h> #include <utils/RefBase.h> namespace android { namespace uirenderer { -class GlFunctorLifecycleListener; - namespace skiapipeline { /** * This drawable wraps a OpenGL functor enabling it to be recorded into a list * of Skia drawing commands. */ -class GLFunctorDrawable : public SkDrawable { +class GLFunctorDrawable : public FunctorDrawable { public: GLFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, SkCanvas* canvas) - : mFunctor(functor), mListener(listener), mBounds(canvas->getLocalClipBounds()) {} + : FunctorDrawable(functor, listener, canvas) {} virtual ~GLFunctorDrawable(); - void syncFunctor() const; + void syncFunctor() const override; protected: - virtual SkRect onGetBounds() override { return mBounds; } virtual void onDraw(SkCanvas* canvas) override; - -private: - Functor* mFunctor; - sp<GlFunctorLifecycleListener> mListener; - const SkRect mBounds; }; }; // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp index ab1d5c2546ed..13d2dae8e281 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.cpp +++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp @@ -15,8 +15,6 @@ */ #include "LayerDrawable.h" -#include "GlLayer.h" -#include "VkLayer.h" #include "GrBackendSurface.h" #include "SkColorFilter.h" @@ -30,91 +28,73 @@ namespace skiapipeline { void LayerDrawable::onDraw(SkCanvas* canvas) { Layer* layer = mLayerUpdater->backingLayer(); if (layer) { - DrawLayer(canvas->getGrContext(), canvas, layer); + DrawLayer(canvas->getGrContext(), canvas, layer, nullptr, nullptr, true); } } bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer, - const SkRect* dstRect) { + const SkRect* srcRect, const SkRect* dstRect, + bool useLayerTransform) { if (context == nullptr) { SkDEBUGF(("Attempting to draw LayerDrawable into an unsupported surface")); return false; } // transform the matrix based on the layer - SkMatrix layerTransform; - layer->getTransform().copyTo(layerTransform); - sk_sp<SkImage> layerImage; + SkMatrix layerTransform = layer->getTransform(); + sk_sp<SkImage> layerImage = layer->getImage(); const int layerWidth = layer->getWidth(); const int layerHeight = layer->getHeight(); - const int bufferWidth = layer->getBufferWidth(); - const int bufferHeight = layer->getBufferHeight(); - if (bufferWidth <= 0 || bufferHeight <=0) { - return false; - } - if (layer->getApi() == Layer::Api::OpenGL) { - GlLayer* glLayer = static_cast<GlLayer*>(layer); - GrGLTextureInfo externalTexture; - externalTexture.fTarget = glLayer->getRenderTarget(); - externalTexture.fID = glLayer->getTextureId(); - // The format may not be GL_RGBA8, but given the DeferredLayerUpdater and GLConsumer don't - // expose that info we use it as our default. Further, given that we only use this texture - // as a source this will not impact how Skia uses the texture. The only potential affect - // this is anticipated to have is that for some format types if we are not bound as an OES - // texture we may get invalid results for SKP capture if we read back the texture. - externalTexture.fFormat = GL_RGBA8; - GrBackendTexture backendTexture(bufferWidth, bufferHeight, GrMipMapped::kNo, externalTexture); - layerImage = SkImage::MakeFromTexture(context, backendTexture, kTopLeft_GrSurfaceOrigin, - kPremul_SkAlphaType, nullptr); - } else { - SkASSERT(layer->getApi() == Layer::Api::Vulkan); - VkLayer* vkLayer = static_cast<VkLayer*>(layer); - canvas->clear(SK_ColorGREEN); - layerImage = vkLayer->getImage(); - } if (layerImage) { SkMatrix textureMatrixInv; - layer->getTexTransform().copyTo(textureMatrixInv); + textureMatrixInv = layer->getTexTransform(); // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed // use bottom left origin and remove flipV and invert transformations. SkMatrix flipV; flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1); textureMatrixInv.preConcat(flipV); textureMatrixInv.preScale(1.0f / layerWidth, 1.0f / layerHeight); - textureMatrixInv.postScale(bufferWidth, bufferHeight); + textureMatrixInv.postScale(layerImage->width(), layerImage->height()); SkMatrix textureMatrix; if (!textureMatrixInv.invert(&textureMatrix)) { textureMatrix = textureMatrixInv; } SkMatrix matrix; - if (dstRect) { - // Destination rectangle is set only when we are trying to read back the content - // of the layer. In this case we don't want to apply layer transform. - matrix = textureMatrix; - } else { + if (useLayerTransform) { matrix = SkMatrix::Concat(layerTransform, textureMatrix); + } else { + matrix = textureMatrix; } SkPaint paint; paint.setAlpha(layer->getAlpha()); paint.setBlendMode(layer->getMode()); paint.setColorFilter(layer->getColorSpaceWithFilter()); - const bool nonIdentityMatrix = !matrix.isIdentity(); if (nonIdentityMatrix) { canvas->save(); canvas->concat(matrix); } const SkMatrix& totalMatrix = canvas->getTotalMatrix(); - if (dstRect) { + if (dstRect || srcRect) { SkMatrix matrixInv; if (!matrix.invert(&matrixInv)) { matrixInv = matrix; } - SkRect srcRect = SkRect::MakeIWH(layerWidth, layerHeight); - matrixInv.mapRect(&srcRect); - SkRect skiaDestRect = *dstRect; + SkRect skiaSrcRect; + if (srcRect) { + skiaSrcRect = *srcRect; + } else { + skiaSrcRect = SkRect::MakeIWH(layerWidth, layerHeight); + } + matrixInv.mapRect(&skiaSrcRect); + SkRect skiaDestRect; + if (dstRect) { + skiaDestRect = *dstRect; + } else { + skiaDestRect = SkRect::MakeIWH(layerWidth, layerHeight); + } matrixInv.mapRect(&skiaDestRect); // If (matrix is identity or an integer translation) and (src/dst buffers size match), // then use nearest neighbor, otherwise use bilerp sampling. @@ -123,13 +103,13 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer // only for SrcOver blending and without color filter (readback uses Src blending). bool isIntegerTranslate = totalMatrix.isTranslate() && SkScalarFraction(skiaDestRect.fLeft + totalMatrix[SkMatrix::kMTransX]) - == SkScalarFraction(srcRect.fLeft) + == SkScalarFraction(skiaSrcRect.fLeft) && SkScalarFraction(skiaDestRect.fTop + totalMatrix[SkMatrix::kMTransY]) - == SkScalarFraction(srcRect.fTop); + == SkScalarFraction(skiaSrcRect.fTop); if (layer->getForceFilter() || !isIntegerTranslate) { paint.setFilterQuality(kLow_SkFilterQuality); } - canvas->drawImageRect(layerImage.get(), srcRect, skiaDestRect, &paint, + canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, &paint, SkCanvas::kFast_SrcRectConstraint); } else { bool isIntegerTranslate = totalMatrix.isTranslate() @@ -146,7 +126,7 @@ bool LayerDrawable::DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer } } - return layerImage; + return layerImage != nullptr; } }; // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/LayerDrawable.h b/libs/hwui/pipeline/skia/LayerDrawable.h index 18d118405a39..5c125908ffb2 100644 --- a/libs/hwui/pipeline/skia/LayerDrawable.h +++ b/libs/hwui/pipeline/skia/LayerDrawable.h @@ -33,7 +33,7 @@ public: explicit LayerDrawable(DeferredLayerUpdater* layerUpdater) : mLayerUpdater(layerUpdater) {} static bool DrawLayer(GrContext* context, SkCanvas* canvas, Layer* layer, - const SkRect* dstRect = nullptr); + const SkRect* srcRect, const SkRect* dstRect, bool useLayerTransform); protected: virtual SkRect onGetBounds() override { diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 6c04d7862979..ea14d11b7b3e 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -21,10 +21,25 @@ #include "SkiaPipeline.h" #include "utils/TraceUtils.h" +#include <optional> + namespace android { namespace uirenderer { namespace skiapipeline { +RenderNodeDrawable::RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer, + bool inReorderingSection) + : mRenderNode(node) + , mRecordedTransform(canvas->getTotalMatrix()) + , mComposeLayer(composeLayer) + , mInReorderingSection(inReorderingSection) {} + +RenderNodeDrawable::~RenderNodeDrawable() { + // Just here to move the destructor into the cpp file where we can access RenderNode. + + // TODO: Detangle the header nightmare. +} + void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList, int nestLevel) { @@ -115,7 +130,6 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) { return; } - SkASSERT(renderNode->getDisplayList()->isSkiaDL()); SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList(); SkAutoCanvasRestore acr(canvas, true); @@ -130,7 +144,7 @@ void RenderNodeDrawable::forceDraw(SkCanvas* canvas) { LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline); const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath(); SkAutoCanvasRestore acr2(canvas, shouldClip); - canvas->setMatrix(mProjectedDisplayList->mProjectedReceiverParentMatrix); + canvas->setMatrix(mProjectedDisplayList->mParentMatrix); if (shouldClip) { clipOutline(*mProjectedDisplayList->mProjectedOutline, canvas, nullptr); } @@ -144,10 +158,10 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip SkPaint* paint) { paint->setFilterQuality(kLow_SkFilterQuality); if (alphaMultiplier < 1.0f || properties.alpha() < 255 || - properties.xferMode() != SkBlendMode::kSrcOver || properties.colorFilter() != nullptr) { + properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr) { paint->setAlpha(properties.alpha() * alphaMultiplier); paint->setBlendMode(properties.xferMode()); - paint->setColorFilter(sk_ref_sp(properties.colorFilter())); + paint->setColorFilter(sk_ref_sp(properties.getColorFilter())); return true; } return false; @@ -159,9 +173,9 @@ public: protected: bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type t) const override { - SkTLazy<SkPaint> defaultPaint; + std::optional<SkPaint> defaultPaint; if (!*paint) { - paint->init(*defaultPaint.init()); + paint->init(defaultPaint.emplace()); } paint->writable()->setAlpha((uint8_t)(*paint)->getAlpha() * mAlpha); return true; @@ -188,9 +202,7 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const { setViewProperties(properties, canvas, &alphaMultiplier); } SkiaDisplayList* displayList = (SkiaDisplayList*)mRenderNode->getDisplayList(); - if (displayList->containsProjectionReceiver()) { - displayList->mProjectedReceiverParentMatrix = canvas->getTotalMatrix(); - } + displayList->mParentMatrix = canvas->getTotalMatrix(); // TODO should we let the bound of the drawable do this for us? const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight()); diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h index ef21cd8a29b5..d746978b0a61 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h @@ -16,6 +16,8 @@ #pragma once +#include "SkiaUtils.h" + #include <SkCanvas.h> #include <SkDrawable.h> #include <SkMatrix.h> @@ -47,11 +49,9 @@ public: * layer into the canvas. */ explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true, - bool inReorderingSection = false) - : mRenderNode(node) - , mRecordedTransform(canvas->getTotalMatrix()) - , mComposeLayer(composeLayer) - , mInReorderingSection(inReorderingSection) {} + bool inReorderingSection = false); + + ~RenderNodeDrawable(); /** * Draws into the canvas this render node and its children. If the node is marked as a @@ -91,7 +91,7 @@ protected: virtual SkRect onGetBounds() override { // We don't want to enable a record time quick reject because the properties // of the RenderNode may be updated on subsequent frames. - return SkRect::MakeLargest(); + return SkRectMakeLargest(); } /** * This function draws into a canvas as forceDraw, but does nothing if the render node has a diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index 25c51f2716e6..dba97fe5ef9f 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -19,10 +19,7 @@ #include "SkiaDisplayList.h" #include "SkiaPipeline.h" -#include <SkBlurMask.h> -#include <SkBlurMaskFilter.h> #include <SkPathOps.h> -#include <SkRRectsGaussianEdgeMaskFilter.h> #include <SkShadowUtils.h> namespace android { @@ -58,6 +55,11 @@ void StartReorderBarrierDrawable::onDraw(SkCanvas* canvas) { if (casterZ >= -NON_ZERO_EPSILON) { // draw only children with negative Z return; } + SkAutoCanvasRestore acr(canvas, true); + // Since we're drawing out of recording order, the child's matrix needs to be applied to the + // canvas. In in-order drawing, the canvas already has the child's matrix applied. + canvas->setMatrix(mDisplayList->mParentMatrix); + canvas->concat(childNode->getRecordedMatrix()); childNode->forceDraw(canvas); drawIndex++; } @@ -105,6 +107,11 @@ void EndReorderBarrierDrawable::onDraw(SkCanvas* canvas) { RenderNodeDrawable* childNode = zChildren[drawIndex]; SkASSERT(childNode); + SkAutoCanvasRestore acr(canvas, true); + // Since we're drawing out of recording order, the child's matrix needs to be applied to the + // canvas. In in-order drawing, the canvas already has the child's matrix applied. + canvas->setMatrix(mStartBarrier->mDisplayList->mParentMatrix); + canvas->concat(childNode->getRecordedMatrix()); childNode->forceDraw(canvas); drawIndex++; @@ -156,10 +163,15 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* } SkAutoCanvasRestore acr(canvas, true); + // Since we're drawing out of recording order, the child's matrix needs to be applied to the + // canvas. In in-order drawing, the canvas already has the child's matrix applied. + canvas->setMatrix(mStartBarrier->mDisplayList->mParentMatrix); SkMatrix shadowMatrix; - mat4 hwuiMatrix; + mat4 hwuiMatrix(caster->getRecordedMatrix()); // TODO we don't pass the optional boolean to treat it as a 4x4 matrix + // applyViewPropertyTransforms gets the same matrix, which render nodes apply with + // RenderNodeDrawable::setViewProperties as a part if their draw. caster->getRenderNode()->applyViewPropertyTransforms(hwuiMatrix); hwuiMatrix.copyTo(shadowMatrix); canvas->concat(shadowMatrix); diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h index 3c48d3604864..26cfa908228c 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.h @@ -17,6 +17,7 @@ #pragma once #include "RenderNodeDrawable.h" +#include "SkiaUtils.h" #include <SkCanvas.h> #include <SkDrawable.h> @@ -41,7 +42,7 @@ public: explicit StartReorderBarrierDrawable(SkiaDisplayList* data); protected: - virtual SkRect onGetBounds() override { return SkRect::MakeLargest(); } + virtual SkRect onGetBounds() override { return SkRectMakeLargest(); } virtual void onDraw(SkCanvas* canvas) override; private: @@ -65,7 +66,7 @@ public: explicit EndReorderBarrierDrawable(StartReorderBarrierDrawable* startBarrier); protected: - virtual SkRect onGetBounds() override { return SkRect::MakeLargest(); } + virtual SkRect onGetBounds() override { return SkRectMakeLargest(); } virtual void onDraw(SkCanvas* canvas) override; private: diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 670074871c71..073b4814305e 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -18,6 +18,8 @@ #include <algorithm> #include <log/log.h> #include <thread> +#include <array> +#include <openssl/sha.h> #include "FileBlobCache.h" #include "Properties.h" #include "utils/TraceUtils.h" @@ -41,7 +43,40 @@ ShaderCache& ShaderCache::get() { return sCache; } -void ShaderCache::initShaderDiskCache() { +bool ShaderCache::validateCache(const void* identity, ssize_t size) { + if (nullptr == identity && size == 0) + return true; + + if (nullptr == identity || size < 0) { + if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) { + ALOGW("ShaderCache::validateCache invalid cache identity"); + } + mBlobCache->clear(); + return false; + } + + SHA256_CTX ctx; + SHA256_Init(&ctx); + + SHA256_Update(&ctx, identity, size); + mIDHash.resize(SHA256_DIGEST_LENGTH); + SHA256_Final(mIDHash.data(), &ctx); + + std::array<uint8_t, SHA256_DIGEST_LENGTH> hash; + auto key = sIDKey; + auto loaded = mBlobCache->get(&key, sizeof(key), hash.data(), hash.size()); + + if (loaded && std::equal(hash.begin(), hash.end(), mIDHash.begin())) + return true; + + if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) { + ALOGW("ShaderCache::validateCache cache validation fails"); + } + mBlobCache->clear(); + return false; +} + +void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { ATRACE_NAME("initShaderDiskCache"); std::lock_guard<std::mutex> lock(mMutex); @@ -50,6 +85,7 @@ void ShaderCache::initShaderDiskCache() { // desktop / laptop GPUs. Thus, disable the shader disk cache for emulator builds. if (!Properties::runningInEmulator && mFilename.length() > 0) { mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename)); + validateCache(identity, size); mInitialized = true; } } @@ -104,6 +140,18 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { return SkData::MakeFromMalloc(valueBuffer, valueSize); } +void ShaderCache::saveToDiskLocked() { + ATRACE_NAME("ShaderCache::saveToDiskLocked"); + if (mInitialized && mBlobCache && mSavePending) { + if (mIDHash.size()) { + auto key = sIDKey; + mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size()); + } + mBlobCache->writeToFile(); + } + mSavePending = false; +} + void ShaderCache::store(const SkData& key, const SkData& data) { ATRACE_NAME("ShaderCache::store"); std::lock_guard<std::mutex> lock(mMutex); @@ -129,11 +177,7 @@ void ShaderCache::store(const SkData& key, const SkData& data) { std::thread deferredSaveThread([this]() { sleep(mDeferredSaveDelay); std::lock_guard<std::mutex> lock(mMutex); - ATRACE_NAME("ShaderCache::saveToDisk"); - if (mInitialized && mBlobCache) { - mBlobCache->writeToFile(); - } - mSavePending = false; + saveToDiskLocked(); }); deferredSaveThread.detach(); } diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 27473d67bd1a..82804cf93785 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -40,12 +40,21 @@ public: ANDROID_API static ShaderCache& get(); /** - * "initShaderDiskCache" loads the serialized cache contents from disk and puts the ShaderCache - * into an initialized state, such that it is able to insert and retrieve entries from the - * cache. This should be called when HWUI pipeline is initialized. When not in the initialized - * state the load and store methods will return without performing any cache operations. + * initShaderDiskCache" loads the serialized cache contents from disk, + * optionally checks that the on-disk cache matches a provided identity, + * and puts the ShaderCache into an initialized state, such that it is + * able to insert and retrieve entries from the cache. If identity is + * non-null and validation fails, the cache is initialized but contains + * no data. If size is less than zero, the cache is initilaized but + * contains no data. + * + * This should be called when HWUI pipeline is initialized. When not in + * the initialized state the load and store methods will return without + * performing any cache operations. */ - virtual void initShaderDiskCache(); + virtual void initShaderDiskCache(const void *identity, ssize_t size); + + virtual void initShaderDiskCache() { initShaderDiskCache(nullptr, 0); } /** * "setFilename" sets the name of the file that should be used to store @@ -83,6 +92,19 @@ private: BlobCache* getBlobCacheLocked(); /** + * "validateCache" updates the cache to match the given identity. If the + * cache currently has the wrong identity, all entries in the cache are cleared. + */ + bool validateCache(const void* identity, ssize_t size); + + /** + * "saveToDiskLocked" attemps to save the current contents of the cache to + * disk. If the identity hash exists, we will insert the identity hash into + * the cache for next validation. + */ + void saveToDiskLocked(); + + /** * "mInitialized" indicates whether the ShaderCache is in the initialized * state. It is initialized to false at construction time, and gets set to * true when initialize is called. @@ -111,6 +133,15 @@ private: std::string mFilename; /** + * "mIDHash" is the current identity hash for the cache validation. It is + * initialized to an empty vector at construction time, and its content is + * generated in the call of the validateCache method. An empty vector + * indicates that cache validation is not performed, and the hash should + * not be stored on disk. + */ + std::vector<uint8_t> mIDHash; + + /** * "mSavePending" indicates whether or not a deferred save operation is * pending. Each time a key/value pair is inserted into the cache via * load, a deferred save is initiated if one is not already pending. @@ -140,6 +171,11 @@ private: */ static ShaderCache sCache; + /** + * "sIDKey" is the cache key of the identity hash + */ + static constexpr uint8_t sIDKey = 0; + friend class ShaderCacheTestUtils; //used for unit testing }; diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index 82179a37f5be..38905138e332 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -29,7 +29,7 @@ namespace skiapipeline { void SkiaDisplayList::syncContents() { for (auto& functor : mChildFunctors) { - functor.syncFunctor(); + functor->syncFunctor(); } for (auto& animatedImage : mAnimatedImages) { animatedImage->syncProperties(); @@ -132,7 +132,6 @@ void SkiaDisplayList::reset() { mChildFunctors.clear(); mChildNodes.clear(); - projectionReceiveIndex = -1; allocator.~LinearAllocator(); new (&allocator) LinearAllocator(); } diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h index 818ec114a5b3..ac7bb7b0950c 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.h +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h @@ -16,20 +16,29 @@ #pragma once -#include "DisplayList.h" #include "hwui/AnimatedImageDrawable.h" -#include "GLFunctorDrawable.h" +#include "FunctorDrawable.h" +#include "RecordingCanvas.h" #include "RenderNodeDrawable.h" +#include "TreeInfo.h" +#include "utils/LinearAllocator.h" -#include <SkLiteDL.h> -#include <SkLiteRecorder.h> #include <deque> namespace android { namespace uirenderer { +namespace renderthread { +class CanvasContext; +} + class Outline; +namespace VectorDrawable { +class Tree; +}; +typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot; + namespace skiapipeline { /** @@ -38,10 +47,11 @@ namespace skiapipeline { * runtime. The downside of this inheritance is that we pay for the overhead * of the parent class construction/destruction without any real benefit. */ -class SkiaDisplayList : public DisplayList { +class SkiaDisplayList { public: - SkiaDisplayList() { SkASSERT(projectionReceiveIndex == -1); } - virtual ~SkiaDisplayList() { + size_t getUsedSize() { return allocator.usedSize(); } + + ~SkiaDisplayList() { /* Given that we are using a LinearStdAllocator to store some of the * SkDrawable contents we must ensure that any other object that is * holding a reference to those drawables is destroyed prior to their @@ -64,33 +74,33 @@ public: * that creates them. Allocator dtor invokes all SkDrawable dtors. */ template <class T, typename... Params> - SkDrawable* allocateDrawable(Params&&... params) { + T* allocateDrawable(Params&&... params) { return allocator.create<T>(std::forward<Params>(params)...); } - bool isSkiaDL() const override { return true; } - /** * Returns true if the DisplayList does not have any recorded content */ - bool isEmpty() const override { return mDisplayList.empty(); } + bool isEmpty() const { return mDisplayList.empty(); } /** * Returns true if this list directly contains a GLFunctor drawing command. */ - bool hasFunctor() const override { return !mChildFunctors.empty(); } + bool hasFunctor() const { return !mChildFunctors.empty(); } /** * Returns true if this list directly contains a VectorDrawable drawing command. */ - bool hasVectorDrawables() const override { return !mVectorDrawables.empty(); } + bool hasVectorDrawables() const { return !mVectorDrawables.empty(); } + + bool hasText() const { return mDisplayList.hasText(); } /** * Attempts to reset and reuse this DisplayList. * * @return true if the displayList will be reused and therefore should not be deleted */ - bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context) override; + bool reuseDisplayList(RenderNode* node, renderthread::CanvasContext* context); /** * ONLY to be called by RenderNode::syncDisplayList so that we can notify any @@ -99,7 +109,7 @@ public: * NOTE: This function can be folded into RenderNode when we no longer need * to subclass from DisplayList */ - void syncContents() override; + void syncContents(); /** * ONLY to be called by RenderNode::prepareTree in order to prepare this @@ -116,25 +126,27 @@ public: bool prepareListAndChildren( TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer, - std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn) override; + std::function<void(RenderNode*, TreeObserver&, TreeInfo&, bool)> childFn); /** * Calls the provided function once for each child of this DisplayList */ - void updateChildren(std::function<void(RenderNode*)> updateFn) override; + void updateChildren(std::function<void(RenderNode*)> updateFn); /** * Returns true if there is a child render node that is a projection receiver. */ inline bool containsProjectionReceiver() const { return mProjectionReceiver; } - void attachRecorder(SkLiteRecorder* recorder, const SkIRect& bounds) { + void attachRecorder(RecordingCanvas* recorder, const SkIRect& bounds) { recorder->reset(&mDisplayList, bounds); } void draw(SkCanvas* canvas) { mDisplayList.draw(canvas); } - void output(std::ostream& output, uint32_t level) override; + void output(std::ostream& output, uint32_t level); + + LinearAllocator allocator; /** * We use std::deque here because (1) we need to iterate through these @@ -142,11 +154,11 @@ public: * cannot relocate. */ std::deque<RenderNodeDrawable> mChildNodes; - std::deque<GLFunctorDrawable> mChildFunctors; + std::deque<FunctorDrawable*> mChildFunctors; std::vector<SkImage*> mMutableImages; std::vector<VectorDrawableRoot*> mVectorDrawables; std::vector<AnimatedImageDrawable*> mAnimatedImages; - SkLiteDL mDisplayList; + DisplayListData mDisplayList; // mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection // receiver. It is set at record time and used at both prepare and draw tree traversals to @@ -159,12 +171,12 @@ public: // node is drawn. const Outline* mProjectedOutline = nullptr; - // mProjectedReceiverParentMatrix is valid when render node tree is traversed during the draw - // pass. Render nodes that have a child receiver node, will store their matrix in - // mProjectedReceiverParentMatrix. Child receiver node will set the matrix and then clip with - // the - // outline of their parent. - SkMatrix mProjectedReceiverParentMatrix; + // mParentMatrix is set and valid when render node tree is traversed during the draw + // pass. Render nodes, which draw in a order different than recording order (e.g. nodes with a + // child receiver node or Z elevation), can use mParentMatrix to calculate the final transform + // without replaying the matrix transform OPs from the display list. + // Child receiver node will set the matrix and then clip with the outline of their parent. + SkMatrix mParentMatrix; }; }; // namespace skiapipeline diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 270527d551a9..d401b385075e 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -17,19 +17,22 @@ #include "SkiaOpenGLPipeline.h" #include "DeferredLayerUpdater.h" -#include "GlLayer.h" #include "LayerDrawable.h" #include "SkiaPipeline.h" #include "SkiaProfileRenderer.h" #include "hwui/Bitmap.h" +#include "private/hwui/DrawGlInfo.h" #include "renderstate/RenderState.h" #include "renderthread/EglManager.h" #include "renderthread/Frame.h" +#include "utils/GLUtils.h" #include "utils/TraceUtils.h" +#include <GLES3/gl3.h> + #include <GrBackendSurface.h> -#include <SkImageInfo.h> #include <SkBlendMode.h> +#include <SkImageInfo.h> #include <cutils/properties.h> #include <strings.h> @@ -41,7 +44,13 @@ namespace uirenderer { namespace skiapipeline { SkiaOpenGLPipeline::SkiaOpenGLPipeline(RenderThread& thread) - : SkiaPipeline(thread), mEglManager(thread.eglManager()) {} + : SkiaPipeline(thread), mEglManager(thread.eglManager()) { + thread.renderState().registerContextCallback(this); +} + +SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { + mRenderThread.renderState().removeContextCallback(this); +} MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { // TODO: Figure out why this workaround is needed, see b/13913604 @@ -60,32 +69,38 @@ Frame SkiaOpenGLPipeline::getFrame() { } bool SkiaOpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const FrameBuilder::LightGeometry& lightGeometry, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, - bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo, + bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) { mEglManager.damageFrame(frame, dirty); + SkColorType colorType = getSurfaceColorType(); // setup surface for fbo0 GrGLFramebufferInfo fboInfo; fboInfo.fFBOID = 0; - GrPixelConfig pixelConfig = - wideColorGamut ? kRGBA_half_GrPixelConfig : kRGBA_8888_GrPixelConfig; + if (colorType == kRGBA_F16_SkColorType) { + fboInfo.fFormat = GL_RGBA16F; + } else if (colorType == kN32_SkColorType) { + // Note: The default preference of pixel format is RGBA_8888, when other + // pixel format is available, we should branch out and do more check. + fboInfo.fFormat = GL_RGBA8; + } else { + LOG_ALWAYS_FATAL("Unsupported color type."); + } - GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, - pixelConfig, fboInfo); + GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo); SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget( - mRenderThread.getGrContext(), backendRT, kBottomLeft_GrSurfaceOrigin, nullptr, &props)); + mRenderThread.getGrContext(), backendRT, kBottomLeft_GrSurfaceOrigin, colorType, + nullptr, &props)); SkiaPipeline::updateLighting(lightGeometry, lightInfo); - renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, contentDrawBounds, - surface); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); layerUpdateQueue->clear(); // Draw visual debugging features @@ -122,77 +137,16 @@ bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect return *requireSwap; } -bool SkiaOpenGLPipeline::copyLayerInto(DeferredLayerUpdater* deferredLayer, SkBitmap* bitmap) { - if (!mRenderThread.getGrContext()) { - return false; - } - - // acquire most recent buffer for drawing - deferredLayer->updateTexImage(); - deferredLayer->apply(); - - // drop the colorSpace as we only support readback into sRGB or extended sRGB - SkImageInfo surfaceInfo = bitmap->info().makeColorSpace(nullptr); - - /* This intermediate surface is present to work around a bug in SwiftShader that - * prevents us from reading the contents of the layer's texture directly. The - * workaround involves first rendering that texture into an intermediate buffer and - * then reading from the intermediate buffer into the bitmap. - */ - sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), - SkBudgeted::kYes, surfaceInfo); - - if (!tmpSurface.get()) { - surfaceInfo = surfaceInfo.makeColorType(SkColorType::kN32_SkColorType); - tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), - SkBudgeted::kYes, surfaceInfo); - if (!tmpSurface.get()) { - ALOGW("Unable to readback GPU contents into the provided bitmap"); - return false; - } - } - - Layer* layer = deferredLayer->backingLayer(); - const SkRect dstRect = SkRect::MakeIWH(bitmap->width(), bitmap->height()); - if (LayerDrawable::DrawLayer(mRenderThread.getGrContext(), tmpSurface->getCanvas(), layer, - &dstRect)) { - sk_sp<SkImage> tmpImage = tmpSurface->makeImageSnapshot(); - if (tmpImage->readPixels(surfaceInfo, bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { - bitmap->notifyPixelsChanged(); - return true; - } - - // if we fail to readback from the GPU directly (e.g. 565) then we attempt to read into 8888 - // and then draw that into the destination format before giving up. - SkBitmap tmpBitmap; - SkImageInfo bitmapInfo = SkImageInfo::MakeN32(bitmap->width(), bitmap->height(), - bitmap->alphaType()); - if (tmpBitmap.tryAllocPixels(bitmapInfo) && - tmpImage->readPixels(bitmapInfo, tmpBitmap.getPixels(), - tmpBitmap.rowBytes(), 0, 0)) { - SkCanvas canvas(*bitmap); - SkPaint paint; - paint.setBlendMode(SkBlendMode::kSrc); - canvas.drawBitmap(tmpBitmap, 0, 0, &paint); - bitmap->notifyPixelsChanged(); - return true; - } - } - - return false; -} - -static Layer* createLayer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight, - sk_sp<SkColorFilter> colorFilter, int alpha, SkBlendMode mode, bool blend) { - GlLayer* layer = - new GlLayer(renderState, layerWidth, layerHeight, colorFilter, alpha, mode, blend); - layer->generateTexture(); - return layer; +DeferredLayerUpdater* SkiaOpenGLPipeline::createTextureLayer() { + mRenderThread.requireGlContext(); + return new DeferredLayerUpdater(mRenderThread.renderState()); } -DeferredLayerUpdater* SkiaOpenGLPipeline::createTextureLayer() { - mEglManager.initialize(); - return new DeferredLayerUpdater(mRenderThread.renderState(), createLayer, Layer::Api::OpenGL); +void SkiaOpenGLPipeline::onContextDestroyed() { + if (mEglSurface != EGL_NO_SURFACE) { + mEglManager.destroySurface(mEglSurface); + mEglSurface = EGL_NO_SURFACE; + } } void SkiaOpenGLPipeline::onStop() { @@ -209,8 +163,14 @@ bool SkiaOpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, } if (surface) { - const bool wideColorGamut = colorMode == ColorMode::WideColorGamut; - mEglSurface = mEglManager.createSurface(surface, wideColorGamut); + mRenderThread.requireGlContext(); + mEglSurface = mEglManager.createSurface(surface, colorMode); + } + + if (colorMode == ColorMode::SRGB) { + mSurfaceColorType = SkColorType::kN32_SkColorType; + } else if (colorMode == ColorMode::WideColorGamut) { + mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType; } if (mEglSurface != EGL_NO_SURFACE) { @@ -244,203 +204,6 @@ void SkiaOpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* func } } -#define FENCE_TIMEOUT 2000000000 - -class AutoEglFence { -public: - AutoEglFence(EGLDisplay display) : mDisplay(display) { - fence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL); - } - - ~AutoEglFence() { - if (fence != EGL_NO_SYNC_KHR) { - eglDestroySyncKHR(mDisplay, fence); - } - } - - EGLSyncKHR fence = EGL_NO_SYNC_KHR; - -private: - EGLDisplay mDisplay = EGL_NO_DISPLAY; -}; - -class AutoEglImage { -public: - AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer) : mDisplay(display) { - EGLint imageAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; - image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, - imageAttrs); - } - - ~AutoEglImage() { - if (image != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(mDisplay, image); - } - } - - EGLImageKHR image = EGL_NO_IMAGE_KHR; - -private: - EGLDisplay mDisplay = EGL_NO_DISPLAY; -}; - -class AutoSkiaGlTexture { -public: - AutoSkiaGlTexture() { - glGenTextures(1, &mTexture); - glBindTexture(GL_TEXTURE_2D, mTexture); - } - - ~AutoSkiaGlTexture() { glDeleteTextures(1, &mTexture); } - -private: - GLuint mTexture = 0; -}; - -static bool isFP16Supported(const sk_sp<GrContext>& grContext) { - static std::once_flag sOnceFlag; - static bool supported = false; - - std::call_once(sOnceFlag, [](const sk_sp<GrContext>& grContext) { - if (!grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) { - supported = false; - return; - } - - sp<GraphicBuffer> buffer = new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_FP16, - GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | - GraphicBuffer::USAGE_SW_READ_NEVER, "tempFp16Buffer"); - status_t error = buffer->initCheck(); - supported = !error; - }, grContext); - - return supported; -} - -sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread, - SkBitmap& skBitmap) { - renderThread.eglManager().initialize(); - - sk_sp<GrContext> grContext = sk_ref_sp(renderThread.getGrContext()); - const SkImageInfo& info = skBitmap.info(); - PixelFormat pixelFormat; - GLint format, type; - bool isSupported = false; - - // TODO: add support for linear blending (when ANDROID_ENABLE_LINEAR_BLENDING is defined) - switch (info.colorType()) { - case kRGBA_8888_SkColorType: - isSupported = true; - // ARGB_4444 is upconverted to RGBA_8888 - case kARGB_4444_SkColorType: - pixelFormat = PIXEL_FORMAT_RGBA_8888; - format = GL_RGBA; - type = GL_UNSIGNED_BYTE; - break; - case kRGBA_F16_SkColorType: - isSupported = isFP16Supported(grContext); - if (isSupported) { - type = GL_HALF_FLOAT; - pixelFormat = PIXEL_FORMAT_RGBA_FP16; - } else { - type = GL_UNSIGNED_BYTE; - pixelFormat = PIXEL_FORMAT_RGBA_8888; - } - format = GL_RGBA; - break; - case kRGB_565_SkColorType: - isSupported = true; - pixelFormat = PIXEL_FORMAT_RGB_565; - format = GL_RGB; - type = GL_UNSIGNED_SHORT_5_6_5; - break; - case kGray_8_SkColorType: - isSupported = true; - pixelFormat = PIXEL_FORMAT_RGBA_8888; - format = GL_LUMINANCE; - type = GL_UNSIGNED_BYTE; - break; - default: - ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType()); - return nullptr; - } - - SkBitmap bitmap; - if (isSupported) { - bitmap = skBitmap; - } else { - bitmap.allocPixels( - SkImageInfo::MakeN32(info.width(), info.height(), info.alphaType(), nullptr)); - bitmap.eraseColor(0); - if (info.colorType() == kRGBA_F16_SkColorType) { - // Drawing RGBA_F16 onto ARGB_8888 is not supported - skBitmap.readPixels(bitmap.info().makeColorSpace(SkColorSpace::MakeSRGB()), - bitmap.getPixels(), bitmap.rowBytes(), 0, 0); - } else { - SkCanvas canvas(bitmap); - canvas.drawBitmap(skBitmap, 0.0f, 0.0f, nullptr); - } - } - - sp<GraphicBuffer> buffer = new GraphicBuffer( - info.width(), info.height(), pixelFormat, - GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | - GraphicBuffer::USAGE_SW_READ_NEVER, - std::string("Bitmap::allocateSkiaHardwareBitmap pid [") + std::to_string(getpid()) + - "]"); - - status_t error = buffer->initCheck(); - if (error < 0) { - ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()"); - return nullptr; - } - - // upload the bitmap into a texture - EGLDisplay display = eglGetCurrentDisplay(); - LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s", - uirenderer::renderthread::EglManager::eglErrorString()); - // We use an EGLImage to access the content of the GraphicBuffer - // The EGL image is later bound to a 2D texture - EGLClientBuffer clientBuffer = (EGLClientBuffer)buffer->getNativeBuffer(); - AutoEglImage autoImage(display, clientBuffer); - if (autoImage.image == EGL_NO_IMAGE_KHR) { - ALOGW("Could not create EGL image, err =%s", - uirenderer::renderthread::EglManager::eglErrorString()); - return nullptr; - } - AutoSkiaGlTexture glTexture; - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image); - GL_CHECKPOINT(MODERATE); - - // glTexSubImage2D is synchronous in sense that it memcpy() from pointer that we provide. - // But asynchronous in sense that driver may upload texture onto hardware buffer when we first - // use it in drawing - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, info.width(), info.height(), format, type, - bitmap.getPixels()); - GL_CHECKPOINT(MODERATE); - - // The fence is used to wait for the texture upload to finish - // properly. We cannot rely on glFlush() and glFinish() as - // some drivers completely ignore these API calls - AutoEglFence autoFence(display); - if (autoFence.fence == EGL_NO_SYNC_KHR) { - LOG_ALWAYS_FATAL("Could not create sync fence %#x", eglGetError()); - return nullptr; - } - // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a - // pipeline flush (similar to what a glFlush() would do.) - EGLint waitStatus = eglClientWaitSyncKHR(display, autoFence.fence, - EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT); - if (waitStatus != EGL_CONDITION_SATISFIED_KHR) { - LOG_ALWAYS_FATAL("Failed to wait for the fence %#x", eglGetError()); - return nullptr; - } - - grContext->resetContext(kTextureBinding_GrGLBackendState); - - return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info())); -} - } /* namespace skiapipeline */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h index 5e013b6697a7..4ab3541d447b 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h @@ -18,6 +18,8 @@ #include "SkiaPipeline.h" +#include "renderstate/RenderState.h" + namespace android { class Bitmap; @@ -25,22 +27,20 @@ class Bitmap; namespace uirenderer { namespace skiapipeline { -class SkiaOpenGLPipeline : public SkiaPipeline { +class SkiaOpenGLPipeline : public SkiaPipeline, public IGpuContextCallback { public: SkiaOpenGLPipeline(renderthread::RenderThread& thread); - virtual ~SkiaOpenGLPipeline() {} + virtual ~SkiaOpenGLPipeline(); renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler) override; bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; - bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior, renderthread::ColorMode colorMode) override; @@ -49,8 +49,9 @@ public: bool isContextReady() override; static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor); - static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread, - SkBitmap& skBitmap); + +protected: + void onContextDestroyed() override; private: renderthread::EglManager& mEglManager; diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp deleted file mode 100644 index 0760f1610891..000000000000 --- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* - * 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 "SkiaOpenGLReadback.h" - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> -#include <GrBackendSurface.h> -#include <SkCanvas.h> -#include <SkSurface.h> -#include <gl/GrGLInterface.h> -#include <gl/GrGLTypes.h> -#include "DeviceInfo.h" -#include "Matrix.h" -#include "Properties.h" -#include "utils/MathUtils.h" - -using namespace android::uirenderer::renderthread; - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -CopyResult SkiaOpenGLReadback::copyImageInto(EGLImageKHR eglImage, const Matrix4& imgTransform, - int imgWidth, int imgHeight, const Rect& srcRect, - SkBitmap* bitmap) { - GLuint sourceTexId; - glGenTextures(1, &sourceTexId); - glBindTexture(GL_TEXTURE_EXTERNAL_OES, sourceTexId); - glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage); - - sk_sp<GrContext> grContext = sk_ref_sp(mRenderThread.getGrContext()); - if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { - sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface()); - LOG_ALWAYS_FATAL_IF(!glInterface.get()); - grContext = GrContext::MakeGL(std::move(glInterface)); - } else { - grContext->resetContext(); - } - - GrGLTextureInfo externalTexture; - externalTexture.fTarget = GL_TEXTURE_EXTERNAL_OES; - externalTexture.fID = sourceTexId; - - GrPixelConfig pixelConfig; - switch (bitmap->colorType()) { - case kRGBA_F16_SkColorType: - pixelConfig = kRGBA_half_GrPixelConfig; - break; - case kN32_SkColorType: - default: - pixelConfig = kRGBA_8888_GrPixelConfig; - break; - } - - if (pixelConfig == kRGBA_half_GrPixelConfig && - !grContext->caps()->isConfigRenderable(kRGBA_half_GrPixelConfig, false)) { - ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported"); - return CopyResult::DestinationInvalid; - } - - GrBackendTexture backendTexture(imgWidth, imgHeight, pixelConfig, externalTexture); - - CopyResult copyResult = CopyResult::UnknownError; - sk_sp<SkImage> image(SkImage::MakeFromAdoptedTexture(grContext.get(), backendTexture, - kTopLeft_GrSurfaceOrigin)); - if (image) { - int displayedWidth = imgWidth, displayedHeight = imgHeight; - // If this is a 90 or 270 degree rotation we need to swap width/height to get the device - // size. - if (imgTransform[Matrix4::kSkewX] >= 0.5f || imgTransform[Matrix4::kSkewX] <= -0.5f) { - std::swap(displayedWidth, displayedHeight); - } - SkRect skiaDestRect = SkRect::MakeWH(bitmap->width(), bitmap->height()); - SkRect skiaSrcRect = srcRect.toSkRect(); - if (skiaSrcRect.isEmpty()) { - skiaSrcRect = SkRect::MakeIWH(displayedWidth, displayedHeight); - } - bool srcNotEmpty = skiaSrcRect.intersect(SkRect::MakeIWH(displayedWidth, displayedHeight)); - - if (srcNotEmpty) { - SkMatrix textureMatrixInv; - imgTransform.copyTo(textureMatrixInv); - // TODO: after skia bug https://bugs.chromium.org/p/skia/issues/detail?id=7075 is fixed - // use bottom left origin and remove flipV and invert transformations. - SkMatrix flipV; - flipV.setAll(1, 0, 0, 0, -1, 1, 0, 0, 1); - textureMatrixInv.preConcat(flipV); - textureMatrixInv.preScale(1.0f / displayedWidth, 1.0f / displayedHeight); - textureMatrixInv.postScale(imgWidth, imgHeight); - SkMatrix textureMatrix; - if (!textureMatrixInv.invert(&textureMatrix)) { - textureMatrix = textureMatrixInv; - } - - textureMatrixInv.mapRect(&skiaSrcRect); - textureMatrixInv.mapRect(&skiaDestRect); - - // we render in an offscreen buffer to scale and to avoid an issue b/62262733 - // with reading incorrect data from EGLImage backed SkImage (likely a driver bug) - sk_sp<SkSurface> scaledSurface = - SkSurface::MakeRenderTarget(grContext.get(), SkBudgeted::kYes, bitmap->info()); - SkPaint paint; - paint.setBlendMode(SkBlendMode::kSrc); - // Apply a filter, which is matching OpenGL pipeline readback behaviour. Filter usage - // is codified by tests using golden images like DecodeAccuracyTest. - bool disableFilter = MathUtils::areEqual(skiaSrcRect.width(), skiaDestRect.width()) - && MathUtils::areEqual(skiaSrcRect.height(), skiaDestRect.height()); - if (!disableFilter) { - paint.setFilterQuality(kLow_SkFilterQuality); - } - scaledSurface->getCanvas()->concat(textureMatrix); - scaledSurface->getCanvas()->drawImageRect(image, skiaSrcRect, skiaDestRect, &paint, - SkCanvas::kFast_SrcRectConstraint); - - image = scaledSurface->makeImageSnapshot(); - - if (image->readPixels(bitmap->info(), bitmap->getPixels(), bitmap->rowBytes(), 0, 0)) { - bitmap->notifyPixelsChanged(); - copyResult = CopyResult::Success; - } - } - } - - // make sure that we have deleted the texture (in the SkImage) before we - // destroy the EGLImage that it was created from - image.reset(); - return copyResult; -} - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.h b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.h deleted file mode 100644 index cc9fb3b0a5e0..000000000000 --- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 "OpenGLReadback.h" - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -class SkiaOpenGLReadback : public OpenGLReadback { -public: - SkiaOpenGLReadback(renderthread::RenderThread& thread) : OpenGLReadback(thread) {} - -protected: - virtual CopyResult copyImageInto(EGLImageKHR eglImage, const Matrix4& imgTransform, - int imgWidth, int imgHeight, const Rect& srcRect, - SkBitmap* bitmap) override; -}; - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index d66cba12ad99..2dfe7c71ca1b 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -81,18 +81,17 @@ void SkiaPipeline::onPrepareTree() { mVectorDrawables.clear(); } -void SkiaPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry, +void SkiaPipeline::renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, bool opaque, - bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo) { + const LightInfo& lightInfo) { updateLighting(lightGeometry, lightInfo); ATRACE_NAME("draw layers"); renderVectorDrawableCache(); - renderLayersImpl(*layerUpdateQueue, opaque, wideColorGamut); + renderLayersImpl(*layerUpdateQueue, opaque); layerUpdateQueue->clear(); } -void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, - bool wideColorGamut) { +void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) { sk_sp<GrContext> cachedContext; // Render all layers that need to be updated, in order. @@ -103,7 +102,6 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, // as not to lose info on what portion is damaged if (CC_LIKELY(layerNode->getLayerSurface() != nullptr)) { SkASSERT(layerNode->getLayerSurface()); - SkASSERT(layerNode->getDisplayList()->isSkiaDL()); SkiaDisplayList* displayList = (SkiaDisplayList*)layerNode->getDisplayList(); if (!displayList || displayList->isEmpty()) { SkDEBUGF(("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName())); @@ -162,7 +160,7 @@ void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, } bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - bool wideColorGamut, ErrorHandler* errorHandler) { + ErrorHandler* errorHandler) { // compute the size of the surface (i.e. texture) to be allocated for this layer const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE; const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE; @@ -170,12 +168,8 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator SkSurface* layer = node->getLayerSurface(); if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) { SkImageInfo info; - if (wideColorGamut) { - info = SkImageInfo::Make(surfaceWidth, surfaceHeight, kRGBA_F16_SkColorType, - kPremul_SkAlphaType); - } else { - info = SkImageInfo::MakeN32Premul(surfaceWidth, surfaceHeight); - } + info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(), + kPremul_SkAlphaType); SkSurfaceProps props(0, kUnknown_SkPixelGeometry); SkASSERT(mRenderThread.getGrContext() != nullptr); node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), @@ -206,10 +200,6 @@ bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator return false; } -void SkiaPipeline::destroyLayer(RenderNode* node) { - node->setLayerSurface(nullptr); -} - void SkiaPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { GrContext* context = thread.getGrContext(); if (context) { @@ -322,19 +312,18 @@ void SkiaPipeline::endCapture(SkSurface* surface) { void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, - bool wideColorGamut, const Rect& contentDrawBounds, - sk_sp<SkSurface> surface) { + const Rect& contentDrawBounds, sk_sp<SkSurface> surface) { renderVectorDrawableCache(); // draw all layers up front - renderLayersImpl(layers, opaque, wideColorGamut); + renderLayersImpl(layers, opaque); // initialize the canvas for the current frame, that might be a recording canvas if SKP // capture is enabled. std::unique_ptr<SkPictureRecorder> recorder; SkCanvas* canvas = tryCapture(surface.get()); - renderFrameImpl(layers, clip, nodes, opaque, wideColorGamut, contentDrawBounds, canvas); + renderFrameImpl(layers, clip, nodes, opaque, contentDrawBounds, canvas); endCapture(surface.get()); @@ -355,13 +344,12 @@ static Rect nodeBounds(RenderNode& node) { void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, const std::vector<sp<RenderNode>>& nodes, bool opaque, - bool wideColorGamut, const Rect& contentDrawBounds, - SkCanvas* canvas) { + const Rect& contentDrawBounds, SkCanvas* canvas) { SkAutoCanvasRestore saver(canvas, true); canvas->androidFramework_setDeviceClipRestriction(clip.roundOut()); // STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293 - if (!opaque || wideColorGamut) { + if (!opaque || getSurfaceColorType() == kRGBA_F16_SkColorType) { canvas->clear(SK_ColorTRANSPARENT); } @@ -494,7 +482,7 @@ void SkiaPipeline::renderOverdraw(const LayerUpdateQueue& layers, const SkRect& // each time a pixel would have been drawn. // Pass true for opaque so we skip the clear - the overdrawCanvas is already zero // initialized. - renderFrameImpl(layers, clip, nodes, true, false, contentDrawBounds, &overdrawCanvas); + renderFrameImpl(layers, clip, nodes, true, contentDrawBounds, &overdrawCanvas); sk_sp<SkImage> counts = offscreen->makeImageSnapshot(); // Draw overdraw colors to the canvas. The color filter will convert counts to colors. diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index 38ad9c09a8aa..42a411a6808c 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -17,7 +17,7 @@ #pragma once #include <SkSurface.h> -#include "FrameBuilder.h" +#include "Lighting.h" #include "hwui/AnimatedImageDrawable.h" #include "renderthread/CanvasContext.h" #include "renderthread/IRenderPipeline.h" @@ -42,24 +42,24 @@ public: void unpinImages() override; void onPrepareTree() override; - void renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo) override; + void renderLayers(const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + bool opaque, const LightInfo& lightInfo) override; bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - bool wideColorGamut, ErrorHandler* errorHandler) override; + ErrorHandler* errorHandler) override; + + SkColorType getSurfaceColorType() const { return mSurfaceColorType; } + sk_sp<SkColorSpace> getSurfaceColorSpace() override { return mSurfaceColorSpace; } void renderFrame(const LayerUpdateQueue& layers, const SkRect& clip, - const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut, + const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect& contentDrawBounds, sk_sp<SkSurface> surface); std::vector<VectorDrawableRoot*>* getVectorDrawables() { return &mVectorDrawables; } - static void destroyLayer(RenderNode* node); - static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); - void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque, bool wideColorGamut); + void renderLayersImpl(const LayerUpdateQueue& layers, bool opaque); static float getLightRadius() { if (CC_UNLIKELY(Properties::overrideLightRadius > 0)) { @@ -97,8 +97,8 @@ public: return mLightCenter; } - static void updateLighting(const FrameBuilder::LightGeometry& lightGeometry, - const BakedOpRenderer::LightInfo& lightInfo) { + static void updateLighting(const LightGeometry& lightGeometry, + const LightInfo& lightInfo) { mLightRadius = lightGeometry.radius; mAmbientShadowAlpha = lightInfo.ambientShadowAlpha; mSpotShadowAlpha = lightInfo.spotShadowAlpha; @@ -109,10 +109,12 @@ protected: void dumpResourceCacheUsage() const; renderthread::RenderThread& mRenderThread; + SkColorType mSurfaceColorType; + sk_sp<SkColorSpace> mSurfaceColorSpace; private: void renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip, - const std::vector<sp<RenderNode>>& nodes, bool opaque, bool wideColorGamut, + const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect& contentDrawBounds, SkCanvas* canvas); /** diff --git a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h index 5ae7d6b0b607..daa4c1839693 100644 --- a/libs/hwui/pipeline/skia/SkiaProfileRenderer.h +++ b/libs/hwui/pipeline/skia/SkiaProfileRenderer.h @@ -16,7 +16,7 @@ #include "IProfileRenderer.h" -#include "BakedOpRenderer.h" +#include "SkCanvas.h" namespace android { namespace uirenderer { diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index f0da660f17b0..45022e733979 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -17,11 +17,14 @@ #include "SkiaRecordingCanvas.h" #include <SkImagePriv.h> +#include "CanvasTransform.h" #include "Layer.h" #include "LayerDrawable.h" #include "NinePatchUtils.h" #include "RenderNode.h" #include "pipeline/skia/AnimatedDrawables.h" +#include "pipeline/skia/GLFunctorDrawable.h" +#include "pipeline/skia/VkInteropFunctorDrawable.h" namespace android { namespace uirenderer { @@ -78,6 +81,11 @@ void SkiaRecordingCanvas::drawCircle(uirenderer::CanvasPropertyPrimitive* x, } void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) { + if (mCurrentBarrier && enableReorder) { + // Already in a re-order section, nothing to do + return; + } + if (nullptr != mCurrentBarrier) { // finish off the existing chunk SkDrawable* drawable = @@ -86,9 +94,8 @@ void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) { drawDrawable(drawable); } if (enableReorder) { - mCurrentBarrier = (StartReorderBarrierDrawable*) - mDisplayList->allocateDrawable<StartReorderBarrierDrawable>( - mDisplayList.get()); + mCurrentBarrier = mDisplayList->allocateDrawable<StartReorderBarrierDrawable>( + mDisplayList.get()); drawDrawable(mCurrentBarrier); } } @@ -110,37 +117,25 @@ void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) { // use staging property, since recording on UI thread if (renderNode->stagingProperties().isProjectionReceiver()) { mDisplayList->mProjectionReceiver = &renderNodeDrawable; - // set projectionReceiveIndex so that RenderNode.hasProjectionReceiver returns true - mDisplayList->projectionReceiveIndex = mDisplayList->mChildNodes.size() - 1; } } void SkiaRecordingCanvas::callDrawGLFunction(Functor* functor, uirenderer::GlFunctorLifecycleListener* listener) { - // Drawable dtor will be invoked when mChildFunctors deque is cleared. - mDisplayList->mChildFunctors.emplace_back(functor, listener, asSkCanvas()); - drawDrawable(&mDisplayList->mChildFunctors.back()); -} - -class VectorDrawable : public SkDrawable { -public: - VectorDrawable(VectorDrawableRoot* tree) - : mRoot(tree) - , mBounds(tree->stagingProperties()->getBounds()) {} - -protected: - virtual SkRect onGetBounds() override { return mBounds; } - virtual void onDraw(SkCanvas* canvas) override { - mRoot->draw(canvas, mBounds); + FunctorDrawable* functorDrawable; + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { + functorDrawable = mDisplayList->allocateDrawable<VkInteropFunctorDrawable>(functor, + listener, asSkCanvas()); + } else { + functorDrawable = mDisplayList->allocateDrawable<GLFunctorDrawable>(functor, listener, + asSkCanvas()); } - -private: - sp<VectorDrawableRoot> mRoot; - SkRect mBounds; -}; + mDisplayList->mChildFunctors.push_back(functorDrawable); + drawDrawable(functorDrawable); +} void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { - drawDrawable(mDisplayList->allocateDrawable<VectorDrawable>(tree)); + mRecorder.drawVectorDrawable(tree); mDisplayList->mVectorDrawables.push_back(tree); } @@ -148,12 +143,45 @@ void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) { // Recording Canvas draw operations: Bitmaps // ---------------------------------------------------------------------------- +SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint, + sk_sp<SkColorFilter> colorSpaceFilter) { + bool fixBlending = false; + bool fixAA = false; + if (paint) { + // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and + // older. + fixBlending = sApiLevel <= 27 && paint->getBlendMode() == SkBlendMode::kClear; + fixAA = paint->isAntiAlias(); + } + + if (fixBlending || fixAA || colorSpaceFilter) { + SkPaint& tmpPaint = paint.writeable(); + + if (fixBlending) { + tmpPaint.setBlendMode(SkBlendMode::kDstOut); + } + + if (colorSpaceFilter) { + if (tmpPaint.getColorFilter()) { + tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter( + tmpPaint.refColorFilter(), std::move(colorSpaceFilter))); + } else { + tmpPaint.setColorFilter(std::move(colorSpaceFilter)); + } + LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter()); + } + + // disabling AA on bitmap draws matches legacy HWUI behavior + tmpPaint.setAntiAlias(false); + } + + return filterPaint(std::move(paint)); +} void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) { - SkPaint tmpPaint; sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - mRecorder.drawImage(image, left, top, bitmapPaint(paint, &tmpPaint, colorFilter)); + mRecorder.drawImage(image, left, top, filterBitmap(paint, std::move(colorFilter)), bitmap.palette()); // if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means // it is not safe to store a raw SkImage pointer, because the image object will be destroyed // when this function ends. @@ -166,10 +194,9 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, con SkAutoCanvasRestore acr(&mRecorder, true); concat(matrix); - SkPaint tmpPaint; sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - mRecorder.drawImage(image, 0, 0, bitmapPaint(paint, &tmpPaint, colorFilter)); + mRecorder.drawImage(image, 0, 0, filterBitmap(paint, std::move(colorFilter)), bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique()) { mDisplayList->mMutableImages.push_back(image.get()); } @@ -181,11 +208,10 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom); SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - SkPaint tmpPaint; sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - mRecorder.drawImageRect(image, srcRect, dstRect, bitmapPaint(paint, &tmpPaint, colorFilter), - SkCanvas::kFast_SrcRectConstraint); + mRecorder.drawImageRect(image, srcRect, dstRect, filterBitmap(paint, std::move(colorFilter)), + SkCanvas::kFast_SrcRectConstraint, bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() && !dstRect.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); @@ -216,23 +242,16 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch lattice.fBounds = nullptr; SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom); - SkPaint tmpPaint; + PaintCoW filteredPaint(paint); + // HWUI always draws 9-patches with bilinear filtering, regardless of what is set in the Paint. + if (!filteredPaint || filteredPaint->getFilterQuality() != kLow_SkFilterQuality) { + filteredPaint.writeable().setFilterQuality(kLow_SkFilterQuality); + } sk_sp<SkColorFilter> colorFilter; sk_sp<SkImage> image = bitmap.makeImage(&colorFilter); - const SkPaint* filteredPaint = bitmapPaint(paint, &tmpPaint, colorFilter); - // Besides kNone, the other three SkFilterQualities are treated the same. And Android's - // Java API only supports kLow and kNone anyway. - if (!filteredPaint || filteredPaint->getFilterQuality() == kNone_SkFilterQuality) { - if (filteredPaint != &tmpPaint) { - if (paint) { - tmpPaint = *paint; - } - filteredPaint = &tmpPaint; - } - tmpPaint.setFilterQuality(kLow_SkFilterQuality); - } - - mRecorder.drawImageLattice(image.get(), lattice, dst, filteredPaint); + mRecorder.drawImageLattice(image, lattice, dst, + filterBitmap(std::move(filteredPaint), std::move(colorFilter)), + bitmap.palette()); if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) { mDisplayList->mMutableImages.push_back(image.get()); } diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h index 93807a5476e6..988728dfe23e 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h @@ -15,7 +15,7 @@ */ #pragma once -#include <SkLiteRecorder.h> +#include "RecordingCanvas.h" #include "ReorderBarrierDrawables.h" #include "SkiaCanvas.h" #include "SkiaDisplayList.h" @@ -76,7 +76,7 @@ public: uirenderer::GlFunctorLifecycleListener* listener) override; private: - SkLiteRecorder mRecorder; + RecordingCanvas mRecorder; std::unique_ptr<SkiaDisplayList> mDisplayList; StartReorderBarrierDrawable* mCurrentBarrier; @@ -89,44 +89,7 @@ private: */ void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height); - inline static const SkPaint* bitmapPaint(const SkPaint* origPaint, SkPaint* tmpPaint, - sk_sp<SkColorFilter> colorSpaceFilter) { - bool fixBlending = false; - bool fixAA = false; - if (origPaint) { - // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and - // older. - fixBlending = sApiLevel <= 27 && origPaint->getBlendMode() == SkBlendMode::kClear; - fixAA = origPaint->isAntiAlias(); - } - - if (fixBlending || fixAA || colorSpaceFilter) { - if (origPaint) { - *tmpPaint = *origPaint; - } - - if (fixBlending) { - tmpPaint->setBlendMode(SkBlendMode::kDstOut); - } - - if (colorSpaceFilter) { - if (tmpPaint->getColorFilter()) { - tmpPaint->setColorFilter(SkColorFilter::MakeComposeFilter( - tmpPaint->refColorFilter(), colorSpaceFilter)); - } else { - tmpPaint->setColorFilter(colorSpaceFilter); - } - LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter()); - } - - // disabling AA on bitmap draws matches legacy HWUI behavior - tmpPaint->setAntiAlias(false); - return tmpPaint; - } else { - return origPaint; - } - } - + PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter); }; }; // namespace skiapipeline diff --git a/libs/hwui/renderstate/PixelBufferState.h b/libs/hwui/pipeline/skia/SkiaUtils.h index f7ae6c575f6a..834446905216 100644 --- a/libs/hwui/renderstate/PixelBufferState.h +++ b/libs/hwui/pipeline/skia/SkiaUtils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2018 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. @@ -13,26 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef RENDERSTATE_PIXELBUFFERSTATE_H -#define RENDERSTATE_PIXELBUFFERSTATE_H -#include <GLES3/gl3.h> +#pragma once -namespace android { -namespace uirenderer { +#include <SkRect.h> -class PixelBufferState { - friend class Caches; // TODO: move to RenderState -public: - bool bind(GLuint buffer); - bool unbind(); +namespace android { -private: - PixelBufferState(); - GLuint mCurrentPixelBuffer; +static inline SkRect SkRectMakeLargest() { + const SkScalar v = SK_ScalarMax; + return { -v, -v, v, v }; }; -} /* namespace uirenderer */ } /* namespace android */ - -#endif // RENDERSTATE_PIXELBUFFERSTATE_H diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp index 5825060f902a..7fc41acefe8a 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp @@ -20,9 +20,9 @@ #include "Readback.h" #include "SkiaPipeline.h" #include "SkiaProfileRenderer.h" -#include "VkLayer.h" #include "renderstate/RenderState.h" #include "renderthread/Frame.h" +#include "VkInteropFunctorDrawable.h" #include <SkSurface.h> #include <SkTypes.h> @@ -62,10 +62,9 @@ Frame SkiaVulkanPipeline::getFrame() { } bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const FrameBuilder::LightGeometry& lightGeometry, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, - bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo, + bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) { sk_sp<SkSurface> backBuffer = mVkSurface->getBackBufferSurface(); @@ -73,8 +72,7 @@ bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty, con return false; } SkiaPipeline::updateLighting(lightGeometry, lightInfo); - renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, wideColorGamut, contentDrawBounds, - backBuffer); + renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, backBuffer); layerUpdateQueue->clear(); // Draw visual debugging features @@ -109,21 +107,10 @@ bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect return *requireSwap; } -bool SkiaVulkanPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { - // TODO: implement copyLayerInto for vulkan. - return false; -} - -static Layer* createLayer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight, - sk_sp<SkColorFilter> colorFilter, int alpha, SkBlendMode mode, - bool blend) { - return new VkLayer(renderState, layerWidth, layerHeight, colorFilter, alpha, mode, blend); -} - DeferredLayerUpdater* SkiaVulkanPipeline::createTextureLayer() { mVkManager.initialize(); - return new DeferredLayerUpdater(mRenderThread.renderState(), createLayer, Layer::Api::Vulkan); + return new DeferredLayerUpdater(mRenderThread.renderState()); } void SkiaVulkanPipeline::onStop() {} @@ -136,8 +123,13 @@ bool SkiaVulkanPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, } if (surface) { - // TODO: handle color mode - mVkSurface = mVkManager.createSurface(surface); + mVkSurface = mVkManager.createSurface(surface, colorMode); + } + + if (colorMode == ColorMode::SRGB) { + mSurfaceColorType = SkColorType::kN32_SkColorType; + } else if (colorMode == ColorMode::WideColorGamut) { + mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType; } return mVkSurface != nullptr; @@ -152,9 +144,7 @@ bool SkiaVulkanPipeline::isContextReady() { } void SkiaVulkanPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) { - // TODO: we currently don't support OpenGL WebView's - DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext; - (*functor)(mode, nullptr); + VkInteropFunctorDrawable::vkInvokeFunctor(functor); } sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread, diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h index 03b4c79f2beb..14c0d69dba33 100644 --- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h @@ -31,14 +31,12 @@ public: renderthread::MakeCurrentResult makeCurrent() override; renderthread::Frame getFrame() override; bool draw(const renderthread::Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, + const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler) override; bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) override; - bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override; DeferredLayerUpdater* createTextureLayer() override; bool setSurface(Surface* window, renderthread::SwapBehavior swapBehavior, renderthread::ColorMode colorMode) override; diff --git a/libs/hwui/pipeline/skia/SkiaVulkanReadback.h b/libs/hwui/pipeline/skia/SkiaVulkanReadback.h deleted file mode 100644 index 65b89d617f7b..000000000000 --- a/libs/hwui/pipeline/skia/SkiaVulkanReadback.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2018 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 "Readback.h" - -namespace android { -namespace uirenderer { -namespace skiapipeline { - -class SkiaVulkanReadback : public Readback { -public: - SkiaVulkanReadback(renderthread::RenderThread& thread) : Readback(thread) {} - - virtual CopyResult copySurfaceInto(Surface& surface, const Rect& srcRect, - SkBitmap* bitmap) override { - //TODO: implement Vulkan readback. - return CopyResult::UnknownError; - } - - virtual CopyResult copyGraphicBufferInto(GraphicBuffer* graphicBuffer, - SkBitmap* bitmap) override { - //TODO: implement Vulkan readback. - return CopyResult::UnknownError; - } -}; - -} /* namespace skiapipeline */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp new file mode 100644 index 000000000000..a594206a2dd9 --- /dev/null +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2018 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 "VkInteropFunctorDrawable.h" +#include <private/hwui/DrawGlInfo.h> + +#include "renderthread/EglManager.h" +#include "thread/ThreadBase.h" +#include "utils/TimeUtils.h" +#include <thread> +#include <utils/Color.h> +#include <utils/Trace.h> +#include <utils/TraceUtils.h> + +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES3/gl3.h> + +#include <utils/GLUtils.h> + +namespace android { +namespace uirenderer { +namespace skiapipeline { + +static std::mutex sLock{}; +static ThreadBase* sGLDrawThread = nullptr; +static renderthread::EglManager sEglManager; + +// ScopedDrawRequest makes sure a GL thread is started and EGL context is initialized on it. +class ScopedDrawRequest { +public: + ScopedDrawRequest() { beginDraw(); } +private: + void beginDraw() { + std::lock_guard{sLock}; + + if (!sGLDrawThread) { + sGLDrawThread = new ThreadBase{}; + } + + if (!sGLDrawThread->isRunning()) { + sGLDrawThread->start("GLFunctorThread"); + } + + if (!sEglManager.hasEglContext()) { + sGLDrawThread->queue().runSync([]() { + sEglManager.initialize(); + }); + } + } +}; + +void VkInteropFunctorDrawable::vkInvokeFunctor(Functor* functor) { + ScopedDrawRequest _drawRequest{}; + sGLDrawThread->queue().runSync([&]() { + EGLDisplay display = sEglManager.eglDisplay(); + DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext; + if (display != EGL_NO_DISPLAY) { + mode = DrawGlInfo::kModeProcess; + } + (*functor)(mode, nullptr); + }); +} + +#define FENCE_TIMEOUT 2000000000 + +void VkInteropFunctorDrawable::onDraw(SkCanvas* canvas) { + ATRACE_CALL(); + + if (canvas->getGrContext() == nullptr) { + SkDEBUGF(("Attempting to draw VkInteropFunctor into an unsupported surface")); + return; + } + + ScopedDrawRequest _drawRequest{}; + + SkImageInfo surfaceInfo = canvas->imageInfo(); + + if (!mFrameBuffer.get() || mFBInfo != surfaceInfo) { + // Buffer will be used as an OpenGL ES render target. + mFrameBuffer = new GraphicBuffer( + //TODO: try to reduce the size of the buffer: possibly by using clip bounds. + static_cast<uint32_t>(surfaceInfo.width()), + static_cast<uint32_t>(surfaceInfo.height()), + ColorTypeToPixelFormat(surfaceInfo.colorType()), + GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | + GraphicBuffer::USAGE_SW_READ_NEVER | GraphicBuffer::USAGE_HW_RENDER, + std::string("VkInteropFunctorDrawable::onDraw pid [") + std::to_string(getpid()) + + "]"); + status_t error = mFrameBuffer->initCheck(); + if (error < 0) { + ALOGW("VkInteropFunctorDrawable::onDraw() failed in GraphicBuffer.create()"); + return; + } + + mFBInfo = surfaceInfo; + } + + //TODO: Synchronization is needed on mFrameBuffer to guarantee that the previous Vulkan + //TODO: draw command has completed. + //TODO: A simple but inefficient way is to flush and issue a QueueWaitIdle call. See + //TODO: GrVkGpu::destroyResources() for example. + bool success = sGLDrawThread->queue().runSync([&]() -> bool { + ATRACE_FORMAT("WebViewDraw_%dx%d", mFBInfo.width(), mFBInfo.height()); + EGLDisplay display = sEglManager.eglDisplay(); + LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, + "Failed to get EGL_DEFAULT_DISPLAY! err=%s", + uirenderer::renderthread::EglManager::eglErrorString()); + // We use an EGLImage to access the content of the GraphicBuffer + // The EGL image is later bound to a 2D texture + EGLClientBuffer clientBuffer = (EGLClientBuffer)mFrameBuffer->getNativeBuffer(); + AutoEglImage autoImage(display, clientBuffer); + if (autoImage.image == EGL_NO_IMAGE_KHR) { + ALOGW("Could not create EGL image, err =%s", + uirenderer::renderthread::EglManager::eglErrorString()); + return false; + } + + AutoSkiaGlTexture glTexture; + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image); + GL_CHECKPOINT(MODERATE); + + glBindTexture(GL_TEXTURE_2D, 0); + + DrawGlInfo info; + SkMatrix44 mat4(canvas->getTotalMatrix()); + SkIRect clipBounds = canvas->getDeviceClipBounds(); + + info.clipLeft = clipBounds.fLeft; + info.clipTop = clipBounds.fTop; + info.clipRight = clipBounds.fRight; + info.clipBottom = clipBounds.fBottom; + info.isLayer = true; + info.width = mFBInfo.width(); + info.height = mFBInfo.height(); + mat4.asColMajorf(&info.transform[0]); + + glViewport(0, 0, info.width, info.height); + + AutoGLFramebuffer glFb; + // Bind texture to the frame buffer. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + glTexture.mTexture, 0); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + ALOGE("Failed framebuffer check for created target buffer: %s", + GLUtils::getGLFramebufferError()); + return false; + } + + glDisable(GL_STENCIL_TEST); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClear(GL_COLOR_BUFFER_BIT); + + (*mFunctor)(DrawGlInfo::kModeDraw, &info); + + EGLSyncKHR glDrawFinishedFence = + eglCreateSyncKHR(eglGetCurrentDisplay(), EGL_SYNC_FENCE_KHR, NULL); + LOG_ALWAYS_FATAL_IF(glDrawFinishedFence == EGL_NO_SYNC_KHR, + "Could not create sync fence %#x", eglGetError()); + glFlush(); + // TODO: export EGLSyncKHR in file descr + // TODO: import file desc in Vulkan Semaphore + // TODO: instead block the GPU: probably by using external Vulkan semaphore. + // Block the CPU until the glFlush finish. + EGLint waitStatus = eglClientWaitSyncKHR(display, glDrawFinishedFence, 0, + FENCE_TIMEOUT); + LOG_ALWAYS_FATAL_IF(waitStatus != EGL_CONDITION_SATISFIED_KHR, + "Failed to wait for the fence %#x", eglGetError()); + eglDestroySyncKHR(display, glDrawFinishedFence); + return true; + }); + + if (!success) { + return; + } + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrcOver); + canvas->save(); + // The size of the image matches the size of the canvas. We've used the matrix already, while + // drawing into the offscreen surface, so we need to reset it here. + canvas->resetMatrix(); + + auto functorImage = SkImage::MakeFromAHardwareBuffer( + reinterpret_cast<AHardwareBuffer*>(mFrameBuffer.get()), kPremul_SkAlphaType, + nullptr, kBottomLeft_GrSurfaceOrigin); + canvas->drawImage(functorImage, 0, 0, &paint); + canvas->restore(); +} + +VkInteropFunctorDrawable::~VkInteropFunctorDrawable() { + if (mListener.get() != nullptr) { + ScopedDrawRequest _drawRequest{}; + sGLDrawThread->queue().runSync([&]() { + mListener->onGlFunctorReleased(mFunctor); + }); + } +} + +void VkInteropFunctorDrawable::syncFunctor() const { + ScopedDrawRequest _drawRequest{}; + sGLDrawThread->queue().runSync([&]() { + (*mFunctor)(DrawGlInfo::kModeSync, nullptr); + }); +} + +}; // namespace skiapipeline +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h new file mode 100644 index 000000000000..3269cfbb8fe3 --- /dev/null +++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 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 "FunctorDrawable.h" + +#include <utils/RefBase.h> +#include <ui/GraphicBuffer.h> + +namespace android { +namespace uirenderer { + +namespace skiapipeline { + +/** + * This drawable wraps a Vulkan functor enabling it to be recorded into a list + * of Skia drawing commands. + */ +class VkInteropFunctorDrawable : public FunctorDrawable { +public: + VkInteropFunctorDrawable(Functor* functor, GlFunctorLifecycleListener* listener, + SkCanvas* canvas) + : FunctorDrawable(functor, listener, canvas) {} + virtual ~VkInteropFunctorDrawable(); + + void syncFunctor() const override; + + static void vkInvokeFunctor(Functor* functor); + +protected: + virtual void onDraw(SkCanvas* canvas) override; + +private: + + // Variables below describe/store temporary offscreen buffer used for Vulkan pipeline. + sp<GraphicBuffer> mFrameBuffer; + SkImageInfo mFBInfo; +}; + +}; // namespace skiapipeline +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/protos/ProtoHelpers.h b/libs/hwui/protos/ProtoHelpers.h deleted file mode 100644 index 833c77f2b8cb..000000000000 --- a/libs/hwui/protos/ProtoHelpers.h +++ /dev/null @@ -1,41 +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. - */ -#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 deleted file mode 100644 index dcff80a24974..000000000000 --- a/libs/hwui/protos/hwui.proto +++ /dev/null @@ -1,99 +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. - */ - -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 deleted file mode 100644 index 5bef01c0ec3c..000000000000 --- a/libs/hwui/renderstate/Blend.cpp +++ /dev/null @@ -1,140 +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 <renderstate/Blend.h> -#include "Program.h" - -#include "ShadowTessellator.h" - -namespace android { -namespace uirenderer { - -/** - * Structure mapping Skia xfermodes to OpenGL blending factors. - */ -struct Blender { - SkBlendMode mode; - GLenum src; - GLenum dst; -}; - -// assumptions made by lookup tables in either this file or ProgramCache -static_assert(0 == static_cast<int>(SkBlendMode::kClear), "SkBlendMode enums have changed"); -static_assert(1 == static_cast<int>(SkBlendMode::kSrc), "SkBlendMode enums have changed"); -static_assert(2 == static_cast<int>(SkBlendMode::kDst), "SkBlendMode enums have changed"); -static_assert(3 == static_cast<int>(SkBlendMode::kSrcOver), "SkBlendMode enums have changed"); -static_assert(4 == static_cast<int>(SkBlendMode::kDstOver), "SkBlendMode enums have changed"); -static_assert(5 == static_cast<int>(SkBlendMode::kSrcIn), "SkBlendMode enums have changed"); -static_assert(6 == static_cast<int>(SkBlendMode::kDstIn), "SkBlendMode enums have changed"); -static_assert(7 == static_cast<int>(SkBlendMode::kSrcOut), "SkBlendMode enums have changed"); -static_assert(8 == static_cast<int>(SkBlendMode::kDstOut), "SkBlendMode enums have changed"); -static_assert(9 == static_cast<int>(SkBlendMode::kSrcATop), "SkBlendMode enums have changed"); -static_assert(10 == static_cast<int>(SkBlendMode::kDstATop), "SkBlendMode enums have changed"); -static_assert(11 == static_cast<int>(SkBlendMode::kXor), "SkBlendMode enums have changed"); -static_assert(12 == static_cast<int>(SkBlendMode::kPlus), "SkBlendMode enums have changed"); -static_assert(13 == static_cast<int>(SkBlendMode::kModulate), "SkBlendMode enums have changed"); -static_assert(14 == static_cast<int>(SkBlendMode::kScreen), "SkBlendMode enums have changed"); -static_assert(15 == static_cast<int>(SkBlendMode::kOverlay), "SkBlendMode enums have changed"); -static_assert(16 == static_cast<int>(SkBlendMode::kDarken), "SkBlendMode enums have changed"); -static_assert(17 == static_cast<int>(SkBlendMode::kLighten), "SkBlendMode enums have changed"); - -// In this array, the index of each Blender equals the value of the first -// entry. For instance, gBlends[1] == gBlends[SkBlendMode::kSrc] -const Blender kBlends[] = {{SkBlendMode::kClear, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kSrc, GL_ONE, GL_ZERO}, - {SkBlendMode::kDst, GL_ZERO, GL_ONE}, - {SkBlendMode::kSrcOver, GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kDstOver, GL_ONE_MINUS_DST_ALPHA, GL_ONE}, - {SkBlendMode::kSrcIn, GL_DST_ALPHA, GL_ZERO}, - {SkBlendMode::kDstIn, GL_ZERO, GL_SRC_ALPHA}, - {SkBlendMode::kSrcOut, GL_ONE_MINUS_DST_ALPHA, GL_ZERO}, - {SkBlendMode::kDstOut, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kSrcATop, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kDstATop, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA}, - {SkBlendMode::kXor, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kPlus, GL_ONE, GL_ONE}, - {SkBlendMode::kModulate, GL_ZERO, GL_SRC_COLOR}, - {SkBlendMode::kScreen, GL_ONE, GL_ONE_MINUS_SRC_COLOR}}; - -// This array contains the swapped version of each SkBlendMode. For instance -// this array's SrcOver blending mode is actually DstOver. You can refer to -// createLayer() for more information on the purpose of this array. -const Blender kBlendsSwap[] = {{SkBlendMode::kClear, GL_ONE_MINUS_DST_ALPHA, GL_ZERO}, - {SkBlendMode::kSrc, GL_ZERO, GL_ONE}, - {SkBlendMode::kDst, GL_ONE, GL_ZERO}, - {SkBlendMode::kSrcOver, GL_ONE_MINUS_DST_ALPHA, GL_ONE}, - {SkBlendMode::kDstOver, GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kSrcIn, GL_ZERO, GL_SRC_ALPHA}, - {SkBlendMode::kDstIn, GL_DST_ALPHA, GL_ZERO}, - {SkBlendMode::kSrcOut, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kDstOut, GL_ONE_MINUS_DST_ALPHA, GL_ZERO}, - {SkBlendMode::kSrcATop, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA}, - {SkBlendMode::kDstATop, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kXor, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, - {SkBlendMode::kPlus, GL_ONE, GL_ONE}, - {SkBlendMode::kModulate, GL_DST_COLOR, GL_ZERO}, - {SkBlendMode::kScreen, GL_ONE_MINUS_DST_COLOR, GL_ONE}}; - -Blend::Blend() : mEnabled(false), mSrcMode(GL_ZERO), mDstMode(GL_ZERO) { - // gl blending off by default -} - -void Blend::invalidate() { - syncEnabled(); - mSrcMode = mDstMode = GL_ZERO; -} - -void Blend::syncEnabled() { - if (mEnabled) { - glEnable(GL_BLEND); - } else { - glDisable(GL_BLEND); - } -} - -void Blend::getFactors(SkBlendMode mode, ModeOrderSwap modeUsage, GLenum* outSrc, GLenum* outDst) { - int index = static_cast<int>(mode); - *outSrc = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[index].src : kBlends[index].src; - *outDst = (modeUsage == ModeOrderSwap::Swap) ? kBlendsSwap[index].dst : kBlends[index].dst; -} - -void Blend::setFactors(GLenum srcMode, GLenum dstMode) { - if ((srcMode == GL_ZERO || srcMode == GL_ONE) && dstMode == GL_ZERO) { - // disable blending - if (mEnabled) { - glDisable(GL_BLEND); - mEnabled = false; - } - } else { - // enable blending - if (!mEnabled) { - glEnable(GL_BLEND); - mEnabled = true; - } - - if (srcMode != mSrcMode || dstMode != mDstMode) { - glBlendFunc(srcMode, dstMode); - mSrcMode = srcMode; - mDstMode = dstMode; - } - } -} - -void Blend::dump() { - ALOGD("Blend: enabled %d, func src %d, dst %d", mEnabled, mSrcMode, mDstMode); -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderstate/Blend.h b/libs/hwui/renderstate/Blend.h deleted file mode 100644 index 7e559bace3f2..000000000000 --- a/libs/hwui/renderstate/Blend.h +++ /dev/null @@ -1,63 +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. - */ -#ifndef RENDERSTATE_BLEND_H -#define RENDERSTATE_BLEND_H - -#include "Vertex.h" - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> -#include <SkBlendMode.h> -#include <memory> - -namespace android { -namespace uirenderer { - -class Blend { - friend class RenderState; - -public: - // dictates whether to swap src/dst - enum class ModeOrderSwap { - NoSwap, - Swap, - }; - void syncEnabled(); - - static void getFactors(SkBlendMode mode, ModeOrderSwap modeUsage, GLenum* outSrc, - GLenum* outDst); - void setFactors(GLenum src, GLenum dst); - - bool getEnabled() { return mEnabled; } - void getFactors(GLenum* src, GLenum* dst) { - *src = mSrcMode; - *dst = mDstMode; - } - - void dump(); - -private: - Blend(); - void invalidate(); - bool mEnabled; - GLenum mSrcMode; - GLenum mDstMode; -}; - -} /* namespace uirenderer */ -} /* namespace android */ - -#endif // RENDERSTATE_BLEND_H diff --git a/libs/hwui/renderstate/MeshState.cpp b/libs/hwui/renderstate/MeshState.cpp deleted file mode 100644 index 4f6c49e67b99..000000000000 --- a/libs/hwui/renderstate/MeshState.cpp +++ /dev/null @@ -1,179 +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 "renderstate/MeshState.h" - -#include "Program.h" - -namespace android { -namespace uirenderer { - -MeshState::MeshState() - : mCurrentIndicesBuffer(0) - , mCurrentPixelBuffer(0) - , mCurrentPositionPointer(this) - , mCurrentPositionStride(0) - , mCurrentTexCoordsPointer(this) - , mCurrentTexCoordsStride(0) - , mTexCoordsArrayEnabled(false) - , mQuadListIndices(0) { - glGenBuffers(1, &mUnitQuadBuffer); - glBindBuffer(GL_ARRAY_BUFFER, mUnitQuadBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(kUnitQuadVertices), kUnitQuadVertices, GL_STATIC_DRAW); - mCurrentBuffer = mUnitQuadBuffer; - - uint16_t regionIndices[kMaxNumberOfQuads * 6]; - for (uint32_t i = 0; i < kMaxNumberOfQuads; i++) { - uint16_t quad = i * 4; - int index = i * 6; - regionIndices[index] = quad; // top-left - regionIndices[index + 1] = quad + 1; // top-right - regionIndices[index + 2] = quad + 2; // bottom-left - regionIndices[index + 3] = quad + 2; // bottom-left - regionIndices[index + 4] = quad + 1; // top-right - regionIndices[index + 5] = quad + 3; // bottom-right - } - glGenBuffers(1, &mQuadListIndices); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mQuadListIndices); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(regionIndices), regionIndices, GL_STATIC_DRAW); - mCurrentIndicesBuffer = mQuadListIndices; - - // position attribute always enabled - glEnableVertexAttribArray(Program::kBindingPosition); -} - -MeshState::~MeshState() { - glDeleteBuffers(1, &mUnitQuadBuffer); - mCurrentBuffer = 0; - - glDeleteBuffers(1, &mQuadListIndices); - mQuadListIndices = 0; -} - -void MeshState::dump() { - ALOGD("MeshState VBOs: unitQuad %d, current %d", mUnitQuadBuffer, mCurrentBuffer); - ALOGD("MeshState IBOs: quadList %d, current %d", mQuadListIndices, mCurrentIndicesBuffer); - ALOGD("MeshState vertices: vertex data %p, stride %d", mCurrentPositionPointer, - mCurrentPositionStride); - ALOGD("MeshState texCoord: data %p, stride %d", mCurrentTexCoordsPointer, - mCurrentTexCoordsStride); -} - -/////////////////////////////////////////////////////////////////////////////// -// Buffer Objects -/////////////////////////////////////////////////////////////////////////////// - -void MeshState::bindMeshBuffer(GLuint buffer) { - if (mCurrentBuffer != buffer) { - glBindBuffer(GL_ARRAY_BUFFER, buffer); - mCurrentBuffer = buffer; - - // buffer has changed, so invalidate cached vertex pos/texcoord pointers - resetVertexPointers(); - } -} - -void MeshState::unbindMeshBuffer() { - return bindMeshBuffer(0); -} - -void MeshState::genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, - GLenum usage) { - if (!*buffer) { - glGenBuffers(1, buffer); - } - bindMeshBuffer(*buffer); - glBufferData(GL_ARRAY_BUFFER, size, data, usage); -} - -void MeshState::updateMeshBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, - const void* data) { - bindMeshBuffer(buffer); - glBufferSubData(GL_ARRAY_BUFFER, offset, size, data); -} - -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 -/////////////////////////////////////////////////////////////////////////////// - -void MeshState::bindPositionVertexPointer(const GLvoid* vertices, GLsizei stride) { - // update pos coords if !current vbo, since vertices may point into mutable memory (e.g. stack) - if (mCurrentBuffer == 0 || vertices != mCurrentPositionPointer || - stride != mCurrentPositionStride) { - glVertexAttribPointer(Program::kBindingPosition, 2, GL_FLOAT, GL_FALSE, stride, vertices); - mCurrentPositionPointer = vertices; - mCurrentPositionStride = stride; - } -} - -void MeshState::bindTexCoordsVertexPointer(const GLvoid* vertices, GLsizei stride) { - // update tex coords if !current vbo, since vertices may point into mutable memory (e.g. stack) - if (mCurrentBuffer == 0 || vertices != mCurrentTexCoordsPointer || - stride != mCurrentTexCoordsStride) { - glVertexAttribPointer(Program::kBindingTexCoords, 2, GL_FLOAT, GL_FALSE, stride, vertices); - mCurrentTexCoordsPointer = vertices; - mCurrentTexCoordsStride = stride; - } -} - -void MeshState::resetVertexPointers() { - mCurrentPositionPointer = this; - mCurrentTexCoordsPointer = this; -} - -void MeshState::enableTexCoordsVertexArray() { - if (!mTexCoordsArrayEnabled) { - glEnableVertexAttribArray(Program::kBindingTexCoords); - mCurrentTexCoordsPointer = this; - mTexCoordsArrayEnabled = true; - } -} - -void MeshState::disableTexCoordsVertexArray() { - if (mTexCoordsArrayEnabled) { - glDisableVertexAttribArray(Program::kBindingTexCoords); - mTexCoordsArrayEnabled = false; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Indices -/////////////////////////////////////////////////////////////////////////////// - -void MeshState::bindIndicesBuffer(const GLuint buffer) { - if (mCurrentIndicesBuffer != buffer) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); - mCurrentIndicesBuffer = buffer; - } -} - -void MeshState::unbindIndicesBuffer() { - if (mCurrentIndicesBuffer) { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - mCurrentIndicesBuffer = 0; - } -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderstate/MeshState.h b/libs/hwui/renderstate/MeshState.h deleted file mode 100644 index 95faf1ebfb02..000000000000 --- a/libs/hwui/renderstate/MeshState.h +++ /dev/null @@ -1,133 +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. - */ -#ifndef RENDERSTATE_MESHSTATE_H -#define RENDERSTATE_MESHSTATE_H - -#include "Vertex.h" - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> -#include <memory> - -namespace android { -namespace uirenderer { - -class Program; - -// Maximum number of quads that pre-allocated meshes can draw -const uint32_t kMaxNumberOfQuads = 2048; - -// This array is never used directly but used as a memcpy source in the -// OpenGLRenderer constructor -const TextureVertex kUnitQuadVertices[] = { - {0, 0, 0, 0}, {1, 0, 1, 0}, {0, 1, 0, 1}, {1, 1, 1, 1}, -}; - -const GLsizei kVertexStride = sizeof(Vertex); -const GLsizei kAlphaVertexStride = sizeof(AlphaVertex); -const GLsizei kTextureVertexStride = sizeof(TextureVertex); -const GLsizei kColorTextureVertexStride = sizeof(ColorTextureVertex); - -const GLsizei kMeshTextureOffset = 2 * sizeof(float); -const GLsizei kVertexAlphaOffset = 2 * sizeof(float); -const GLsizei kVertexAAWidthOffset = 2 * sizeof(float); -const GLsizei kVertexAALengthOffset = 3 * sizeof(float); -const GLsizei kUnitQuadCount = 4; - -class MeshState { -private: - friend class RenderState; - -public: - ~MeshState(); - void dump(); - /////////////////////////////////////////////////////////////////////////////// - // Buffer objects - /////////////////////////////////////////////////////////////////////////////// - - /** - * Binds the specified VBO if needed. If buffer == 0, binds default simple textured quad. - */ - void bindMeshBuffer(GLuint buffer); - - /** - * Unbinds the current VBO if active. - */ - void unbindMeshBuffer(); - - void genOrUpdateMeshBuffer(GLuint* buffer, GLsizeiptr size, const void* data, GLenum usage); - void updateMeshBufferSubData(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data); - void deleteMeshBuffer(GLuint); - - /////////////////////////////////////////////////////////////////////////////// - // Vertices - /////////////////////////////////////////////////////////////////////////////// - /** - * Binds an attrib to the specified float vertex pointer. - * Assumes a stride of gTextureVertexStride and a size of 2. - */ - void bindPositionVertexPointer(const GLvoid* vertices, GLsizei stride = kTextureVertexStride); - - /** - * Binds an attrib to the specified float vertex pointer. - * Assumes a stride of gTextureVertexStride and a size of 2. - */ - void bindTexCoordsVertexPointer(const GLvoid* vertices, GLsizei stride = kTextureVertexStride); - - /** - * Resets the vertex pointers. - */ - void resetVertexPointers(); - - void enableTexCoordsVertexArray(); - void disableTexCoordsVertexArray(); - - /////////////////////////////////////////////////////////////////////////////// - // Indices - /////////////////////////////////////////////////////////////////////////////// - void bindIndicesBuffer(const GLuint buffer); - void unbindIndicesBuffer(); - - /////////////////////////////////////////////////////////////////////////////// - // Getters - for use in Glop building - /////////////////////////////////////////////////////////////////////////////// - GLuint getUnitQuadVBO() { return mUnitQuadBuffer; } - GLuint getQuadListIBO() { return mQuadListIndices; } - -private: - MeshState(); - - GLuint mUnitQuadBuffer; - - GLuint mCurrentBuffer; - GLuint mCurrentIndicesBuffer; - GLuint mCurrentPixelBuffer; - - const void* mCurrentPositionPointer; - GLsizei mCurrentPositionStride; - const void* mCurrentTexCoordsPointer; - GLsizei mCurrentTexCoordsStride; - - bool mTexCoordsArrayEnabled; - - // Global index buffer - GLuint mQuadListIndices; -}; - -} /* namespace uirenderer */ -} /* namespace android */ - -#endif // RENDERSTATE_MESHSTATE_H diff --git a/libs/hwui/renderstate/OffscreenBufferPool.cpp b/libs/hwui/renderstate/OffscreenBufferPool.cpp deleted file mode 100644 index a0f5cb9d4e09..000000000000 --- a/libs/hwui/renderstate/OffscreenBufferPool.cpp +++ /dev/null @@ -1,217 +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 "OffscreenBufferPool.h" - -#include "Caches.h" -#include "renderstate/RenderState.h" -#include "utils/FatVector.h" -#include "utils/TraceUtils.h" - -#include <utils/Color.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, bool wideColorGamut) - : GpuMemoryTracker(GpuObjectType::OffscreenBuffer) - , renderState(renderState) - , viewportWidth(viewportWidth) - , viewportHeight(viewportHeight) - , texture(caches) - , wideColorGamut(wideColorGamut) { - uint32_t width = computeIdealDimension(viewportWidth); - uint32_t height = computeIdealDimension(viewportHeight); - ATRACE_FORMAT("Allocate %ux%u HW Layer", width, height); - caches.textureState().activateTexture(0); - texture.resize(width, height, wideColorGamut ? GL_RGBA16F : caches.rgbaInternalFormat(), - GL_RGBA); - texture.blend = true; - texture.setWrap(GL_CLAMP_TO_EDGE); - // not setting filter on texture, since it's set when drawing, based on transform -} - -Rect OffscreenBuffer::getTextureCoordinates() { - const float texX = 1.0f / static_cast<float>(texture.width()); - const float texY = 1.0f / static_cast<float>(texture.height()); - return Rect(0, viewportHeight * texY, viewportWidth * texX, 0); -} - -void OffscreenBuffer::dirty(Rect dirtyArea) { - dirtyArea.doIntersect(0, 0, viewportWidth, viewportHeight); - if (!dirtyArea.isEmpty()) { - region.orSelf( - android::Rect(dirtyArea.left, dirtyArea.top, dirtyArea.right, dirtyArea.bottom)); - } -} - -void OffscreenBuffer::updateMeshFromRegion() { - // avoid T-junctions as they cause artifacts in between the resultant - // geometry when complex transforms occur. - // TODO: generate the safeRegion only if necessary based on drawing transform - Region safeRegion = Region::createTJunctionFreeRegion(region); - - size_t count; - const android::Rect* rects = safeRegion.getArray(&count); - - const float texX = 1.0f / float(texture.width()); - const float texY = 1.0f / float(texture.height()); - - FatVector<TextureVertex, 64> meshVector(count * - 4); // uses heap if more than 64 vertices needed - TextureVertex* mesh = &meshVector[0]; - for (size_t i = 0; i < count; i++) { - const android::Rect* r = &rects[i]; - - const float u1 = r->left * texX; - const float v1 = (viewportHeight - r->top) * texY; - const float u2 = r->right * texX; - const float v2 = (viewportHeight - r->bottom) * texY; - - TextureVertex::set(mesh++, r->left, r->top, u1, v1); - TextureVertex::set(mesh++, r->right, r->top, u2, v1); - TextureVertex::set(mesh++, r->left, r->bottom, u1, v2); - TextureVertex::set(mesh++, r->right, r->bottom, u2, v2); - } - elementCount = count * 6; - renderState.meshState().genOrUpdateMeshBuffer( - &vbo, sizeof(TextureVertex) * count * 4, &meshVector[0], - GL_DYNAMIC_DRAW); // TODO: GL_STATIC_DRAW if savelayer -} - -uint32_t OffscreenBuffer::computeIdealDimension(uint32_t dimension) { - return uint32_t(ceilf(dimension / float(LAYER_SIZE)) * LAYER_SIZE); -} - -OffscreenBuffer::~OffscreenBuffer() { - ATRACE_FORMAT("Destroy %ux%u HW Layer", texture.width(), texture.height()); - texture.deleteTexture(); - renderState.meshState().deleteMeshBuffer(vbo); - elementCount = 0; - vbo = 0; -} - -/////////////////////////////////////////////////////////////////////////////// -// OffscreenBufferPool -/////////////////////////////////////////////////////////////////////////////// - -OffscreenBufferPool::OffscreenBufferPool() - // 4 screen-sized RGBA_8888 textures - : mMaxSize(DeviceInfo::multiplyByResolution(4 * 4)) {} - -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; - - deltaInt = int(lhs.height) - int(rhs.height); - if (deltaInt != 0) return deltaInt; - - return int(lhs.wideColorGamut) - int(rhs.wideColorGamut); -} - -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, bool wideColorGamut) { - OffscreenBuffer* layer = nullptr; - - Entry entry(width, height, wideColorGamut); - 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, - wideColorGamut); - } - - return layer; -} - -OffscreenBuffer* OffscreenBufferPool::resize(OffscreenBuffer* layer, const uint32_t width, - const uint32_t height) { - RenderState& renderState = layer->renderState; - if (layer->texture.width() == OffscreenBuffer::computeIdealDimension(width) && - layer->texture.height() == OffscreenBuffer::computeIdealDimension(height)) { - // resize in place - layer->viewportWidth = width; - layer->viewportHeight = height; - - // entire area will be repainted (and may be smaller) so clear usage region - layer->region.clear(); - return layer; - } - bool wideColorGamut = layer->wideColorGamut; - putOrDelete(layer); - return get(renderState, width, height, wideColorGamut); -} - -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 deleted file mode 100644 index 08ae052da391..000000000000 --- a/libs/hwui/renderstate/OffscreenBufferPool.h +++ /dev/null @@ -1,162 +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. - */ - -#ifndef ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H -#define ANDROID_HWUI_OFFSCREEN_BUFFER_POOL_H - -#include <GpuMemoryTracker.h> -#include <ui/Region.h> -#include "Caches.h" -#include "Texture.h" -#include "utils/Macros.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, bool wideColorGamut = false); - ~OffscreenBuffer(); - - Rect getTextureCoordinates(); - - void dirty(Rect dirtyArea); - - // must be called prior to rendering, to construct/update vertex buffer - void updateMeshFromRegion(); - - // Set by RenderNode for HW layers, TODO for clipped saveLayers - void setWindowTransform(const Matrix4& transform) { - inverseTransformInWindow.loadInverse(transform); - } - - static uint32_t computeIdealDimension(uint32_t dimension); - - uint32_t getSizeInBytes() { return texture.objectSize(); } - - RenderState& renderState; - - uint32_t viewportWidth; - uint32_t viewportHeight; - Texture texture; - - bool wideColorGamut = false; - - // 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; - - bool hasRenderedSinceRepaint; -}; - -/** - * 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, bool wideColorGamut = false); - - 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, bool wideColorGamut) - : width(OffscreenBuffer::computeIdealDimension(layerWidth)) - , height(OffscreenBuffer::computeIdealDimension(layerHeight)) - , wideColorGamut(wideColorGamut) {} - - explicit Entry(OffscreenBuffer* layer) - : layer(layer) - , width(layer->texture.width()) - , height(layer->texture.height()) - , wideColorGamut(layer->wideColorGamut) {} - - 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; - bool wideColorGamut = false; - }; // 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/PixelBufferState.cpp b/libs/hwui/renderstate/PixelBufferState.cpp deleted file mode 100644 index 3a6efb833c47..000000000000 --- a/libs/hwui/renderstate/PixelBufferState.cpp +++ /dev/null @@ -1,42 +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 "renderstate/PixelBufferState.h" - -namespace android { -namespace uirenderer { - -PixelBufferState::PixelBufferState() : mCurrentPixelBuffer(0) {} - -bool PixelBufferState::bind(GLuint buffer) { - if (mCurrentPixelBuffer != buffer) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer); - mCurrentPixelBuffer = buffer; - return true; - } - return false; -} - -bool PixelBufferState::unbind() { - if (mCurrentPixelBuffer) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - mCurrentPixelBuffer = 0; - return true; - } - return false; -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp index 5e33353c3ac6..fad9440be73f 100644 --- a/libs/hwui/renderstate/RenderState.cpp +++ b/libs/hwui/renderstate/RenderState.cpp @@ -14,220 +14,26 @@ * limitations under the License. */ #include "renderstate/RenderState.h" -#include <GpuMemoryTracker.h> -#include "DeferredLayerUpdater.h" -#include "GlLayer.h" -#include "VkLayer.h" -#include "renderthread/CanvasContext.h" -#include "renderthread/EglManager.h" -#include "utils/GLUtils.h" - -#include <algorithm> - -#include <ui/ColorSpace.h> +#include "renderthread/RenderThread.h" +#include "GpuMemoryTracker.h" namespace android { namespace uirenderer { -RenderState::RenderState(renderthread::RenderThread& thread) - : mRenderThread(thread), mViewportWidth(0), mViewportHeight(0), mFramebuffer(0) { +RenderState::RenderState(renderthread::RenderThread& thread) : mRenderThread(thread) { mThreadId = pthread_self(); } -RenderState::~RenderState() { - LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil, - "State object lifecycle not managed correctly"); -} - -void RenderState::onGLContextCreated() { - LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil, - "State object lifecycle not managed correctly"); - GpuMemoryTracker::onGpuContextCreated(); - - mBlend = new Blend(); - mMeshState = new MeshState(); - mScissor = new Scissor(); - mStencil = new Stencil(); - - // Deferred because creation needs GL context for texture limits - if (!mLayerPool) { - mLayerPool = new OffscreenBufferPool(); - } - - // This is delayed because the first access of Caches makes GL calls - if (!mCaches) { - mCaches = &Caches::createInstance(*this); - } - mCaches->init(); -} - -static void layerLostGlContext(Layer* layer) { - LOG_ALWAYS_FATAL_IF(layer->getApi() != Layer::Api::OpenGL, - "layerLostGlContext on non GL layer"); - static_cast<GlLayer*>(layer)->onGlContextLost(); -} - -void RenderState::onGLContextDestroyed() { - mLayerPool->clear(); - - // TODO: reset all cached state in state objects - std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerLostGlContext); - - mCaches->terminate(); - - delete mBlend; - mBlend = nullptr; - delete mMeshState; - mMeshState = nullptr; - delete mScissor; - mScissor = nullptr; - delete mStencil; - mStencil = nullptr; - - destroyLayersInUpdater(); - GpuMemoryTracker::onGpuContextDestroyed(); -} - -void RenderState::onVkContextCreated() { - LOG_ALWAYS_FATAL_IF(mBlend || mMeshState || mScissor || mStencil, - "State object lifecycle not managed correctly"); +void RenderState::onContextCreated() { GpuMemoryTracker::onGpuContextCreated(); } -static void layerDestroyedVkContext(Layer* layer) { - LOG_ALWAYS_FATAL_IF(layer->getApi() != Layer::Api::Vulkan, - "layerLostVkContext on non Vulkan layer"); - static_cast<VkLayer*>(layer)->onVkContextDestroyed(); -} - -void RenderState::onVkContextDestroyed() { - std::for_each(mActiveLayers.begin(), mActiveLayers.end(), layerDestroyedVkContext); - destroyLayersInUpdater(); - GpuMemoryTracker::onGpuContextDestroyed(); -} - -GrContext* RenderState::getGrContext() const { - return mRenderThread.getGrContext(); -} - -void RenderState::flush(Caches::FlushMode mode) { - switch (mode) { - case Caches::FlushMode::Full: - // fall through - case Caches::FlushMode::Moderate: - // fall through - case Caches::FlushMode::Layers: - if (mLayerPool) mLayerPool->clear(); - break; - } - if (mCaches) mCaches->flush(mode); -} - -void RenderState::onBitmapDestroyed(uint32_t pixelRefId) { - if (mCaches && mCaches->textureCache.destroyTexture(pixelRefId)) { - glFlush(); - GL_CHECKPOINT(MODERATE); - } -} - -void RenderState::setViewport(GLsizei width, GLsizei height) { - mViewportWidth = width; - mViewportHeight = height; - glViewport(0, 0, mViewportWidth, mViewportHeight); -} - -void RenderState::getViewport(GLsizei* outWidth, GLsizei* outHeight) { - *outWidth = mViewportWidth; - *outHeight = mViewportHeight; -} - -void RenderState::bindFramebuffer(GLuint fbo) { - if (mFramebuffer != fbo) { - mFramebuffer = fbo; - glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); - } -} - -GLuint RenderState::createFramebuffer() { - GLuint ret; - glGenFramebuffers(1, &ret); - return ret; -} - -void RenderState::deleteFramebuffer(GLuint fbo) { - if (mFramebuffer == fbo) { - // GL defines that deleting the currently bound FBO rebinds FBO 0. - // Reflect this in our cached value. - mFramebuffer = 0; - } - glDeleteFramebuffers(1, &fbo); -} - -void RenderState::invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info) { - if (mode == DrawGlInfo::kModeProcessNoContext) { - // If there's no context we don't need to interrupt as there's - // no gl state to save/restore - (*functor)(mode, info); - } else { - interruptForFunctorInvoke(); - (*functor)(mode, info); - resumeFromFunctorInvoke(); - } -} - -void RenderState::interruptForFunctorInvoke() { - mCaches->setProgram(nullptr); - mCaches->textureState().resetActiveTexture(); - meshState().unbindMeshBuffer(); - meshState().unbindIndicesBuffer(); - meshState().resetVertexPointers(); - meshState().disableTexCoordsVertexArray(); - debugOverdraw(false, false); - // TODO: We need a way to know whether the functor is sRGB aware (b/32072673) - if (mCaches->extensions().hasLinearBlending() && mCaches->extensions().hasSRGBWriteControl()) { - glDisable(GL_FRAMEBUFFER_SRGB_EXT); - } -} - -void RenderState::resumeFromFunctorInvoke() { - if (mCaches->extensions().hasLinearBlending() && mCaches->extensions().hasSRGBWriteControl()) { - glEnable(GL_FRAMEBUFFER_SRGB_EXT); - } - - glViewport(0, 0, mViewportWidth, mViewportHeight); - glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer); - debugOverdraw(false, false); - - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - - scissor().invalidate(); - blend().invalidate(); - - mCaches->textureState().activateTexture(0); - mCaches->textureState().resetBoundTextures(); -} - -void RenderState::debugOverdraw(bool enable, bool clear) { - if (Properties::debugOverdraw && mFramebuffer == 0) { - if (clear) { - scissor().setEnabled(false); - stencil().clear(); - } - if (enable) { - stencil().enableDebugWrite(); - } else { - stencil().disable(); - } +void RenderState::onContextDestroyed() { + for(auto callback : mContextCallbacks) { + callback->onContextDestroyed(); } -} - -static void destroyLayerInUpdater(DeferredLayerUpdater* layerUpdater) { - layerUpdater->destroyLayer(); -} - -void RenderState::destroyLayersInUpdater() { - std::for_each(mActiveLayerUpdaters.begin(), mActiveLayerUpdaters.end(), destroyLayerInUpdater); + GpuMemoryTracker::onGpuContextDestroyed(); } void RenderState::postDecStrong(VirtualLightRefBase* object) { @@ -242,230 +48,5 @@ void RenderState::postDecStrong(VirtualLightRefBase* object) { // Render /////////////////////////////////////////////////////////////////////////////// -void RenderState::render(const Glop& glop, const Matrix4& orthoMatrix, - bool overrideDisableBlending) { - const Glop::Mesh& mesh = glop.mesh; - const Glop::Mesh::Vertices& vertices = mesh.vertices; - const Glop::Mesh::Indices& indices = mesh.indices; - const Glop::Fill& fill = glop.fill; - - GL_CHECKPOINT(MODERATE); - - // --------------------------------------------- - // ---------- Program + uniform setup ---------- - // --------------------------------------------- - mCaches->setProgram(fill.program); - - if (fill.colorEnabled) { - fill.program->setColor(fill.color); - } - - fill.program->set(orthoMatrix, glop.transform.modelView, glop.transform.meshTransform(), - glop.transform.transformFlags & TransformFlags::OffsetByFudgeFactor); - - // Color filter uniforms - 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::ColorFilterMode::Matrix) { - glUniformMatrix4fv(mCaches->program().getUniform("colorMatrix"), 1, GL_FALSE, - fill.filter.matrix.matrix); - glUniform4fv(mCaches->program().getUniform("colorMatrixVector"), 1, - fill.filter.matrix.vector); - } - - // Round rect clipping uniforms - if (glop.roundRectClipState) { - // TODO: avoid query, and cache values (or RRCS ptr) in program - const RoundRectClipState* state = glop.roundRectClipState; - const Rect& innerRect = state->innerRect; - - // add half pixel to round out integer rect space to cover pixel centers - float roundedOutRadius = state->radius + 0.5f; - - // Divide by the radius to simplify the calculations in the fragment shader - // roundRectPos is also passed from vertex shader relative to top/left & radius - glUniform4f(fill.program->getUniform("roundRectInnerRectLTWH"), - innerRect.left / roundedOutRadius, innerRect.top / roundedOutRadius, - (innerRect.right - innerRect.left) / roundedOutRadius, - (innerRect.bottom - innerRect.top) / roundedOutRadius); - - glUniformMatrix4fv(fill.program->getUniform("roundRectInvTransform"), 1, GL_FALSE, - &state->matrix.data[0]); - - glUniform1f(fill.program->getUniform("roundRectRadius"), roundedOutRadius); - } - - GL_CHECKPOINT(MODERATE); - - // -------------------------------- - // ---------- Mesh setup ---------- - // -------------------------------- - // vertices - meshState().bindMeshBuffer(vertices.bufferObject); - meshState().bindPositionVertexPointer(vertices.position, vertices.stride); - - // indices - meshState().bindIndicesBuffer(indices.bufferObject); - - // texture - if (fill.texture.texture != nullptr) { - const Glop::Fill::TextureData& texture = fill.texture; - // texture always takes slot 0, shader samplers increment from there - mCaches->textureState().activateTexture(0); - - mCaches->textureState().bindTexture(texture.texture->target(), texture.texture->id()); - if (texture.clamp != GL_INVALID_ENUM) { - texture.texture->setWrap(texture.clamp, false, false); - } - if (texture.filter != GL_INVALID_ENUM) { - texture.texture->setFilter(texture.filter, false, false); - } - - if (texture.textureTransform) { - glUniformMatrix4fv(fill.program->getUniform("mainTextureTransform"), 1, GL_FALSE, - &texture.textureTransform->data[0]); - } - } - - // vertex attributes (tex coord, color, alpha) - if (vertices.attribFlags & VertexAttribFlags::TextureCoord) { - meshState().enableTexCoordsVertexArray(); - meshState().bindTexCoordsVertexPointer(vertices.texCoord, vertices.stride); - } else { - meshState().disableTexCoordsVertexArray(); - } - int colorLocation = -1; - if (vertices.attribFlags & VertexAttribFlags::Color) { - colorLocation = fill.program->getAttrib("colors"); - glEnableVertexAttribArray(colorLocation); - glVertexAttribPointer(colorLocation, 4, GL_FLOAT, GL_FALSE, vertices.stride, - vertices.color); - } - int alphaLocation = -1; - if (vertices.attribFlags & VertexAttribFlags::Alpha) { - // NOTE: alpha vertex position is computed assuming no VBO - const void* alphaCoords = ((const GLbyte*)vertices.position) + kVertexAlphaOffset; - alphaLocation = fill.program->getAttrib("vtxAlpha"); - glEnableVertexAttribArray(alphaLocation); - glVertexAttribPointer(alphaLocation, 1, GL_FLOAT, GL_FALSE, vertices.stride, alphaCoords); - } - // Shader uniforms - SkiaShader::apply(*mCaches, fill.skiaShaderData, mViewportWidth, mViewportHeight); - - GL_CHECKPOINT(MODERATE); - Texture* texture = (fill.skiaShaderData.skiaShaderType & kBitmap_SkiaShaderType) - ? fill.skiaShaderData.bitmapData.bitmapTexture - : nullptr; - const AutoTexture autoCleanup(texture); - - // If we have a shader and a base texture, the base texture is assumed to be an alpha mask - // which means the color space conversion applies to the shader's bitmap - Texture* colorSpaceTexture = texture != nullptr ? texture : fill.texture.texture; - if (colorSpaceTexture != nullptr) { - if (colorSpaceTexture->hasColorSpaceConversion()) { - const ColorSpaceConnector* connector = colorSpaceTexture->getColorSpaceConnector(); - glUniformMatrix3fv(fill.program->getUniform("colorSpaceMatrix"), 1, GL_FALSE, - connector->getTransform().asArray()); - } - - TransferFunctionType transferFunction = colorSpaceTexture->getTransferFunctionType(); - if (transferFunction != TransferFunctionType::None) { - const ColorSpaceConnector* connector = colorSpaceTexture->getColorSpaceConnector(); - const ColorSpace& source = connector->getSource(); - - switch (transferFunction) { - case TransferFunctionType::None: - break; - case TransferFunctionType::Full: - glUniform1fv(fill.program->getUniform("transferFunction"), 7, - reinterpret_cast<const float*>(&source.getTransferParameters().g)); - break; - case TransferFunctionType::Limited: - glUniform1fv(fill.program->getUniform("transferFunction"), 5, - reinterpret_cast<const float*>(&source.getTransferParameters().g)); - break; - case TransferFunctionType::Gamma: - glUniform1f(fill.program->getUniform("transferFunctionGamma"), - source.getTransferParameters().g); - break; - } - } - } - - // ------------------------------------ - // ---------- GL state setup ---------- - // ------------------------------------ - if (CC_UNLIKELY(overrideDisableBlending)) { - blend().setFactors(GL_ZERO, GL_ZERO); - } else { - blend().setFactors(glop.blend.src, glop.blend.dst); - } - - GL_CHECKPOINT(MODERATE); - - // ------------------------------------ - // ---------- Actual drawing ---------- - // ------------------------------------ - if (indices.bufferObject == meshState().getQuadListIBO()) { - // Since the indexed quad list is of limited length, we loop over - // the glDrawXXX method while updating the vertex pointer - GLsizei elementsCount = mesh.elementCount; - const GLbyte* vertexData = static_cast<const GLbyte*>(vertices.position); - while (elementsCount > 0) { - GLsizei drawCount = std::min(elementsCount, (GLsizei)kMaxNumberOfQuads * 6); - GLsizei vertexCount = (drawCount / 6) * 4; - meshState().bindPositionVertexPointer(vertexData, vertices.stride); - if (vertices.attribFlags & VertexAttribFlags::TextureCoord) { - meshState().bindTexCoordsVertexPointer(vertexData + kMeshTextureOffset, - vertices.stride); - } - - if (mCaches->extensions().getMajorGlVersion() >= 3) { - glDrawRangeElements(mesh.primitiveMode, 0, vertexCount - 1, drawCount, - GL_UNSIGNED_SHORT, nullptr); - } else { - glDrawElements(mesh.primitiveMode, drawCount, GL_UNSIGNED_SHORT, nullptr); - } - elementsCount -= drawCount; - vertexData += vertexCount * vertices.stride; - } - } else if (indices.bufferObject || indices.indices) { - if (mCaches->extensions().getMajorGlVersion() >= 3) { - // use glDrawRangeElements to reduce CPU overhead (otherwise the driver has to determine - // the min/max index values) - glDrawRangeElements(mesh.primitiveMode, 0, mesh.vertexCount - 1, mesh.elementCount, - GL_UNSIGNED_SHORT, indices.indices); - } else { - glDrawElements(mesh.primitiveMode, mesh.elementCount, GL_UNSIGNED_SHORT, - indices.indices); - } - } else { - glDrawArrays(mesh.primitiveMode, 0, mesh.elementCount); - } - - GL_CHECKPOINT(MODERATE); - - // ----------------------------------- - // ---------- Mesh teardown ---------- - // ----------------------------------- - if (vertices.attribFlags & VertexAttribFlags::Alpha) { - glDisableVertexAttribArray(alphaLocation); - } - if (vertices.attribFlags & VertexAttribFlags::Color) { - glDisableVertexAttribArray(colorLocation); - } - - GL_CHECKPOINT(MODERATE); -} - -void RenderState::dump() { - blend().dump(); - meshState().dump(); - scissor().dump(); - stencil().dump(); -} - } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h index e033cf2c5046..ff5d02fe359a 100644 --- a/libs/hwui/renderstate/RenderState.h +++ b/libs/hwui/renderstate/RenderState.h @@ -16,132 +16,59 @@ #ifndef RENDERSTATE_H #define RENDERSTATE_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 <GLES2/gl2.h> -#include <GLES2/gl2ext.h> -#include <private/hwui/DrawGlInfo.h> -#include <ui/Region.h> -#include <utils/Functor.h> -#include <utils/Mutex.h> #include <utils/RefBase.h> #include <set> -class GrContext; - namespace android { namespace uirenderer { -class Caches; class Layer; -class DeferredLayerUpdater; namespace renderthread { class CacheManager; -class CanvasContext; class RenderThread; } -// TODO: Replace Cache's GL state tracking with this. For now it's more a thin +class IGpuContextCallback { +public: + virtual void onContextDestroyed() = 0; +protected: + virtual ~IGpuContextCallback() {} +}; + // wrapper of Caches for users to migrate to. class RenderState { PREVENT_COPY_AND_ASSIGN(RenderState); friend class renderthread::RenderThread; - friend class Caches; friend class renderthread::CacheManager; public: - void onGLContextCreated(); - void onGLContextDestroyed(); - - void onVkContextCreated(); - void onVkContextDestroyed(); - - void flush(Caches::FlushMode flushMode); - void onBitmapDestroyed(uint32_t pixelRefId); - - void setViewport(GLsizei width, GLsizei height); - void getViewport(GLsizei* outWidth, GLsizei* outHeight); - - void bindFramebuffer(GLuint fbo); - GLuint getFramebuffer() { return mFramebuffer; } - GLuint createFramebuffer(); - void deleteFramebuffer(GLuint fbo); - - void invokeFunctor(Functor* functor, DrawGlInfo::Mode mode, DrawGlInfo* info); - - void debugOverdraw(bool enable, bool clear); + void registerContextCallback(IGpuContextCallback* cb) { mContextCallbacks.insert(cb); } + void removeContextCallback(IGpuContextCallback* cb) { mContextCallbacks.erase(cb); } void registerLayer(Layer* layer) { mActiveLayers.insert(layer); } void unregisterLayer(Layer* layer) { mActiveLayers.erase(layer); } - void registerCanvasContext(renderthread::CanvasContext* context) { - mRegisteredContexts.insert(context); - } - - void unregisterCanvasContext(renderthread::CanvasContext* context) { - mRegisteredContexts.erase(context); - } - - void registerDeferredLayerUpdater(DeferredLayerUpdater* layerUpdater) { - mActiveLayerUpdaters.insert(layerUpdater); - } - - void unregisterDeferredLayerUpdater(DeferredLayerUpdater* layerUpdater) { - mActiveLayerUpdaters.erase(layerUpdater); - } - // TODO: This system is a little clunky feeling, this could use some // more thinking... void postDecStrong(VirtualLightRefBase* object); - void render(const Glop& glop, const Matrix4& orthoMatrix, bool overrideDisableBlending); - - Blend& blend() { return *mBlend; } - MeshState& meshState() { return *mMeshState; } - Scissor& scissor() { return *mScissor; } - Stencil& stencil() { return *mStencil; } - - OffscreenBufferPool& layerPool() { return *mLayerPool; } - - GrContext* getGrContext() const; - - void dump(); + renderthread::RenderThread& getRenderThread() const { return mRenderThread; } private: - void interruptForFunctorInvoke(); - void resumeFromFunctorInvoke(); - void destroyLayersInUpdater(); - explicit RenderState(renderthread::RenderThread& thread); - ~RenderState(); - - renderthread::RenderThread& mRenderThread; - Caches* mCaches = nullptr; + ~RenderState() {} - Blend* mBlend = nullptr; - MeshState* mMeshState = nullptr; - Scissor* mScissor = nullptr; - Stencil* mStencil = nullptr; - - OffscreenBufferPool* mLayerPool = nullptr; + // Context notifications are only to be triggered by renderthread::RenderThread + void onContextCreated(); + void onContextDestroyed(); + std::set<IGpuContextCallback*> mContextCallbacks; std::set<Layer*> mActiveLayers; - std::set<DeferredLayerUpdater*> mActiveLayerUpdaters; - std::set<renderthread::CanvasContext*> mRegisteredContexts; - - GLsizei mViewportWidth; - GLsizei mViewportHeight; - GLuint mFramebuffer; + renderthread::RenderThread& mRenderThread; pthread_t mThreadId; }; diff --git a/libs/hwui/renderstate/Scissor.cpp b/libs/hwui/renderstate/Scissor.cpp deleted file mode 100644 index e37ed029542b..000000000000 --- a/libs/hwui/renderstate/Scissor.cpp +++ /dev/null @@ -1,103 +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 "renderstate/Scissor.h" - -#include "Rect.h" - -#include <utils/Log.h> - -namespace android { -namespace uirenderer { - -Scissor::Scissor() - : mEnabled(false), mScissorX(0), mScissorY(0), mScissorWidth(0), mScissorHeight(0) {} - -bool Scissor::setEnabled(bool enabled) { - if (mEnabled != enabled) { - if (enabled) { - glEnable(GL_SCISSOR_TEST); - } else { - glDisable(GL_SCISSOR_TEST); - } - mEnabled = enabled; - return true; - } - return false; -} - -bool Scissor::set(GLint x, GLint y, GLint width, GLint height) { - if (mEnabled && - (x != mScissorX || y != mScissorY || width != mScissorWidth || height != mScissorHeight)) { - if (x < 0) { - width += x; - x = 0; - } - if (y < 0) { - height += y; - y = 0; - } - if (width < 0) { - width = 0; - } - if (height < 0) { - height = 0; - } - glScissor(x, y, width, height); - - mScissorX = x; - mScissorY = y; - mScissorWidth = width; - mScissorHeight = height; - - return true; - } - 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; -} - -void Scissor::invalidate() { - mEnabled = glIsEnabled(GL_SCISSOR_TEST); - setEnabled(true); - reset(); -} - -void Scissor::dump() { - ALOGD("Scissor: enabled %d, %d %d %d %d", mEnabled, mScissorX, mScissorY, mScissorWidth, - mScissorHeight); -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderstate/Scissor.h b/libs/hwui/renderstate/Scissor.h deleted file mode 100644 index 2b04f4e1384a..000000000000 --- a/libs/hwui/renderstate/Scissor.h +++ /dev/null @@ -1,51 +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. - */ -#ifndef RENDERSTATE_SCISSOR_H -#define RENDERSTATE_SCISSOR_H - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> - -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(); - -private: - Scissor(); - void invalidate(); - bool mEnabled; - GLint mScissorX; - GLint mScissorY; - GLint mScissorWidth; - GLint mScissorHeight; -}; - -} /* namespace uirenderer */ -} /* namespace android */ - -#endif // RENDERSTATE_SCISSOR_H diff --git a/libs/hwui/renderstate/Stencil.cpp b/libs/hwui/renderstate/Stencil.cpp deleted file mode 100644 index dc465fc7a6f8..000000000000 --- a/libs/hwui/renderstate/Stencil.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2012 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 "renderstate/Stencil.h" - -#include "Caches.h" -#include "Debug.h" -#include "Extensions.h" -#include "Properties.h" - -#include <GLES2/gl2ext.h> - -namespace android { -namespace uirenderer { - -#if DEBUG_STENCIL -#define STENCIL_WRITE_VALUE 0xff -#define STENCIL_MASK_VALUE 0xff -#else -#define STENCIL_WRITE_VALUE 0x1 -#define STENCIL_MASK_VALUE 0x1 -#endif - -uint8_t Stencil::getStencilSize() { - return STENCIL_BUFFER_SIZE; -} - -/** - * This method will return either GL_STENCIL_INDEX4_OES if supported, - * GL_STENCIL_INDEX8 if not. - * - * Layers can't use a single bit stencil because multi-rect ClipArea needs a high enough - * stencil resolution to represent the summation of multiple intersecting rect geometries. - */ -GLenum Stencil::getLayerStencilFormat() { -#if !DEBUG_STENCIL - const Extensions& extensions = DeviceInfo::get()->extensions(); - if (extensions.has4BitStencil()) { - return GL_STENCIL_INDEX4_OES; - } -#endif - return GL_STENCIL_INDEX8; -} - -void Stencil::clear() { - glStencilMask(0xff); - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - - if (mState == StencilState::Test) { - // reset to test state, with immutable stencil - glStencilMask(0); - } -} - -void Stencil::enableTest(int incrementThreshold) { - if (mState != StencilState::Test) { - enable(); - if (incrementThreshold > 0) { - glStencilFunc(GL_EQUAL, incrementThreshold, 0xff); - } else { - glStencilFunc(GL_EQUAL, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE); - } - // We only want to test, let's keep everything - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glStencilMask(0); - mState = StencilState::Test; - } -} - -void Stencil::enableWrite(int incrementThreshold) { - if (mState != StencilState::Write) { - enable(); - if (incrementThreshold > 0) { - glStencilFunc(GL_ALWAYS, 1, 0xff); - // The test always passes so the first two values are meaningless - glStencilOp(GL_INCR, GL_INCR, GL_INCR); - } else { - glStencilFunc(GL_ALWAYS, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE); - // The test always passes so the first two values are meaningless - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - } - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - glStencilMask(0xff); - mState = StencilState::Write; - } -} - -void Stencil::enableDebugTest(GLint value, bool greater) { - enable(); - 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 = StencilState::Test; - glStencilMask(0); -} - -void Stencil::enableDebugWrite() { - enable(); - glStencilFunc(GL_ALWAYS, 0x1, 0xffffffff); - // 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 = StencilState::Write; - glStencilMask(0xff); -} - -void Stencil::enable() { - if (mState == StencilState::Disabled) { - glEnable(GL_STENCIL_TEST); - } -} - -void Stencil::disable() { - if (mState != StencilState::Disabled) { - glDisable(GL_STENCIL_TEST); - mState = StencilState::Disabled; - } -} - -void Stencil::dump() { - ALOGD("Stencil: state %d", mState); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/renderstate/Stencil.h b/libs/hwui/renderstate/Stencil.h deleted file mode 100644 index 95f372344ee4..000000000000 --- a/libs/hwui/renderstate/Stencil.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2012 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_STENCIL_H -#define ANDROID_HWUI_STENCIL_H - -#include <GLES2/gl2.h> - -#include <cutils/compiler.h> - -namespace android { -namespace uirenderer { - -/////////////////////////////////////////////////////////////////////////////// -// Stencil buffer management -/////////////////////////////////////////////////////////////////////////////// - -class ANDROID_API Stencil { -public: - /** - * Returns the desired size for the stencil buffer. If the returned value - * is 0, then no stencil buffer is required. - */ - ANDROID_API static uint8_t getStencilSize(); - - static GLenum getLayerStencilFormat(); - - /** - * Clears the stencil buffer. - */ - void clear(); - - /** - * Enables stencil test. When the stencil test is enabled the stencil buffer is not written - * into. An increment threshold of zero causes the stencil to use a constant reference value - * and GL_EQUAL for the test. A non-zero increment threshold causes the stencil to use that - * value as the reference value and GL_EQUAL for the test. - */ - void enableTest(int incrementThreshold); - - /** - * Enables stencil write. When stencil write is enabled, the stencil - * test always succeeds and the value 0x1 is written in the stencil - * buffer for each fragment. An increment threshold of zero causes the stencil to use a constant - * reference value and GL_EQUAL for the test. A non-zero increment threshold causes the stencil - * to use that value as the reference value and GL_EQUAL for the test. - */ - void enableWrite(int incrementThreshold); - - /** - * The test passes only when equal to the specified value. - */ - void enableDebugTest(GLint value, bool greater = false); - - /** - * Used for debugging. The stencil test always passes and increments. - */ - void enableDebugWrite(); - - /** - * Disables stencil test and write. - */ - void disable(); - - /** - * Indicates whether either test or write is enabled. - */ - bool isEnabled() { return mState != StencilState::Disabled; } - - /** - * Indicates whether testing only is enabled. - */ - bool isTestEnabled() { return mState == StencilState::Test; } - - bool isWriteEnabled() { return mState == StencilState::Write; } - - void dump(); - -private: - enum class StencilState { Disabled, Test, Write }; - - void enable(); - StencilState mState = StencilState::Disabled; - -}; // class Stencil - -}; // namespace uirenderer -}; // namespace android - -#endif // ANDROID_HWUI_STENCIL_H diff --git a/libs/hwui/renderstate/TextureState.cpp b/libs/hwui/renderstate/TextureState.cpp deleted file mode 100644 index 470b4f5de97f..000000000000 --- a/libs/hwui/renderstate/TextureState.cpp +++ /dev/null @@ -1,147 +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 "renderstate/TextureState.h" - -#include "Caches.h" -#include "utils/TraceUtils.h" - -#include <GLES3/gl3.h> -#include <SkBitmap.h> -#include <SkCanvas.h> -#include <memory> - -namespace android { -namespace uirenderer { - -// Width of mShadowLutTexture, defines how accurate the shadow alpha lookup table is -static const int SHADOW_LUT_SIZE = 128; - -// Must define as many texture units as specified by kTextureUnitsCount -const GLenum kTextureUnits[] = {GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2, GL_TEXTURE3}; - -TextureState::TextureState() : mTextureUnit(0) { - glActiveTexture(kTextureUnits[0]); - resetBoundTextures(); - - GLint maxTextureUnits; - 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); -} - -TextureState::~TextureState() { - if (mShadowLutTexture != nullptr) { - mShadowLutTexture->deleteTexture(); - } -} - -/** - * Maps shadow geometry 'alpha' varying (1 for darkest, 0 for transparent) to - * darkness at that spot. Input values of 0->1 should be mapped within the same - * range, but can affect the curve for a different visual falloff. - * - * This is used to populate the shadow LUT texture for quick lookup in the - * shadow shader. - */ -static float computeShadowOpacity(float ratio) { - // exponential falloff function provided by UX - float val = 1 - ratio; - return exp(-val * val * 4.0) - 0.018; -} - -void TextureState::constructTexture(Caches& caches) { - if (mShadowLutTexture == nullptr) { - mShadowLutTexture.reset(new Texture(caches)); - - unsigned char bytes[SHADOW_LUT_SIZE]; - for (int i = 0; i < SHADOW_LUT_SIZE; i++) { - float inputRatio = i / (SHADOW_LUT_SIZE - 1.0f); - bytes[i] = computeShadowOpacity(inputRatio) * 255; - } - mShadowLutTexture->upload(GL_ALPHA, SHADOW_LUT_SIZE, 1, GL_ALPHA, GL_UNSIGNED_BYTE, &bytes); - mShadowLutTexture->setFilter(GL_LINEAR); - mShadowLutTexture->setWrap(GL_CLAMP_TO_EDGE); - } -} - -void TextureState::activateTexture(GLuint textureUnit) { - LOG_ALWAYS_FATAL_IF(textureUnit >= kTextureUnitsCount, - "Tried to use texture unit index %d, only %d exist", textureUnit, - kTextureUnitsCount); - if (mTextureUnit != textureUnit) { - glActiveTexture(kTextureUnits[textureUnit]); - mTextureUnit = textureUnit; - } -} - -void TextureState::resetActiveTexture() { - mTextureUnit = -1; -} - -void TextureState::bindTexture(GLuint texture) { - if (mBoundTextures[mTextureUnit] != texture) { - glBindTexture(GL_TEXTURE_2D, texture); - mBoundTextures[mTextureUnit] = texture; - } -} - -void TextureState::bindTexture(GLenum target, GLuint texture) { - if (target == GL_TEXTURE_2D) { - bindTexture(texture); - } else { - // GLConsumer directly calls glBindTexture() with - // target=GL_TEXTURE_EXTERNAL_OES, don't cache this target - // since the cached state could be stale - glBindTexture(target, texture); - } -} - -void TextureState::deleteTexture(GLuint texture) { - // When glDeleteTextures() is called on a currently bound texture, - // OpenGL ES specifies that the texture is then considered unbound - // Consider the following series of calls: - // - // glGenTextures -> creates texture name 2 - // glBindTexture(2) - // glDeleteTextures(2) -> 2 is now unbound - // glGenTextures -> can return 2 again - // - // If we don't call glBindTexture(2) after the second glGenTextures - // call, any texture operation will be performed on the default - // texture (name=0) - - unbindTexture(texture); - - glDeleteTextures(1, &texture); -} - -void TextureState::resetBoundTextures() { - for (int i = 0; i < kTextureUnitsCount; i++) { - mBoundTextures[i] = 0; - } -} - -void TextureState::unbindTexture(GLuint texture) { - for (int i = 0; i < kTextureUnitsCount; i++) { - if (mBoundTextures[i] == texture) { - mBoundTextures[i] = 0; - } - } -} - -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderstate/TextureState.h b/libs/hwui/renderstate/TextureState.h deleted file mode 100644 index f1996d431fa2..000000000000 --- a/libs/hwui/renderstate/TextureState.h +++ /dev/null @@ -1,98 +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. - */ -#ifndef RENDERSTATE_TEXTURESTATE_H -#define RENDERSTATE_TEXTURESTATE_H - -#include "Texture.h" -#include "Vertex.h" - -#include <GLES2/gl2.h> -#include <GLES2/gl2ext.h> -#include <memory> - -namespace android { -namespace uirenderer { - -class Texture; - -class TextureState { - friend class Caches; // TODO: move to RenderState -public: - void constructTexture(Caches& caches); - - /** - * Activate the specified texture unit. The texture unit must - * be specified using an integer number (0 for GL_TEXTURE0 etc.) - */ - void activateTexture(GLuint textureUnit); - - /** - * Invalidate the cached value of the active texture unit. - */ - void resetActiveTexture(); - - /** - * Binds the specified texture as a GL_TEXTURE_2D texture. - * All texture bindings must be performed with this method or - * bindTexture(GLenum, GLuint). - */ - void bindTexture(GLuint texture); - - /** - * Binds the specified texture with the specified render target. - * All texture bindings must be performed with this method or - * bindTexture(GLuint). - */ - void bindTexture(GLenum target, GLuint texture); - - /** - * Deletes the specified texture and clears it from the cache - * of bound textures. - * All textures must be deleted using this method. - */ - void deleteTexture(GLuint texture); - - /** - * Signals that the cache of bound textures should be cleared. - * Other users of the context may have altered which textures are bound. - */ - void resetBoundTextures(); - - /** - * Clear the cache of bound textures. - */ - void unbindTexture(GLuint texture); - - Texture* getShadowLutTexture() { return mShadowLutTexture.get(); } - -private: - // total number of texture units available for use - static const int kTextureUnitsCount = 4; - - TextureState(); - ~TextureState(); - GLuint mTextureUnit; - - // Caches texture bindings for the GL_TEXTURE_2D target - GLuint mBoundTextures[kTextureUnitsCount]; - - std::unique_ptr<Texture> mShadowLutTexture; -}; - -} /* namespace uirenderer */ -} /* namespace android */ - -#endif // RENDERSTATE_BLEND_H diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index f510a2055309..7acc44ca65b7 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -21,6 +21,7 @@ #include "RenderThread.h" #include "pipeline/skia/ShaderCache.h" #include "pipeline/skia/SkiaMemoryTracer.h" +#include "Properties.h" #include "renderstate/RenderState.h" #include <GrContextOptions.h> @@ -50,9 +51,6 @@ CacheManager::CacheManager(const DisplayInfo& display) : mMaxSurfaceArea(display mVectorDrawableAtlas = new skiapipeline::VectorDrawableAtlas( mMaxSurfaceArea / 2, skiapipeline::VectorDrawableAtlas::StorageMode::disallowSharedSurface); - if (Properties::isSkiaEnabled()) { - skiapipeline::ShaderCache::get().initShaderDiskCache(); - } } void CacheManager::reset(sk_sp<GrContext> context) { @@ -105,7 +103,7 @@ public: } }; -void CacheManager::configureContext(GrContextOptions* contextOptions) { +void CacheManager::configureContext(GrContextOptions* contextOptions, const void* identity, ssize_t size) { contextOptions->fAllowPathMaskCaching = true; float screenMP = mMaxSurfaceArea / 1024.0f / 1024.0f; @@ -135,7 +133,10 @@ void CacheManager::configureContext(GrContextOptions* contextOptions) { contextOptions->fExecutor = mTaskProcessor.get(); } - contextOptions->fPersistentCache = &skiapipeline::ShaderCache::get(); + auto& cache = skiapipeline::ShaderCache::get(); + cache.initShaderDiskCache(identity, size); + contextOptions->fPersistentCache = &cache; + contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting; } void CacheManager::trimMemory(TrimMemoryMode mode) { @@ -215,11 +216,12 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) log.appendFormat(" Layer Info:\n"); } + const char* layerType = Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL + ? "GlLayer" : "VkLayer"; size_t layerMemoryTotal = 0; for (std::set<Layer*>::iterator it = renderState->mActiveLayers.begin(); it != renderState->mActiveLayers.end(); it++) { const Layer* layer = *it; - const char* layerType = layer->getApi() == Layer::Api::OpenGL ? "GlLayer" : "VkLayer"; log.appendFormat(" %s size %dx%d\n", layerType, layer->getWidth(), layer->getHeight()); layerMemoryTotal += layer->getWidth() * layer->getHeight() * 4; diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h index 7d733525194f..35fc91a42510 100644 --- a/libs/hwui/renderthread/CacheManager.h +++ b/libs/hwui/renderthread/CacheManager.h @@ -44,7 +44,7 @@ class CacheManager { public: enum class TrimMemoryMode { Complete, UiHidden }; - void configureContext(GrContextOptions* context); + void configureContext(GrContextOptions* context, const void* identity, ssize_t size); void trimMemory(TrimMemoryMode mode); void trimStaleResources(); void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index bfa2ae7e72ff..f1a522ecd588 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -18,26 +18,20 @@ #include <GpuMemoryTracker.h> #include "AnimationContext.h" -#include "Caches.h" #include "EglManager.h" #include "Frame.h" #include "LayerUpdateQueue.h" -#include "OpenGLPipeline.h" #include "Properties.h" #include "RenderThread.h" #include "hwui/Canvas.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/SkiaPipeline.h" #include "pipeline/skia/SkiaVulkanPipeline.h" -#include "protos/hwui.pb.h" -#include "renderstate/RenderState.h" -#include "renderstate/Stencil.h" #include "utils/GLUtils.h" #include "utils/TimeUtils.h" #include "../Properties.h" #include <cutils/properties.h> -#include <google/protobuf/io/zero_copy_stream_impl.h> #include <private/hwui/DrawGlInfo.h> #include <strings.h> @@ -51,8 +45,6 @@ #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 @@ -70,9 +62,6 @@ CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent, auto renderType = Properties::getRenderPipelineType(); switch (renderType) { - case RenderPipelineType::OpenGL: - return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, - std::make_unique<OpenGLPipeline>(thread)); case RenderPipelineType::SkiaGL: return new CanvasContext(thread, translucent, rootRenderNode, contextFactory, std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread)); @@ -86,29 +75,10 @@ CanvasContext* CanvasContext::create(RenderThread& thread, bool translucent, return nullptr; } -void CanvasContext::destroyLayer(RenderNode* node) { - auto renderType = Properties::getRenderPipelineType(); - switch (renderType) { - case RenderPipelineType::OpenGL: - OpenGLPipeline::destroyLayer(node); - break; - case RenderPipelineType::SkiaGL: - case RenderPipelineType::SkiaVulkan: - skiapipeline::SkiaPipeline::destroyLayer(node); - break; - default: - LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType); - break; - } -} - void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) { ATRACE_CALL(); auto renderType = Properties::getRenderPipelineType(); switch (renderType) { - case RenderPipelineType::OpenGL: - OpenGLPipeline::invokeFunctor(thread, functor); - break; case RenderPipelineType::SkiaGL: skiapipeline::SkiaOpenGLPipeline::invokeFunctor(thread, functor); break; @@ -122,19 +92,7 @@ void CanvasContext::invokeFunctor(const RenderThread& thread, Functor* functor) } void CanvasContext::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { - auto renderType = Properties::getRenderPipelineType(); - switch (renderType) { - case RenderPipelineType::OpenGL: - OpenGLPipeline::prepareToDraw(thread, bitmap); - break; - case RenderPipelineType::SkiaGL: - case RenderPipelineType::SkiaVulkan: - skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap); - break; - default: - LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType); - break; - } + skiapipeline::SkiaPipeline::prepareToDraw(thread, bitmap); } CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, @@ -150,13 +108,11 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* , mRenderPipeline(std::move(renderPipeline)) { rootRenderNode->makeRoot(); mRenderNodes.emplace_back(rootRenderNode); - mRenderThread.renderState().registerCanvasContext(this); mProfiler.setDensity(mRenderThread.mainDisplayInfo().density); } CanvasContext::~CanvasContext() { destroy(); - mRenderThread.renderState().unregisterCanvasContext(this); for (auto& node : mRenderNodes) { node->clearRoot(); } @@ -188,7 +144,7 @@ void CanvasContext::setSurface(sp<Surface>&& surface) { mNativeSurface = std::move(surface); - ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::Srgb; + ColorMode colorMode = mWideColorGamut ? ColorMode::WideColorGamut : ColorMode::SRGB; bool hasSurface = mRenderPipeline->setSurface(mNativeSurface.get(), mSwapBehavior, colorMode); mFrameNumber = -1; @@ -224,14 +180,20 @@ void CanvasContext::setStopped(bool stopped) { } } -void CanvasContext::setup(float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { - mLightGeometry.radius = lightRadius; +void CanvasContext::allocateBuffers() { + if (mNativeSurface) { + mNativeSurface->allocateBuffers(); + } +} + +void CanvasContext::setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { mLightInfo.ambientShadowAlpha = ambientShadowAlpha; mLightInfo.spotShadowAlpha = spotShadowAlpha; } -void CanvasContext::setLightCenter(const Vector3& lightCenter) { +void CanvasContext::setLightGeometry(const Vector3& lightCenter, float lightRadius) { mLightGeometry.center = lightCenter; + mLightGeometry.radius = lightRadius; } void CanvasContext::setOpaque(bool opaque) { @@ -453,7 +415,7 @@ void CanvasContext::draw() { SkRect windowDirty = computeDirtyRect(frame, &dirty); bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, - mContentDrawBounds, mOpaque, mWideColorGamut, mLightInfo, + mContentDrawBounds, mOpaque, mLightInfo, mRenderNodes, &(profiler())); int64_t frameCompleteNr = mFrameCompleteCallbacks.size() ? getFrameNumber() : -1; @@ -531,13 +493,6 @@ void CanvasContext::draw() { } GpuMemoryTracker::onFrameCompleted(); -#ifdef BUGREPORT_FONT_CACHE_USAGE - auto renderType = Properties::getRenderPipelineType(); - if (RenderPipelineType::OpenGL == renderType) { - Caches& caches = Caches::getInstance(); - caches.fontRenderer.getFontRenderer().historyTracker().frameCompleted(); - } -#endif } // Called by choreographer to do an RT-driven animation @@ -599,17 +554,12 @@ void CanvasContext::buildLayer(RenderNode* node) { // purposes when the frame is actually drawn node->setPropertyFieldsDirty(RenderNode::GENERIC); - mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue, mOpaque, mWideColorGamut, - mLightInfo); + mRenderPipeline->renderLayers(mLightGeometry, &mLayerUpdateQueue, mOpaque, mLightInfo); node->incStrong(nullptr); mPrefetchedLayers.insert(node); } -bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { - return mRenderPipeline->copyLayerInto(layer, bitmap); -} - void CanvasContext::destroyHardwareResources() { stopDrawing(); if (mRenderPipeline->isContextReady()) { @@ -622,37 +572,15 @@ void CanvasContext::destroyHardwareResources() { } void CanvasContext::trimMemory(RenderThread& thread, int level) { - auto renderType = Properties::getRenderPipelineType(); - switch (renderType) { - case RenderPipelineType::OpenGL: { - // No context means nothing to free - if (!thread.eglManager().hasEglContext()) return; - ATRACE_CALL(); - if (level >= TRIM_MEMORY_COMPLETE) { - thread.renderState().flush(Caches::FlushMode::Full); - thread.eglManager().destroy(); - } else if (level >= TRIM_MEMORY_UI_HIDDEN) { - thread.renderState().flush(Caches::FlushMode::Moderate); - } - break; - } - case RenderPipelineType::SkiaGL: - case RenderPipelineType::SkiaVulkan: { - // No context means nothing to free - if (!thread.getGrContext()) return; - ATRACE_CALL(); - if (level >= TRIM_MEMORY_COMPLETE) { - thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); - thread.eglManager().destroy(); - thread.vulkanManager().destroy(); - } else if (level >= TRIM_MEMORY_UI_HIDDEN) { - thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden); - } - break; - } - default: - LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType); - break; + ATRACE_CALL(); + if (!thread.getGrContext()) return; + ATRACE_CALL(); + if (level >= TRIM_MEMORY_COMPLETE) { + thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete); + thread.destroyGlContext(); + thread.vulkanManager().destroy(); + } else if (level >= TRIM_MEMORY_UI_HIDDEN) { + thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden); } } @@ -673,39 +601,6 @@ void CanvasContext::setName(const std::string&& name) { mJankTracker.setDescription(JankTrackerType::Window, std::move(name)); } -void CanvasContext::serializeDisplayListTree() { -#if ENABLE_RENDERNODE_SERIALIZATION - using namespace google::protobuf::io; - char package[128]; - // Check whether tracing is enabled for this process. - FILE* file = fopen("/proc/self/cmdline", "r"); - if (file) { - if (!fgets(package, 128, file)) { - ALOGE("Error reading cmdline: %s (%d)", strerror(errno), errno); - fclose(file); - return; - } - fclose(file); - } else { - ALOGE("Error opening /proc/self/cmdline: %s (%d)", strerror(errno), errno); - return; - } - char path[1024]; - snprintf(path, 1024, "/data/data/%s/cache/rendertree_dump", package); - int fd = open(path, O_CREAT | O_WRONLY, S_IRWXU | S_IRGRP | S_IROTH); - if (fd == -1) { - ALOGD("Failed to open '%s'", path); - return; - } - proto::RenderNode tree; - // TODO: Streaming writes? - mRootRenderNode->copyTo(&tree); - std::string data = tree.SerializeAsString(); - write(fd, data.c_str(), data.length()); - close(fd); -#endif -} - void CanvasContext::waitOnFences() { if (mFrameFences.size()) { ATRACE_CALL(); diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 1c4e02d7df70..70be4a6d7730 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -16,10 +16,8 @@ #pragma once -#include "BakedOpDispatcher.h" -#include "BakedOpRenderer.h" #include "DamageAccumulator.h" -#include "FrameBuilder.h" +#include "Lighting.h" #include "FrameInfo.h" #include "FrameInfoVisualizer.h" #include "FrameMetricsReporter.h" @@ -77,8 +75,7 @@ public: */ bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator, ErrorHandler* errorHandler) { - return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, mWideColorGamut, - errorHandler); + return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, errorHandler); } /** @@ -99,12 +96,6 @@ public: */ void unpinImages() { mRenderPipeline->unpinImages(); } - /** - * Destroy any layers that have been attached to the provided RenderNode removing - * any state that may have been set during createOrUpdateLayer(). - */ - static void destroyLayer(RenderNode* node); - static void invokeFunctor(const RenderThread& thread, Functor* functor); static void prepareToDraw(const RenderThread& thread, Bitmap* bitmap); @@ -122,9 +113,10 @@ public: bool pauseSurface(); void setStopped(bool stopped); bool hasSurface() { return mNativeSurface.get(); } + void allocateBuffers(); - void setup(float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); - void setLightCenter(const Vector3& lightCenter); + void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); + void setLightGeometry(const Vector3& lightCenter, float lightRadius); void setOpaque(bool opaque); void setWideGamut(bool wideGamut); bool makeCurrent(); @@ -137,7 +129,6 @@ public: void prepareAndDraw(RenderNode* node); void buildLayer(RenderNode* node); - bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap); void markLayerInUse(RenderNode* node); void destroyHardwareResources(); @@ -155,8 +146,6 @@ public: void setName(const std::string&& name); - void serializeDisplayListTree(); - void addRenderNode(RenderNode* node, bool placeFront); void removeRenderNode(RenderNode* node); @@ -194,6 +183,23 @@ public: mFrameCompleteCallbacks.push_back(std::move(func)); } + void setForceDark(bool enable) { + mUseForceDark = enable; + } + + bool useForceDark() { + // The force-dark override has the highest priority, followed by the disable setting + // for the feature as a whole, followed last by whether or not this context has had + // force dark set (typically automatically done via UIMode) + if (Properties::forceDarkMode) { + return true; + } + if (!Properties::enableForceDarkSupport) { + return false; + } + return mUseForceDark; + } + private: CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline); @@ -240,8 +246,9 @@ private: bool mOpaque; bool mWideColorGamut = false; - BakedOpRenderer::LightInfo mLightInfo; - FrameBuilder::LightGeometry mLightGeometry = {{0, 0, 0}, 0}; + bool mUseForceDark = false; + LightInfo mLightInfo; + LightGeometry mLightGeometry = {{0, 0, 0}, 0}; bool mHaveNewSurface = false; DamageAccumulator mDamageAccumulator; diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index 2c46762fee5c..696cfaef3cd7 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -32,7 +32,6 @@ namespace android { namespace uirenderer { class DeferredLayerUpdater; -class DisplayList; class RenderNode; namespace renderthread { diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp index 6e239e357cf6..d4ffddde8def 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -16,27 +16,21 @@ #include "EglManager.h" -#include <string> - #include <cutils/properties.h> #include <log/log.h> +#include <private/gui/SyncFeatures.h> +#include <utils/Trace.h> +#include "utils/Color.h" #include "utils/StringUtils.h" -#include "Caches.h" -#include "DeviceInfo.h" #include "Frame.h" #include "Properties.h" -#include "RenderThread.h" -#include "Texture.h" -#include "renderstate/RenderState.h" #include <EGL/eglext.h> -#include <GrContextOptions.h> -#include <gl/GrGLInterface.h> +#include <GLES/gl.h> -#ifdef HWUI_GLES_WRAP_ENABLED -#include "debug/GlesDriver.h" -#endif +#include <string> +#include <vector> #define GLES_VERSION 2 @@ -82,18 +76,23 @@ static struct { bool pixelFormatFloat = false; bool glColorSpace = false; bool scRGB = false; + bool displayP3 = false; bool contextPriority = false; + bool surfacelessContext = false; } EglExtensions; -EglManager::EglManager(RenderThread& thread) - : mRenderThread(thread) - , mEglDisplay(EGL_NO_DISPLAY) +EglManager::EglManager() + : mEglDisplay(EGL_NO_DISPLAY) , mEglConfig(nullptr) , mEglConfigWideGamut(nullptr) , mEglContext(EGL_NO_CONTEXT) , mPBufferSurface(EGL_NO_SURFACE) , mCurrentSurface(EGL_NO_SURFACE) {} +EglManager::~EglManager() { + destroy(); +} + void EglManager::initialize() { if (hasEglContext()) return; @@ -126,26 +125,7 @@ void EglManager::initialize() { loadConfigs(); createContext(); createPBufferSurface(); - makeCurrent(mPBufferSurface); - DeviceInfo::initialize(); - mRenderThread.renderState().onGLContextCreated(); - - if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { -#ifdef HWUI_GLES_WRAP_ENABLED - debug::GlesDriver* driver = debug::GlesDriver::get(); - sk_sp<const GrGLInterface> glInterface(driver->getSkiaInterface()); -#else - sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface()); -#endif - LOG_ALWAYS_FATAL_IF(!glInterface.get()); - - GrContextOptions options; - options.fDisableDistanceFieldPaths = true; - mRenderThread.cacheManager().configureContext(&options); - sk_sp<GrContext> grContext(GrContext::MakeGL(std::move(glInterface), options)); - LOG_ALWAYS_FATAL_IF(!grContext.get()); - mRenderThread.setGrContext(grContext); - } + makeCurrent(mPBufferSurface, nullptr, /* force */ true); } void EglManager::initExtensions() { @@ -169,7 +149,9 @@ void EglManager::initExtensions() { #else EglExtensions.scRGB = extensions.has("EGL_EXT_gl_colorspace_scrgb"); #endif + EglExtensions.displayP3 = extensions.has("EGL_EXT_gl_colorspace_display_p3"); EglExtensions.contextPriority = extensions.has("EGL_IMG_context_priority"); + EglExtensions.surfacelessContext = extensions.has("EGL_KHR_surfaceless_context"); } bool EglManager::hasEglContext() { @@ -180,6 +162,10 @@ void EglManager::loadConfigs() { ALOGD("Swap behavior %d", static_cast<int>(mSwapBehavior)); EGLint swapBehavior = (mSwapBehavior == SwapBehavior::Preserved) ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0; + + // Note: The default pixel format is RGBA_8888, when other formats are + // available, we should check the target pixel format and configure the + // attributes list properly. EGLint attribs[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, @@ -195,7 +181,7 @@ void EglManager::loadConfigs() { EGL_CONFIG_CAVEAT, EGL_NONE, EGL_STENCIL_SIZE, - Stencil::getStencilSize(), + STENCIL_BUFFER_SIZE, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, EGL_NONE}; @@ -232,7 +218,7 @@ void EglManager::loadConfigs() { EGL_DEPTH_SIZE, 0, EGL_STENCIL_SIZE, - Stencil::getStencilSize(), + STENCIL_BUFFER_SIZE, EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior, EGL_NONE}; @@ -269,17 +255,18 @@ void EglManager::createPBufferSurface() { LOG_ALWAYS_FATAL_IF(mEglDisplay == EGL_NO_DISPLAY, "usePBufferSurface() called on uninitialized GlobalContext!"); - if (mPBufferSurface == EGL_NO_SURFACE) { + if (mPBufferSurface == EGL_NO_SURFACE && !EglExtensions.surfacelessContext) { EGLint attribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; mPBufferSurface = eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs); } } -EGLSurface EglManager::createSurface(EGLNativeWindowType window, bool wideColorGamut) { - initialize(); +EGLSurface EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) { + LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized"); - wideColorGamut = wideColorGamut && EglExtensions.glColorSpace && EglExtensions.scRGB && - EglExtensions.pixelFormatFloat && EglExtensions.noConfigContext; + bool wideColorGamut = colorMode == ColorMode::WideColorGamut && EglExtensions.glColorSpace && + EglExtensions.scRGB && EglExtensions.pixelFormatFloat && + EglExtensions.noConfigContext; // The color space we want to use depends on whether linear blending is turned // on and whether the app has requested wide color gamut rendering. When wide @@ -289,9 +276,9 @@ EGLSurface EglManager::createSurface(EGLNativeWindowType window, bool wideColorG // When wide gamut rendering is off: // - Blending is done by default in gamma space, which requires using a // linear EGL color space (the GPU uses the color values as is) - // - If linear blending is on, we must use the sRGB EGL color space (the - // GPU will perform sRGB to linear and linear to SRGB conversions before - // and after blending) + // - If linear blending is on, we must use the non-linear EGL color space + // (the GPU will perform sRGB to linear and linear to SRGB conversions + // before and after blending) // // When wide gamut rendering is on we cannot rely on the GPU performing // linear blending for us. We use two different color spaces to tag the @@ -299,7 +286,7 @@ EGLSurface EglManager::createSurface(EGLNativeWindowType window, bool wideColorG // - Gamma blending (default) requires the use of the scRGB-nl color space // - Linear blending requires the use of the scRGB color space - // Not all Android targets support the EGL_GL_COLOR_SPACE_KHR extension + // Not all Android targets support the EGL_GL_COLORSPACE_KHR extension // We insert to placeholders to set EGL_GL_COLORSPACE_KHR and its value. // According to section 3.4.1 of the EGL specification, the attributes // list is considered empty if the first entry is EGL_NONE @@ -350,10 +337,10 @@ void EglManager::destroySurface(EGLSurface surface) { void EglManager::destroy() { if (mEglDisplay == EGL_NO_DISPLAY) return; - mRenderThread.setGrContext(nullptr); - mRenderThread.renderState().onGLContextDestroyed(); eglDestroyContext(mEglDisplay, mEglContext); - eglDestroySurface(mEglDisplay, mPBufferSurface); + if (mPBufferSurface != EGL_NO_SURFACE) { + eglDestroySurface(mEglDisplay, mPBufferSurface); + } eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglTerminate(mEglDisplay); eglReleaseThread(); @@ -364,8 +351,8 @@ void EglManager::destroy() { mCurrentSurface = EGL_NO_SURFACE; } -bool EglManager::makeCurrent(EGLSurface surface, EGLint* errOut) { - if (isCurrent(surface)) return false; +bool EglManager::makeCurrent(EGLSurface surface, EGLint* errOut, bool force) { + if (!force && isCurrent(surface)) return false; if (surface == EGL_NO_SURFACE) { // Ensure we always have a valid surface & context @@ -485,6 +472,109 @@ bool EglManager::setPreserveBuffer(EGLSurface surface, bool preserve) { return preserved; } +status_t EglManager::fenceWait(sp<Fence>& fence) { + if (!hasEglContext()) { + ALOGE("EglManager::fenceWait: EGLDisplay not initialized"); + return INVALID_OPERATION; + } + + if (SyncFeatures::getInstance().useWaitSync() && + SyncFeatures::getInstance().useNativeFenceSync()) { + // Block GPU on the fence. + // Create an EGLSyncKHR from the current fence. + int fenceFd = fence->dup(); + if (fenceFd == -1) { + ALOGE("EglManager::fenceWait: error dup'ing fence fd: %d", errno); + return -errno; + } + EGLint attribs[] = { + EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd, + EGL_NONE + }; + EGLSyncKHR sync = eglCreateSyncKHR(mEglDisplay, + EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); + if (sync == EGL_NO_SYNC_KHR) { + close(fenceFd); + ALOGE("EglManager::fenceWait: error creating EGL fence: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + + // XXX: The spec draft is inconsistent as to whether this should + // return an EGLint or void. Ignore the return value for now, as + // it's not strictly needed. + eglWaitSyncKHR(mEglDisplay, sync, 0); + EGLint eglErr = eglGetError(); + eglDestroySyncKHR(mEglDisplay, sync); + if (eglErr != EGL_SUCCESS) { + ALOGE("EglManager::fenceWait: error waiting for EGL fence: %#x", eglErr); + return UNKNOWN_ERROR; + } + } else { + // Block CPU on the fence. + status_t err = fence->waitForever("EglManager::fenceWait"); + if (err != NO_ERROR) { + ALOGE("EglManager::fenceWait: error waiting for fence: %d", err); + return err; + } + } + return OK; +} + +status_t EglManager::createReleaseFence(bool useFenceSync, EGLSyncKHR* eglFence, + sp<Fence>& nativeFence) { + if (!hasEglContext()) { + ALOGE("EglManager::createReleaseFence: EGLDisplay not initialized"); + return INVALID_OPERATION; + } + + if (SyncFeatures::getInstance().useNativeFenceSync()) { + EGLSyncKHR sync = eglCreateSyncKHR(mEglDisplay, + EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); + if (sync == EGL_NO_SYNC_KHR) { + ALOGE("EglManager::createReleaseFence: error creating EGL fence: %#x", + eglGetError()); + return UNKNOWN_ERROR; + } + glFlush(); + int fenceFd = eglDupNativeFenceFDANDROID(mEglDisplay, sync); + eglDestroySyncKHR(mEglDisplay, sync); + if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + ALOGE("EglManager::createReleaseFence: error dup'ing native fence " + "fd: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + nativeFence = new Fence(fenceFd); + *eglFence = EGL_NO_SYNC_KHR; + } else if (useFenceSync && SyncFeatures::getInstance().useFenceSync()) { + if (*eglFence != EGL_NO_SYNC_KHR) { + // There is already a fence for the current slot. We need to + // wait on that before replacing it with another fence to + // ensure that all outstanding buffer accesses have completed + // before the producer accesses it. + EGLint result = eglClientWaitSyncKHR(mEglDisplay, *eglFence, 0, 1000000000); + if (result == EGL_FALSE) { + ALOGE("EglManager::createReleaseFence: error waiting for previous fence: %#x", + eglGetError()); + return UNKNOWN_ERROR; + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + ALOGE("EglManager::createReleaseFence: timeout waiting for previous fence"); + return TIMED_OUT; + } + eglDestroySyncKHR(mEglDisplay, *eglFence); + } + + // Create a fence for the outstanding accesses in the current + // OpenGL ES context. + *eglFence = eglCreateSyncKHR(mEglDisplay, EGL_SYNC_FENCE_KHR, nullptr); + if (*eglFence == EGL_NO_SYNC_KHR) { + ALOGE("EglManager::createReleaseFence: error creating fence: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + glFlush(); + } + return OK; +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h index ef9effbf9953..55c81d42d8a0 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -17,10 +17,14 @@ #define EGLMANAGER_H #include <EGL/egl.h> +#include <EGL/eglext.h> +#include <SkImageInfo.h> #include <SkRect.h> #include <cutils/compiler.h> +#include <ui/Fence.h> #include <ui/GraphicBuffer.h> #include <utils/StrongPointer.h> +#include "IRenderPipeline.h" namespace android { namespace uirenderer { @@ -33,20 +37,24 @@ class RenderThread; // and EGLConfig, which are re-used by CanvasContext class EglManager { public: + explicit EglManager(); + + ~EglManager(); + static const char* eglErrorString(); - // Returns true on success, false on failure + void initialize(); bool hasEglContext(); - EGLSurface createSurface(EGLNativeWindowType window, bool wideColorGamut); + EGLSurface createSurface(EGLNativeWindowType window, ColorMode colorMode); void destroySurface(EGLSurface surface); void destroy(); 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); + bool makeCurrent(EGLSurface surface, EGLint* errOut = nullptr, bool force = false); Frame beginFrame(EGLSurface surface); void damageFrame(const Frame& frame, const SkRect& dirty); // If this returns true it is mandatory that swapBuffers is called @@ -60,11 +68,17 @@ public: void fence(); + EGLDisplay eglDisplay() const { return mEglDisplay; } + + // Inserts a wait on fence command into the OpenGL ES command stream. If EGL extension + // support is missing, block the CPU on the fence. + status_t fenceWait(sp<Fence>& fence); + + // Creates a fence that is signaled, when all the pending GL commands are flushed. + // Depending on installed extensions, the result is either Android native fence or EGL fence. + status_t createReleaseFence(bool useFenceSync, EGLSyncKHR* eglFence, sp<Fence>& nativeFence); + private: - friend class RenderThread; - explicit EglManager(RenderThread& thread); - // EglContext is never destroyed, method is purposely not implemented - ~EglManager(); void initExtensions(); void createPBufferSurface(); @@ -72,14 +86,11 @@ private: void createContext(); EGLint queryBufferAge(EGLSurface surface); - RenderThread& mRenderThread; - EGLDisplay mEglDisplay; EGLConfig mEglConfig; EGLConfig mEglConfigWideGamut; EGLContext mEglContext; EGLSurface mPBufferSurface; - EGLSurface mCurrentSurface; enum class SwapBehavior { diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h index b1de49733c09..4972554c65cc 100644 --- a/libs/hwui/renderthread/IRenderPipeline.h +++ b/libs/hwui/renderthread/IRenderPipeline.h @@ -16,8 +16,12 @@ #pragma once +#include "DamageAccumulator.h" #include "FrameInfoVisualizer.h" +#include "LayerUpdateQueue.h" +#include "Lighting.h" #include "SwapBehavior.h" +#include "hwui/Bitmap.h" #include <SkRect.h> #include <utils/RefBase.h> @@ -32,13 +36,18 @@ namespace uirenderer { class DeferredLayerUpdater; class ErrorHandler; +class TaskManager; namespace renderthread { enum class MakeCurrentResult { AlreadyCurrent, Failed, Succeeded }; enum class ColorMode { - Srgb, + // SRGB means HWUI will produce buffer in SRGB color space. + SRGB, + // WideColorGamut means HWUI would support rendering scRGB non-linear into + // a signed buffer with enough range to support the wide color gamut of the + // display. WideColorGamut, // Hdr }; @@ -50,30 +59,31 @@ public: virtual MakeCurrentResult makeCurrent() = 0; virtual Frame getFrame() = 0; virtual bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const FrameBuilder::LightGeometry& lightGeometry, + const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, - bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo, + bool opaque, const LightInfo& lightInfo, const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) = 0; virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, FrameInfo* currentFrameInfo, bool* requireSwap) = 0; - virtual bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) = 0; virtual DeferredLayerUpdater* createTextureLayer() = 0; virtual bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) = 0; virtual void onStop() = 0; virtual bool isSurfaceReady() = 0; virtual bool isContextReady() = 0; virtual void onDestroyHardwareResources() = 0; - virtual void renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo) = 0; + virtual void renderLayers(const LightGeometry& lightGeometry, + LayerUpdateQueue* layerUpdateQueue, bool opaque, + const LightInfo& lightInfo) = 0; virtual TaskManager* getTaskManager() = 0; virtual bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - bool wideColorGamut, ErrorHandler* errorHandler) = 0; + ErrorHandler* errorHandler) = 0; virtual bool pinImages(std::vector<SkImage*>& mutableImages) = 0; virtual bool pinImages(LsaVector<sk_sp<Bitmap>>& images) = 0; virtual void unpinImages() = 0; virtual void onPrepareTree() = 0; + virtual SkColorType getSurfaceColorType() const = 0; + virtual sk_sp<SkColorSpace> getSurfaceColorSpace() = 0; virtual ~IRenderPipeline() {} }; diff --git a/libs/hwui/renderthread/OpenGLPipeline.cpp b/libs/hwui/renderthread/OpenGLPipeline.cpp deleted file mode 100644 index f96001ebdd57..000000000000 --- a/libs/hwui/renderthread/OpenGLPipeline.cpp +++ /dev/null @@ -1,452 +0,0 @@ -/* - * 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 "OpenGLPipeline.h" - -#include "DeferredLayerUpdater.h" -#include "EglManager.h" -#include "Frame.h" -#include "GlLayer.h" -#include "OpenGLReadback.h" -#include "ProfileRenderer.h" -#include "renderstate/RenderState.h" -#include "TreeInfo.h" - -#include <cutils/properties.h> -#include <strings.h> - -namespace android { -namespace uirenderer { -namespace renderthread { - -OpenGLPipeline::OpenGLPipeline(RenderThread& thread) - : mEglManager(thread.eglManager()), mRenderThread(thread) {} - -MakeCurrentResult OpenGLPipeline::makeCurrent() { - // TODO: Figure out why this workaround is needed, see b/13913604 - // In the meantime this matches the behavior of GLRenderer, so it is not a regression - EGLint error = 0; - bool haveNewSurface = mEglManager.makeCurrent(mEglSurface, &error); - - Caches::getInstance().textureCache.resetMarkInUse(this); - if (!haveNewSurface) { - return MakeCurrentResult::AlreadyCurrent; - } - return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded; -} - -Frame OpenGLPipeline::getFrame() { - LOG_ALWAYS_FATAL_IF(mEglSurface == EGL_NO_SURFACE, - "drawRenderNode called on a context with no surface!"); - return mEglManager.beginFrame(mEglSurface); -} - -bool OpenGLPipeline::draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds, - bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo, - const std::vector<sp<RenderNode>>& renderNodes, - FrameInfoVisualizer* profiler) { - mEglManager.damageFrame(frame, dirty); - - bool drew = false; - - auto& caches = Caches::getInstance(); - FrameBuilder frameBuilder(dirty, frame.width(), frame.height(), lightGeometry, caches); - - frameBuilder.deferLayers(*layerUpdateQueue); - layerUpdateQueue->clear(); - - frameBuilder.deferRenderNodeScene(renderNodes, contentDrawBounds); - - BakedOpRenderer renderer(caches, mRenderThread.renderState(), opaque, wideColorGamut, - lightInfo); - frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); - ProfileRenderer profileRenderer(renderer); - profiler->draw(profileRenderer); - drew = renderer.didDraw(); - - // post frame cleanup - caches.clearGarbage(); - caches.pathCache.trim(); - caches.tessellationCache.trim(); - -#if DEBUG_MEMORY_USAGE - caches.dumpMemoryUsage(); -#else - if (CC_UNLIKELY(Properties::debugLevel & kDebugMemory)) { - caches.dumpMemoryUsage(); - } -#endif - - return drew; -} - -bool OpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, - FrameInfo* currentFrameInfo, bool* requireSwap) { - GL_CHECKPOINT(LOW); - - // Even if we decided to cancel the frame, from the perspective of jank - // metrics the frame was swapped at this point - currentFrameInfo->markSwapBuffers(); - - *requireSwap = drew || mEglManager.damageRequiresSwap(); - - if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) { - return false; - } - - return *requireSwap; -} - -bool OpenGLPipeline::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) { - ATRACE_CALL(); - // acquire most recent buffer for drawing - layer->updateTexImage(); - layer->apply(); - return OpenGLReadbackImpl::copyLayerInto(mRenderThread, - static_cast<GlLayer&>(*layer->backingLayer()), bitmap); -} - -static Layer* createLayer(RenderState& renderState, uint32_t layerWidth, uint32_t layerHeight, - sk_sp<SkColorFilter> colorFilter, int alpha, SkBlendMode mode, - bool blend) { - GlLayer* layer = - new GlLayer(renderState, layerWidth, layerHeight, colorFilter, alpha, mode, blend); - Caches::getInstance().textureState().activateTexture(0); - layer->generateTexture(); - return layer; -} - -DeferredLayerUpdater* OpenGLPipeline::createTextureLayer() { - mEglManager.initialize(); - return new DeferredLayerUpdater(mRenderThread.renderState(), createLayer, Layer::Api::OpenGL); -} - -void OpenGLPipeline::onStop() { - if (mEglManager.isCurrent(mEglSurface)) { - mEglManager.makeCurrent(EGL_NO_SURFACE); - } -} - -bool OpenGLPipeline::setSurface(Surface* surface, SwapBehavior swapBehavior, ColorMode colorMode) { - if (mEglSurface != EGL_NO_SURFACE) { - mEglManager.destroySurface(mEglSurface); - mEglSurface = EGL_NO_SURFACE; - } - - if (surface) { - const bool wideColorGamut = colorMode == ColorMode::WideColorGamut; - mEglSurface = mEglManager.createSurface(surface, wideColorGamut); - } - - if (mEglSurface != EGL_NO_SURFACE) { - const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer); - mBufferPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer); - return true; - } - - return false; -} - -bool OpenGLPipeline::isSurfaceReady() { - return CC_UNLIKELY(mEglSurface != EGL_NO_SURFACE); -} - -bool OpenGLPipeline::isContextReady() { - return CC_LIKELY(mEglManager.hasEglContext()); -} - -void OpenGLPipeline::onDestroyHardwareResources() { - 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); - mRenderThread.renderState().flush(Caches::FlushMode::Layers); -} - -void OpenGLPipeline::renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, - bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo) { - static const std::vector<sp<RenderNode>> emptyNodeList; - auto& caches = Caches::getInstance(); - FrameBuilder frameBuilder(*layerUpdateQueue, lightGeometry, caches); - layerUpdateQueue->clear(); - // TODO: Handle wide color gamut contexts - BakedOpRenderer renderer(caches, mRenderThread.renderState(), opaque, wideColorGamut, - lightInfo); - LOG_ALWAYS_FATAL_IF(renderer.didDraw(), "shouldn't draw in buildlayer case"); - frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); -} - -TaskManager* OpenGLPipeline::getTaskManager() { - return &Caches::getInstance().tasks; -} - -static bool layerMatchesWH(OffscreenBuffer* layer, int width, int height) { - return layer->viewportWidth == (uint32_t)width && layer->viewportHeight == (uint32_t)height; -} - -bool OpenGLPipeline::createOrUpdateLayer(RenderNode* node, - const DamageAccumulator& damageAccumulator, - bool wideColorGamut, - ErrorHandler* errorHandler) { - RenderState& renderState = mRenderThread.renderState(); - OffscreenBufferPool& layerPool = renderState.layerPool(); - bool transformUpdateNeeded = false; - if (node->getLayer() == nullptr) { - node->setLayer( - layerPool.get(renderState, node->getWidth(), node->getHeight(), wideColorGamut)); - transformUpdateNeeded = true; - } else if (!layerMatchesWH(node->getLayer(), node->getWidth(), node->getHeight())) { - // TODO: remove now irrelevant, currently enqueued damage (respecting damage ordering) - // Or, ideally, maintain damage between frames on node/layer so ordering is always correct - if (node->properties().fitsOnLayer()) { - node->setLayer(layerPool.resize(node->getLayer(), node->getWidth(), node->getHeight())); - } else { - destroyLayer(node); - } - transformUpdateNeeded = true; - } - - if (transformUpdateNeeded && node->getLayer()) { - // update the transform in window of the layer to reset its origin wrt light source position - Matrix4 windowTransform; - damageAccumulator.computeCurrentTransform(&windowTransform); - node->getLayer()->setWindowTransform(windowTransform); - } - - if (!node->hasLayer()) { - Caches::getInstance().dumpMemoryUsage(); - if (errorHandler) { - std::ostringstream err; - err << "Unable to create layer for " << node->getName(); - const int maxTextureSize = Caches::getInstance().maxTextureSize; - if (node->getWidth() > maxTextureSize || node->getHeight() > maxTextureSize) { - err << ", size " << node->getWidth() << "x" << node->getHeight() - << " exceeds max size " << maxTextureSize; - } else { - err << ", see logcat for more info"; - } - errorHandler->onError(err.str()); - } - } - - return transformUpdateNeeded; -} - -bool OpenGLPipeline::pinImages(LsaVector<sk_sp<Bitmap>>& images) { - TextureCache& cache = Caches::getInstance().textureCache; - bool prefetchSucceeded = true; - for (auto& bitmapResource : images) { - prefetchSucceeded &= cache.prefetchAndMarkInUse(this, bitmapResource.get()); - } - return prefetchSucceeded; -} - -void OpenGLPipeline::unpinImages() { - Caches::getInstance().textureCache.resetMarkInUse(this); -} - -void OpenGLPipeline::destroyLayer(RenderNode* node) { - if (OffscreenBuffer* layer = node->getLayer()) { - layer->renderState.layerPool().putOrDelete(layer); - node->setLayer(nullptr); - } -} - -void OpenGLPipeline::prepareToDraw(const RenderThread& thread, Bitmap* bitmap) { - if (Caches::hasInstance() && thread.eglManager().hasEglContext()) { - ATRACE_NAME("Bitmap#prepareToDraw task"); - Caches::getInstance().textureCache.prefetch(bitmap); - } -} - -void OpenGLPipeline::invokeFunctor(const RenderThread& thread, Functor* functor) { - DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext; - if (thread.eglManager().hasEglContext()) { - mode = DrawGlInfo::kModeProcess; - } - thread.renderState().invokeFunctor(functor, mode, nullptr); -} - -#define FENCE_TIMEOUT 2000000000 - -class AutoEglFence { -public: - AutoEglFence(EGLDisplay display) : mDisplay(display) { - fence = eglCreateSyncKHR(mDisplay, EGL_SYNC_FENCE_KHR, NULL); - } - - ~AutoEglFence() { - if (fence != EGL_NO_SYNC_KHR) { - eglDestroySyncKHR(mDisplay, fence); - } - } - - EGLSyncKHR fence = EGL_NO_SYNC_KHR; - -private: - EGLDisplay mDisplay = EGL_NO_DISPLAY; -}; - -class AutoEglImage { -public: - AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer) : mDisplay(display) { - EGLint imageAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; - image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, - imageAttrs); - } - - ~AutoEglImage() { - if (image != EGL_NO_IMAGE_KHR) { - eglDestroyImageKHR(mDisplay, image); - } - } - - EGLImageKHR image = EGL_NO_IMAGE_KHR; - -private: - EGLDisplay mDisplay = EGL_NO_DISPLAY; -}; - -class AutoGlTexture { -public: - AutoGlTexture(uirenderer::Caches& caches) : mCaches(caches) { - glGenTextures(1, &mTexture); - caches.textureState().bindTexture(mTexture); - } - - ~AutoGlTexture() { mCaches.textureState().deleteTexture(mTexture); } - -private: - uirenderer::Caches& mCaches; - GLuint mTexture = 0; -}; - -static bool uploadBitmapToGraphicBuffer(uirenderer::Caches& caches, SkBitmap& bitmap, - GraphicBuffer& buffer, GLint format, GLint type) { - EGLDisplay display = eglGetCurrentDisplay(); - LOG_ALWAYS_FATAL_IF(display == EGL_NO_DISPLAY, "Failed to get EGL_DEFAULT_DISPLAY! err=%s", - uirenderer::renderthread::EglManager::eglErrorString()); - // We use an EGLImage to access the content of the GraphicBuffer - // The EGL image is later bound to a 2D texture - EGLClientBuffer clientBuffer = (EGLClientBuffer)buffer.getNativeBuffer(); - AutoEglImage autoImage(display, clientBuffer); - if (autoImage.image == EGL_NO_IMAGE_KHR) { - ALOGW("Could not create EGL image, err =%s", - uirenderer::renderthread::EglManager::eglErrorString()); - return false; - } - AutoGlTexture glTexture(caches); - glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, autoImage.image); - - GL_CHECKPOINT(MODERATE); - - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(), format, type, - bitmap.getPixels()); - - GL_CHECKPOINT(MODERATE); - - // The fence is used to wait for the texture upload to finish - // properly. We cannot rely on glFlush() and glFinish() as - // some drivers completely ignore these API calls - AutoEglFence autoFence(display); - if (autoFence.fence == EGL_NO_SYNC_KHR) { - LOG_ALWAYS_FATAL("Could not create sync fence %#x", eglGetError()); - return false; - } - // The flag EGL_SYNC_FLUSH_COMMANDS_BIT_KHR will trigger a - // pipeline flush (similar to what a glFlush() would do.) - EGLint waitStatus = eglClientWaitSyncKHR(display, autoFence.fence, - EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, FENCE_TIMEOUT); - if (waitStatus != EGL_CONDITION_SATISFIED_KHR) { - LOG_ALWAYS_FATAL("Failed to wait for the fence %#x", eglGetError()); - return false; - } - return true; -} - -// TODO: handle SRGB sanely -static PixelFormat internalFormatToPixelFormat(GLint internalFormat) { - switch (internalFormat) { - case GL_LUMINANCE: - return PIXEL_FORMAT_RGBA_8888; - case GL_SRGB8_ALPHA8: - return PIXEL_FORMAT_RGBA_8888; - case GL_RGBA: - return PIXEL_FORMAT_RGBA_8888; - case GL_RGB: - return PIXEL_FORMAT_RGB_565; - case GL_RGBA16F: - return PIXEL_FORMAT_RGBA_FP16; - default: - LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", internalFormat); - return PIXEL_FORMAT_UNKNOWN; - } -} - -sk_sp<Bitmap> OpenGLPipeline::allocateHardwareBitmap(RenderThread& renderThread, - SkBitmap& skBitmap) { - renderThread.eglManager().initialize(); - uirenderer::Caches& caches = uirenderer::Caches::getInstance(); - - const SkImageInfo& info = skBitmap.info(); - if (info.colorType() == kUnknown_SkColorType || info.colorType() == kAlpha_8_SkColorType) { - ALOGW("unable to create hardware bitmap of colortype: %d", info.colorType()); - return nullptr; - } - - bool needSRGB = uirenderer::transferFunctionCloseToSRGB(skBitmap.info().colorSpace()); - bool hasLinearBlending = caches.extensions().hasLinearBlending(); - GLint format, type, internalFormat; - uirenderer::Texture::colorTypeToGlFormatAndType(caches, skBitmap.colorType(), - needSRGB && hasLinearBlending, &internalFormat, - &format, &type); - - PixelFormat pixelFormat = internalFormatToPixelFormat(internalFormat); - sp<GraphicBuffer> buffer = new GraphicBuffer( - info.width(), info.height(), pixelFormat, - GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | - GraphicBuffer::USAGE_SW_READ_NEVER, - std::string("Bitmap::allocateHardwareBitmap pid [") + std::to_string(getpid()) + "]"); - - status_t error = buffer->initCheck(); - if (error < 0) { - ALOGW("createGraphicBuffer() failed in GraphicBuffer.create()"); - return nullptr; - } - - SkBitmap bitmap; - if (CC_UNLIKELY( - uirenderer::Texture::hasUnsupportedColorType(skBitmap.info(), hasLinearBlending))) { - sk_sp<SkColorSpace> sRGB = SkColorSpace::MakeSRGB(); - bitmap = uirenderer::Texture::uploadToN32(skBitmap, hasLinearBlending, std::move(sRGB)); - } else { - bitmap = skBitmap; - } - - if (!uploadBitmapToGraphicBuffer(caches, bitmap, *buffer, format, type)) { - return nullptr; - } - return sk_sp<Bitmap>(new Bitmap(buffer.get(), bitmap.info())); -} - -} /* namespace renderthread */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderthread/OpenGLPipeline.h b/libs/hwui/renderthread/OpenGLPipeline.h deleted file mode 100644 index 9859e931fd85..000000000000 --- a/libs/hwui/renderthread/OpenGLPipeline.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 "BakedOpDispatcher.h" -#include "BakedOpRenderer.h" -#include "CanvasContext.h" -#include "FrameBuilder.h" -#include "IRenderPipeline.h" - -namespace android { -namespace uirenderer { -namespace renderthread { - -class OpenGLPipeline : public IRenderPipeline { -public: - OpenGLPipeline(RenderThread& thread); - virtual ~OpenGLPipeline() {} - - MakeCurrentResult makeCurrent() override; - Frame getFrame() override; - bool draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty, - const FrameBuilder::LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue, - const Rect& contentDrawBounds, bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo, - const std::vector<sp<RenderNode>>& renderNodes, - FrameInfoVisualizer* profiler) override; - bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty, - FrameInfo* currentFrameInfo, bool* requireSwap) override; - bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) override; - DeferredLayerUpdater* createTextureLayer() override; - bool setSurface(Surface* window, SwapBehavior swapBehavior, ColorMode colorMode) override; - void onStop() override; - bool isSurfaceReady() override; - bool isContextReady() override; - void onDestroyHardwareResources() override; - void renderLayers(const FrameBuilder::LightGeometry& lightGeometry, - LayerUpdateQueue* layerUpdateQueue, bool opaque, bool wideColorGamut, - const BakedOpRenderer::LightInfo& lightInfo) override; - TaskManager* getTaskManager() override; - bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator, - bool wideColorGamut, ErrorHandler* errorHandler) override; - bool pinImages(std::vector<SkImage*>& mutableImages) override { return false; } - bool pinImages(LsaVector<sk_sp<Bitmap>>& images) override; - void unpinImages() override; - void onPrepareTree() override {} - static void destroyLayer(RenderNode* node); - static void prepareToDraw(const RenderThread& thread, Bitmap* bitmap); - static void invokeFunctor(const RenderThread& thread, Functor* functor); - static sk_sp<Bitmap> allocateHardwareBitmap(RenderThread& thread, SkBitmap& skBitmap); - -private: - EglManager& mEglManager; - EGLSurface mEglSurface = EGL_NO_SURFACE; - bool mBufferPreserved = false; - RenderThread& mRenderThread; -}; - -} /* namespace renderthread */ -} /* namespace uirenderer */ -} /* namespace android */ diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index 020761110ef0..085812a00f71 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -21,6 +21,7 @@ #include "Properties.h" #include "Readback.h" #include "Rect.h" +#include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/VectorDrawableAtlas.h" #include "renderstate/RenderState.h" #include "renderthread/CanvasContext.h" @@ -65,10 +66,7 @@ void RenderProxy::setSwapBehavior(SwapBehavior swapBehavior) { bool RenderProxy::loadSystemProperties() { return mRenderThread.queue().runSync([this]() -> bool { - bool needsRedraw = false; - if (Caches::hasInstance()) { - needsRedraw = Properties::load(); - } + bool needsRedraw = Properties::load(); if (mContext->profiler().consumeProperties()) { needsRedraw = true; } @@ -82,22 +80,16 @@ void RenderProxy::setName(const char* name) { mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); }); } -void RenderProxy::initialize(const sp<Surface>& surface) { +void RenderProxy::setSurface(const sp<Surface>& surface) { mRenderThread.queue().post( [ this, surf = surface ]() mutable { mContext->setSurface(std::move(surf)); }); } -void RenderProxy::allocateBuffers(const sp<Surface>& surface) { - mRenderThread.queue().post( - [ surf = surface ]() mutable { surf->allocateBuffers(); }); -} - -void RenderProxy::updateSurface(const sp<Surface>& surface) { - mRenderThread.queue().post( - [ this, surf = surface ]() mutable { mContext->setSurface(std::move(surf)); }); +void RenderProxy::allocateBuffers() { + mRenderThread.queue().post([=]() { mContext->allocateBuffers(); }); } -bool RenderProxy::pauseSurface(const sp<Surface>& surface) { +bool RenderProxy::pause() { return mRenderThread.queue().runSync([this]() -> bool { return mContext->pauseSurface(); }); } @@ -105,13 +97,13 @@ void RenderProxy::setStopped(bool stopped) { mRenderThread.queue().runSync([this, stopped]() { mContext->setStopped(stopped); }); } -void RenderProxy::setup(float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { +void RenderProxy::setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) { mRenderThread.queue().post( - [=]() { mContext->setup(lightRadius, ambientShadowAlpha, spotShadowAlpha); }); + [=]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); }); } -void RenderProxy::setLightCenter(const Vector3& lightCenter) { - mRenderThread.queue().post([=]() { mContext->setLightCenter(lightCenter); }); +void RenderProxy::setLightGeometry(const Vector3& lightCenter, float lightRadius) { + mRenderThread.queue().post([=]() { mContext->setLightGeometry(lightCenter, lightRadius); }); } void RenderProxy::setOpaque(bool opaque) { @@ -162,8 +154,10 @@ void RenderProxy::buildLayer(RenderNode* node) { } bool RenderProxy::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap& bitmap) { - return mRenderThread.queue().runSync( - [&]() -> bool { return mContext->copyLayerInto(layer, &bitmap); }); + auto& thread = RenderThread::getInstance(); + return thread.queue().runSync( + [&]() -> bool { return thread.readback().copyLayerInto(layer, &bitmap) + == CopyResult::Success; }); } void RenderProxy::pushLayerUpdate(DeferredLayerUpdater* layer) { @@ -200,8 +194,11 @@ void RenderProxy::fence() { mRenderThread.queue().runSync([]() {}); } -void RenderProxy::staticFence() { - RenderThread::getInstance().queue().runSync([]() {}); +int RenderProxy::maxTextureSize() { + static int maxTextureSize = RenderThread::getInstance().queue().runSync([]() { + return DeviceInfo::get()->maxTextureSize(); + }); + return maxTextureSize; } void RenderProxy::stopDrawing() { @@ -283,10 +280,6 @@ void RenderProxy::setFrameCompleteCallback(std::function<void(int64_t)>&& callba mDrawFrameTask.setFrameCompleteCallback(std::move(callback)); } -void RenderProxy::serializeDisplayListTree() { - mRenderThread.queue().post([=]() { mContext->serializeDisplayListTree(); }); -} - void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observerPtr) { mRenderThread.queue().post([ this, observer = sp{observerPtr} ]() { mContext->addFrameMetricsObserver(observer.get()); @@ -299,6 +292,12 @@ void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observerPtr) }); } +void RenderProxy::setForceDark(bool enable) { + mRenderThread.queue().post([this, enable]() { + mContext->setForceDark(enable); + }); +} + int RenderProxy::copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap) { auto& thread = RenderThread::getInstance(); @@ -334,30 +333,18 @@ void RenderProxy::prepareToDraw(Bitmap& bitmap) { } } -sk_sp<Bitmap> RenderProxy::allocateHardwareBitmap(SkBitmap& bitmap) { - auto& thread = RenderThread::getInstance(); - return thread.queue().runSync([&]() -> auto { return thread.allocateHardwareBitmap(bitmap); }); -} - -int RenderProxy::copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap) { +int RenderProxy::copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap) { RenderThread& thread = RenderThread::getInstance(); - if (Properties::isSkiaEnabled() && gettid() == thread.getTid()) { + if (gettid() == thread.getTid()) { // TODO: fix everything that hits this. We should never be triggering a readback ourselves. - return (int)thread.readback().copyGraphicBufferInto(buffer, bitmap); + return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); } else { return thread.queue().runSync([&]() -> int { - return (int)thread.readback().copyGraphicBufferInto(buffer, bitmap); + return (int)thread.readback().copyHWBitmapInto(hwBitmap, bitmap); }); } } -void RenderProxy::onBitmapDestroyed(uint32_t pixelRefId) { - if (!RenderThread::hasInstance()) return; - RenderThread& thread = RenderThread::getInstance(); - thread.queue().post( - [&thread, pixelRefId]() { thread.renderState().onBitmapDestroyed(pixelRefId); }); -} - void RenderProxy::disableVsync() { Properties::disableVsync = true; } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 0d29b4bcc317..6668c5840c3e 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -69,13 +69,12 @@ public: ANDROID_API bool loadSystemProperties(); ANDROID_API void setName(const char* name); - ANDROID_API void initialize(const sp<Surface>& surface); - ANDROID_API void allocateBuffers(const sp<Surface>& surface); - ANDROID_API void updateSurface(const sp<Surface>& surface); - ANDROID_API bool pauseSurface(const sp<Surface>& surface); + ANDROID_API void setSurface(const sp<Surface>& surface); + ANDROID_API void allocateBuffers(); + ANDROID_API bool pause(); ANDROID_API void setStopped(bool stopped); - ANDROID_API void setup(float lightRadius, uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); - ANDROID_API void setLightCenter(const Vector3& lightCenter); + ANDROID_API void setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha); + ANDROID_API void setLightGeometry(const Vector3& lightCenter, float lightRadius); ANDROID_API void setOpaque(bool opaque); ANDROID_API void setWideGamut(bool wideGamut); ANDROID_API int64_t* frameInfo(); @@ -96,7 +95,7 @@ public: ANDROID_API static void overrideProperty(const char* name, const char* value); ANDROID_API void fence(); - ANDROID_API static void staticFence(); + ANDROID_API static int maxTextureSize(); ANDROID_API void stopDrawing(); ANDROID_API void notifyFramePending(); @@ -110,8 +109,6 @@ public: ANDROID_API static void setProcessStatsBuffer(int fd); ANDROID_API int getRenderThreadTid(); - ANDROID_API void serializeDisplayListTree(); - ANDROID_API void addRenderNode(RenderNode* node, bool placeFront); ANDROID_API void removeRenderNode(RenderNode* node); ANDROID_API void drawRenderNode(RenderNode* node); @@ -121,17 +118,13 @@ public: ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer); ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer); - ANDROID_API long getDroppedFrameReportCount(); + ANDROID_API void setForceDark(bool enable); ANDROID_API static int copySurfaceInto(sp<Surface>& surface, int left, int top, int right, int bottom, SkBitmap* bitmap); ANDROID_API static void prepareToDraw(Bitmap& bitmap); - static sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& bitmap); - - static int copyGraphicBufferInto(GraphicBuffer* buffer, SkBitmap* bitmap); - - static void onBitmapDestroyed(uint32_t pixelRefId); + static int copyHWBitmapInto(Bitmap* hwBitmap, SkBitmap* bitmap); ANDROID_API static void disableVsync(); diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp index 6a2a025da121..207673c1c8dd 100644 --- a/libs/hwui/renderthread/RenderThread.cpp +++ b/libs/hwui/renderthread/RenderThread.cpp @@ -19,19 +19,23 @@ #include "CanvasContext.h" #include "DeviceInfo.h" #include "EglManager.h" -#include "OpenGLReadback.h" +#include "Readback.h" #include "RenderProxy.h" #include "VulkanManager.h" #include "hwui/Bitmap.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" -#include "pipeline/skia/SkiaOpenGLReadback.h" -#include "pipeline/skia/SkiaVulkanReadback.h" #include "pipeline/skia/SkiaVulkanPipeline.h" #include "renderstate/RenderState.h" -#include "renderthread/OpenGLPipeline.h" #include "utils/FatVector.h" #include "utils/TimeUtils.h" +#ifdef HWUI_GLES_WRAP_ENABLED +#include "debug/GlesDriver.h" +#endif + +#include <GrContextOptions.h> +#include <gl/GrGLInterface.h> + #include <gui/DisplayEventReceiver.h> #include <sys/resource.h> #include <utils/Condition.h> @@ -93,14 +97,11 @@ public: DummyVsyncSource(RenderThread* renderThread) : mRenderThread(renderThread) {} virtual void requestNextVsync() override { - mRenderThread->queue().postDelayed(16_ms, [this]() { - mRenderThread->drainDisplayEventQueue(); - }); + mRenderThread->queue().postDelayed(16_ms, + [this]() { mRenderThread->drainDisplayEventQueue(); }); } - virtual nsecs_t latestVsyncEvent() override { - return systemTime(CLOCK_MONOTONIC); - } + virtual nsecs_t latestVsyncEvent() override { return systemTime(CLOCK_MONOTONIC); } private: RenderThread* mRenderThread; @@ -147,13 +148,13 @@ void RenderThread::initializeDisplayEventReceiver() { auto receiver = std::make_unique<DisplayEventReceiver>(); status_t status = receiver->initCheck(); LOG_ALWAYS_FATAL_IF(status != NO_ERROR, - "Initialization of DisplayEventReceiver " - "failed with status: %d", - status); + "Initialization of DisplayEventReceiver " + "failed with status: %d", + status); // Register the FD mLooper->addFd(receiver->getFd(), 0, Looper::EVENT_INPUT, - RenderThread::displayEventReceiverCallback, this); + RenderThread::displayEventReceiverCallback, this); mVsyncSource = new DisplayEventReceiverWrapper(std::move(receiver)); } else { mVsyncSource = new DummyVsyncSource(this); @@ -161,16 +162,48 @@ void RenderThread::initializeDisplayEventReceiver() { } void RenderThread::initThreadLocals() { - mDisplayInfo = DeviceInfo::queryDisplayInfo(); + mDisplayInfo = DeviceInfo::get()->displayInfo(); nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / mDisplayInfo.fps); mTimeLord.setFrameInterval(frameIntervalNanos); initializeDisplayEventReceiver(); - mEglManager = new EglManager(*this); + mEglManager = new EglManager(); mRenderState = new RenderState(*this); mVkManager = new VulkanManager(*this); mCacheManager = new CacheManager(mDisplayInfo); } +void RenderThread::requireGlContext() { + if (mEglManager->hasEglContext()) { + return; + } + mEglManager->initialize(); + +#ifdef HWUI_GLES_WRAP_ENABLED + debug::GlesDriver* driver = debug::GlesDriver::get(); + sk_sp<const GrGLInterface> glInterface(driver->getSkiaInterface()); +#else + sk_sp<const GrGLInterface> glInterface(GrGLCreateNativeInterface()); +#endif + LOG_ALWAYS_FATAL_IF(!glInterface.get()); + + GrContextOptions options; + options.fPreferExternalImagesOverES3 = true; + options.fDisableDistanceFieldPaths = true; + auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION)); + auto size = glesVersion ? strlen(glesVersion) : -1; + cacheManager().configureContext(&options, glesVersion, size); + sk_sp<GrContext> grContext(GrContext::MakeGL(std::move(glInterface), options)); + LOG_ALWAYS_FATAL_IF(!grContext.get()); + setGrContext(grContext); +} + +void RenderThread::destroyGlContext() { + if (mEglManager->hasEglContext()) { + setGrContext(nullptr); + mEglManager->destroy(); + } +} + void RenderThread::dumpGraphicsMemory(int fd) { globalProfileData()->dump(fd); @@ -178,16 +211,6 @@ void RenderThread::dumpGraphicsMemory(int fd) { String8 pipeline; auto renderType = Properties::getRenderPipelineType(); switch (renderType) { - case RenderPipelineType::OpenGL: { - if (Caches::hasInstance()) { - cachesOutput.appendFormat("Caches:\n"); - Caches::getInstance().dumpMemoryUsage(cachesOutput); - } else { - cachesOutput.appendFormat("No caches instance."); - } - pipeline.appendFormat("FrameBuilder"); - break; - } case RenderPipelineType::SkiaGL: { mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState); pipeline.appendFormat("Skia (OpenGL)"); @@ -209,21 +232,7 @@ void RenderThread::dumpGraphicsMemory(int fd) { Readback& RenderThread::readback() { if (!mReadback) { - auto renderType = Properties::getRenderPipelineType(); - switch (renderType) { - case RenderPipelineType::OpenGL: - mReadback = new OpenGLReadbackImpl(*this); - break; - case RenderPipelineType::SkiaGL: - mReadback = new skiapipeline::SkiaOpenGLReadback(*this); - break; - case RenderPipelineType::SkiaVulkan: - mReadback = new skiapipeline::SkiaVulkanReadback(*this); - break; - default: - LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t)renderType); - break; - } + mReadback = new Readback(*this); } return *mReadback; @@ -232,9 +241,14 @@ Readback& RenderThread::readback() { void RenderThread::setGrContext(sk_sp<GrContext> context) { mCacheManager->reset(context); if (mGrContext) { + mRenderState->onContextDestroyed(); mGrContext->releaseResourcesAndAbandonContext(); } mGrContext = std::move(context); + if (mGrContext) { + mRenderState->onContextCreated(); + DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize()); + } } int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) { @@ -347,10 +361,6 @@ void RenderThread::pushBackFrameCallback(IFrameCallback* callback) { sk_sp<Bitmap> RenderThread::allocateHardwareBitmap(SkBitmap& skBitmap) { auto renderType = Properties::getRenderPipelineType(); switch (renderType) { - case RenderPipelineType::OpenGL: - return OpenGLPipeline::allocateHardwareBitmap(*this, skBitmap); - case RenderPipelineType::SkiaGL: - return skiapipeline::SkiaOpenGLPipeline::allocateHardwareBitmap(*this, skBitmap); case RenderPipelineType::SkiaVulkan: return skiapipeline::SkiaVulkanPipeline::allocateHardwareBitmap(*this, skBitmap); default: diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h index e9c264917905..2384f9541ec0 100644 --- a/libs/hwui/renderthread/RenderThread.h +++ b/libs/hwui/renderthread/RenderThread.h @@ -74,7 +74,7 @@ class RenderThread : private ThreadBase { PREVENT_COPY_AND_ASSIGN(RenderThread); public: - // Sets a callback that fires before any RenderThread setup has occured. + // Sets a callback that fires before any RenderThread setup has occurred. ANDROID_API static void setOnStartHook(void (*onStartHook)()); WorkQueue& queue() { return ThreadBase::queue(); } @@ -103,6 +103,9 @@ public: sk_sp<Bitmap> allocateHardwareBitmap(SkBitmap& skBitmap); void dumpGraphicsMemory(int fd); + void requireGlContext(); + void destroyGlContext(); + /** * isCurrent provides a way to query, if the caller is running on * the render thread. diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 21c91a26745b..f96b1f897921 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -16,7 +16,6 @@ #include "VulkanManager.h" -#include "DeviceInfo.h" #include "Properties.h" #include "RenderThread.h" #include "renderstate/RenderState.h" @@ -25,50 +24,293 @@ #include <GrBackendSurface.h> #include <GrContext.h> #include <GrTypes.h> +#include <GrTypes.h> +#include <vk/GrVkExtensions.h> #include <vk/GrVkTypes.h> namespace android { namespace uirenderer { namespace renderthread { -#define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F) -#define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F) +#define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F) +#define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F) +#define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F) VulkanManager::VulkanManager(RenderThread& thread) : mRenderThread(thread) {} void VulkanManager::destroy() { - if (!hasVkContext()) return; - - mRenderThread.renderState().onVkContextDestroyed(); mRenderThread.setGrContext(nullptr); + // We don't need to explicitly free the command buffer since it automatically gets freed when we + // delete the VkCommandPool below. + mDummyCB = VK_NULL_HANDLE; + if (VK_NULL_HANDLE != mCommandPool) { - mDestroyCommandPool(mBackendContext->fDevice, mCommandPool, nullptr); + mDestroyCommandPool(mDevice, mCommandPool, nullptr); mCommandPool = VK_NULL_HANDLE; } - mBackendContext.reset(); + + if (mDevice != VK_NULL_HANDLE) { + mDeviceWaitIdle(mDevice); + mDestroyDevice(mDevice, nullptr); + } + + if (mInstance != VK_NULL_HANDLE) { + mDestroyInstance(mInstance, nullptr); + } + + mGraphicsQueue = VK_NULL_HANDLE; + mPresentQueue = VK_NULL_HANDLE; + mDevice = VK_NULL_HANDLE; + mPhysicalDevice = VK_NULL_HANDLE; + mInstance = VK_NULL_HANDLE; } -void VulkanManager::initialize() { - if (hasVkContext()) { - return; +bool VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFeatures2& features) { + VkResult err; + + constexpr VkApplicationInfo app_info = { + VK_STRUCTURE_TYPE_APPLICATION_INFO, // sType + nullptr, // pNext + "android framework", // pApplicationName + 0, // applicationVersion + "android framework", // pEngineName + 0, // engineVerison + VK_MAKE_VERSION(1, 1, 0), // apiVersion + }; + + std::vector<const char*> instanceExtensions; + { + GET_PROC(EnumerateInstanceExtensionProperties); + + uint32_t extensionCount = 0; + err = mEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + if (VK_SUCCESS != err) { + return false; + } + std::unique_ptr<VkExtensionProperties[]> extensions( + new VkExtensionProperties[extensionCount]); + err = mEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.get()); + if (VK_SUCCESS != err) { + return false; + } + bool hasKHRSurfaceExtension = false; + bool hasKHRAndroidSurfaceExtension = false; + for (uint32_t i = 0; i < extensionCount; ++i) { + instanceExtensions.push_back(extensions[i].extensionName); + if (!strcmp(extensions[i].extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) { + hasKHRSurfaceExtension = true; + } + if (!strcmp(extensions[i].extensionName,VK_KHR_ANDROID_SURFACE_EXTENSION_NAME)) { + hasKHRAndroidSurfaceExtension = true; + } + } + if (!hasKHRSurfaceExtension || !hasKHRAndroidSurfaceExtension) { + this->destroy(); + return false; + } + } + + const VkInstanceCreateInfo instance_create = { + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, // sType + nullptr, // pNext + 0, // flags + &app_info, // pApplicationInfo + 0, // enabledLayerNameCount + nullptr, // ppEnabledLayerNames + (uint32_t) instanceExtensions.size(), // enabledExtensionNameCount + instanceExtensions.data(), // ppEnabledExtensionNames + }; + + GET_PROC(CreateInstance); + err = mCreateInstance(&instance_create, nullptr, &mInstance); + if (err < 0) { + this->destroy(); + return false; } - auto canPresent = [](VkInstance, VkPhysicalDevice, uint32_t) { return true; }; + GET_INST_PROC(DestroyInstance); + GET_INST_PROC(EnumeratePhysicalDevices); + GET_INST_PROC(GetPhysicalDeviceProperties); + GET_INST_PROC(GetPhysicalDeviceQueueFamilyProperties); + GET_INST_PROC(GetPhysicalDeviceFeatures2); + GET_INST_PROC(CreateDevice); + GET_INST_PROC(EnumerateDeviceExtensionProperties); + GET_INST_PROC(CreateAndroidSurfaceKHR); + GET_INST_PROC(DestroySurfaceKHR); + GET_INST_PROC(GetPhysicalDeviceSurfaceSupportKHR); + GET_INST_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR); + GET_INST_PROC(GetPhysicalDeviceSurfaceFormatsKHR); + GET_INST_PROC(GetPhysicalDeviceSurfacePresentModesKHR); + + uint32_t gpuCount; + err = mEnumeratePhysicalDevices(mInstance, &gpuCount, nullptr); + if (err) { + this->destroy(); + return false; + } + if (!gpuCount) { + this->destroy(); + return false; + } + // Just returning the first physical device instead of getting the whole array. Since there + // should only be one device on android. + gpuCount = 1; + err = mEnumeratePhysicalDevices(mInstance, &gpuCount, &mPhysicalDevice); + // VK_INCOMPLETE is returned when the count we provide is less than the total device count. + if (err && VK_INCOMPLETE != err) { + this->destroy(); + return false; + } - mBackendContext.reset(GrVkBackendContext::Create(vkGetInstanceProcAddr, vkGetDeviceProcAddr, - &mPresentQueueIndex, canPresent)); - LOG_ALWAYS_FATAL_IF(!mBackendContext.get()); + VkPhysicalDeviceProperties physDeviceProperties; + mGetPhysicalDeviceProperties(mPhysicalDevice, &physDeviceProperties); + if (physDeviceProperties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) { + this->destroy(); + return false; + } - // Get all the addresses of needed vulkan functions - VkInstance instance = mBackendContext->fInstance; - VkDevice device = mBackendContext->fDevice; - GET_PROC(CreateAndroidSurfaceKHR); - GET_PROC(DestroySurfaceKHR); - GET_PROC(GetPhysicalDeviceSurfaceSupportKHR); - GET_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR); - GET_PROC(GetPhysicalDeviceSurfaceFormatsKHR); - GET_PROC(GetPhysicalDeviceSurfacePresentModesKHR); + // query to get the initial queue props size + uint32_t queueCount; + mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr); + if (!queueCount) { + this->destroy(); + return false; + } + + // now get the actual queue props + std::unique_ptr<VkQueueFamilyProperties[]> queueProps(new VkQueueFamilyProperties[queueCount]); + mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, queueProps.get()); + + // iterate to find the graphics queue + mGraphicsQueueIndex = queueCount; + for (uint32_t i = 0; i < queueCount; i++) { + if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + mGraphicsQueueIndex = i; + break; + } + } + if (mGraphicsQueueIndex == queueCount) { + this->destroy(); + return false; + } + + // All physical devices and queue families on Android must be capable of + // presentation with any native window. So just use the first one. + mPresentQueueIndex = 0; + + std::vector<const char*> deviceExtensions; + { + uint32_t extensionCount = 0; + err = mEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &extensionCount, + nullptr); + if (VK_SUCCESS != err) { + this->destroy(); + return false; + } + std::unique_ptr<VkExtensionProperties[]> extensions( + new VkExtensionProperties[extensionCount]); + err = mEnumerateDeviceExtensionProperties(mPhysicalDevice, nullptr, &extensionCount, + extensions.get()); + if (VK_SUCCESS != err) { + this->destroy(); + return false; + } + bool hasKHRSwapchainExtension = false; + for (uint32_t i = 0; i < extensionCount; ++i) { + deviceExtensions.push_back(extensions[i].extensionName); + if (!strcmp(extensions[i].extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) { + hasKHRSwapchainExtension = true; + } + } + if (!hasKHRSwapchainExtension) { + this->destroy(); + return false; + } + } + + auto getProc = [] (const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); + }; + grExtensions.init(getProc, mInstance, mPhysicalDevice, instanceExtensions.size(), + instanceExtensions.data(), deviceExtensions.size(), deviceExtensions.data()); + + if (!grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) { + this->destroy(); + return false; + } + + memset(&features, 0, sizeof(VkPhysicalDeviceFeatures2)); + features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2; + features.pNext = nullptr; + + // Setup all extension feature structs we may want to use. + void** tailPNext = &features.pNext; + + if (grExtensions.hasExtension(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, 2)) { + VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT* blend; + blend = (VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT*) malloc( + sizeof(VkPhysicalDeviceBlendOperationAdvancedFeaturesEXT)); + LOG_ALWAYS_FATAL_IF(!blend); + blend->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BLEND_OPERATION_ADVANCED_FEATURES_EXT; + blend->pNext = nullptr; + *tailPNext = blend; + tailPNext = &blend->pNext; + } + + // query to get the physical device features + mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features); + // this looks like it would slow things down, + // and we can't depend on it on all platforms + features.features.robustBufferAccess = VK_FALSE; + + float queuePriorities[1] = { 0.0 }; + + const VkDeviceQueueCreateInfo queueInfo[2] = { + { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType + nullptr, // pNext + 0, // VkDeviceQueueCreateFlags + mGraphicsQueueIndex, // queueFamilyIndex + 1, // queueCount + queuePriorities, // pQueuePriorities + }, + { + VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType + nullptr, // pNext + 0, // VkDeviceQueueCreateFlags + mPresentQueueIndex, // queueFamilyIndex + 1, // queueCount + queuePriorities, // pQueuePriorities + } + }; + uint32_t queueInfoCount = (mPresentQueueIndex != mGraphicsQueueIndex) ? 2 : 1; + + const VkDeviceCreateInfo deviceInfo = { + VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, // sType + &features, // pNext + 0, // VkDeviceCreateFlags + queueInfoCount, // queueCreateInfoCount + queueInfo, // pQueueCreateInfos + 0, // layerCount + nullptr, // ppEnabledLayerNames + (uint32_t) deviceExtensions.size(), // extensionCount + deviceExtensions.data(), // ppEnabledExtensionNames + nullptr, // ppEnabledFeatures + }; + + err = mCreateDevice(mPhysicalDevice, &deviceInfo, nullptr, &mDevice); + if (err) { + this->destroy(); + return false; + } + + GET_DEV_PROC(GetDeviceQueue); + GET_DEV_PROC(DeviceWaitIdle); + GET_DEV_PROC(DestroyDevice); GET_DEV_PROC(CreateSwapchainKHR); GET_DEV_PROC(DestroySwapchainKHR); GET_DEV_PROC(GetSwapchainImagesKHR); @@ -88,39 +330,103 @@ void VulkanManager::initialize() { GET_DEV_PROC(DeviceWaitIdle); GET_DEV_PROC(CreateSemaphore); GET_DEV_PROC(DestroySemaphore); + GET_DEV_PROC(ImportSemaphoreFdKHR); + GET_DEV_PROC(GetSemaphoreFdKHR); GET_DEV_PROC(CreateFence); GET_DEV_PROC(DestroyFence); GET_DEV_PROC(WaitForFences); GET_DEV_PROC(ResetFences); + return true; +} + +static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) { + // All Vulkan structs that could be part of the features chain will start with the + // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader + // so we can get access to the pNext for the next struct. + struct CommonVulkanHeader { + VkStructureType sType; + void* pNext; + }; + + void* pNext = features.pNext; + while (pNext) { + void* current = pNext; + pNext = static_cast<CommonVulkanHeader*>(current)->pNext; + free(current); + } +} + +void VulkanManager::initialize() { + if (mDevice != VK_NULL_HANDLE) { + return; + } + + GET_PROC(EnumerateInstanceVersion); + uint32_t instanceVersion = 0; + LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion)); + LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0)); + + GrVkExtensions extensions; + VkPhysicalDeviceFeatures2 features; + LOG_ALWAYS_FATAL_IF(!this->setupDevice(extensions, features)); + + mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue); + + auto getProc = [] (const char* proc_name, VkInstance instance, VkDevice device) { + if (device != VK_NULL_HANDLE) { + return vkGetDeviceProcAddr(device, proc_name); + } + return vkGetInstanceProcAddr(instance, proc_name); + }; + + GrVkBackendContext backendContext; + backendContext.fInstance = mInstance; + backendContext.fPhysicalDevice = mPhysicalDevice; + backendContext.fDevice = mDevice; + backendContext.fQueue = mGraphicsQueue; + backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex; + backendContext.fInstanceVersion = instanceVersion; + backendContext.fVkExtensions = &extensions; + backendContext.fDeviceFeatures2 = &features; + backendContext.fGetProc = std::move(getProc); + // create the command pool for the command buffers if (VK_NULL_HANDLE == mCommandPool) { VkCommandPoolCreateInfo commandPoolInfo; memset(&commandPoolInfo, 0, sizeof(VkCommandPoolCreateInfo)); commandPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; // this needs to be on the render queue - commandPoolInfo.queueFamilyIndex = mBackendContext->fGraphicsQueueIndex; + commandPoolInfo.queueFamilyIndex = mGraphicsQueueIndex; commandPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; - SkDEBUGCODE(VkResult res =) mCreateCommandPool(mBackendContext->fDevice, &commandPoolInfo, - nullptr, &mCommandPool); + SkDEBUGCODE(VkResult res =) mCreateCommandPool(mDevice, &commandPoolInfo, nullptr, + &mCommandPool); SkASSERT(VK_SUCCESS == res); } + LOG_ALWAYS_FATAL_IF(mCommandPool == VK_NULL_HANDLE); + + if (!setupDummyCommandBuffer()) { + this->destroy(); + return; + } + LOG_ALWAYS_FATAL_IF(mDummyCB == VK_NULL_HANDLE); + - mGetDeviceQueue(mBackendContext->fDevice, mPresentQueueIndex, 0, &mPresentQueue); + mGetDeviceQueue(mDevice, mPresentQueueIndex, 0, &mPresentQueue); GrContextOptions options; options.fDisableDistanceFieldPaths = true; - mRenderThread.cacheManager().configureContext(&options); - sk_sp<GrContext> grContext(GrContext::MakeVulkan(mBackendContext, options)); + // TODO: get a string describing the SPIR-V compiler version and use it here + mRenderThread.cacheManager().configureContext(&options, nullptr, 0); + sk_sp<GrContext> grContext(GrContext::MakeVulkan(backendContext, options)); LOG_ALWAYS_FATAL_IF(!grContext.get()); mRenderThread.setGrContext(grContext); - DeviceInfo::initialize(mRenderThread.getGrContext()->caps()->maxRenderTargetSize()); + + free_features_extensions_structs(features); if (Properties::enablePartialUpdates && Properties::useBufferAge) { mSwapBehavior = SwapBehavior::BufferAge; } - - mRenderThread.renderState().onVkContextCreated(); } // Returns the next BackbufferInfo to use for the next draw. The function will make sure all @@ -138,8 +444,7 @@ VulkanSurface::BackbufferInfo* VulkanManager::getAvailableBackbuffer(VulkanSurfa // Before we reuse a backbuffer, make sure its fences have all signaled so that we can safely // reuse its commands buffers. - VkResult res = - mWaitForFences(mBackendContext->fDevice, 2, backbuffer->mUsageFences, true, UINT64_MAX); + VkResult res = mWaitForFences(mDevice, 2, backbuffer->mUsageFences, true, UINT64_MAX); if (res != VK_SUCCESS) { return nullptr; } @@ -153,12 +458,12 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface* surface) { VkResult res; - res = mResetFences(mBackendContext->fDevice, 2, backbuffer->mUsageFences); + res = mResetFences(mDevice, 2, backbuffer->mUsageFences); SkASSERT(VK_SUCCESS == res); // The acquire will signal the attached mAcquireSemaphore. We use this to know the image has // finished presenting and that it is safe to begin sending new commands to the returned image. - res = mAcquireNextImageKHR(mBackendContext->fDevice, surface->mSwapchain, UINT64_MAX, + res = mAcquireNextImageKHR(mDevice, surface->mSwapchain, UINT64_MAX, backbuffer->mAcquireSemaphore, VK_NULL_HANDLE, &backbuffer->mImageIndex); @@ -173,11 +478,11 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface* surface) { return nullptr; } backbuffer = getAvailableBackbuffer(surface); - res = mResetFences(mBackendContext->fDevice, 2, backbuffer->mUsageFences); + res = mResetFences(mDevice, 2, backbuffer->mUsageFences); SkASSERT(VK_SUCCESS == res); // acquire the image - res = mAcquireNextImageKHR(mBackendContext->fDevice, surface->mSwapchain, UINT64_MAX, + res = mAcquireNextImageKHR(mDevice, surface->mSwapchain, UINT64_MAX, backbuffer->mAcquireSemaphore, VK_NULL_HANDLE, &backbuffer->mImageIndex); @@ -189,13 +494,11 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface* surface) { // set up layout transfer from initial to color attachment VkImageLayout layout = surface->mImageInfos[backbuffer->mImageIndex].mImageLayout; SkASSERT(VK_IMAGE_LAYOUT_UNDEFINED == layout || VK_IMAGE_LAYOUT_PRESENT_SRC_KHR == layout); - VkPipelineStageFlags srcStageMask = (VK_IMAGE_LAYOUT_UNDEFINED == layout) - ? VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT - : VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; - VkAccessFlags srcAccessMask = - (VK_IMAGE_LAYOUT_UNDEFINED == layout) ? 0 : VK_ACCESS_MEMORY_READ_BIT; - VkAccessFlags dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + VkAccessFlags srcAccessMask = 0; + VkAccessFlags dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkImageMemoryBarrier imageMemoryBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // sType @@ -205,7 +508,7 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface* surface) { layout, // oldLayout VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // newLayout mPresentQueueIndex, // srcQueueFamilyIndex - mBackendContext->fGraphicsQueueIndex, // dstQueueFamilyIndex + mGraphicsQueueIndex, // dstQueueFamilyIndex surface->mImages[backbuffer->mImageIndex], // image {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1} // subresourceRange }; @@ -236,14 +539,17 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface* surface) { submitInfo.signalSemaphoreCount = 0; // Attach first fence to submission here so we can track when the command buffer finishes. - mQueueSubmit(mBackendContext->fQueue, 1, &submitInfo, backbuffer->mUsageFences[0]); + mQueueSubmit(mGraphicsQueue, 1, &submitInfo, backbuffer->mUsageFences[0]); // We need to notify Skia that we changed the layout of the wrapped VkImage - GrVkImageInfo* imageInfo; sk_sp<SkSurface> skSurface = surface->mImageInfos[backbuffer->mImageIndex].mSurface; - skSurface->getRenderTargetHandle((GrBackendObject*)&imageInfo, - SkSurface::kFlushRead_BackendHandleAccess); - imageInfo->updateImageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + GrBackendRenderTarget backendRT = skSurface->getBackendRenderTarget( + SkSurface::kFlushRead_BackendHandleAccess); + if (!backendRT.isValid()) { + SkASSERT(backendRT.isValid()); + return nullptr; + } + backendRT.setVkImageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); surface->mBackbuffer = std::move(skSurface); return surface->mBackbuffer.get(); @@ -252,17 +558,14 @@ SkSurface* VulkanManager::getBackbufferSurface(VulkanSurface* surface) { void VulkanManager::destroyBuffers(VulkanSurface* surface) { if (surface->mBackbuffers) { for (uint32_t i = 0; i < surface->mImageCount + 1; ++i) { - mWaitForFences(mBackendContext->fDevice, 2, surface->mBackbuffers[i].mUsageFences, true, - UINT64_MAX); + mWaitForFences(mDevice, 2, surface->mBackbuffers[i].mUsageFences, true, UINT64_MAX); surface->mBackbuffers[i].mImageIndex = -1; - mDestroySemaphore(mBackendContext->fDevice, surface->mBackbuffers[i].mAcquireSemaphore, - nullptr); - mDestroySemaphore(mBackendContext->fDevice, surface->mBackbuffers[i].mRenderSemaphore, - nullptr); - mFreeCommandBuffers(mBackendContext->fDevice, mCommandPool, 2, - surface->mBackbuffers[i].mTransitionCmdBuffers); - mDestroyFence(mBackendContext->fDevice, surface->mBackbuffers[i].mUsageFences[0], 0); - mDestroyFence(mBackendContext->fDevice, surface->mBackbuffers[i].mUsageFences[1], 0); + mDestroySemaphore(mDevice, surface->mBackbuffers[i].mAcquireSemaphore, nullptr); + mDestroySemaphore(mDevice, surface->mBackbuffers[i].mRenderSemaphore, nullptr); + mFreeCommandBuffers(mDevice, mCommandPool, 2, + surface->mBackbuffers[i].mTransitionCmdBuffers); + mDestroyFence(mDevice, surface->mBackbuffers[i].mUsageFences[0], 0); + mDestroyFence(mDevice, surface->mBackbuffers[i].mUsageFences[1], 0); } } @@ -279,29 +582,27 @@ void VulkanManager::destroySurface(VulkanSurface* surface) { if (VK_NULL_HANDLE != mPresentQueue) { mQueueWaitIdle(mPresentQueue); } - mDeviceWaitIdle(mBackendContext->fDevice); + mDeviceWaitIdle(mDevice); destroyBuffers(surface); if (VK_NULL_HANDLE != surface->mSwapchain) { - mDestroySwapchainKHR(mBackendContext->fDevice, surface->mSwapchain, nullptr); + mDestroySwapchainKHR(mDevice, surface->mSwapchain, nullptr); surface->mSwapchain = VK_NULL_HANDLE; } if (VK_NULL_HANDLE != surface->mVkSurface) { - mDestroySurfaceKHR(mBackendContext->fInstance, surface->mVkSurface, nullptr); + mDestroySurfaceKHR(mInstance, surface->mVkSurface, nullptr); surface->mVkSurface = VK_NULL_HANDLE; } delete surface; } void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExtent2D extent) { - mGetSwapchainImagesKHR(mBackendContext->fDevice, surface->mSwapchain, &surface->mImageCount, - nullptr); + mGetSwapchainImagesKHR(mDevice, surface->mSwapchain, &surface->mImageCount, nullptr); SkASSERT(surface->mImageCount); surface->mImages = new VkImage[surface->mImageCount]; - mGetSwapchainImagesKHR(mBackendContext->fDevice, surface->mSwapchain, &surface->mImageCount, - surface->mImages); + mGetSwapchainImagesKHR(mDevice, surface->mSwapchain, &surface->mImageCount, surface->mImages); SkSurfaceProps props(0, kUnknown_SkPixelGeometry); @@ -320,7 +621,9 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt VulkanSurface::ImageInfo& imageInfo = surface->mImageInfos[i]; imageInfo.mSurface = SkSurface::MakeFromBackendRenderTarget( - mRenderThread.getGrContext(), backendRT, kTopLeft_GrSurfaceOrigin, nullptr, &props); + mRenderThread.getGrContext(), backendRT, kTopLeft_GrSurfaceOrigin, + surface->mColorMode == ColorMode::WideColorGamut ? kRGBA_F16_SkColorType + : kRGBA_8888_SkColorType, nullptr, &props); } SkASSERT(mCommandPool != VK_NULL_HANDLE); @@ -350,15 +653,15 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt for (uint32_t i = 0; i < surface->mImageCount + 1; ++i) { SkDEBUGCODE(VkResult res); surface->mBackbuffers[i].mImageIndex = -1; - SkDEBUGCODE(res =) mCreateSemaphore(mBackendContext->fDevice, &semaphoreInfo, nullptr, + SkDEBUGCODE(res =) mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &surface->mBackbuffers[i].mAcquireSemaphore); - SkDEBUGCODE(res =) mCreateSemaphore(mBackendContext->fDevice, &semaphoreInfo, nullptr, + SkDEBUGCODE(res =) mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &surface->mBackbuffers[i].mRenderSemaphore); - SkDEBUGCODE(res =) mAllocateCommandBuffers(mBackendContext->fDevice, &commandBuffersInfo, + SkDEBUGCODE(res =) mAllocateCommandBuffers(mDevice, &commandBuffersInfo, surface->mBackbuffers[i].mTransitionCmdBuffers); - SkDEBUGCODE(res =) mCreateFence(mBackendContext->fDevice, &fenceInfo, nullptr, + SkDEBUGCODE(res =) mCreateFence(mDevice, &fenceInfo, nullptr, &surface->mBackbuffers[i].mUsageFences[0]); - SkDEBUGCODE(res =) mCreateFence(mBackendContext->fDevice, &fenceInfo, nullptr, + SkDEBUGCODE(res =) mCreateFence(mDevice, &fenceInfo, nullptr, &surface->mBackbuffers[i].mUsageFences[1]); SkASSERT(VK_SUCCESS == res); } @@ -368,35 +671,35 @@ void VulkanManager::createBuffers(VulkanSurface* surface, VkFormat format, VkExt bool VulkanManager::createSwapchain(VulkanSurface* surface) { // check for capabilities VkSurfaceCapabilitiesKHR caps; - VkResult res = mGetPhysicalDeviceSurfaceCapabilitiesKHR(mBackendContext->fPhysicalDevice, + VkResult res = mGetPhysicalDeviceSurfaceCapabilitiesKHR(mPhysicalDevice, surface->mVkSurface, &caps); if (VK_SUCCESS != res) { return false; } uint32_t surfaceFormatCount; - res = mGetPhysicalDeviceSurfaceFormatsKHR(mBackendContext->fPhysicalDevice, surface->mVkSurface, + res = mGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice, surface->mVkSurface, &surfaceFormatCount, nullptr); if (VK_SUCCESS != res) { return false; } FatVector<VkSurfaceFormatKHR, 4> surfaceFormats(surfaceFormatCount); - res = mGetPhysicalDeviceSurfaceFormatsKHR(mBackendContext->fPhysicalDevice, surface->mVkSurface, + res = mGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDevice, surface->mVkSurface, &surfaceFormatCount, surfaceFormats.data()); if (VK_SUCCESS != res) { return false; } uint32_t presentModeCount; - res = mGetPhysicalDeviceSurfacePresentModesKHR(mBackendContext->fPhysicalDevice, + res = mGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDevice, surface->mVkSurface, &presentModeCount, nullptr); if (VK_SUCCESS != res) { return false; } FatVector<VkPresentModeKHR, VK_PRESENT_MODE_RANGE_SIZE_KHR> presentModes(presentModeCount); - res = mGetPhysicalDeviceSurfacePresentModesKHR(mBackendContext->fPhysicalDevice, + res = mGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDevice, surface->mVkSurface, &presentModeCount, presentModes.data()); if (VK_SUCCESS != res) { @@ -435,37 +738,27 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { ? VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR : VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; - // Pick our surface format. For now, just make sure it matches our sRGB request: - VkFormat surfaceFormat = VK_FORMAT_UNDEFINED; + VkFormat surfaceFormat = VK_FORMAT_R8G8B8A8_UNORM; VkColorSpaceKHR colorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; - - bool wantSRGB = false; -#ifdef ANDROID_ENABLE_LINEAR_BLENDING - wantSRGB = true; -#endif + if (surface->mColorMode == ColorMode::WideColorGamut) { + surfaceFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + colorSpace = VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT; + } + bool foundSurfaceFormat = false; for (uint32_t i = 0; i < surfaceFormatCount; ++i) { - // We are assuming we can get either R8G8B8A8_UNORM or R8G8B8A8_SRGB - VkFormat desiredFormat = wantSRGB ? VK_FORMAT_R8G8B8A8_SRGB : VK_FORMAT_R8G8B8A8_UNORM; - if (desiredFormat == surfaceFormats[i].format) { - surfaceFormat = surfaceFormats[i].format; - colorSpace = surfaceFormats[i].colorSpace; + if (surfaceFormat == surfaceFormats[i].format + && colorSpace == surfaceFormats[i].colorSpace) { + foundSurfaceFormat = true; + break; } } - if (VK_FORMAT_UNDEFINED == surfaceFormat) { + if (!foundSurfaceFormat) { return false; } - // If mailbox mode is available, use it, as it is the lowest-latency non- - // tearing mode. If not, fall back to FIFO which is always available. + // FIFO is always available and will match what we do on GL so just pick that here. VkPresentModeKHR mode = VK_PRESENT_MODE_FIFO_KHR; - for (uint32_t i = 0; i < presentModeCount; ++i) { - // use mailbox - if (VK_PRESENT_MODE_MAILBOX_KHR == presentModes[i]) { - mode = presentModes[i]; - break; - } - } VkSwapchainCreateInfoKHR swapchainCreateInfo; memset(&swapchainCreateInfo, 0, sizeof(VkSwapchainCreateInfoKHR)); @@ -478,8 +771,8 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { swapchainCreateInfo.imageArrayLayers = 1; swapchainCreateInfo.imageUsage = usageFlags; - uint32_t queueFamilies[] = {mBackendContext->fGraphicsQueueIndex, mPresentQueueIndex}; - if (mBackendContext->fGraphicsQueueIndex != mPresentQueueIndex) { + uint32_t queueFamilies[] = {mGraphicsQueueIndex, mPresentQueueIndex}; + if (mGraphicsQueueIndex != mPresentQueueIndex) { swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchainCreateInfo.queueFamilyIndexCount = 2; swapchainCreateInfo.pQueueFamilyIndices = queueFamilies; @@ -495,19 +788,18 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { swapchainCreateInfo.clipped = true; swapchainCreateInfo.oldSwapchain = surface->mSwapchain; - res = mCreateSwapchainKHR(mBackendContext->fDevice, &swapchainCreateInfo, nullptr, - &surface->mSwapchain); + res = mCreateSwapchainKHR(mDevice, &swapchainCreateInfo, nullptr, &surface->mSwapchain); if (VK_SUCCESS != res) { return false; } // destroy the old swapchain if (swapchainCreateInfo.oldSwapchain != VK_NULL_HANDLE) { - mDeviceWaitIdle(mBackendContext->fDevice); + mDeviceWaitIdle(mDevice); destroyBuffers(surface); - mDestroySwapchainKHR(mBackendContext->fDevice, swapchainCreateInfo.oldSwapchain, nullptr); + mDestroySwapchainKHR(mDevice, swapchainCreateInfo.oldSwapchain, nullptr); } createBuffers(surface, surfaceFormat, extent); @@ -515,14 +807,14 @@ bool VulkanManager::createSwapchain(VulkanSurface* surface) { return true; } -VulkanSurface* VulkanManager::createSurface(ANativeWindow* window) { +VulkanSurface* VulkanManager::createSurface(ANativeWindow* window, ColorMode colorMode) { initialize(); if (!window) { return nullptr; } - VulkanSurface* surface = new VulkanSurface(); + VulkanSurface* surface = new VulkanSurface(colorMode); VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo; memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR)); @@ -531,20 +823,18 @@ VulkanSurface* VulkanManager::createSurface(ANativeWindow* window) { surfaceCreateInfo.flags = 0; surfaceCreateInfo.window = window; - VkResult res = mCreateAndroidSurfaceKHR(mBackendContext->fInstance, &surfaceCreateInfo, nullptr, - &surface->mVkSurface); + VkResult res = mCreateAndroidSurfaceKHR(mInstance, &surfaceCreateInfo, nullptr, + &surface->mVkSurface); if (VK_SUCCESS != res) { delete surface; return nullptr; } SkDEBUGCODE(VkBool32 supported; res = mGetPhysicalDeviceSurfaceSupportKHR( - mBackendContext->fPhysicalDevice, mPresentQueueIndex, - surface->mVkSurface, &supported); - // All physical devices and queue families on Android must be capable of - // presentation with any - // native window. - SkASSERT(VK_SUCCESS == res && supported);); + mPhysicalDevice, mPresentQueueIndex, surface->mVkSurface, &supported); + // All physical devices and queue families on Android must be capable of + // presentation with any native window. + SkASSERT(VK_SUCCESS == res && supported);); if (!createSwapchain(surface)) { destroySurface(surface); @@ -555,17 +845,19 @@ VulkanSurface* VulkanManager::createSurface(ANativeWindow* window) { } // Helper to know which src stage flags we need to set when transitioning to the present layout -static VkPipelineStageFlags layoutToPipelineStageFlags(const VkImageLayout layout) { +static VkPipelineStageFlags layoutToPipelineSrcStageFlags(const VkImageLayout layout) { if (VK_IMAGE_LAYOUT_GENERAL == layout) { return VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; } else if (VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL == layout || VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL == layout) { return VK_PIPELINE_STAGE_TRANSFER_BIT; - } else if (VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL == layout || - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL == layout || - VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL == layout || - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout) { - return VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + } else if (VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL == layout) { + return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + } else if (VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL == layout || + VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL == layout) { + return VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; + } else if (VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL == layout) { + return VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else if (VK_IMAGE_LAYOUT_PREINITIALIZED == layout) { return VK_PIPELINE_STAGE_HOST_BIT; } @@ -601,26 +893,31 @@ static VkAccessFlags layoutToSrcAccessMask(const VkImageLayout layout) { void VulkanManager::swapBuffers(VulkanSurface* surface) { if (CC_UNLIKELY(Properties::waitForGpuCompletion)) { ATRACE_NAME("Finishing GPU work"); - mDeviceWaitIdle(mBackendContext->fDevice); + mDeviceWaitIdle(mDevice); } SkASSERT(surface->mBackbuffers); VulkanSurface::BackbufferInfo* backbuffer = surface->mBackbuffers + surface->mCurrentBackbufferIndex; - GrVkImageInfo* imageInfo; + SkSurface* skSurface = surface->mImageInfos[backbuffer->mImageIndex].mSurface.get(); - skSurface->getRenderTargetHandle((GrBackendObject*)&imageInfo, - SkSurface::kFlushRead_BackendHandleAccess); + GrBackendRenderTarget backendRT = skSurface->getBackendRenderTarget( + SkSurface::kFlushRead_BackendHandleAccess); + SkASSERT(backendRT.isValid()); + + GrVkImageInfo imageInfo; + SkAssertResult(backendRT.getVkImageInfo(&imageInfo)); + // Check to make sure we never change the actually wrapped image - SkASSERT(imageInfo->fImage == surface->mImages[backbuffer->mImageIndex]); + SkASSERT(imageInfo.fImage == surface->mImages[backbuffer->mImageIndex]); // We need to transition the image to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR and make sure that all // previous work is complete for before presenting. So we first add the necessary barrier here. - VkImageLayout layout = imageInfo->fImageLayout; - VkPipelineStageFlags srcStageMask = layoutToPipelineStageFlags(layout); + VkImageLayout layout = imageInfo.fImageLayout; + VkPipelineStageFlags srcStageMask = layoutToPipelineSrcStageFlags(layout); VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; VkAccessFlags srcAccessMask = layoutToSrcAccessMask(layout); - VkAccessFlags dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; + VkAccessFlags dstAccessMask = 0; VkImageMemoryBarrier imageMemoryBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, // sType @@ -629,7 +926,7 @@ void VulkanManager::swapBuffers(VulkanSurface* surface) { dstAccessMask, // inputMask layout, // oldLayout VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, // newLayout - mBackendContext->fGraphicsQueueIndex, // srcQueueFamilyIndex + mGraphicsQueueIndex, // srcQueueFamilyIndex mPresentQueueIndex, // dstQueueFamilyIndex surface->mImages[backbuffer->mImageIndex], // image {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1} // subresourceRange @@ -661,7 +958,7 @@ void VulkanManager::swapBuffers(VulkanSurface* surface) { submitInfo.pSignalSemaphores = &backbuffer->mRenderSemaphore; // Attach second fence to submission here so we can track when the command buffer finishes. - mQueueSubmit(mBackendContext->fQueue, 1, &submitInfo, backbuffer->mUsageFences[1]); + mQueueSubmit(mGraphicsQueue, 1, &submitInfo, backbuffer->mUsageFences[1]); // Submit present operation to present queue. We use a semaphore here to make sure all rendering // to the image is complete and that the layout has been change to present on the graphics @@ -697,6 +994,160 @@ int VulkanManager::getAge(VulkanSurface* surface) { return surface->mCurrentTime - lastUsed; } +bool VulkanManager::setupDummyCommandBuffer() { + if (mDummyCB != VK_NULL_HANDLE) { + return true; + } + + VkCommandBufferAllocateInfo commandBuffersInfo; + memset(&commandBuffersInfo, 0, sizeof(VkCommandBufferAllocateInfo)); + commandBuffersInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + commandBuffersInfo.pNext = nullptr; + commandBuffersInfo.commandPool = mCommandPool; + commandBuffersInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + commandBuffersInfo.commandBufferCount = 1; + + VkResult err = mAllocateCommandBuffers(mDevice, &commandBuffersInfo, &mDummyCB); + if (err != VK_SUCCESS) { + // It is probably unnecessary to set this back to VK_NULL_HANDLE, but we set it anyways to + // make sure the driver didn't set a value and then return a failure. + mDummyCB = VK_NULL_HANDLE; + return false; + } + + VkCommandBufferBeginInfo beginInfo; + memset(&beginInfo, 0, sizeof(VkCommandBufferBeginInfo)); + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; + + mBeginCommandBuffer(mDummyCB, &beginInfo); + mEndCommandBuffer(mDummyCB); + return true; +} + +status_t VulkanManager::fenceWait(sp<Fence>& fence) { + if (!hasVkContext()) { + ALOGE("VulkanManager::fenceWait: VkDevice not initialized"); + return INVALID_OPERATION; + } + + // Block GPU on the fence. + int fenceFd = fence->dup(); + if (fenceFd == -1) { + ALOGE("VulkanManager::fenceWait: error dup'ing fence fd: %d", errno); + return -errno; + } + + VkSemaphoreCreateInfo semaphoreInfo; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = nullptr; + semaphoreInfo.flags = 0; + VkSemaphore semaphore; + VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); + if (VK_SUCCESS != err) { + ALOGE("Failed to create import semaphore, err: %d", err); + return UNKNOWN_ERROR; + } + VkImportSemaphoreFdInfoKHR importInfo; + importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR; + importInfo.pNext = nullptr; + importInfo.semaphore = semaphore; + importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT; + importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + importInfo.fd = fenceFd; + + err = mImportSemaphoreFdKHR(mDevice, &importInfo); + if (VK_SUCCESS != err) { + ALOGE("Failed to import semaphore, err: %d", err); + return UNKNOWN_ERROR; + } + + LOG_ALWAYS_FATAL_IF(mDummyCB == VK_NULL_HANDLE); + + VkPipelineStageFlags waitDstStageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + + VkSubmitInfo submitInfo; + memset(&submitInfo, 0, sizeof(VkSubmitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.waitSemaphoreCount = 1; + // Wait to make sure aquire semaphore set above has signaled. + submitInfo.pWaitSemaphores = &semaphore; + submitInfo.pWaitDstStageMask = &waitDstStageFlags; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &mDummyCB; + submitInfo.signalSemaphoreCount = 0; + + mQueueSubmit(mGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + + // On Android when we import a semaphore, it is imported using temporary permanence. That + // means as soon as we queue the semaphore for a wait it reverts to its previous permanent + // state before importing. This means it will now be in an idle state with no pending + // signal or wait operations, so it is safe to immediately delete it. + mDestroySemaphore(mDevice, semaphore, nullptr); + return OK; +} + +status_t VulkanManager::createReleaseFence(sp<Fence>& nativeFence) { + if (!hasVkContext()) { + ALOGE("VulkanManager::createReleaseFence: VkDevice not initialized"); + return INVALID_OPERATION; + } + + VkExportSemaphoreCreateInfo exportInfo; + exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO; + exportInfo.pNext = nullptr; + exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + VkSemaphoreCreateInfo semaphoreInfo; + semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + semaphoreInfo.pNext = &exportInfo; + semaphoreInfo.flags = 0; + VkSemaphore semaphore; + VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore); + if (VK_SUCCESS != err) { + ALOGE("VulkanManager::createReleaseFence: Failed to create semaphore"); + return INVALID_OPERATION; + } + + LOG_ALWAYS_FATAL_IF(mDummyCB == VK_NULL_HANDLE); + + VkSubmitInfo submitInfo; + memset(&submitInfo, 0, sizeof(VkSubmitInfo)); + submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submitInfo.waitSemaphoreCount = 0; + submitInfo.pWaitSemaphores = nullptr; + submitInfo.pWaitDstStageMask = nullptr; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &mDummyCB; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = &semaphore; + + mQueueSubmit(mGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); + + VkSemaphoreGetFdInfoKHR getFdInfo; + getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR; + getFdInfo.pNext = nullptr; + getFdInfo.semaphore = semaphore; + getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT; + + int fenceFd = 0; + + err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd); + if (VK_SUCCESS != err) { + ALOGE("VulkanManager::createReleaseFence: Failed to get semaphore Fd"); + return INVALID_OPERATION; + } + nativeFence = new Fence(fenceFd); + + // Exporting a semaphore with copy transference via vkGetSemahporeFdKHR, has the same effect of + // destroying the semaphore and creating a new one with the same handle, and the payloads + // ownership is move to the Fd we created. Thus the semahpore is in a state that we can delete + // it and we don't need to wait on the command buffer we submitted to finish. + mDestroySemaphore(mDevice, semaphore, nullptr); + + return OK; +} + } /* namespace renderthread */ } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index c319c9ec209f..6702649402e6 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -17,10 +17,18 @@ #ifndef VULKANMANAGER_H #define VULKANMANAGER_H +#if !defined(VK_USE_PLATFORM_ANDROID_KHR) +# define VK_USE_PLATFORM_ANDROID_KHR +#endif +#include <vulkan/vulkan.h> + #include <SkSurface.h> +#include <ui/Fence.h> +#include <utils/StrongPointer.h> #include <vk/GrVkBackendContext.h> +#include "IRenderPipeline.h" -#include <vulkan/vulkan.h> +class GrVkExtensions; namespace android { namespace uirenderer { @@ -30,7 +38,7 @@ class RenderThread; class VulkanSurface { public: - VulkanSurface() {} + VulkanSurface(ColorMode colorMode) : mColorMode(colorMode) {} sk_sp<SkSurface> getBackBufferSurface() { return mBackbuffer; } @@ -66,6 +74,7 @@ private: VkImage* mImages = nullptr; ImageInfo* mImageInfos; uint16_t mCurrentTime = 0; + ColorMode mColorMode; }; // This class contains the shared global Vulkan objects, such as VkInstance, VkDevice and VkQueue, @@ -79,11 +88,11 @@ public: void initialize(); // Quick check to see if the VulkanManager has been initialized. - bool hasVkContext() { return mBackendContext.get() != nullptr; } + bool hasVkContext() { return mDevice != VK_NULL_HANDLE; } // Given a window this creates a new VkSurfaceKHR and VkSwapchain and stores them inside a new // VulkanSurface object which is returned. - VulkanSurface* createSurface(ANativeWindow* window); + VulkanSurface* createSurface(ANativeWindow* window, ColorMode colorMode); // Destroy the VulkanSurface and all associated vulkan objects. void destroySurface(VulkanSurface* surface); @@ -105,12 +114,22 @@ public: // Presents the current VkImage. void swapBuffers(VulkanSurface* surface); + // Inserts a wait on fence command into the Vulkan command buffer. + status_t fenceWait(sp<Fence>& fence); + + // Creates a fence that is signaled, when all the pending Vulkan commands are flushed. + status_t createReleaseFence(sp<Fence>& nativeFence); + private: friend class RenderThread; explicit VulkanManager(RenderThread& thread); ~VulkanManager() { destroy(); } + // Sets up the VkInstance and VkDevice objects. Also fills out the passed in + // VkPhysicalDeviceFeatures struct. + bool setupDevice(GrVkExtensions&, VkPhysicalDeviceFeatures2&); + void destroyBuffers(VulkanSurface* surface); bool createSwapchain(VulkanSurface* surface); @@ -118,6 +137,8 @@ private: VulkanSurface::BackbufferInfo* getAvailableBackbuffer(VulkanSurface* surface); + bool setupDummyCommandBuffer(); + // simple wrapper class that exists only to initialize a pointer to NULL template <typename FNPTR_TYPE> class VkPtr { @@ -148,7 +169,23 @@ private: VkPtr<PFN_vkQueuePresentKHR> mQueuePresentKHR; VkPtr<PFN_vkCreateSharedSwapchainsKHR> mCreateSharedSwapchainsKHR; - // Additional vulkan functions + // Instance Functions + VkPtr<PFN_vkEnumerateInstanceVersion> mEnumerateInstanceVersion; + VkPtr<PFN_vkEnumerateInstanceExtensionProperties> mEnumerateInstanceExtensionProperties; + VkPtr<PFN_vkCreateInstance> mCreateInstance; + + VkPtr<PFN_vkDestroyInstance> mDestroyInstance; + VkPtr<PFN_vkEnumeratePhysicalDevices> mEnumeratePhysicalDevices; + VkPtr<PFN_vkGetPhysicalDeviceProperties> mGetPhysicalDeviceProperties; + VkPtr<PFN_vkGetPhysicalDeviceQueueFamilyProperties> mGetPhysicalDeviceQueueFamilyProperties; + VkPtr<PFN_vkGetPhysicalDeviceFeatures2> mGetPhysicalDeviceFeatures2; + VkPtr<PFN_vkCreateDevice> mCreateDevice; + VkPtr<PFN_vkEnumerateDeviceExtensionProperties> mEnumerateDeviceExtensionProperties; + + // Device Functions + VkPtr<PFN_vkGetDeviceQueue> mGetDeviceQueue; + VkPtr<PFN_vkDeviceWaitIdle> mDeviceWaitIdle; + VkPtr<PFN_vkDestroyDevice> mDestroyDevice; VkPtr<PFN_vkCreateCommandPool> mCreateCommandPool; VkPtr<PFN_vkDestroyCommandPool> mDestroyCommandPool; VkPtr<PFN_vkAllocateCommandBuffers> mAllocateCommandBuffers; @@ -158,13 +195,13 @@ private: VkPtr<PFN_vkEndCommandBuffer> mEndCommandBuffer; VkPtr<PFN_vkCmdPipelineBarrier> mCmdPipelineBarrier; - VkPtr<PFN_vkGetDeviceQueue> mGetDeviceQueue; VkPtr<PFN_vkQueueSubmit> mQueueSubmit; VkPtr<PFN_vkQueueWaitIdle> mQueueWaitIdle; - VkPtr<PFN_vkDeviceWaitIdle> mDeviceWaitIdle; VkPtr<PFN_vkCreateSemaphore> mCreateSemaphore; VkPtr<PFN_vkDestroySemaphore> mDestroySemaphore; + VkPtr<PFN_vkImportSemaphoreFdKHR> mImportSemaphoreFdKHR; + VkPtr<PFN_vkGetSemaphoreFdKHR> mGetSemaphoreFdKHR; VkPtr<PFN_vkCreateFence> mCreateFence; VkPtr<PFN_vkDestroyFence> mDestroyFence; VkPtr<PFN_vkWaitForFences> mWaitForFences; @@ -172,11 +209,18 @@ private: RenderThread& mRenderThread; - sk_sp<const GrVkBackendContext> mBackendContext; + VkInstance mInstance = VK_NULL_HANDLE; + VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; + VkDevice mDevice = VK_NULL_HANDLE; + + uint32_t mGraphicsQueueIndex; + VkQueue mGraphicsQueue = VK_NULL_HANDLE; uint32_t mPresentQueueIndex; VkQueue mPresentQueue = VK_NULL_HANDLE; VkCommandPool mCommandPool = VK_NULL_HANDLE; + VkCommandBuffer mDummyCB = VK_NULL_HANDLE; + enum class SwapBehavior { Discard, BufferAge, diff --git a/libs/hwui/surfacetexture/EGLConsumer.cpp b/libs/hwui/surfacetexture/EGLConsumer.cpp new file mode 100644 index 000000000000..c8220c6cb0d4 --- /dev/null +++ b/libs/hwui/surfacetexture/EGLConsumer.cpp @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2018 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 <inttypes.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <cutils/compiler.h> +#include <gui/BufferItem.h> +#include <gui/BufferQueue.h> +#include <private/gui/SyncFeatures.h> +#include "EGLConsumer.h" +#include "SurfaceTexture.h" + +#include <utils/Log.h> +#include <utils/String8.h> +#include <utils/Trace.h> + +#define PROT_CONTENT_EXT_STR "EGL_EXT_protected_content" +#define EGL_PROTECTED_CONTENT_EXT 0x32C0 + +namespace android { + +// Macros for including the SurfaceTexture name in log messages +#define EGC_LOGV(x, ...) ALOGV("[%s] " x, st.mName.string(), ##__VA_ARGS__) +#define EGC_LOGD(x, ...) ALOGD("[%s] " x, st.mName.string(), ##__VA_ARGS__) +#define EGC_LOGW(x, ...) ALOGW("[%s] " x, st.mName.string(), ##__VA_ARGS__) +#define EGC_LOGE(x, ...) ALOGE("[%s] " x, st.mName.string(), ##__VA_ARGS__) + +static const struct { + uint32_t width, height; + char const* bits; +} kDebugData = {15, 12, + "_______________" + "_______________" + "_____XX_XX_____" + "__X_X_____X_X__" + "__X_XXXXXXX_X__" + "__XXXXXXXXXXX__" + "___XX_XXX_XX___" + "____XXXXXXX____" + "_____X___X_____" + "____X_____X____" + "_______________" + "_______________"}; + +Mutex EGLConsumer::sStaticInitLock; +sp<GraphicBuffer> EGLConsumer::sReleasedTexImageBuffer; + +static bool hasEglProtectedContentImpl() { + EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + const char* exts = eglQueryString(dpy, EGL_EXTENSIONS); + size_t cropExtLen = strlen(PROT_CONTENT_EXT_STR); + size_t extsLen = strlen(exts); + bool equal = !strcmp(PROT_CONTENT_EXT_STR, exts); + bool atStart = !strncmp(PROT_CONTENT_EXT_STR " ", exts, cropExtLen + 1); + bool atEnd = (cropExtLen + 1) < extsLen && + !strcmp(" " PROT_CONTENT_EXT_STR, exts + extsLen - (cropExtLen + 1)); + bool inMiddle = strstr(exts, " " PROT_CONTENT_EXT_STR " "); + return equal || atStart || atEnd || inMiddle; +} + +static bool hasEglProtectedContent() { + // Only compute whether the extension is present once the first time this + // function is called. + static bool hasIt = hasEglProtectedContentImpl(); + return hasIt; +} + +EGLConsumer::EGLConsumer() : mEglDisplay(EGL_NO_DISPLAY), mEglContext(EGL_NO_CONTEXT) {} + +status_t EGLConsumer::updateTexImage(SurfaceTexture& st) { + // Make sure the EGL state is the same as in previous calls. + status_t err = checkAndUpdateEglStateLocked(st); + if (err != NO_ERROR) { + return err; + } + + BufferItem item; + + // Acquire the next buffer. + // In asynchronous mode the list is guaranteed to be one buffer + // deep, while in synchronous mode we use the oldest buffer. + err = st.acquireBufferLocked(&item, 0); + if (err != NO_ERROR) { + if (err == BufferQueue::NO_BUFFER_AVAILABLE) { + // We always bind the texture even if we don't update its contents. + EGC_LOGV("updateTexImage: no buffers were available"); + glBindTexture(st.mTexTarget, st.mTexName); + err = NO_ERROR; + } else { + EGC_LOGE("updateTexImage: acquire failed: %s (%d)", strerror(-err), err); + } + return err; + } + + // Release the previous buffer. + err = updateAndReleaseLocked(item, nullptr, st); + if (err != NO_ERROR) { + // We always bind the texture. + glBindTexture(st.mTexTarget, st.mTexName); + return err; + } + + // Bind the new buffer to the GL texture, and wait until it's ready. + return bindTextureImageLocked(st); +} + +status_t EGLConsumer::releaseTexImage(SurfaceTexture& st) { + // Make sure the EGL state is the same as in previous calls. + status_t err = NO_ERROR; + + // if we're detached, no need to validate EGL's state -- we won't use it. + if (st.mOpMode == SurfaceTexture::OpMode::attachedToGL) { + err = checkAndUpdateEglStateLocked(st, true); + if (err != NO_ERROR) { + return err; + } + } + + // Update the EGLConsumer state. + int buf = st.mCurrentTexture; + if (buf != BufferQueue::INVALID_BUFFER_SLOT) { + EGC_LOGV("releaseTexImage: (slot=%d, mOpMode=%d)", buf, (int)st.mOpMode); + + // if we're detached, we just use the fence that was created in detachFromContext() + // so... basically, nothing more to do here. + if (st.mOpMode == SurfaceTexture::OpMode::attachedToGL) { + // Do whatever sync ops we need to do before releasing the slot. + err = syncForReleaseLocked(mEglDisplay, st); + if (err != NO_ERROR) { + EGC_LOGE("syncForReleaseLocked failed (slot=%d), err=%d", buf, err); + return err; + } + } + + err = st.releaseBufferLocked(buf, st.mSlots[buf].mGraphicBuffer, mEglDisplay, + EGL_NO_SYNC_KHR); + if (err < NO_ERROR) { + EGC_LOGE("releaseTexImage: failed to release buffer: %s (%d)", strerror(-err), err); + return err; + } + + if (mReleasedTexImage == nullptr) { + mReleasedTexImage = new EglImage(getDebugTexImageBuffer()); + } + + st.mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT; + mCurrentTextureImage = mReleasedTexImage; + st.mCurrentCrop.makeInvalid(); + st.mCurrentTransform = 0; + st.mCurrentTimestamp = 0; + st.mCurrentDataSpace = HAL_DATASPACE_UNKNOWN; + st.mCurrentFence = Fence::NO_FENCE; + st.mCurrentFenceTime = FenceTime::NO_FENCE; + + // detached, don't touch the texture (and we may not even have an + // EGLDisplay here. + if (st.mOpMode == SurfaceTexture::OpMode::attachedToGL) { + // This binds a dummy buffer (mReleasedTexImage). + status_t result = bindTextureImageLocked(st); + if (result != NO_ERROR) { + return result; + } + } + } + + return NO_ERROR; +} + +sp<GraphicBuffer> EGLConsumer::getDebugTexImageBuffer() { + Mutex::Autolock _l(sStaticInitLock); + if (CC_UNLIKELY(sReleasedTexImageBuffer == nullptr)) { + // The first time, create the debug texture in case the application + // continues to use it. + sp<GraphicBuffer> buffer = new GraphicBuffer( + kDebugData.width, kDebugData.height, PIXEL_FORMAT_RGBA_8888, + GraphicBuffer::USAGE_SW_WRITE_RARELY, "[EGLConsumer debug texture]"); + uint32_t* bits; + buffer->lock(GraphicBuffer::USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&bits)); + uint32_t stride = buffer->getStride(); + uint32_t height = buffer->getHeight(); + memset(bits, 0, stride * height * 4); + for (uint32_t y = 0; y < kDebugData.height; y++) { + for (uint32_t x = 0; x < kDebugData.width; x++) { + bits[x] = (kDebugData.bits[y + kDebugData.width + x] == 'X') ? 0xFF000000 + : 0xFFFFFFFF; + } + bits += stride; + } + buffer->unlock(); + sReleasedTexImageBuffer = buffer; + } + return sReleasedTexImageBuffer; +} + +void EGLConsumer::onAcquireBufferLocked(BufferItem* item, SurfaceTexture& st) { + // If item->mGraphicBuffer is not null, this buffer has not been acquired + // before, so any prior EglImage created is using a stale buffer. This + // replaces any old EglImage with a new one (using the new buffer). + int slot = item->mSlot; + if (item->mGraphicBuffer != nullptr || mEglSlots[slot].mEglImage.get() == nullptr) { + mEglSlots[slot].mEglImage = new EglImage(st.mSlots[slot].mGraphicBuffer); + } +} + +void EGLConsumer::onReleaseBufferLocked(int buf) { + mEglSlots[buf].mEglFence = EGL_NO_SYNC_KHR; +} + +status_t EGLConsumer::updateAndReleaseLocked(const BufferItem& item, PendingRelease* pendingRelease, + SurfaceTexture& st) { + status_t err = NO_ERROR; + + int slot = item.mSlot; + + if (st.mOpMode != SurfaceTexture::OpMode::attachedToGL) { + EGC_LOGE( + "updateAndRelease: EGLConsumer is not attached to an OpenGL " + "ES context"); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR); + return INVALID_OPERATION; + } + + // Confirm state. + err = checkAndUpdateEglStateLocked(st); + if (err != NO_ERROR) { + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR); + return err; + } + + // Ensure we have a valid EglImageKHR for the slot, creating an EglImage + // if nessessary, for the gralloc buffer currently in the slot in + // ConsumerBase. + // We may have to do this even when item.mGraphicBuffer == NULL (which + // means the buffer was previously acquired). + err = mEglSlots[slot].mEglImage->createIfNeeded(mEglDisplay); + if (err != NO_ERROR) { + EGC_LOGW("updateAndRelease: unable to createImage on display=%p slot=%d", mEglDisplay, + slot); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, EGL_NO_SYNC_KHR); + return UNKNOWN_ERROR; + } + + // Do whatever sync ops we need to do before releasing the old slot. + if (slot != st.mCurrentTexture) { + err = syncForReleaseLocked(mEglDisplay, st); + if (err != NO_ERROR) { + // Release the buffer we just acquired. It's not safe to + // release the old buffer, so instead we just drop the new frame. + // As we are still under lock since acquireBuffer, it is safe to + // release by slot. + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, mEglDisplay, + EGL_NO_SYNC_KHR); + return err; + } + } + + EGC_LOGV( + "updateAndRelease: (slot=%d buf=%p) -> (slot=%d buf=%p)", st.mCurrentTexture, + mCurrentTextureImage != nullptr ? mCurrentTextureImage->graphicBufferHandle() : nullptr, + slot, st.mSlots[slot].mGraphicBuffer->handle); + + // Hang onto the pointer so that it isn't freed in the call to + // releaseBufferLocked() if we're in shared buffer mode and both buffers are + // the same. + sp<EglImage> nextTextureImage = mEglSlots[slot].mEglImage; + + // release old buffer + if (st.mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + if (pendingRelease == nullptr) { + status_t status = st.releaseBufferLocked( + st.mCurrentTexture, mCurrentTextureImage->graphicBuffer(), mEglDisplay, + mEglSlots[st.mCurrentTexture].mEglFence); + if (status < NO_ERROR) { + EGC_LOGE("updateAndRelease: failed to release buffer: %s (%d)", strerror(-status), + status); + err = status; + // keep going, with error raised [?] + } + } else { + pendingRelease->currentTexture = st.mCurrentTexture; + pendingRelease->graphicBuffer = mCurrentTextureImage->graphicBuffer(); + pendingRelease->display = mEglDisplay; + pendingRelease->fence = mEglSlots[st.mCurrentTexture].mEglFence; + pendingRelease->isPending = true; + } + } + + // Update the EGLConsumer state. + st.mCurrentTexture = slot; + mCurrentTextureImage = nextTextureImage; + st.mCurrentCrop = item.mCrop; + st.mCurrentTransform = item.mTransform; + st.mCurrentScalingMode = item.mScalingMode; + st.mCurrentTimestamp = item.mTimestamp; + st.mCurrentDataSpace = item.mDataSpace; + st.mCurrentFence = item.mFence; + st.mCurrentFenceTime = item.mFenceTime; + st.mCurrentFrameNumber = item.mFrameNumber; + + st.computeCurrentTransformMatrixLocked(); + + return err; +} + +status_t EGLConsumer::bindTextureImageLocked(SurfaceTexture& st) { + if (mEglDisplay == EGL_NO_DISPLAY) { + ALOGE("bindTextureImage: invalid display"); + return INVALID_OPERATION; + } + + GLenum error; + while ((error = glGetError()) != GL_NO_ERROR) { + EGC_LOGW("bindTextureImage: clearing GL error: %#04x", error); + } + + glBindTexture(st.mTexTarget, st.mTexName); + if (st.mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT && mCurrentTextureImage == nullptr) { + EGC_LOGE("bindTextureImage: no currently-bound texture"); + return NO_INIT; + } + + status_t err = mCurrentTextureImage->createIfNeeded(mEglDisplay); + if (err != NO_ERROR) { + EGC_LOGW("bindTextureImage: can't create image on display=%p slot=%d", mEglDisplay, + st.mCurrentTexture); + return UNKNOWN_ERROR; + } + mCurrentTextureImage->bindToTextureTarget(st.mTexTarget); + + // In the rare case that the display is terminated and then initialized + // again, we can't detect that the display changed (it didn't), but the + // image is invalid. In this case, repeat the exact same steps while + // forcing the creation of a new image. + if ((error = glGetError()) != GL_NO_ERROR) { + glBindTexture(st.mTexTarget, st.mTexName); + status_t result = mCurrentTextureImage->createIfNeeded(mEglDisplay, true); + if (result != NO_ERROR) { + EGC_LOGW("bindTextureImage: can't create image on display=%p slot=%d", mEglDisplay, + st.mCurrentTexture); + return UNKNOWN_ERROR; + } + mCurrentTextureImage->bindToTextureTarget(st.mTexTarget); + if ((error = glGetError()) != GL_NO_ERROR) { + EGC_LOGE("bindTextureImage: error binding external image: %#04x", error); + return UNKNOWN_ERROR; + } + } + + // Wait for the new buffer to be ready. + return doGLFenceWaitLocked(st); +} + +status_t EGLConsumer::checkAndUpdateEglStateLocked(SurfaceTexture& st, bool contextCheck) { + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if (!contextCheck) { + // if this is the first time we're called, mEglDisplay/mEglContext have + // never been set, so don't error out (below). + if (mEglDisplay == EGL_NO_DISPLAY) { + mEglDisplay = dpy; + } + if (mEglContext == EGL_NO_CONTEXT) { + mEglContext = ctx; + } + } + + if (mEglDisplay != dpy || dpy == EGL_NO_DISPLAY) { + EGC_LOGE("checkAndUpdateEglState: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if (mEglContext != ctx || ctx == EGL_NO_CONTEXT) { + EGC_LOGE("checkAndUpdateEglState: invalid current EGLContext"); + return INVALID_OPERATION; + } + + mEglDisplay = dpy; + mEglContext = ctx; + return NO_ERROR; +} + +status_t EGLConsumer::detachFromContext(SurfaceTexture& st) { + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if (mEglDisplay != dpy && mEglDisplay != EGL_NO_DISPLAY) { + EGC_LOGE("detachFromContext: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if (mEglContext != ctx && mEglContext != EGL_NO_CONTEXT) { + EGC_LOGE("detachFromContext: invalid current EGLContext"); + return INVALID_OPERATION; + } + + if (dpy != EGL_NO_DISPLAY && ctx != EGL_NO_CONTEXT) { + status_t err = syncForReleaseLocked(dpy, st); + if (err != OK) { + return err; + } + + glDeleteTextures(1, &st.mTexName); + } + + mEglDisplay = EGL_NO_DISPLAY; + mEglContext = EGL_NO_CONTEXT; + + return OK; +} + +status_t EGLConsumer::attachToContext(uint32_t tex, SurfaceTexture& st) { + // Initialize mCurrentTextureImage if there is a current buffer from past attached state. + int slot = st.mCurrentTexture; + if (slot != BufferItem::INVALID_BUFFER_SLOT) { + if (!mEglSlots[slot].mEglImage.get()) { + mEglSlots[slot].mEglImage = new EglImage(st.mSlots[slot].mGraphicBuffer); + } + mCurrentTextureImage = mEglSlots[slot].mEglImage; + } + + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if (dpy == EGL_NO_DISPLAY) { + EGC_LOGE("attachToContext: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if (ctx == EGL_NO_CONTEXT) { + EGC_LOGE("attachToContext: invalid current EGLContext"); + return INVALID_OPERATION; + } + + // We need to bind the texture regardless of whether there's a current + // buffer. + glBindTexture(st.mTexTarget, GLuint(tex)); + + mEglDisplay = dpy; + mEglContext = ctx; + st.mTexName = tex; + st.mOpMode = SurfaceTexture::OpMode::attachedToGL; + + if (mCurrentTextureImage != nullptr) { + // This may wait for a buffer a second time. This is likely required if + // this is a different context, since otherwise the wait could be skipped + // by bouncing through another context. For the same context the extra + // wait is redundant. + status_t err = bindTextureImageLocked(st); + if (err != NO_ERROR) { + return err; + } + } + + return OK; +} + +status_t EGLConsumer::syncForReleaseLocked(EGLDisplay dpy, SurfaceTexture& st) { + EGC_LOGV("syncForReleaseLocked"); + + if (st.mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + if (SyncFeatures::getInstance().useNativeFenceSync()) { + EGLSyncKHR sync = eglCreateSyncKHR(dpy, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); + if (sync == EGL_NO_SYNC_KHR) { + EGC_LOGE("syncForReleaseLocked: error creating EGL fence: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + glFlush(); + int fenceFd = eglDupNativeFenceFDANDROID(dpy, sync); + eglDestroySyncKHR(dpy, sync); + if (fenceFd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + EGC_LOGE( + "syncForReleaseLocked: error dup'ing native fence " + "fd: %#x", + eglGetError()); + return UNKNOWN_ERROR; + } + sp<Fence> fence(new Fence(fenceFd)); + status_t err = st.addReleaseFenceLocked(st.mCurrentTexture, + mCurrentTextureImage->graphicBuffer(), fence); + if (err != OK) { + EGC_LOGE( + "syncForReleaseLocked: error adding release fence: " + "%s (%d)", + strerror(-err), err); + return err; + } + } else if (st.mUseFenceSync && SyncFeatures::getInstance().useFenceSync()) { + EGLSyncKHR fence = mEglSlots[st.mCurrentTexture].mEglFence; + if (fence != EGL_NO_SYNC_KHR) { + // There is already a fence for the current slot. We need to + // wait on that before replacing it with another fence to + // ensure that all outstanding buffer accesses have completed + // before the producer accesses it. + EGLint result = eglClientWaitSyncKHR(dpy, fence, 0, 1000000000); + if (result == EGL_FALSE) { + EGC_LOGE( + "syncForReleaseLocked: error waiting for previous " + "fence: %#x", + eglGetError()); + return UNKNOWN_ERROR; + } else if (result == EGL_TIMEOUT_EXPIRED_KHR) { + EGC_LOGE( + "syncForReleaseLocked: timeout waiting for previous " + "fence"); + return TIMED_OUT; + } + eglDestroySyncKHR(dpy, fence); + } + + // Create a fence for the outstanding accesses in the current + // OpenGL ES context. + fence = eglCreateSyncKHR(dpy, EGL_SYNC_FENCE_KHR, nullptr); + if (fence == EGL_NO_SYNC_KHR) { + EGC_LOGE("syncForReleaseLocked: error creating fence: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + glFlush(); + mEglSlots[st.mCurrentTexture].mEglFence = fence; + } + } + + return OK; +} + +status_t EGLConsumer::doGLFenceWaitLocked(SurfaceTexture& st) const { + EGLDisplay dpy = eglGetCurrentDisplay(); + EGLContext ctx = eglGetCurrentContext(); + + if (mEglDisplay != dpy || mEglDisplay == EGL_NO_DISPLAY) { + EGC_LOGE("doGLFenceWait: invalid current EGLDisplay"); + return INVALID_OPERATION; + } + + if (mEglContext != ctx || mEglContext == EGL_NO_CONTEXT) { + EGC_LOGE("doGLFenceWait: invalid current EGLContext"); + return INVALID_OPERATION; + } + + if (st.mCurrentFence->isValid()) { + if (SyncFeatures::getInstance().useWaitSync() && + SyncFeatures::getInstance().useNativeFenceSync()) { + // Create an EGLSyncKHR from the current fence. + int fenceFd = st.mCurrentFence->dup(); + if (fenceFd == -1) { + EGC_LOGE("doGLFenceWait: error dup'ing fence fd: %d", errno); + return -errno; + } + EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd, EGL_NONE}; + EGLSyncKHR sync = eglCreateSyncKHR(dpy, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs); + if (sync == EGL_NO_SYNC_KHR) { + close(fenceFd); + EGC_LOGE("doGLFenceWait: error creating EGL fence: %#x", eglGetError()); + return UNKNOWN_ERROR; + } + + // XXX: The spec draft is inconsistent as to whether this should + // return an EGLint or void. Ignore the return value for now, as + // it's not strictly needed. + eglWaitSyncKHR(dpy, sync, 0); + EGLint eglErr = eglGetError(); + eglDestroySyncKHR(dpy, sync); + if (eglErr != EGL_SUCCESS) { + EGC_LOGE("doGLFenceWait: error waiting for EGL fence: %#x", eglErr); + return UNKNOWN_ERROR; + } + } else { + status_t err = st.mCurrentFence->waitForever("EGLConsumer::doGLFenceWaitLocked"); + if (err != NO_ERROR) { + EGC_LOGE("doGLFenceWait: error waiting for fence: %d", err); + return err; + } + } + } + + return NO_ERROR; +} + +void EGLConsumer::onFreeBufferLocked(int slotIndex) { + mEglSlots[slotIndex].mEglImage.clear(); +} + +void EGLConsumer::onAbandonLocked() { + mCurrentTextureImage.clear(); +} + +EGLConsumer::EglImage::EglImage(sp<GraphicBuffer> graphicBuffer) + : mGraphicBuffer(graphicBuffer), mEglImage(EGL_NO_IMAGE_KHR), mEglDisplay(EGL_NO_DISPLAY) {} + +EGLConsumer::EglImage::~EglImage() { + if (mEglImage != EGL_NO_IMAGE_KHR) { + if (!eglDestroyImageKHR(mEglDisplay, mEglImage)) { + ALOGE("~EglImage: eglDestroyImageKHR failed"); + } + eglTerminate(mEglDisplay); + } +} + +status_t EGLConsumer::EglImage::createIfNeeded(EGLDisplay eglDisplay, bool forceCreation) { + // If there's an image and it's no longer valid, destroy it. + bool haveImage = mEglImage != EGL_NO_IMAGE_KHR; + bool displayInvalid = mEglDisplay != eglDisplay; + if (haveImage && (displayInvalid || forceCreation)) { + if (!eglDestroyImageKHR(mEglDisplay, mEglImage)) { + ALOGE("createIfNeeded: eglDestroyImageKHR failed"); + } + eglTerminate(mEglDisplay); + mEglImage = EGL_NO_IMAGE_KHR; + mEglDisplay = EGL_NO_DISPLAY; + } + + // If there's no image, create one. + if (mEglImage == EGL_NO_IMAGE_KHR) { + mEglDisplay = eglDisplay; + mEglImage = createImage(mEglDisplay, mGraphicBuffer); + } + + // Fail if we can't create a valid image. + if (mEglImage == EGL_NO_IMAGE_KHR) { + mEglDisplay = EGL_NO_DISPLAY; + const sp<GraphicBuffer>& buffer = mGraphicBuffer; + ALOGE("Failed to create image. size=%ux%u st=%u usage=%#" PRIx64 " fmt=%d", + buffer->getWidth(), buffer->getHeight(), buffer->getStride(), buffer->getUsage(), + buffer->getPixelFormat()); + return UNKNOWN_ERROR; + } + + return OK; +} + +void EGLConsumer::EglImage::bindToTextureTarget(uint32_t texTarget) { + glEGLImageTargetTexture2DOES(texTarget, static_cast<GLeglImageOES>(mEglImage)); +} + +EGLImageKHR EGLConsumer::EglImage::createImage(EGLDisplay dpy, + const sp<GraphicBuffer>& graphicBuffer) { + EGLClientBuffer cbuf = static_cast<EGLClientBuffer>(graphicBuffer->getNativeBuffer()); + const bool createProtectedImage = + (graphicBuffer->getUsage() & GRALLOC_USAGE_PROTECTED) && hasEglProtectedContent(); + EGLint attrs[] = { + EGL_IMAGE_PRESERVED_KHR, + EGL_TRUE, + createProtectedImage ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE, + createProtectedImage ? EGL_TRUE : EGL_NONE, + EGL_NONE, + }; + eglInitialize(dpy, nullptr, nullptr); + EGLImageKHR image = + eglCreateImageKHR(dpy, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, cbuf, attrs); + if (image == EGL_NO_IMAGE_KHR) { + EGLint error = eglGetError(); + ALOGE("error creating EGLImage: %#x", error); + eglTerminate(dpy); + } + return image; +} + +}; // namespace android diff --git a/libs/hwui/surfacetexture/EGLConsumer.h b/libs/hwui/surfacetexture/EGLConsumer.h new file mode 100644 index 000000000000..eccb08298f6f --- /dev/null +++ b/libs/hwui/surfacetexture/EGLConsumer.h @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2018 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 <EGL/egl.h> +#include <EGL/eglext.h> + +#include <gui/BufferQueueDefs.h> + +#include <ui/FenceTime.h> +#include <ui/GraphicBuffer.h> +#include <utils/Mutex.h> + +namespace android { + +class SurfaceTexture; + +/* + * EGLConsumer implements the parts of SurfaceTexture that deal with + * textures attached to an GL context. + */ +class EGLConsumer { +public: + EGLConsumer(); + + /** + * updateTexImage acquires the most recently queued buffer, and sets the + * image contents of the target texture to it. + * + * This call may only be made while the OpenGL ES context to which the + * target texture belongs is bound to the calling thread. + * + * This calls doGLFenceWait to ensure proper synchronization. + */ + status_t updateTexImage(SurfaceTexture& st); + + /* + * releaseTexImage releases the texture acquired in updateTexImage(). + * This is intended to be used in single buffer mode. + * + * This call may only be made while the OpenGL ES context to which the + * target texture belongs is bound to the calling thread. + */ + status_t releaseTexImage(SurfaceTexture& st); + + /** + * detachFromContext detaches the EGLConsumer from the calling thread's + * current OpenGL ES context. This context must be the same as the context + * that was current for previous calls to updateTexImage. + * + * Detaching a EGLConsumer from an OpenGL ES context will result in the + * deletion of the OpenGL ES texture object into which the images were being + * streamed. After a EGLConsumer has been detached from the OpenGL ES + * context calls to updateTexImage will fail returning INVALID_OPERATION + * until the EGLConsumer is attached to a new OpenGL ES context using the + * attachToContext method. + */ + status_t detachFromContext(SurfaceTexture& st); + + /** + * attachToContext attaches a EGLConsumer that is currently in the + * 'detached' state to the current OpenGL ES context. A EGLConsumer is + * in the 'detached' state iff detachFromContext has successfully been + * called and no calls to attachToContext have succeeded since the last + * detachFromContext call. Calls to attachToContext made on a + * EGLConsumer that is not in the 'detached' state will result in an + * INVALID_OPERATION error. + * + * The tex argument specifies the OpenGL ES texture object name in the + * new context into which the image contents will be streamed. A successful + * call to attachToContext will result in this texture object being bound to + * the texture target and populated with the image contents that were + * current at the time of the last call to detachFromContext. + */ + status_t attachToContext(uint32_t tex, SurfaceTexture& st); + + /** + * onAcquireBufferLocked amends the ConsumerBase method to update the + * mEglSlots array in addition to the ConsumerBase behavior. + */ + void onAcquireBufferLocked(BufferItem* item, SurfaceTexture& st); + + /** + * onReleaseBufferLocked amends the ConsumerBase method to update the + * mEglSlots array in addition to the ConsumerBase. + */ + void onReleaseBufferLocked(int slot); + + /** + * onFreeBufferLocked frees up the given buffer slot. If the slot has been + * initialized this will release the reference to the GraphicBuffer in that + * slot and destroy the EGLImage in that slot. Otherwise it has no effect. + */ + void onFreeBufferLocked(int slotIndex); + + /** + * onAbandonLocked amends the ConsumerBase method to clear + * mCurrentTextureImage in addition to the ConsumerBase behavior. + */ + void onAbandonLocked(); + +protected: + struct PendingRelease { + PendingRelease() + : isPending(false) + , currentTexture(-1) + , graphicBuffer() + , display(nullptr) + , fence(nullptr) {} + + bool isPending; + int currentTexture; + sp<GraphicBuffer> graphicBuffer; + EGLDisplay display; + EGLSyncKHR fence; + }; + + /** + * This releases the buffer in the slot referenced by mCurrentTexture, + * then updates state to refer to the BufferItem, which must be a + * newly-acquired buffer. If pendingRelease is not null, the parameters + * which would have been passed to releaseBufferLocked upon the successful + * completion of the method will instead be returned to the caller, so that + * it may call releaseBufferLocked itself later. + */ + status_t updateAndReleaseLocked(const BufferItem& item, PendingRelease* pendingRelease, + SurfaceTexture& st); + + /** + * Binds mTexName and the current buffer to mTexTarget. Uses + * mCurrentTexture if it's set, mCurrentTextureImage if not. If the + * bind succeeds, this calls doGLFenceWait. + */ + status_t bindTextureImageLocked(SurfaceTexture& st); + + /** + * Gets the current EGLDisplay and EGLContext values, and compares them + * to mEglDisplay and mEglContext. If the fields have been previously + * set, the values must match; if not, the fields are set to the current + * values. + * The contextCheck argument is used to ensure that a GL context is + * properly set; when set to false, the check is not performed. + */ + status_t checkAndUpdateEglStateLocked(SurfaceTexture& st, bool contextCheck = false); + + /** + * EglImage is a utility class for tracking and creating EGLImageKHRs. There + * is primarily just one image per slot, but there is also special cases: + * - For releaseTexImage, we use a debug image (mReleasedTexImage) + * - After freeBuffer, we must still keep the current image/buffer + * Reference counting EGLImages lets us handle all these cases easily while + * also only creating new EGLImages from buffers when required. + */ + class EglImage : public LightRefBase<EglImage> { + public: + EglImage(sp<GraphicBuffer> graphicBuffer); + + /** + * createIfNeeded creates an EGLImage if required (we haven't created + * one yet, or the EGLDisplay or crop-rect has changed). + */ + status_t createIfNeeded(EGLDisplay display, bool forceCreate = false); + + /** + * This calls glEGLImageTargetTexture2DOES to bind the image to the + * texture in the specified texture target. + */ + void bindToTextureTarget(uint32_t texTarget); + + const sp<GraphicBuffer>& graphicBuffer() { return mGraphicBuffer; } + const native_handle* graphicBufferHandle() { + return mGraphicBuffer == nullptr ? nullptr : mGraphicBuffer->handle; + } + + private: + // Only allow instantiation using ref counting. + friend class LightRefBase<EglImage>; + virtual ~EglImage(); + + // createImage creates a new EGLImage from a GraphicBuffer. + EGLImageKHR createImage(EGLDisplay dpy, const sp<GraphicBuffer>& graphicBuffer); + + // Disallow copying + EglImage(const EglImage& rhs); + void operator=(const EglImage& rhs); + + // mGraphicBuffer is the buffer that was used to create this image. + sp<GraphicBuffer> mGraphicBuffer; + + // mEglImage is the EGLImage created from mGraphicBuffer. + EGLImageKHR mEglImage; + + // mEGLDisplay is the EGLDisplay that was used to create mEglImage. + EGLDisplay mEglDisplay; + + // mCropRect is the crop rectangle passed to EGL when mEglImage + // was created. + Rect mCropRect; + }; + + /** + * doGLFenceWaitLocked inserts a wait command into the OpenGL ES command + * stream to ensure that it is safe for future OpenGL ES commands to + * access the current texture buffer. + */ + status_t doGLFenceWaitLocked(SurfaceTexture& st) const; + + /** + * syncForReleaseLocked performs the synchronization needed to release the + * current slot from an OpenGL ES context. If needed it will set the + * current slot's fence to guard against a producer accessing the buffer + * before the outstanding accesses have completed. + */ + status_t syncForReleaseLocked(EGLDisplay dpy, SurfaceTexture& st); + + /** + * returns a graphic buffer used when the texture image has been released + */ + static sp<GraphicBuffer> getDebugTexImageBuffer(); + + /** + * The default consumer usage flags that EGLConsumer always sets on its + * BufferQueue instance; these will be OR:d with any additional flags passed + * from the EGLConsumer user. In particular, EGLConsumer will always + * consume buffers as hardware textures. + */ + static const uint64_t DEFAULT_USAGE_FLAGS = GraphicBuffer::USAGE_HW_TEXTURE; + + /** + * mCurrentTextureImage is the EglImage/buffer of the current texture. It's + * possible that this buffer is not associated with any buffer slot, so we + * must track it separately in order to support the getCurrentBuffer method. + */ + sp<EglImage> mCurrentTextureImage; + + /** + * EGLSlot contains the information and object references that + * EGLConsumer maintains about a BufferQueue buffer slot. + */ + struct EglSlot { + EglSlot() : mEglFence(EGL_NO_SYNC_KHR) {} + + /** + * mEglImage is the EGLImage created from mGraphicBuffer. + */ + sp<EglImage> mEglImage; + + /** + * mFence is the EGL sync object that must signal before the buffer + * associated with this buffer slot may be dequeued. It is initialized + * to EGL_NO_SYNC_KHR when the buffer is created and (optionally, based + * on a compile-time option) set to a new sync object in updateTexImage. + */ + EGLSyncKHR mEglFence; + }; + + /** + * mEglDisplay is the EGLDisplay with which this EGLConsumer is currently + * associated. It is intialized to EGL_NO_DISPLAY and gets set to the + * current display when updateTexImage is called for the first time and when + * attachToContext is called. + */ + EGLDisplay mEglDisplay; + + /** + * mEglContext is the OpenGL ES context with which this EGLConsumer is + * currently associated. It is initialized to EGL_NO_CONTEXT and gets set + * to the current GL context when updateTexImage is called for the first + * time and when attachToContext is called. + */ + EGLContext mEglContext; + + /** + * mEGLSlots stores the buffers that have been allocated by the BufferQueue + * for each buffer slot. It is initialized to null pointers, and gets + * filled in with the result of BufferQueue::acquire when the + * client dequeues a buffer from a + * slot that has not yet been used. The buffer allocated to a slot will also + * be replaced if the requested buffer usage or geometry differs from that + * of the buffer allocated to a slot. + */ + EglSlot mEglSlots[BufferQueueDefs::NUM_BUFFER_SLOTS]; + + /** + * protects static initialization + */ + static Mutex sStaticInitLock; + + /** + * mReleasedTexImageBuffer is a dummy buffer used when in single buffer + * mode and releaseTexImage() has been called + */ + static sp<GraphicBuffer> sReleasedTexImageBuffer; + sp<EglImage> mReleasedTexImage; +}; + +}; // namespace android diff --git a/libs/hwui/surfacetexture/ImageConsumer.cpp b/libs/hwui/surfacetexture/ImageConsumer.cpp new file mode 100644 index 000000000000..9ffccfb4d340 --- /dev/null +++ b/libs/hwui/surfacetexture/ImageConsumer.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2018 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 "ImageConsumer.h" +#include <gui/BufferQueue.h> +#include "Properties.h" +#include "SurfaceTexture.h" +#include "renderstate/RenderState.h" +#include "renderthread/EglManager.h" +#include "renderthread/RenderThread.h" +#include "renderthread/VulkanManager.h" + +// Macro for including the SurfaceTexture name in log messages +#define IMG_LOGE(x, ...) ALOGE("[%s] " x, st.mName.string(), ##__VA_ARGS__) + +namespace android { + +void ImageConsumer::onFreeBufferLocked(int slotIndex) { + mImageSlots[slotIndex].mImage.reset(); +} + +void ImageConsumer::onAcquireBufferLocked(BufferItem* item) { + // If item->mGraphicBuffer is not null, this buffer has not been acquired + // before, so any prior SkImage is created with a stale buffer. This resets the stale SkImage. + if (item->mGraphicBuffer != nullptr) { + mImageSlots[item->mSlot].mImage.reset(); + } +} + +void ImageConsumer::onReleaseBufferLocked(int buf) { + mImageSlots[buf].mEglFence = EGL_NO_SYNC_KHR; +} + +void ImageConsumer::ImageSlot::createIfNeeded(sp<GraphicBuffer> graphicBuffer) { + if (!mImage.get()) { + mImage = graphicBuffer.get() + ? SkImage::MakeFromAHardwareBuffer( + reinterpret_cast<AHardwareBuffer*>(graphicBuffer.get()), + kPremul_SkAlphaType) + : nullptr; + } +} + +sk_sp<SkImage> ImageConsumer::dequeueImage(bool* queueEmpty, SurfaceTexture& st, + uirenderer::RenderState& renderState) { + BufferItem item; + status_t err; + err = st.acquireBufferLocked(&item, 0); + if (err != OK) { + if (err != BufferQueue::NO_BUFFER_AVAILABLE) { + IMG_LOGE("Error acquiring buffer: %s (%d)", strerror(err), err); + } else { + int slot = st.mCurrentTexture; + if (slot != BufferItem::INVALID_BUFFER_SLOT) { + *queueEmpty = true; + mImageSlots[slot].createIfNeeded(st.mSlots[slot].mGraphicBuffer); + return mImageSlots[slot].mImage; + } + } + return nullptr; + } + + int slot = item.mSlot; + if (item.mFence->isValid()) { + // Wait on the producer fence for the buffer to be ready. + if (uirenderer::Properties::getRenderPipelineType() == + uirenderer::RenderPipelineType::SkiaGL) { + err = renderState.getRenderThread().eglManager().fenceWait(item.mFence); + } else { + err = renderState.getRenderThread().vulkanManager().fenceWait(item.mFence); + } + if (err != OK) { + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY, + EGL_NO_SYNC_KHR); + return nullptr; + } + } + + // Release old buffer. + if (st.mCurrentTexture != BufferItem::INVALID_BUFFER_SLOT) { + // If needed, set the released slot's fence to guard against a producer accessing the + // buffer before the outstanding accesses have completed. + sp<Fence> releaseFence; + EGLDisplay display = EGL_NO_DISPLAY; + if (uirenderer::Properties::getRenderPipelineType() == + uirenderer::RenderPipelineType::SkiaGL) { + auto& eglManager = renderState.getRenderThread().eglManager(); + display = eglManager.eglDisplay(); + err = eglManager.createReleaseFence(st.mUseFenceSync, &mImageSlots[slot].mEglFence, + releaseFence); + } else { + err = renderState.getRenderThread().vulkanManager().createReleaseFence(releaseFence); + } + if (OK != err) { + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY, + EGL_NO_SYNC_KHR); + return nullptr; + } + + if (releaseFence.get()) { + status_t err = st.addReleaseFenceLocked( + st.mCurrentTexture, st.mSlots[st.mCurrentTexture].mGraphicBuffer, releaseFence); + if (err != OK) { + IMG_LOGE("dequeueImage: error adding release fence: %s (%d)", strerror(-err), err); + st.releaseBufferLocked(slot, st.mSlots[slot].mGraphicBuffer, EGL_NO_DISPLAY, + EGL_NO_SYNC_KHR); + return nullptr; + } + } + + // Finally release the old buffer. + status_t status = st.releaseBufferLocked( + st.mCurrentTexture, st.mSlots[st.mCurrentTexture].mGraphicBuffer, display, + mImageSlots[st.mCurrentTexture].mEglFence); + if (status < NO_ERROR) { + IMG_LOGE("dequeueImage: failed to release buffer: %s (%d)", strerror(-status), status); + err = status; + // Keep going, with error raised. + } + } + + // Update the state. + st.mCurrentTexture = slot; + st.mCurrentCrop = item.mCrop; + st.mCurrentTransform = item.mTransform; + st.mCurrentScalingMode = item.mScalingMode; + st.mCurrentTimestamp = item.mTimestamp; + st.mCurrentDataSpace = item.mDataSpace; + st.mCurrentFence = item.mFence; + st.mCurrentFenceTime = item.mFenceTime; + st.mCurrentFrameNumber = item.mFrameNumber; + st.computeCurrentTransformMatrixLocked(); + + *queueEmpty = false; + mImageSlots[slot].createIfNeeded(st.mSlots[slot].mGraphicBuffer); + return mImageSlots[slot].mImage; +} + +} /* namespace android */ diff --git a/libs/hwui/surfacetexture/ImageConsumer.h b/libs/hwui/surfacetexture/ImageConsumer.h new file mode 100644 index 000000000000..31ee8db52874 --- /dev/null +++ b/libs/hwui/surfacetexture/ImageConsumer.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2018 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 <EGL/egl.h> +#include <EGL/eglext.h> + +#include <gui/BufferQueueDefs.h> + +#include <SkImage.h> +#include <cutils/compiler.h> +#include <gui/BufferItem.h> +#include <system/graphics.h> + +namespace android { + +namespace uirenderer { +class RenderState; +} + +class SurfaceTexture; + +/* + * ImageConsumer implements the parts of SurfaceTexture that deal with + * images consumed by HWUI view system. + */ +class ImageConsumer { +public: + sk_sp<SkImage> dequeueImage(bool* queueEmpty, SurfaceTexture& cb, + uirenderer::RenderState& renderState); + + /** + * onAcquireBufferLocked amends the ConsumerBase method to update the + * mImageSlots array in addition to the ConsumerBase behavior. + */ + void onAcquireBufferLocked(BufferItem* item); + + /** + * onReleaseBufferLocked amends the ConsumerBase method to update the + * mImageSlots array in addition to the ConsumerBase. + */ + void onReleaseBufferLocked(int slot); + + /** + * onFreeBufferLocked frees up the given buffer slot. If the slot has been + * initialized this will release the reference to the GraphicBuffer in that + * slot and destroy the SkImage in that slot. Otherwise it has no effect. + */ + void onFreeBufferLocked(int slotIndex); + +private: + /** + * ImageSlot contains the information and object references that + * ImageConsumer maintains about a BufferQueue buffer slot. + */ + struct ImageSlot { + ImageSlot() : mEglFence(EGL_NO_SYNC_KHR) {} + + // mImage is the SkImage created from mGraphicBuffer. + sk_sp<SkImage> mImage; + + /** + * mEglFence is the EGL sync object that must signal before the buffer + * associated with this buffer slot may be dequeued. + */ + EGLSyncKHR mEglFence; + + void createIfNeeded(sp<GraphicBuffer> graphicBuffer); + }; + + /** + * ImageConsumer stores the SkImages that have been allocated by the BufferQueue + * for each buffer slot. It is initialized to null pointers, and gets + * filled in with the result of BufferQueue::acquire when the + * client dequeues a buffer from a + * slot that has not yet been used. The buffer allocated to a slot will also + * be replaced if the requested buffer usage or geometry differs from that + * of the buffer allocated to a slot. + */ + ImageSlot mImageSlots[BufferQueueDefs::NUM_BUFFER_SLOTS]; +}; + +}; /* namespace android */ diff --git a/libs/hwui/surfacetexture/SurfaceTexture.cpp b/libs/hwui/surfacetexture/SurfaceTexture.cpp new file mode 100644 index 000000000000..4bff715822e8 --- /dev/null +++ b/libs/hwui/surfacetexture/SurfaceTexture.cpp @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2018 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/compiler.h> +#include <gui/BufferQueue.h> +#include <math/mat4.h> +#include <system/window.h> + +#include <utils/Trace.h> + +#include "Matrix.h" +#include "SurfaceTexture.h" + +namespace android { + +// Macros for including the SurfaceTexture name in log messages +#define SFT_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__) +#define SFT_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__) +#define SFT_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__) +#define SFT_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__) + +static const mat4 mtxIdentity; + +SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, + uint32_t texTarget, bool useFenceSync, bool isControlledByApp) + : ConsumerBase(bq, isControlledByApp) + , mCurrentCrop(Rect::EMPTY_RECT) + , mCurrentTransform(0) + , mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE) + , mCurrentFence(Fence::NO_FENCE) + , mCurrentTimestamp(0) + , mCurrentDataSpace(HAL_DATASPACE_UNKNOWN) + , mCurrentFrameNumber(0) + , mDefaultWidth(1) + , mDefaultHeight(1) + , mFilteringEnabled(true) + , mTexName(tex) + , mUseFenceSync(useFenceSync) + , mTexTarget(texTarget) + , mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT) + , mOpMode(OpMode::attachedToGL) { + SFT_LOGV("SurfaceTexture"); + + memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix)); + + mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS); +} + +SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t texTarget, + bool useFenceSync, bool isControlledByApp) + : ConsumerBase(bq, isControlledByApp) + , mCurrentCrop(Rect::EMPTY_RECT) + , mCurrentTransform(0) + , mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE) + , mCurrentFence(Fence::NO_FENCE) + , mCurrentTimestamp(0) + , mCurrentDataSpace(HAL_DATASPACE_UNKNOWN) + , mCurrentFrameNumber(0) + , mDefaultWidth(1) + , mDefaultHeight(1) + , mFilteringEnabled(true) + , mTexName(0) + , mUseFenceSync(useFenceSync) + , mTexTarget(texTarget) + , mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT) + , mOpMode(OpMode::detached) { + SFT_LOGV("SurfaceTexture"); + + memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix)); + + mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS); +} + +status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h) { + Mutex::Autolock lock(mMutex); + if (mAbandoned) { + SFT_LOGE("setDefaultBufferSize: SurfaceTexture is abandoned!"); + return NO_INIT; + } + mDefaultWidth = w; + mDefaultHeight = h; + return mConsumer->setDefaultBufferSize(w, h); +} + +status_t SurfaceTexture::updateTexImage() { + ATRACE_CALL(); + SFT_LOGV("updateTexImage"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + SFT_LOGE("updateTexImage: SurfaceTexture is abandoned!"); + return NO_INIT; + } + + return mEGLConsumer.updateTexImage(*this); +} + +status_t SurfaceTexture::releaseTexImage() { + // releaseTexImage can be invoked even when not attached to a GL context. + ATRACE_CALL(); + SFT_LOGV("releaseTexImage"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + SFT_LOGE("releaseTexImage: SurfaceTexture is abandoned!"); + return NO_INIT; + } + + return mEGLConsumer.releaseTexImage(*this); +} + +status_t SurfaceTexture::acquireBufferLocked(BufferItem* item, nsecs_t presentWhen, + uint64_t maxFrameNumber) { + status_t err = ConsumerBase::acquireBufferLocked(item, presentWhen, maxFrameNumber); + if (err != NO_ERROR) { + return err; + } + + switch (mOpMode) { + case OpMode::attachedToView: + mImageConsumer.onAcquireBufferLocked(item); + break; + case OpMode::attachedToGL: + mEGLConsumer.onAcquireBufferLocked(item, *this); + break; + case OpMode::detached: + break; + } + + return NO_ERROR; +} + +status_t SurfaceTexture::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer, + EGLDisplay display, EGLSyncKHR eglFence) { + // release the buffer if it hasn't already been discarded by the + // BufferQueue. This can happen, for example, when the producer of this + // buffer has reallocated the original buffer slot after this buffer + // was acquired. + status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer, display, eglFence); + // We could be releasing an EGL buffer, even if not currently attached to a GL context. + mImageConsumer.onReleaseBufferLocked(buf); + mEGLConsumer.onReleaseBufferLocked(buf); + return err; +} + +status_t SurfaceTexture::detachFromContext() { + ATRACE_CALL(); + SFT_LOGV("detachFromContext"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + SFT_LOGE("detachFromContext: abandoned SurfaceTexture"); + return NO_INIT; + } + + if (mOpMode != OpMode::attachedToGL) { + SFT_LOGE("detachFromContext: SurfaceTexture is not attached to a GL context"); + return INVALID_OPERATION; + } + + status_t err = mEGLConsumer.detachFromContext(*this); + if (err == OK) { + mOpMode = OpMode::detached; + } + + return err; +} + +status_t SurfaceTexture::attachToContext(uint32_t tex) { + ATRACE_CALL(); + SFT_LOGV("attachToContext"); + Mutex::Autolock lock(mMutex); + + if (mAbandoned) { + SFT_LOGE("attachToContext: abandoned SurfaceTexture"); + return NO_INIT; + } + + if (mOpMode != OpMode::detached) { + SFT_LOGE( + "attachToContext: SurfaceTexture is already attached to a " + "context"); + return INVALID_OPERATION; + } + + if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + // release possible ImageConsumer cache + mImageConsumer.onFreeBufferLocked(mCurrentTexture); + } + + return mEGLConsumer.attachToContext(tex, *this); +} + +void SurfaceTexture::attachToView() { + ATRACE_CALL(); + Mutex::Autolock _l(mMutex); + if (mAbandoned) { + SFT_LOGE("attachToView: abandoned SurfaceTexture"); + return; + } + if (mOpMode == OpMode::detached) { + mOpMode = OpMode::attachedToView; + + if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + // release possible EGLConsumer texture cache + mEGLConsumer.onFreeBufferLocked(mCurrentTexture); + mEGLConsumer.onAbandonLocked(); + } + } else { + SFT_LOGE("attachToView: already attached"); + } +} + +void SurfaceTexture::detachFromView() { + ATRACE_CALL(); + Mutex::Autolock _l(mMutex); + + if (mAbandoned) { + SFT_LOGE("detachFromView: abandoned SurfaceTexture"); + return; + } + + if (mOpMode == OpMode::attachedToView) { + mOpMode = OpMode::detached; + } else { + SFT_LOGE("detachFromView: not attached to View"); + } +} + +uint32_t SurfaceTexture::getCurrentTextureTarget() const { + return mTexTarget; +} + +void SurfaceTexture::getTransformMatrix(float mtx[16]) { + Mutex::Autolock lock(mMutex); + memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix)); +} + +void SurfaceTexture::setFilteringEnabled(bool enabled) { + Mutex::Autolock lock(mMutex); + if (mAbandoned) { + SFT_LOGE("setFilteringEnabled: SurfaceTexture is abandoned!"); + return; + } + bool needsRecompute = mFilteringEnabled != enabled; + mFilteringEnabled = enabled; + + if (needsRecompute && mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT) { + SFT_LOGD("setFilteringEnabled called with no current item"); + } + + if (needsRecompute && mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) { + computeCurrentTransformMatrixLocked(); + } +} + +void SurfaceTexture::computeCurrentTransformMatrixLocked() { + SFT_LOGV("computeCurrentTransformMatrixLocked"); + sp<GraphicBuffer> buf = (mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT) + ? nullptr + : mSlots[mCurrentTexture].mGraphicBuffer; + if (buf == nullptr) { + SFT_LOGD("computeCurrentTransformMatrixLocked: no current item"); + } + computeTransformMatrix(mCurrentTransformMatrix, buf, mCurrentCrop, mCurrentTransform, + mFilteringEnabled); +} + +void SurfaceTexture::computeTransformMatrix(float outTransform[16], const sp<GraphicBuffer>& buf, + const Rect& cropRect, uint32_t transform, + bool filtering) { + // Transform matrices + static const mat4 mtxFlipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + static const mat4 mtxFlipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1); + static const mat4 mtxRot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1); + + mat4 xform; + if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) { + xform *= mtxFlipH; + } + if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) { + xform *= mtxFlipV; + } + if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) { + xform *= mtxRot90; + } + + if (!cropRect.isEmpty() && buf.get()) { + float tx = 0.0f, ty = 0.0f, sx = 1.0f, sy = 1.0f; + float bufferWidth = buf->getWidth(); + float bufferHeight = buf->getHeight(); + float shrinkAmount = 0.0f; + if (filtering) { + // In order to prevent bilinear sampling beyond the edge of the + // crop rectangle we may need to shrink it by 2 texels in each + // dimension. Normally this would just need to take 1/2 a texel + // off each end, but because the chroma channels of YUV420 images + // are subsampled we may need to shrink the crop region by a whole + // texel on each side. + switch (buf->getPixelFormat()) { + case PIXEL_FORMAT_RGBA_8888: + case PIXEL_FORMAT_RGBX_8888: + case PIXEL_FORMAT_RGBA_FP16: + case PIXEL_FORMAT_RGBA_1010102: + case PIXEL_FORMAT_RGB_888: + case PIXEL_FORMAT_RGB_565: + case PIXEL_FORMAT_BGRA_8888: + // We know there's no subsampling of any channels, so we + // only need to shrink by a half a pixel. + shrinkAmount = 0.5; + break; + + default: + // If we don't recognize the format, we must assume the + // worst case (that we care about), which is YUV420. + shrinkAmount = 1.0; + break; + } + } + + // Only shrink the dimensions that are not the size of the buffer. + if (cropRect.width() < bufferWidth) { + tx = (float(cropRect.left) + shrinkAmount) / bufferWidth; + sx = (float(cropRect.width()) - (2.0f * shrinkAmount)) / bufferWidth; + } + if (cropRect.height() < bufferHeight) { + ty = (float(bufferHeight - cropRect.bottom) + shrinkAmount) / bufferHeight; + sy = (float(cropRect.height()) - (2.0f * shrinkAmount)) / bufferHeight; + } + + mat4 crop(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1); + xform = crop * xform; + } + + // SurfaceFlinger expects the top of its window textures to be at a Y + // coordinate of 0, so SurfaceTexture must behave the same way. We don't + // want to expose this to applications, however, so we must add an + // additional vertical flip to the transform after all the other transforms. + xform = mtxFlipV * xform; + + memcpy(outTransform, xform.asArray(), sizeof(xform)); +} + +Rect SurfaceTexture::scaleDownCrop(const Rect& crop, uint32_t bufferWidth, uint32_t bufferHeight) { + Rect outCrop = crop; + + uint32_t newWidth = static_cast<uint32_t>(crop.width()); + uint32_t newHeight = static_cast<uint32_t>(crop.height()); + + if (newWidth * bufferHeight > newHeight * bufferWidth) { + newWidth = newHeight * bufferWidth / bufferHeight; + ALOGV("too wide: newWidth = %d", newWidth); + } else if (newWidth * bufferHeight < newHeight * bufferWidth) { + newHeight = newWidth * bufferHeight / bufferWidth; + ALOGV("too tall: newHeight = %d", newHeight); + } + + uint32_t currentWidth = static_cast<uint32_t>(crop.width()); + uint32_t currentHeight = static_cast<uint32_t>(crop.height()); + + // The crop is too wide + if (newWidth < currentWidth) { + uint32_t dw = currentWidth - newWidth; + auto halfdw = dw / 2; + outCrop.left += halfdw; + // Not halfdw because it would subtract 1 too few when dw is odd + outCrop.right -= (dw - halfdw); + // The crop is too tall + } else if (newHeight < currentHeight) { + uint32_t dh = currentHeight - newHeight; + auto halfdh = dh / 2; + outCrop.top += halfdh; + // Not halfdh because it would subtract 1 too few when dh is odd + outCrop.bottom -= (dh - halfdh); + } + + ALOGV("getCurrentCrop final crop [%d,%d,%d,%d]", outCrop.left, outCrop.top, outCrop.right, + outCrop.bottom); + + return outCrop; +} + +nsecs_t SurfaceTexture::getTimestamp() { + SFT_LOGV("getTimestamp"); + Mutex::Autolock lock(mMutex); + return mCurrentTimestamp; +} + +android_dataspace SurfaceTexture::getCurrentDataSpace() { + SFT_LOGV("getCurrentDataSpace"); + Mutex::Autolock lock(mMutex); + return mCurrentDataSpace; +} + +uint64_t SurfaceTexture::getFrameNumber() { + SFT_LOGV("getFrameNumber"); + Mutex::Autolock lock(mMutex); + return mCurrentFrameNumber; +} + +Rect SurfaceTexture::getCurrentCrop() const { + Mutex::Autolock lock(mMutex); + return (mCurrentScalingMode == NATIVE_WINDOW_SCALING_MODE_SCALE_CROP) + ? scaleDownCrop(mCurrentCrop, mDefaultWidth, mDefaultHeight) + : mCurrentCrop; +} + +uint32_t SurfaceTexture::getCurrentTransform() const { + Mutex::Autolock lock(mMutex); + return mCurrentTransform; +} + +uint32_t SurfaceTexture::getCurrentScalingMode() const { + Mutex::Autolock lock(mMutex); + return mCurrentScalingMode; +} + +sp<Fence> SurfaceTexture::getCurrentFence() const { + Mutex::Autolock lock(mMutex); + return mCurrentFence; +} + +std::shared_ptr<FenceTime> SurfaceTexture::getCurrentFenceTime() const { + Mutex::Autolock lock(mMutex); + return mCurrentFenceTime; +} + +void SurfaceTexture::freeBufferLocked(int slotIndex) { + SFT_LOGV("freeBufferLocked: slotIndex=%d", slotIndex); + if (slotIndex == mCurrentTexture) { + mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT; + } + // The slotIndex buffer could have EGL or SkImage cache, but there is no way to tell for sure. + // Buffers can be freed after SurfaceTexture has detached from GL context or View. + mImageConsumer.onFreeBufferLocked(slotIndex); + mEGLConsumer.onFreeBufferLocked(slotIndex); + ConsumerBase::freeBufferLocked(slotIndex); +} + +void SurfaceTexture::abandonLocked() { + SFT_LOGV("abandonLocked"); + mEGLConsumer.onAbandonLocked(); + ConsumerBase::abandonLocked(); +} + +status_t SurfaceTexture::setConsumerUsageBits(uint64_t usage) { + return ConsumerBase::setConsumerUsageBits(usage | DEFAULT_USAGE_FLAGS); +} + +void SurfaceTexture::dumpLocked(String8& result, const char* prefix) const { + result.appendFormat( + "%smTexName=%d mCurrentTexture=%d\n" + "%smCurrentCrop=[%d,%d,%d,%d] mCurrentTransform=%#x\n", + prefix, mTexName, mCurrentTexture, prefix, mCurrentCrop.left, mCurrentCrop.top, + mCurrentCrop.right, mCurrentCrop.bottom, mCurrentTransform); + + ConsumerBase::dumpLocked(result, prefix); +} + +sk_sp<SkImage> SurfaceTexture::dequeueImage(SkMatrix& transformMatrix, android_dataspace& dataSpace, + bool* queueEmpty, + uirenderer::RenderState& renderState) { + Mutex::Autolock _l(mMutex); + + if (mAbandoned) { + SFT_LOGE("dequeueImage: SurfaceTexture is abandoned!"); + return nullptr; + } + + if (mOpMode != OpMode::attachedToView) { + SFT_LOGE("dequeueImage: SurfaceTexture is not attached to a View"); + return nullptr; + } + + auto image = mImageConsumer.dequeueImage(queueEmpty, *this, renderState); + if (image.get()) { + uirenderer::mat4(mCurrentTransformMatrix).copyTo(transformMatrix); + dataSpace = mCurrentDataSpace; + } + return image; +} + +}; // namespace android diff --git a/libs/hwui/surfacetexture/SurfaceTexture.h b/libs/hwui/surfacetexture/SurfaceTexture.h new file mode 100644 index 000000000000..db392a9f8476 --- /dev/null +++ b/libs/hwui/surfacetexture/SurfaceTexture.h @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2018 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 <gui/BufferQueueDefs.h> +#include <gui/ConsumerBase.h> + +#include <ui/FenceTime.h> +#include <ui/GraphicBuffer.h> + +#include <utils/Mutex.h> +#include <utils/String8.h> + +#include "EGLConsumer.h" +#include "ImageConsumer.h" + +namespace android { + +namespace uirenderer { +class RenderState; +} + +/* + * SurfaceTexture consumes buffers of graphics data from a BufferQueue, + * and makes them available to HWUI render thread as a SkImage and to + * an application GL render thread as an OpenGL texture. + * + * When attached to an application GL render thread, a typical usage + * pattern is to set up the SurfaceTexture with the + * desired options, and call updateTexImage() when a new frame is desired. + * If a new frame is available, the texture will be updated. If not, + * the previous contents are retained. + * + * When attached to a HWUI render thread, the TextureView implementation + * calls dequeueImage, which either pulls a new SkImage or returns the + * last cached SkImage if BufferQueue is empty. + * When attached to HWUI render thread, SurfaceTexture is compatible to + * both Vulkan and GL drawing pipelines. + */ +class ANDROID_API SurfaceTexture : public ConsumerBase { +public: + enum { TEXTURE_EXTERNAL = 0x8D65 }; // GL_TEXTURE_EXTERNAL_OES + typedef ConsumerBase::FrameAvailableListener FrameAvailableListener; + + /** + * SurfaceTexture constructs a new SurfaceTexture object. If the constructor with + * the tex parameter is used, tex indicates the name of the OpenGL ES + * texture to which images are to be streamed. texTarget specifies the + * OpenGL ES texture target to which the texture will be bound in + * updateTexImage. useFenceSync specifies whether fences should be used to + * synchronize access to buffers if that behavior is enabled at + * compile-time. + * + * A SurfaceTexture may be detached from one OpenGL ES context and then + * attached to a different context using the detachFromContext and + * attachToContext methods, respectively. The intention of these methods is + * purely to allow a SurfaceTexture to be transferred from one consumer + * context to another. If such a transfer is not needed there is no + * requirement that either of these methods be called. + * + * If the constructor with the tex parameter is used, the SurfaceTexture is + * created in a state where it is considered attached to an OpenGL ES + * context for the purposes of the attachToContext and detachFromContext + * methods. However, despite being considered "attached" to a context, the + * specific OpenGL ES context doesn't get latched until the first call to + * updateTexImage. After that point, all calls to updateTexImage must be + * made with the same OpenGL ES context current. + * + * If the constructor without the tex parameter is used, the SurfaceTexture is + * created in a detached state, and attachToContext must be called before + * calls to updateTexImage. + */ + SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, uint32_t texureTarget, + bool useFenceSync, bool isControlledByApp); + + SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t texureTarget, bool useFenceSync, + bool isControlledByApp); + + /** + * updateTexImage acquires the most recently queued buffer, and sets the + * image contents of the target texture to it. + * + * This call may only be made while the OpenGL ES context to which the + * target texture belongs is bound to the calling thread. + * + * This calls doGLFenceWait to ensure proper synchronization. + */ + status_t updateTexImage(); + + /** + * releaseTexImage releases the texture acquired in updateTexImage(). + * This is intended to be used in single buffer mode. + * + * This call may only be made while the OpenGL ES context to which the + * target texture belongs is bound to the calling thread. + */ + status_t releaseTexImage(); + + /** + * getTransformMatrix retrieves the 4x4 texture coordinate transform matrix + * associated with the texture image set by the most recent call to + * updateTexImage. + * + * This transform matrix maps 2D homogeneous texture coordinates of the form + * (s, t, 0, 1) with s and t in the inclusive range [0, 1] to the texture + * coordinate that should be used to sample that location from the texture. + * Sampling the texture outside of the range of this transform is undefined. + * + * This transform is necessary to compensate for transforms that the stream + * content producer may implicitly apply to the content. By forcing users of + * a SurfaceTexture to apply this transform we avoid performing an extra + * copy of the data that would be needed to hide the transform from the + * user. + * + * The matrix is stored in column-major order so that it may be passed + * directly to OpenGL ES via the glLoadMatrixf or glUniformMatrix4fv + * functions. + */ + void getTransformMatrix(float mtx[16]); + + /** + * Computes the transform matrix documented by getTransformMatrix + * from the BufferItem sub parts. + */ + static void computeTransformMatrix(float outTransform[16], const sp<GraphicBuffer>& buf, + const Rect& cropRect, uint32_t transform, bool filtering); + + /** + * Scale the crop down horizontally or vertically such that it has the + * same aspect ratio as the buffer does. + */ + static Rect scaleDownCrop(const Rect& crop, uint32_t bufferWidth, uint32_t bufferHeight); + + /** + * getTimestamp retrieves the timestamp associated with the texture image + * set by the most recent call to updateTexImage. + * + * The timestamp is in nanoseconds, and is monotonically increasing. Its + * other semantics (zero point, etc) are source-dependent and should be + * documented by the source. + */ + int64_t getTimestamp(); + + /** + * getDataSpace retrieves the DataSpace associated with the texture image + * set by the most recent call to updateTexImage. + */ + android_dataspace getCurrentDataSpace(); + + /** + * getFrameNumber retrieves the frame number associated with the texture + * image set by the most recent call to updateTexImage. + * + * The frame number is an incrementing counter set to 0 at the creation of + * the BufferQueue associated with this consumer. + */ + uint64_t getFrameNumber(); + + /** + * setDefaultBufferSize is used to set the size of buffers returned by + * requestBuffers when a with and height of zero is requested. + * A call to setDefaultBufferSize() may trigger requestBuffers() to + * be called from the client. + * The width and height parameters must be no greater than the minimum of + * GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv). + * An error due to invalid dimensions might not be reported until + * updateTexImage() is called. + */ + status_t setDefaultBufferSize(uint32_t width, uint32_t height); + + /** + * setFilteringEnabled sets whether the transform matrix should be computed + * for use with bilinear filtering. + */ + void setFilteringEnabled(bool enabled); + + /** + * getCurrentTextureTarget returns the texture target of the current + * texture as returned by updateTexImage(). + */ + uint32_t getCurrentTextureTarget() const; + + /** + * getCurrentCrop returns the cropping rectangle of the current buffer. + */ + Rect getCurrentCrop() const; + + /** + * getCurrentTransform returns the transform of the current buffer. + */ + uint32_t getCurrentTransform() const; + + /** + * getCurrentScalingMode returns the scaling mode of the current buffer. + */ + uint32_t getCurrentScalingMode() const; + + /** + * getCurrentFence returns the fence indicating when the current buffer is + * ready to be read from. + */ + sp<Fence> getCurrentFence() const; + + /** + * getCurrentFence returns the FenceTime indicating when the current + * buffer is ready to be read from. + */ + std::shared_ptr<FenceTime> getCurrentFenceTime() const; + + /** + * setConsumerUsageBits overrides the ConsumerBase method to OR + * DEFAULT_USAGE_FLAGS to usage. + */ + status_t setConsumerUsageBits(uint64_t usage); + + /** + * detachFromContext detaches the SurfaceTexture from the calling thread's + * current OpenGL ES context. This context must be the same as the context + * that was current for previous calls to updateTexImage. + * + * Detaching a SurfaceTexture from an OpenGL ES context will result in the + * deletion of the OpenGL ES texture object into which the images were being + * streamed. After a SurfaceTexture has been detached from the OpenGL ES + * context calls to updateTexImage will fail returning INVALID_OPERATION + * until the SurfaceTexture is attached to a new OpenGL ES context using the + * attachToContext method. + */ + status_t detachFromContext(); + + /** + * attachToContext attaches a SurfaceTexture that is currently in the + * 'detached' state to the current OpenGL ES context. A SurfaceTexture is + * in the 'detached' state iff detachFromContext has successfully been + * called and no calls to attachToContext have succeeded since the last + * detachFromContext call. Calls to attachToContext made on a + * SurfaceTexture that is not in the 'detached' state will result in an + * INVALID_OPERATION error. + * + * The tex argument specifies the OpenGL ES texture object name in the + * new context into which the image contents will be streamed. A successful + * call to attachToContext will result in this texture object being bound to + * the texture target and populated with the image contents that were + * current at the time of the last call to detachFromContext. + */ + status_t attachToContext(uint32_t tex); + + sk_sp<SkImage> dequeueImage(SkMatrix& transformMatrix, android_dataspace& dataSpace, + bool* queueEmpty, uirenderer::RenderState& renderState); + + /** + * attachToView attaches a SurfaceTexture that is currently in the + * 'detached' state to HWUI View system. + */ + void attachToView(); + + /** + * detachFromView detaches a SurfaceTexture from HWUI View system. + */ + void detachFromView(); + +protected: + /** + * abandonLocked overrides the ConsumerBase method to clear + * mCurrentTextureImage in addition to the ConsumerBase behavior. + */ + virtual void abandonLocked(); + + /** + * dumpLocked overrides the ConsumerBase method to dump SurfaceTexture- + * specific info in addition to the ConsumerBase behavior. + */ + virtual void dumpLocked(String8& result, const char* prefix) const override; + + /** + * acquireBufferLocked overrides the ConsumerBase method to update the + * mEglSlots array in addition to the ConsumerBase behavior. + */ + virtual status_t acquireBufferLocked(BufferItem* item, nsecs_t presentWhen, + uint64_t maxFrameNumber = 0) override; + + /** + * releaseBufferLocked overrides the ConsumerBase method to update the + * mEglSlots array in addition to the ConsumerBase. + */ + virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer, + EGLDisplay display, EGLSyncKHR eglFence) override; + + /** + * freeBufferLocked frees up the given buffer slot. If the slot has been + * initialized this will release the reference to the GraphicBuffer in that + * slot and destroy the EGLImage in that slot. Otherwise it has no effect. + * + * This method must be called with mMutex locked. + */ + virtual void freeBufferLocked(int slotIndex); + + /** + * computeCurrentTransformMatrixLocked computes the transform matrix for the + * current texture. It uses mCurrentTransform and the current GraphicBuffer + * to compute this matrix and stores it in mCurrentTransformMatrix. + * mCurrentTextureImage must not be NULL. + */ + void computeCurrentTransformMatrixLocked(); + + /** + * The default consumer usage flags that SurfaceTexture always sets on its + * BufferQueue instance; these will be OR:d with any additional flags passed + * from the SurfaceTexture user. In particular, SurfaceTexture will always + * consume buffers as hardware textures. + */ + static const uint64_t DEFAULT_USAGE_FLAGS = GraphicBuffer::USAGE_HW_TEXTURE; + + /** + * mCurrentCrop is the crop rectangle that applies to the current texture. + * It gets set each time updateTexImage is called. + */ + Rect mCurrentCrop; + + /** + * mCurrentTransform is the transform identifier for the current texture. It + * gets set each time updateTexImage is called. + */ + uint32_t mCurrentTransform; + + /** + * mCurrentScalingMode is the scaling mode for the current texture. It gets + * set each time updateTexImage is called. + */ + uint32_t mCurrentScalingMode; + + /** + * mCurrentFence is the fence received from BufferQueue in updateTexImage. + */ + sp<Fence> mCurrentFence; + + /** + * The FenceTime wrapper around mCurrentFence. + */ + std::shared_ptr<FenceTime> mCurrentFenceTime{FenceTime::NO_FENCE}; + + /** + * mCurrentTransformMatrix is the transform matrix for the current texture. + * It gets computed by computeTransformMatrix each time updateTexImage is + * called. + */ + float mCurrentTransformMatrix[16]; + + /** + * mCurrentTimestamp is the timestamp for the current texture. It + * gets set each time updateTexImage is called. + */ + int64_t mCurrentTimestamp; + + /** + * mCurrentDataSpace is the dataspace for the current texture. It + * gets set each time updateTexImage is called. + */ + android_dataspace mCurrentDataSpace; + + /** + * mCurrentFrameNumber is the frame counter for the current texture. + * It gets set each time updateTexImage is called. + */ + uint64_t mCurrentFrameNumber; + + uint32_t mDefaultWidth, mDefaultHeight; + + /** + * mFilteringEnabled indicates whether the transform matrix is computed for + * use with bilinear filtering. It defaults to true and is changed by + * setFilteringEnabled(). + */ + bool mFilteringEnabled; + + /** + * mTexName is the name of the OpenGL texture to which streamed images will + * be bound when updateTexImage is called. It is set at construction time + * and can be changed with a call to attachToContext. + */ + uint32_t mTexName; + + /** + * mUseFenceSync indicates whether creation of the EGL_KHR_fence_sync + * extension should be used to prevent buffers from being dequeued before + * it's safe for them to be written. It gets set at construction time and + * never changes. + */ + const bool mUseFenceSync; + + /** + * mTexTarget is the GL texture target with which the GL texture object is + * associated. It is set in the constructor and never changed. It is + * almost always GL_TEXTURE_EXTERNAL_OES except for one use case in Android + * Browser. In that case it is set to GL_TEXTURE_2D to allow + * glCopyTexSubImage to read from the texture. This is a hack to work + * around a GL driver limitation on the number of FBO attachments, which the + * browser's tile cache exceeds. + */ + const uint32_t mTexTarget; + + /** + * mCurrentTexture is the buffer slot index of the buffer that is currently + * bound to the OpenGL texture. It is initialized to INVALID_BUFFER_SLOT, + * indicating that no buffer slot is currently bound to the texture. Note, + * however, that a value of INVALID_BUFFER_SLOT does not necessarily mean + * that no buffer is bound to the texture. A call to setBufferCount will + * reset mCurrentTexture to INVALID_BUFFER_SLOT. + */ + int mCurrentTexture; + + enum class OpMode { detached, attachedToView, attachedToGL }; + /** + * mOpMode indicates whether the SurfaceTexture is currently attached to + * an OpenGL ES context or the HWUI view system. For legacy reasons, this is initialized to, + * "attachedToGL" indicating that the SurfaceTexture is considered to be attached to + * whatever GL context is current at the time of the first updateTexImage call. + * It is set to "detached" by detachFromContext, and then set to "attachedToGL" again by + * attachToContext. + * attachToView/detachFromView are used to attach/detach from HWUI view system. + */ + OpMode mOpMode; + + /** + * mEGLConsumer has SurfaceTexture logic used when attached to GL context. + */ + EGLConsumer mEGLConsumer; + + /** + * mImageConsumer has SurfaceTexture logic used when attached to HWUI view system. + */ + ImageConsumer mImageConsumer; + + friend class ImageConsumer; + friend class EGLConsumer; +}; + +// ---------------------------------------------------------------------------- +}; // namespace android diff --git a/libs/hwui/tests/common/LeakChecker.cpp b/libs/hwui/tests/common/LeakChecker.cpp index 5b361548eeda..d2d37dcb34f2 100644 --- a/libs/hwui/tests/common/LeakChecker.cpp +++ b/libs/hwui/tests/common/LeakChecker.cpp @@ -16,7 +16,6 @@ #include "LeakChecker.h" -#include "Caches.h" #include "TestUtils.h" #include <memunreachable/memunreachable.h> @@ -71,9 +70,6 @@ void LeakChecker::checkForLeaks() { // thread-local caches so some leaks will not be properly tagged as leaks UnreachableMemoryInfo rtMemInfo; TestUtils::runOnRenderThread([&rtMemInfo](renderthread::RenderThread& thread) { - if (Caches::hasInstance()) { - Caches::getInstance().tasks.stop(); - } // Check for leaks if (!GetUnreachableMemory(rtMemInfo)) { cerr << "Failed to get unreachable memory!" << endl; diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp index 02ac97e0ed5c..66b9b85bdbe7 100644 --- a/libs/hwui/tests/common/TestUtils.cpp +++ b/libs/hwui/tests/common/TestUtils.cpp @@ -19,16 +19,15 @@ #include "DeferredLayerUpdater.h" #include "hwui/Paint.h" -#include <SkClipStack.h> #include <minikin/Layout.h> #include <pipeline/skia/SkiaOpenGLPipeline.h> #include <pipeline/skia/SkiaVulkanPipeline.h> #include <renderthread/EglManager.h> -#include <renderthread/OpenGLPipeline.h> #include <renderthread/VulkanManager.h> #include <utils/Unicode.h> -#include <SkGlyphCache.h> +#include "SkColorData.h" +#include "SkUnPreMultiply.h" namespace android { namespace uirenderer { @@ -53,9 +52,7 @@ SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater( renderthread::RenderThread& renderThread) { android::uirenderer::renderthread::IRenderPipeline* pipeline; - if (Properties::getRenderPipelineType() == RenderPipelineType::OpenGL) { - pipeline = new renderthread::OpenGLPipeline(renderThread); - } else if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { + if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaGL) { pipeline = new skiapipeline::SkiaOpenGLPipeline(renderThread); } else { pipeline = new skiapipeline::SkiaVulkanPipeline(renderThread); @@ -70,61 +67,28 @@ sp<DeferredLayerUpdater> TestUtils::createTextureLayerUpdater( renderthread::RenderThread& renderThread, uint32_t width, uint32_t height, const SkMatrix& transform) { sp<DeferredLayerUpdater> layerUpdater = createTextureLayerUpdater(renderThread); - layerUpdater->backingLayer()->getTransform().load(transform); + layerUpdater->backingLayer()->getTransform() = transform; layerUpdater->setSize(width, height); layerUpdater->setTransform(&transform); // updateLayer so it's ready to draw - layerUpdater->updateLayer(true, Matrix4::identity().data, HAL_DATASPACE_UNKNOWN); - if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) { - static_cast<GlLayer*>(layerUpdater->backingLayer()) - ->setRenderTarget(GL_TEXTURE_EXTERNAL_OES); - } + SkMatrix identity; + identity.setIdentity(); + layerUpdater->updateLayer(true, identity, HAL_DATASPACE_UNKNOWN, nullptr); return layerUpdater; } -void TestUtils::layoutTextUnscaled(const SkPaint& paint, const char* text, - std::vector<glyph_t>* outGlyphs, - std::vector<float>* outPositions, float* outTotalAdvance, - Rect* outBounds) { - Rect bounds; - float totalAdvance = 0; - SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry); - SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &SkMatrix::I()); - while (*text != '\0') { - size_t nextIndex = 0; - int32_t unichar = utf32_from_utf8_at(text, 4, 0, &nextIndex); - text += nextIndex; - - glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar); - autoCache.getCache()->unicharToGlyph(unichar); - - // push glyph and its relative position - outGlyphs->push_back(glyph); - outPositions->push_back(totalAdvance); - outPositions->push_back(0); - - // compute bounds - SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar); - Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight); - glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop); - bounds.unionWith(glyphBounds); - - // advance next character - SkScalar skWidth; - paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL); - totalAdvance += skWidth; - } - *outBounds = bounds; - *outTotalAdvance = totalAdvance; -} - void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, float y) { auto utf16 = asciiToUtf16(text); + uint32_t length = strlen(text); SkPaint glyphPaint(paint); glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR, + canvas->drawText( + utf16.get(), length, // text buffer + 0, length, // draw range + 0, length, // context range + x, y, minikin::Bidi::LTR, glyphPaint, nullptr, nullptr /* measured text */); } @@ -143,7 +107,7 @@ void TestUtils::TestTask::run() { if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { renderThread.vulkanManager().initialize(); } else { - renderThread.eglManager().initialize(); + renderThread.requireGlContext(); } rtCallback(renderThread); @@ -151,8 +115,7 @@ void TestUtils::TestTask::run() { if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) { renderThread.vulkanManager().destroy(); } else { - renderThread.renderState().flush(Caches::FlushMode::Full); - renderThread.eglManager().destroy(); + renderThread.destroyGlContext(); } } diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h index 2752ae9a8036..c5db861d4f48 100644 --- a/libs/hwui/tests/common/TestUtils.h +++ b/libs/hwui/tests/common/TestUtils.h @@ -16,21 +16,17 @@ #pragma once -#include <DeviceInfo.h> #include <DisplayList.h> #include <Matrix.h> #include <Properties.h> #include <Rect.h> #include <RenderNode.h> -#include <Snapshot.h> #include <hwui/Bitmap.h> #include <pipeline/skia/SkiaRecordingCanvas.h> +#include <private/hwui/DrawGlInfo.h> #include <renderstate/RenderState.h> #include <renderthread/RenderThread.h> -#include <RecordedOp.h> -#include <RecordingCanvas.h> - #include <memory> namespace android { @@ -60,18 +56,6 @@ namespace uirenderer { Properties::overrideRenderPipelineType(oldType); \ }; -/** - * Like gtests' TEST, but only runs with the OpenGL RenderPipelineType - */ -#define OPENGL_PIPELINE_TEST(test_case_name, test_name) \ - class test_case_name##_##test_name##_HwuiTest { \ - public: \ - static void doTheThing(); \ - }; \ - INNER_PIPELINE_TEST(test_case_name, test_name, OpenGL, \ - test_case_name##_##test_name##_HwuiTest::doTheThing()) \ - void test_case_name##_##test_name##_HwuiTest::doTheThing() - #define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \ INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, \ TestUtils::runOnRenderThread( \ @@ -86,7 +70,6 @@ namespace uirenderer { public: \ static void doTheThing(renderthread::RenderThread& renderThread); \ }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, OpenGL); \ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \ /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \ /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \ @@ -94,18 +77,6 @@ namespace uirenderer { renderthread::RenderThread& renderThread) /** - * Like RENDERTHREAD_TEST, but only runs with the OpenGL RenderPipelineType - */ -#define RENDERTHREAD_OPENGL_PIPELINE_TEST(test_case_name, test_name) \ - class test_case_name##_##test_name##_RenderThreadTest { \ - public: \ - static void doTheThing(renderthread::RenderThread& renderThread); \ - }; \ - INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, OpenGL); \ - void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \ - renderthread::RenderThread& renderThread) - -/** * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes */ #define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \ @@ -169,14 +140,6 @@ public: return true; } - static std::unique_ptr<Snapshot> makeSnapshot(const Matrix4& transform, const Rect& clip) { - std::unique_ptr<Snapshot> snapshot(new Snapshot()); - // store clip first, so it isn't transformed - snapshot->setClip(clip.left, clip.top, clip.right, clip.bottom); - *(snapshot->transform) = transform; - return snapshot; - } - static sk_sp<Bitmap> createBitmap(int width, int height, SkColorType colorType = kN32_SkColorType) { SkImageInfo info = SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType); @@ -207,12 +170,6 @@ public: static sp<RenderNode> createNode( int left, int top, int right, int bottom, std::function<void(RenderProperties& props, Canvas& 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); @@ -230,12 +187,6 @@ public: static sp<RenderNode> createNode( int left, int top, int right, int bottom, std::function<void(RenderProperties& props, RecordingCanvasType& 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); @@ -261,11 +212,6 @@ public: std::function<void(RenderProperties& props, skiapipeline::SkiaRecordingCanvas& canvas)> setup, const char* name = nullptr, skiapipeline::SkiaDisplayList* displayList = nullptr) { -#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(); if (name) { node->setName(name); @@ -325,11 +271,6 @@ public: static SkColor interpolateColor(float fraction, SkColor start, SkColor end); - static void layoutTextUnscaled(const SkPaint& paint, const char* text, - std::vector<glyph_t>* outGlyphs, - std::vector<float>* outPositions, float* outTotalAdvance, - Rect* outBounds); - static void drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint, float x, float y); @@ -365,16 +306,10 @@ private: } auto displayList = node->getDisplayList(); if (displayList) { - if (displayList->isSkiaDL()) { - for (auto&& childDr : static_cast<skiapipeline::SkiaDisplayList*>( - const_cast<DisplayList*>(displayList)) - ->mChildNodes) { - syncHierarchyPropertiesAndDisplayListImpl(childDr.getRenderNode()); - } - } else { - for (auto&& childOp : displayList->getChildren()) { - syncHierarchyPropertiesAndDisplayListImpl(childOp->renderNode); - } + for (auto&& childDr : static_cast<skiapipeline::SkiaDisplayList*>( + const_cast<DisplayList*>(displayList)) + ->mChildNodes) { + syncHierarchyPropertiesAndDisplayListImpl(childDr.getRenderNode()); } } } diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp index fd8c252ff318..9a1ee54bff49 100644 --- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp +++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp @@ -46,7 +46,6 @@ class ListViewAnimation : public TestListViewSceneBase { 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); const SkPoint pos[] = {{SkIntToScalar(size / 2), diff --git a/libs/hwui/tests/common/scenes/TestSceneBase.h b/libs/hwui/tests/common/scenes/TestSceneBase.h index 792312a6a7a4..6f76a502ae3e 100644 --- a/libs/hwui/tests/common/scenes/TestSceneBase.h +++ b/libs/hwui/tests/common/scenes/TestSceneBase.h @@ -16,7 +16,7 @@ #pragma once -#include "RecordingCanvas.h" +#include "hwui/Canvas.h" #include "RenderNode.h" #include "tests/common/TestContext.h" #include "tests/common/TestScene.h" diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp index 5f5a92e55bf2..5fa008b5b4df 100644 --- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp +++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp @@ -132,10 +132,10 @@ void run(const TestScene::Info& info, const TestScene::Options& opts, ContextFactory factory; std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode.get(), &factory)); proxy->loadSystemProperties(); - proxy->initialize(surface); + proxy->setSurface(surface); float lightX = width / 2.0; - proxy->setup(dp(800.0f), 255 * 0.075, 255 * 0.15); - proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)}); + proxy->setLightAlpha(255 * 0.075, 255 * 0.15); + proxy->setLightGeometry((Vector3){lightX, dp(-200.0f), dp(800.0f)}, dp(800.0f)); // Do a few cold runs then reset the stats so that the caches are all hot int warmupFrameCount = 5; diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index 6caaf8d7616e..524dfb0fe4ef 100644 --- a/libs/hwui/tests/macrobench/main.cpp +++ b/libs/hwui/tests/macrobench/main.cpp @@ -19,7 +19,6 @@ #include "Properties.h" #include "hwui/Typeface.h" -#include "protos/hwui.pb.h" #include <benchmark/benchmark.h> #include <getopt.h> @@ -67,7 +66,7 @@ OPTIONS: --onscreen Render tests on device screen. By default tests are offscreen rendered --benchmark_format Set output format. Possible values are tabular, json, csv - --renderer=TYPE Sets the render pipeline to use. May be opengl, skiagl, or skiavk + --renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk )"); } @@ -145,9 +144,7 @@ static bool setBenchmarkFormat(const char* format) { } static bool setRenderer(const char* renderer) { - if (!strcmp(renderer, "opengl")) { - Properties::overrideRenderPipelineType(RenderPipelineType::OpenGL); - } else if (!strcmp(renderer, "skiagl")) { + if (!strcmp(renderer, "skiagl")) { Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL); } else if (!strcmp(renderer, "skiavk")) { Properties::overrideRenderPipelineType(RenderPipelineType::SkiaVulkan); diff --git a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp index 0aaf7731c927..70423a70157b 100644 --- a/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp +++ b/libs/hwui/tests/microbench/DisplayListCanvasBench.cpp @@ -17,7 +17,8 @@ #include <benchmark/benchmark.h> #include "DisplayList.h" -#include "RecordingCanvas.h" +#include "hwui/Canvas.h" +#include "pipeline/skia/SkiaDisplayList.h" #include "tests/common/TestUtils.h" using namespace android; @@ -25,7 +26,7 @@ using namespace android::uirenderer; void BM_DisplayList_alloc(benchmark::State& benchState) { while (benchState.KeepRunning()) { - auto displayList = new DisplayList(); + auto displayList = new skiapipeline::SkiaDisplayList(); benchmark::DoNotOptimize(displayList); delete displayList; } @@ -34,7 +35,7 @@ BENCHMARK(BM_DisplayList_alloc); void BM_DisplayList_alloc_theoretical(benchmark::State& benchState) { while (benchState.KeepRunning()) { - auto displayList = new char[sizeof(DisplayList)]; + auto displayList = new char[sizeof(skiapipeline::SkiaDisplayList)]; benchmark::DoNotOptimize(displayList); delete[] displayList; } @@ -114,52 +115,6 @@ void BM_DisplayListCanvas_record_simpleBitmapView(benchmark::State& benchState) } BENCHMARK(BM_DisplayListCanvas_record_simpleBitmapView); -class NullClient : public CanvasStateClient { - void onViewportInitialized() override {} - void onSnapshotRestored(const Snapshot& removed, const Snapshot& restored) {} - GLuint getTargetFbo() const override { return 0; } -}; - -void BM_CanvasState_saverestore(benchmark::State& benchState) { - NullClient client; - CanvasState state(client); - state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); - - while (benchState.KeepRunning()) { - state.save(SaveFlags::MatrixClip); - state.save(SaveFlags::MatrixClip); - benchmark::DoNotOptimize(&state); - state.restore(); - state.restore(); - } -} -BENCHMARK(BM_CanvasState_saverestore); - -void BM_CanvasState_init(benchmark::State& benchState) { - NullClient client; - CanvasState state(client); - state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); - - while (benchState.KeepRunning()) { - state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); - benchmark::DoNotOptimize(&state); - } -} -BENCHMARK(BM_CanvasState_init); - -void BM_CanvasState_translate(benchmark::State& benchState) { - NullClient client; - CanvasState state(client); - state.initializeSaveStack(100, 100, 0, 0, 100, 100, Vector3()); - - while (benchState.KeepRunning()) { - state.translate(5, 5, 0); - benchmark::DoNotOptimize(&state); - state.translate(-5, -5, 0); - } -} -BENCHMARK(BM_CanvasState_translate); - void BM_DisplayListCanvas_basicViewGroupDraw(benchmark::State& benchState) { sp<RenderNode> child = TestUtils::createNode(50, 50, 100, 100, [](auto& props, auto& canvas) { canvas.drawColor(0xFFFFFFFF, SkBlendMode::kSrcOver); diff --git a/libs/hwui/tests/microbench/FontBench.cpp b/libs/hwui/tests/microbench/FontBench.cpp deleted file mode 100644 index 4e9b540a5c3d..000000000000 --- a/libs/hwui/tests/microbench/FontBench.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <benchmark/benchmark.h> - -#include "GammaFontRenderer.h" -#include "tests/common/TestUtils.h" - -#include <SkPaint.h> - -using namespace android; -using namespace android::uirenderer; - -void BM_FontRenderer_precache_cachehits(benchmark::State& state) { - TestUtils::runOnRenderThread([&state](renderthread::RenderThread& thread) { - SkPaint paint; - paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - GammaFontRenderer gammaFontRenderer; - FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); - fontRenderer.setFont(&paint, SkMatrix::I()); - - std::vector<glyph_t> glyphs; - std::vector<float> positions; - float totalAdvance; - uirenderer::Rect bounds; - TestUtils::layoutTextUnscaled(paint, "This is a test", &glyphs, &positions, &totalAdvance, - &bounds); - - fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I()); - - while (state.KeepRunning()) { - fontRenderer.precache(&paint, glyphs.data(), glyphs.size(), SkMatrix::I()); - } - }); -} -BENCHMARK(BM_FontRenderer_precache_cachehits); diff --git a/libs/hwui/tests/microbench/FrameBuilderBench.cpp b/libs/hwui/tests/microbench/FrameBuilderBench.cpp deleted file mode 100644 index b6217665d743..000000000000 --- a/libs/hwui/tests/microbench/FrameBuilderBench.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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 "BakedOpDispatcher.h" -#include "BakedOpRenderer.h" -#include "BakedOpState.h" -#include "FrameBuilder.h" -#include "LayerUpdateQueue.h" -#include "RecordedOp.h" -#include "RecordingCanvas.h" -#include "Vector.h" -#include "tests/common/TestContext.h" -#include "tests/common/TestScene.h" -#include "tests/common/TestUtils.h" - -#include <vector> - -using namespace android; -using namespace android::uirenderer; -using namespace android::uirenderer::renderthread; -using namespace android::uirenderer::test; - -const FrameBuilder::LightGeometry sLightGeometry = {{100, 100, 100}, 50}; -const BakedOpRenderer::LightInfo sLightInfo = {128, 128}; - -static sp<RenderNode> createTestNode() { - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - sk_sp<Bitmap> bitmap(TestUtils::createBitmap(10, 10)); - SkPaint paint; - - // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects. - // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group. - canvas.save(SaveFlags::MatrixClip); - for (int i = 0; i < 30; i++) { - canvas.translate(0, 10); - canvas.drawRect(0, 0, 10, 10, paint); - canvas.drawBitmap(*bitmap, 5, 0, nullptr); - } - canvas.restore(); - }); - TestUtils::syncHierarchyPropertiesAndDisplayList(node); - return node; -} - -void BM_FrameBuilder_defer(benchmark::State& state) { - TestUtils::runOnRenderThread([&state](RenderThread& thread) { - auto node = createTestNode(); - while (state.KeepRunning()) { - FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*node); - benchmark::DoNotOptimize(&frameBuilder); - } - }); -} -BENCHMARK(BM_FrameBuilder_defer); - -void BM_FrameBuilder_deferAndRender(benchmark::State& state) { - TestUtils::runOnRenderThread([&state](RenderThread& thread) { - auto node = createTestNode(); - - RenderState& renderState = thread.renderState(); - Caches& caches = Caches::getInstance(); - - while (state.KeepRunning()) { - FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, sLightGeometry, caches); - frameBuilder.deferRenderNode(*node); - - BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo); - frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); - benchmark::DoNotOptimize(&renderer); - } - }); -} -BENCHMARK(BM_FrameBuilder_deferAndRender); - -static sp<RenderNode> getSyncedSceneNode(const char* sceneName) { - gDisplay = getBuiltInDisplay(); // switch to real display if present - - TestContext testContext; - TestScene::Options opts; - std::unique_ptr<TestScene> scene(TestScene::testMap()[sceneName].createScene(opts)); - - sp<RenderNode> rootNode = TestUtils::createNode<RecordingCanvas>( - 0, 0, gDisplay.w, gDisplay.h, - [&scene](RenderProperties& props, RecordingCanvas& canvas) { - scene->createContent(gDisplay.w, gDisplay.h, canvas); - }); - - TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode); - return rootNode; -} - -static auto SCENES = { - "listview", -}; - -void BM_FrameBuilder_defer_scene(benchmark::State& state) { - TestUtils::runOnRenderThread([&state](RenderThread& thread) { - const char* sceneName = *(SCENES.begin() + state.range(0)); - state.SetLabel(sceneName); - auto node = getSyncedSceneNode(sceneName); - while (state.KeepRunning()) { - FrameBuilder frameBuilder(SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, - gDisplay.h, sLightGeometry, Caches::getInstance()); - frameBuilder.deferRenderNode(*node); - benchmark::DoNotOptimize(&frameBuilder); - } - }); -} -BENCHMARK(BM_FrameBuilder_defer_scene)->DenseRange(0, SCENES.size() - 1); - -void BM_FrameBuilder_deferAndRender_scene(benchmark::State& state) { - TestUtils::runOnRenderThread([&state](RenderThread& thread) { - const char* sceneName = *(SCENES.begin() + state.range(0)); - state.SetLabel(sceneName); - auto node = getSyncedSceneNode(sceneName); - - RenderState& renderState = thread.renderState(); - Caches& caches = Caches::getInstance(); - - while (state.KeepRunning()) { - FrameBuilder frameBuilder(SkRect::MakeWH(gDisplay.w, gDisplay.h), gDisplay.w, - gDisplay.h, sLightGeometry, Caches::getInstance()); - frameBuilder.deferRenderNode(*node); - - BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo); - frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); - benchmark::DoNotOptimize(&renderer); - } - }); -} -BENCHMARK(BM_FrameBuilder_deferAndRender_scene)->DenseRange(0, SCENES.size() - 1); diff --git a/libs/hwui/tests/microbench/ShadowBench.cpp b/libs/hwui/tests/microbench/ShadowBench.cpp deleted file mode 100644 index 12da7837e2e9..000000000000 --- a/libs/hwui/tests/microbench/ShadowBench.cpp +++ /dev/null @@ -1,101 +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 <benchmark/benchmark.h> - -#include "Matrix.h" -#include "Rect.h" -#include "TessellationCache.h" -#include "Vector.h" -#include "VertexBuffer.h" - -#include <SkPath.h> - -#include <memory> - -using namespace android; -using namespace android::uirenderer; - -struct ShadowTestData { - Matrix4 drawTransform; - Rect localClip; - Matrix4 casterTransformXY; - Matrix4 casterTransformZ; - Vector3 lightCenter; - float lightRadius; -}; - -void createShadowTestData(ShadowTestData* out) { - static float SAMPLE_DRAW_TRANSFORM[] = { - 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, - }; - static float SAMPLE_CASTERXY[] = { - 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 32, 32, 0, 1, - }; - static float SAMPLE_CASTERZ[] = { - 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 32, 32, 32, 1, - }; - static Rect SAMPLE_CLIP(0, 0, 1536, 2048); - static Vector3 SAMPLE_LIGHT_CENTER{768, -400, 1600}; - static float SAMPLE_LIGHT_RADIUS = 1600; - - out->drawTransform.load(SAMPLE_DRAW_TRANSFORM); - out->localClip = SAMPLE_CLIP; - out->casterTransformXY.load(SAMPLE_CASTERXY); - out->casterTransformZ.load(SAMPLE_CASTERZ); - out->lightCenter = SAMPLE_LIGHT_CENTER; - out->lightRadius = SAMPLE_LIGHT_RADIUS; -} - -static inline void tessellateShadows(ShadowTestData& testData, bool opaque, const SkPath& shape, - VertexBuffer* ambient, VertexBuffer* spot) { - tessellateShadows(&testData.drawTransform, &testData.localClip, opaque, &shape, - &testData.casterTransformXY, &testData.casterTransformZ, testData.lightCenter, - testData.lightRadius, *ambient, *spot); -} - -void BM_TessellateShadows_roundrect_opaque(benchmark::State& state) { - ShadowTestData shadowData; - createShadowTestData(&shadowData); - SkPath path; - path.addRoundRect(SkRect::MakeWH(100, 100), 5, 5); - - while (state.KeepRunning()) { - VertexBuffer ambient; - VertexBuffer spot; - tessellateShadows(shadowData, true, path, &ambient, &spot); - benchmark::DoNotOptimize(&ambient); - benchmark::DoNotOptimize(&spot); - } -} -BENCHMARK(BM_TessellateShadows_roundrect_opaque); - -void BM_TessellateShadows_roundrect_translucent(benchmark::State& state) { - ShadowTestData shadowData; - createShadowTestData(&shadowData); - SkPath path; - path.reset(); - path.addRoundRect(SkRect::MakeLTRB(0, 0, 100, 100), 5, 5); - - while (state.KeepRunning()) { - std::unique_ptr<VertexBuffer> ambient(new VertexBuffer); - std::unique_ptr<VertexBuffer> spot(new VertexBuffer); - tessellateShadows(shadowData, false, path, ambient.get(), spot.get()); - benchmark::DoNotOptimize(ambient.get()); - benchmark::DoNotOptimize(spot.get()); - } -} -BENCHMARK(BM_TessellateShadows_roundrect_translucent); diff --git a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp b/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp deleted file mode 100644 index 09f0b06ded39..000000000000 --- a/libs/hwui/tests/unit/BakedOpDispatcherTests.cpp +++ /dev/null @@ -1,289 +0,0 @@ -/* - * 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 <BakedOpDispatcher.h> -#include <BakedOpRenderer.h> -#include <FrameBuilder.h> -#include <LayerUpdateQueue.h> -#include <RecordedOp.h> -#include <hwui/Paint.h> -#include <tests/common/TestUtils.h> -#include <utils/Color.h> - -#include <SkBlurDrawLooper.h> -#include <SkDashPathEffect.h> -#include <SkPath.h> - -using namespace android::uirenderer; - -static BakedOpRenderer::LightInfo sLightInfo; -const FrameBuilder::LightGeometry sLightGeometry = {{100, 100, 100}, 50}; - -class ValidatingBakedOpRenderer : public BakedOpRenderer { -public: - ValidatingBakedOpRenderer(RenderState& renderState, - std::function<void(const Glop& glop)> validator) - : BakedOpRenderer(Caches::getInstance(), renderState, true, false, sLightInfo) - , mValidator(validator) { - mGlopReceiver = ValidatingGlopReceiver; - } - -private: - static void ValidatingGlopReceiver(BakedOpRenderer& renderer, const Rect* dirtyBounds, - const ClipBase* clip, const Glop& glop) { - auto vbor = reinterpret_cast<ValidatingBakedOpRenderer*>(&renderer); - vbor->mValidator(glop); - } - std::function<void(const Glop& glop)> mValidator; -}; - -typedef void (*TestBakedOpReceiver)(BakedOpRenderer&, const BakedOpState&); - -static void testUnmergedGlopDispatch(renderthread::RenderThread& renderThread, RecordedOp* op, - std::function<void(const Glop& glop)> glopVerifier, - int expectedGlopCount = 1) { - // Create op, and wrap with basic state. - LinearAllocator allocator; - auto snapshot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(100, 100)); - auto state = BakedOpState::tryConstruct(allocator, *snapshot, *op); - ASSERT_NE(nullptr, state); - - int glopCount = 0; - auto glopReceiver = [&glopVerifier, &glopCount, &expectedGlopCount](const Glop& glop) { - ASSERT_LE(glopCount++, expectedGlopCount) << expectedGlopCount << "glop(s) expected"; - glopVerifier(glop); - }; - ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver); - -// Dispatch based on op type created, similar to Frame/LayerBuilder dispatch behavior -#define X(Type) \ - [](BakedOpRenderer& renderer, const BakedOpState& state) { \ - BakedOpDispatcher::on##Type(renderer, static_cast<const Type&>(*(state.op)), state); \ - }, - static TestBakedOpReceiver unmergedReceivers[] = BUILD_RENDERABLE_OP_LUT(X); -#undef X - unmergedReceivers[op->opId](renderer, *state); - ASSERT_EQ(expectedGlopCount, glopCount) << "Exactly " << expectedGlopCount - << "Glop(s) expected"; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTexture_positionOvalArc) { - SkPaint strokePaint; - strokePaint.setStyle(SkPaint::kStroke_Style); - strokePaint.setStrokeWidth(4); - - float intervals[] = {1.0f, 1.0f}; - strokePaint.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0)); - - auto textureGlopVerifier = [](const Glop& glop) { - // validate glop produced by renderPathTexture (so texture, unit quad) - auto texture = glop.fill.texture.texture; - ASSERT_NE(nullptr, texture); - float expectedOffset = floor(4 * 1.5f + 0.5f); - EXPECT_EQ(expectedOffset, reinterpret_cast<PathTexture*>(texture)->offset) - << "Should see conservative offset from PathCache::computeBounds"; - Rect expectedBounds(10, 15, 20, 25); - expectedBounds.outset(expectedOffset); - - Matrix4 expectedModelView; - expectedModelView.loadTranslate(10 - expectedOffset, 15 - expectedOffset, 0); - expectedModelView.scale(10 + 2 * expectedOffset, 10 + 2 * expectedOffset, 1); - EXPECT_EQ(expectedModelView, glop.transform.modelView) - << "X and Y offsets, and scale both applied to model view"; - }; - - // Arc and Oval will render functionally the same glop, differing only in texture content - ArcOp arcOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint, 0, 270, true); - testUnmergedGlopDispatch(renderThread, &arcOp, textureGlopVerifier); - - OvalOp ovalOp(Rect(10, 15, 20, 25), Matrix4::identity(), nullptr, &strokePaint); - testUnmergedGlopDispatch(renderThread, &ovalOp, textureGlopVerifier); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, onLayerOp_bufferless) { - SkPaint layerPaint; - layerPaint.setAlpha(128); - OffscreenBuffer* buffer = nullptr; // no providing a buffer, should hit rect fallback case - LayerOp op(Rect(10, 10), Matrix4::identity(), nullptr, &layerPaint, &buffer); - testUnmergedGlopDispatch(renderThread, &op, - [](const Glop& glop) { ADD_FAILURE() << "Nothing should happen"; }, 0); -} - -static int getGlopTransformFlags(renderthread::RenderThread& renderThread, RecordedOp* op) { - int result = 0; - testUnmergedGlopDispatch(renderThread, op, [&result](const Glop& glop) { - result = glop.transform.transformFlags; - }); - return result; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, offsetFlags) { - Rect bounds(10, 15, 20, 25); - SkPaint paint; - SkPaint aaPaint; - aaPaint.setAntiAlias(true); - - RoundRectOp roundRectOp(bounds, Matrix4::identity(), nullptr, &paint, 0, 270); - EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &roundRectOp)) - << "Expect no offset for round rect op."; - - const float points[4] = {0.5, 0.5, 1.0, 1.0}; - PointsOp antiAliasedPointsOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4); - EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedPointsOp)) - << "Expect no offset for AA points."; - PointsOp pointsOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4); - EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &pointsOp)) - << "Expect an offset for non-AA points."; - - LinesOp antiAliasedLinesOp(bounds, Matrix4::identity(), nullptr, &aaPaint, points, 4); - EXPECT_EQ(TransformFlags::None, getGlopTransformFlags(renderThread, &antiAliasedLinesOp)) - << "Expect no offset for AA lines."; - LinesOp linesOp(bounds, Matrix4::identity(), nullptr, &paint, points, 4); - EXPECT_EQ(TransformFlags::OffsetByFudgeFactor, getGlopTransformFlags(renderThread, &linesOp)) - << "Expect an offset for non-AA lines."; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, renderTextWithShadow) { - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - - android::Paint shadowPaint; - shadowPaint.setColor(SK_ColorRED); - - SkScalar sigma = Blur::convertRadiusToSigma(5); - shadowPaint.setLooper(SkBlurDrawLooper::Make(SK_ColorWHITE, sigma, 3, 3)); - - TestUtils::drawUtf8ToCanvas(&canvas, "A", shadowPaint, 25, 25); - TestUtils::drawUtf8ToCanvas(&canvas, "B", shadowPaint, 50, 50); - }); - - int glopCount = 0; - auto glopReceiver = [&glopCount](const Glop& glop) { - if (glopCount < 2) { - // two white shadows - EXPECT_EQ(FloatColor({1, 1, 1, 1}), glop.fill.color); - } else { - // two text draws merged into one, drawn after both shadows - EXPECT_EQ(FloatColor({1, 0, 0, 1}), glop.fill.color); - } - glopCount++; - }; - - ValidatingBakedOpRenderer renderer(renderThread.renderState(), glopReceiver); - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); - ASSERT_EQ(3, glopCount) << "Exactly three glops expected"; -} - -static void validateLayerDraw(renderthread::RenderThread& renderThread, - std::function<void(const Glop& glop)> validator) { - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - props.mutateLayerProperties().setType(LayerType::RenderLayer); - - // provide different blend mode, so decoration draws contrast - props.mutateLayerProperties().setXferMode(SkBlendMode::kSrc); - canvas.drawColor(Color::Black, SkBlendMode::kSrcOver); - }); - OffscreenBuffer** layerHandle = node->getLayerHandle(); - - auto syncedNode = TestUtils::getSyncedNode(node); - - // create RenderNode's layer here in same way prepareTree would - OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); - *layerHandle = &layer; - { - LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid - layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(0, 0, 100, 100)); - - ValidatingBakedOpRenderer renderer(renderThread.renderState(), validator); - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferLayers(layerUpdateQueue); - frameBuilder.deferRenderNode(*syncedNode); - frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); - } - - // clean up layer pointer, so we can safely destruct RenderNode - *layerHandle = nullptr; -} - -static FloatColor makeFloatColor(uint32_t color) { - FloatColor c; - c.set(color); - return c; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, layerUpdateProperties) { - for (bool debugOverdraw : {false, true}) { - for (bool debugLayersUpdates : {false, true}) { - ScopedProperty<bool> ovdProp(Properties::debugOverdraw, debugOverdraw); - ScopedProperty<bool> lupProp(Properties::debugLayersUpdates, debugLayersUpdates); - - int glopCount = 0; - validateLayerDraw(renderThread, [&glopCount, &debugLayersUpdates](const Glop& glop) { - if (glopCount == 0) { - // 0 - Black layer fill - EXPECT_TRUE(glop.fill.colorEnabled); - EXPECT_EQ(makeFloatColor(Color::Black), glop.fill.color); - } else if (glopCount == 1) { - // 1 - Uncolored (textured) layer draw - EXPECT_FALSE(glop.fill.colorEnabled); - } else if (glopCount == 2) { - // 2 - layer overlay, if present - EXPECT_TRUE(glop.fill.colorEnabled); - // blend srcover, different from that of layer - EXPECT_EQ(GLenum(GL_ONE), glop.blend.src); - EXPECT_EQ(GLenum(GL_ONE_MINUS_SRC_ALPHA), glop.blend.dst); - EXPECT_EQ(makeFloatColor(debugLayersUpdates ? 0x7f00ff00 : 0), glop.fill.color) - << "Should be transparent green if debugLayersUpdates"; - } else if (glopCount < 7) { - // 3 - 6 - overdraw indicator overlays, if present - EXPECT_TRUE(glop.fill.colorEnabled); - uint32_t expectedColor = Caches::getInstance().getOverdrawColor(glopCount - 2); - ASSERT_EQ(makeFloatColor(expectedColor), glop.fill.color); - } else { - ADD_FAILURE() << "Too many glops observed"; - } - glopCount++; - }); - int expectedCount = 2; - if (debugLayersUpdates || debugOverdraw) expectedCount++; - if (debugOverdraw) expectedCount += 4; - EXPECT_EQ(expectedCount, glopCount); - } - } -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpDispatcher, pathTextureSnapping) { - Rect bounds(10, 15, 20, 25); - SkPaint paint; - SkPath path; - path.addRect(SkRect::MakeXYWH(1.5, 3.8, 100, 90)); - PathOp op(bounds, Matrix4::identity(), nullptr, &paint, &path); - testUnmergedGlopDispatch(renderThread, &op, [](const Glop& glop) { - auto texture = glop.fill.texture.texture; - ASSERT_NE(nullptr, texture); - EXPECT_EQ(1, reinterpret_cast<PathTexture*>(texture)->left); - EXPECT_EQ(3, reinterpret_cast<PathTexture*>(texture)->top); - }); -} diff --git a/libs/hwui/tests/unit/BakedOpRendererTests.cpp b/libs/hwui/tests/unit/BakedOpRendererTests.cpp deleted file mode 100644 index 1a3ec39a00d0..000000000000 --- a/libs/hwui/tests/unit/BakedOpRendererTests.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> - -#include <BakedOpRenderer.h> -#include <GlopBuilder.h> -#include <tests/common/TestUtils.h> - -using namespace android::uirenderer; - -const BakedOpRenderer::LightInfo sLightInfo = {128, 128}; - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, startRepaintLayer_clear) { - BakedOpRenderer renderer(Caches::getInstance(), renderThread.renderState(), true, false, - sLightInfo); - OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200u, 200u); - - layer.dirty(Rect(200, 200)); - { - renderer.startRepaintLayer(&layer, Rect(200, 200)); - EXPECT_TRUE(layer.region.isEmpty()) << "Repaint full layer should clear region"; - renderer.endLayer(); - } - - layer.dirty(Rect(200, 200)); - { - renderer.startRepaintLayer(&layer, Rect(100, 200)); // repainting left side - EXPECT_TRUE(layer.region.isRect()); - // ALOGD("bounds %d %d %d %d", RECT_ARGS(layer.region.getBounds())); - EXPECT_EQ(android::Rect(100, 0, 200, 200), layer.region.getBounds()) - << "Left side being repainted, so right side should be clear"; - renderer.endLayer(); - } - - // right side is now only dirty portion - { - renderer.startRepaintLayer(&layer, Rect(100, 0, 200, 200)); // repainting right side - EXPECT_TRUE(layer.region.isEmpty()) - << "Now right side being repainted, so region should be entirely clear"; - renderer.endLayer(); - } -} - -static void drawFirstOp(RenderState& renderState, int color, SkBlendMode mode) { - BakedOpRenderer renderer(Caches::getInstance(), renderState, true, false, sLightInfo); - - renderer.startFrame(100, 100, Rect(100, 100)); - SkPaint paint; - paint.setColor(color); - paint.setBlendMode(mode); - - Rect dest(0, 0, 100, 100); - Glop glop; - GlopBuilder(renderState, Caches::getInstance(), &glop) - .setRoundRectClipState(nullptr) - .setMeshUnitQuad() - .setFillPaint(paint, 1.0f) - .setTransform(Matrix4::identity(), TransformFlags::None) - .setModelViewMapUnitToRectSnap(dest) - .build(); - renderer.renderGlop(nullptr, nullptr, glop); - renderer.endFrame(Rect(100, 100)); -} - -static void verifyBlend(RenderState& renderState, GLenum expectedSrc, GLenum expectedDst) { - EXPECT_TRUE(renderState.blend().getEnabled()); - GLenum src; - GLenum dst; - renderState.blend().getFactors(&src, &dst); - EXPECT_EQ(expectedSrc, src); - EXPECT_EQ(expectedDst, dst); -} - -static void verifyBlendDisabled(RenderState& renderState) { - EXPECT_FALSE(renderState.blend().getEnabled()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, firstDrawBlend_clear) { - // initialize blend state to nonsense value - renderThread.renderState().blend().setFactors(GL_ONE, GL_ONE); - - drawFirstOp(renderThread.renderState(), 0xfeff0000, SkBlendMode::kClear); - verifyBlend(renderThread.renderState(), GL_ZERO, GL_ONE_MINUS_SRC_ALPHA); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(BakedOpRenderer, firstDrawBlend_srcover) { - // initialize blend state to nonsense value - renderThread.renderState().blend().setFactors(GL_ONE, GL_ONE); - - drawFirstOp(renderThread.renderState(), 0xfeff0000, SkBlendMode::kSrcOver); - verifyBlendDisabled(renderThread.renderState()); -} diff --git a/libs/hwui/tests/unit/BakedOpStateTests.cpp b/libs/hwui/tests/unit/BakedOpStateTests.cpp deleted file mode 100644 index 6f8e24917767..000000000000 --- a/libs/hwui/tests/unit/BakedOpStateTests.cpp +++ /dev/null @@ -1,266 +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 <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, 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, 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, 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, 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, false); - 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, false); - - 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, false); - - 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, false); - - 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 deleted file mode 100644 index 4c03811b0c96..000000000000 --- a/libs/hwui/tests/unit/CanvasStateTests.cpp +++ /dev/null @@ -1,160 +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 "CanvasState.h" - -#include "Matrix.h" -#include "Rect.h" -#include "hwui/Canvas.h" -#include "utils/LinearAllocator.h" - -#include <SkClipOp.h> -#include <SkPath.h> -#include <gtest/gtest.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, SkClipOp::kIntersect); - ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(100, 100)); - - state.clipRect(10, 10, 200, 200, SkClipOp::kIntersect); - ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10, 100, 100)); - - state.clipRect(50, 50, 150, 150, SkClipOp::kReplace_deprecated); - ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(50, 50, 150, 150)); -} - -TEST(CanvasState, complexClipping) { - CanvasState state(sNullClient); - state.initializeSaveStack(200, 200, 0, 0, 200, 200, Vector3()); - - state.save(SaveFlags::MatrixClip); - { - // rotated clip causes complex clip - state.rotate(10); - EXPECT_TRUE(state.clipIsSimple()); - state.clipRect(0, 0, 200, 200, SkClipOp::kIntersect); - EXPECT_FALSE(state.clipIsSimple()); - } - state.restore(); - - state.save(SaveFlags::MatrixClip); - { - // subtracted clip causes complex clip - EXPECT_TRUE(state.clipIsSimple()); - state.clipRect(50, 50, 150, 150, SkClipOp::kDifference); - EXPECT_FALSE(state.clipIsSimple()); - } - state.restore(); - - state.save(SaveFlags::MatrixClip); - { - // complex path causes complex clip - SkPath path; - path.addOval(SkRect::MakeWH(200, 200)); - EXPECT_TRUE(state.clipIsSimple()); - state.clipPath(&path, SkClipOp::kDifference); - EXPECT_FALSE(state.clipIsSimple()); - } - state.restore(); -} - -TEST(CanvasState, saveAndRestore) { - CanvasState state(sNullClient); - state.initializeSaveStack(200, 200, 0, 0, 200, 200, Vector3()); - - state.save(SaveFlags::Clip); - { - state.clipRect(0, 0, 10, 10, SkClipOp::kIntersect); - ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); - } - state.restore(); - ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(200, 200)); // verify restore - - Matrix4 simpleTranslate; - simpleTranslate.loadTranslate(10, 10, 0); - state.save(SaveFlags::Matrix); - { - state.translate(10, 10, 0); - EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate)); - } - state.restore(); - EXPECT_FALSE(approxEqual(*state.currentTransform(), simpleTranslate)); -} - -TEST(CanvasState, saveAndRestoreButNotTooMuch) { - CanvasState state(sNullClient); - state.initializeSaveStack(200, 200, 0, 0, 200, 200, Vector3()); - - state.save(SaveFlags::Matrix); // NOTE: clip not saved - { - state.clipRect(0, 0, 10, 10, SkClipOp::kIntersect); - ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); - } - state.restore(); - ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10)); // verify not restored - - Matrix4 simpleTranslate; - simpleTranslate.loadTranslate(10, 10, 0); - state.save(SaveFlags::Clip); // NOTE: matrix not saved - { - state.translate(10, 10, 0); - EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate)); - } - state.restore(); - EXPECT_TRUE(approxEqual(*state.currentTransform(), simpleTranslate)); // verify not restored -} -} -} diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp deleted file mode 100644 index 450bb679a45f..000000000000 --- a/libs/hwui/tests/unit/ClipAreaTests.cpp +++ /dev/null @@ -1,353 +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 <SkPath.h> -#include <SkRegion.h> -#include <gtest/gtest.h> - -#include "ClipArea.h" - -#include "Matrix.h" -#include "Rect.h" -#include "utils/LinearAllocator.h" - -namespace android { -namespace uirenderer { - -static Rect kViewportBounds(2048, 2048); - -static ClipArea createClipArea() { - ClipArea area; - area.setViewportDimensions(kViewportBounds.getWidth(), kViewportBounds.getHeight()); - return area; -} - -TEST(TransformedRectangle, basics) { - Rect r(0, 0, 100, 100); - Matrix4 minus90; - minus90.loadRotate(-90); - minus90.mapRect(r); - Rect r2(20, 40, 120, 60); - - Matrix4 m90; - m90.loadRotate(90); - TransformedRectangle tr(r, m90); - EXPECT_TRUE(tr.canSimplyIntersectWith(tr)); - - Matrix4 m0; - TransformedRectangle tr0(r2, m0); - EXPECT_FALSE(tr.canSimplyIntersectWith(tr0)); - - Matrix4 m45; - m45.loadRotate(45); - TransformedRectangle tr2(r, m45); - EXPECT_FALSE(tr2.canSimplyIntersectWith(tr)); -} - -TEST(RectangleList, basics) { - RectangleList list; - EXPECT_TRUE(list.isEmpty()); - - Rect r(0, 0, 100, 100); - Matrix4 m45; - m45.loadRotate(45); - list.set(r, m45); - EXPECT_FALSE(list.isEmpty()); - - Rect r2(20, 20, 200, 200); - list.intersectWith(r2, m45); - EXPECT_FALSE(list.isEmpty()); - EXPECT_EQ(1, list.getTransformedRectanglesCount()); - - Rect r3(20, 20, 200, 200); - Matrix4 m30; - m30.loadRotate(30); - list.intersectWith(r2, m30); - EXPECT_FALSE(list.isEmpty()); - EXPECT_EQ(2, list.getTransformedRectanglesCount()); - - SkRegion clip; - clip.setRect(0, 0, 2000, 2000); - SkRegion rgn(list.convertToRegion(clip)); - EXPECT_FALSE(rgn.isEmpty()); -} - -TEST(ClipArea, basics) { - ClipArea area(createClipArea()); - EXPECT_FALSE(area.isEmpty()); -} - -TEST(ClipArea, paths) { - ClipArea area(createClipArea()); - SkPath path; - SkScalar r = 100; - path.addCircle(r, r, r); - area.clipPathWithTransform(path, &Matrix4::identity(), SkRegion::kIntersect_Op); - EXPECT_FALSE(area.isEmpty()); - EXPECT_FALSE(area.isSimple()); - EXPECT_FALSE(area.isRectangleList()); - - Rect clipRect(area.getClipRect()); - Rect expected(0, 0, r * 2, r * 2); - EXPECT_EQ(expected, clipRect); - SkRegion clipRegion(area.getClipRegion()); - auto skRect(clipRegion.getBounds()); - Rect regionBounds; - regionBounds.set(skRect); - EXPECT_EQ(expected, regionBounds); -} - -TEST(ClipArea, replaceNegative) { - ClipArea area(createClipArea()); - area.setClip(0, 0, 100, 100); - - Rect expected(-50, -50, 50, 50); - area.clipRectWithTransform(expected, &Matrix4::identity(), SkRegion::kReplace_Op); - EXPECT_EQ(expected, area.getClipRect()); -} - -TEST(ClipArea, serializeClip) { - ClipArea area(createClipArea()); - LinearAllocator allocator; - - // unset clip - EXPECT_EQ(nullptr, area.serializeClip(allocator)); - - // rect clip - area.setClip(0, 0, 200, 200); - { - auto serializedClip = area.serializeClip(allocator); - ASSERT_NE(nullptr, serializedClip); - ASSERT_EQ(ClipMode::Rectangle, serializedClip->mode); - ASSERT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot"; - EXPECT_EQ(Rect(200, 200), serializedClip->rect); - EXPECT_EQ(serializedClip, area.serializeClip(allocator)) - << "Requery of clip on unmodified ClipArea must return same pointer."; - } - - // rect list - Matrix4 rotate; - rotate.loadRotate(5.0f); - area.clipRectWithTransform(Rect(50, 50, 150, 150), &rotate, SkRegion::kIntersect_Op); - { - auto serializedClip = area.serializeClip(allocator); - ASSERT_NE(nullptr, serializedClip); - ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode); - ASSERT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot"; - auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip); - EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount()); - EXPECT_EQ(Rect(37, 54, 145, 163), clipRectList->rect); - EXPECT_EQ(serializedClip, area.serializeClip(allocator)) - << "Requery of clip on unmodified ClipArea must return same pointer."; - } - - // region - SkPath circlePath; - circlePath.addCircle(100, 100, 100); - area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op); - { - auto serializedClip = area.serializeClip(allocator); - ASSERT_NE(nullptr, serializedClip); - ASSERT_EQ(ClipMode::Region, serializedClip->mode); - ASSERT_TRUE(serializedClip->intersectWithRoot) << "Replace op, so expect intersectWithRoot"; - auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip); - EXPECT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds()) - << "Clip region should be 200x200"; - EXPECT_EQ(Rect(200, 200), clipRegion->rect); - EXPECT_EQ(serializedClip, area.serializeClip(allocator)) - << "Requery of clip on unmodified ClipArea must return same pointer."; - } -} - -TEST(ClipArea, serializeClip_pathIntersectWithRoot) { - ClipArea area(createClipArea()); - LinearAllocator allocator; - SkPath circlePath; - circlePath.addCircle(100, 100, 100); - area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kIntersect_Op); - - auto serializedClip = area.serializeClip(allocator); - ASSERT_NE(nullptr, serializedClip); - EXPECT_FALSE(serializedClip->intersectWithRoot) << "No replace, so no intersectWithRoot"; -} - -TEST(ClipArea, serializeIntersectedClip) { - ClipArea area(createClipArea()); - LinearAllocator allocator; - - // simple state; - EXPECT_EQ(nullptr, area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity())); - area.setClip(0, 0, 200, 200); - { - auto origRectClip = area.serializeClip(allocator); - ASSERT_NE(nullptr, origRectClip); - EXPECT_EQ(origRectClip, - area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity())); - } - - // rect - { - ClipRect recordedClip(Rect(100, 100)); - Matrix4 translateScale; - translateScale.loadTranslate(100, 100, 0); - translateScale.scale(2, 3, 1); - auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale); - ASSERT_NE(nullptr, resolvedClip); - ASSERT_EQ(ClipMode::Rectangle, resolvedClip->mode); - EXPECT_EQ(Rect(100, 100, 200, 200), resolvedClip->rect); - - EXPECT_EQ(resolvedClip, - area.serializeIntersectedClip(allocator, &recordedClip, translateScale)) - << "Must return previous serialization, since input is same"; - - ClipRect recordedClip2(Rect(100, 100)); - EXPECT_NE(resolvedClip, - area.serializeIntersectedClip(allocator, &recordedClip2, translateScale)) - << "Shouldn't return previous serialization, since matrix location is different"; - } - - // rect list - Matrix4 rotate; - rotate.loadRotate(2.0f); - area.clipRectWithTransform(Rect(200, 200), &rotate, SkRegion::kIntersect_Op); - { - ClipRect recordedClip(Rect(100, 100)); - auto resolvedClip = - area.serializeIntersectedClip(allocator, &recordedClip, Matrix4::identity()); - ASSERT_NE(nullptr, resolvedClip); - ASSERT_EQ(ClipMode::RectangleList, resolvedClip->mode); - auto clipRectList = reinterpret_cast<const ClipRectList*>(resolvedClip); - EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount()); - } - - // region - SkPath circlePath; - circlePath.addCircle(100, 100, 100); - area.clipPathWithTransform(circlePath, &Matrix4::identity(), SkRegion::kReplace_Op); - { - SkPath ovalPath; - ovalPath.addOval(SkRect::MakeLTRB(50, 0, 150, 200)); - - ClipRegion recordedClip; - recordedClip.region.setPath(ovalPath, SkRegion(SkIRect::MakeWH(200, 200))); - recordedClip.rect = Rect(200, 200); - - Matrix4 translate10x20; - translate10x20.loadTranslate(10, 20, 0); - auto resolvedClip = area.serializeIntersectedClip( - allocator, &recordedClip, - translate10x20); // Note: only translate for now, others not handled correctly - ASSERT_NE(nullptr, resolvedClip); - ASSERT_EQ(ClipMode::Region, resolvedClip->mode); - auto clipRegion = reinterpret_cast<const ClipRegion*>(resolvedClip); - EXPECT_EQ(SkIRect::MakeLTRB(60, 20, 160, 200), clipRegion->region.getBounds()); - } -} - -TEST(ClipArea, serializeIntersectedClip_snap) { - ClipArea area(createClipArea()); - area.setClip(100.2, 100.4, 500.6, 500.8); - LinearAllocator allocator; - - { - // no recorded clip case - auto resolvedClip = area.serializeIntersectedClip(allocator, nullptr, Matrix4::identity()); - EXPECT_EQ(Rect(100, 100, 501, 501), resolvedClip->rect); - } - { - // recorded clip case - ClipRect recordedClip(Rect(100.12, 100.74)); - Matrix4 translateScale; - translateScale.loadTranslate(100, 100, 0); - translateScale.scale(2, 3, - 1); // recorded clip will have non-int coords, even after transform - auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale); - ASSERT_NE(nullptr, resolvedClip); - EXPECT_EQ(ClipMode::Rectangle, resolvedClip->mode); - EXPECT_EQ(Rect(100, 100, 300, 402), resolvedClip->rect); - } -} - -TEST(ClipArea, serializeIntersectedClip_scale) { - ClipArea area(createClipArea()); - area.setClip(0, 0, 400, 400); - LinearAllocator allocator; - - SkPath circlePath; - circlePath.addCircle(50, 50, 50); - - ClipRegion recordedClip; - recordedClip.region.setPath(circlePath, SkRegion(SkIRect::MakeWH(100, 100))); - recordedClip.rect = Rect(100, 100); - - Matrix4 translateScale; - translateScale.loadTranslate(100, 100, 0); - translateScale.scale(2, 2, 1); - auto resolvedClip = area.serializeIntersectedClip(allocator, &recordedClip, translateScale); - - ASSERT_NE(nullptr, resolvedClip); - EXPECT_EQ(ClipMode::Region, resolvedClip->mode); - EXPECT_EQ(Rect(100, 100, 300, 300), resolvedClip->rect); - auto clipRegion = reinterpret_cast<const ClipRegion*>(resolvedClip); - EXPECT_EQ(SkIRect::MakeLTRB(100, 100, 300, 300), clipRegion->region.getBounds()); -} - -TEST(ClipArea, applyTransformToRegion_identity) { - SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); - ClipArea::applyTransformToRegion(Matrix4::identity(), ®ion); - EXPECT_TRUE(region.isRect()); - EXPECT_EQ(SkIRect::MakeLTRB(1, 2, 3, 4), region.getBounds()); -} - -TEST(ClipArea, applyTransformToRegion_translate) { - SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); - Matrix4 transform; - transform.loadTranslate(10, 20, 0); - ClipArea::applyTransformToRegion(transform, ®ion); - EXPECT_TRUE(region.isRect()); - EXPECT_EQ(SkIRect::MakeLTRB(11, 22, 13, 24), region.getBounds()); -} - -TEST(ClipArea, applyTransformToRegion_scale) { - SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); - Matrix4 transform; - transform.loadScale(2, 3, 1); - ClipArea::applyTransformToRegion(transform, ®ion); - EXPECT_TRUE(region.isRect()); - EXPECT_EQ(SkIRect::MakeLTRB(2, 6, 6, 12), region.getBounds()); -} - -TEST(ClipArea, applyTransformToRegion_translateScale) { - SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); - Matrix4 transform; - transform.translate(10, 20); - transform.scale(2, 3, 1); - ClipArea::applyTransformToRegion(transform, ®ion); - EXPECT_TRUE(region.isRect()); - EXPECT_EQ(SkIRect::MakeLTRB(12, 26, 16, 32), region.getBounds()); -} - -TEST(ClipArea, applyTransformToRegion_rotate90) { - SkRegion region(SkIRect::MakeLTRB(1, 2, 3, 4)); - Matrix4 transform; - transform.loadRotate(90); - ClipArea::applyTransformToRegion(transform, ®ion); - EXPECT_TRUE(region.isRect()); - EXPECT_EQ(SkIRect::MakeLTRB(-4, 1, -2, 3), region.getBounds()); -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp index f29830f0e34b..6c8775b1bdbb 100644 --- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp +++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp @@ -15,12 +15,13 @@ */ #include "DeferredLayerUpdater.h" -#include "GlLayer.h" #include "Properties.h" #include "tests/common/TestUtils.h" #include <gtest/gtest.h> +#include <SkBitmap.h> +#include <SkImage.h> using namespace android; using namespace android::uirenderer; @@ -31,10 +32,6 @@ RENDERTHREAD_TEST(DeferredLayerUpdater, updateLayer) { layerUpdater->setBlend(true); // updates are deferred so the backing layer should still be in its default state - if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) { - GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer()); - EXPECT_EQ((uint32_t)GL_NONE, glLayer->getRenderTarget()); - } EXPECT_EQ(0u, layerUpdater->backingLayer()->getWidth()); EXPECT_EQ(0u, layerUpdater->backingLayer()->getHeight()); EXPECT_FALSE(layerUpdater->backingLayer()->getForceFilter()); @@ -42,19 +39,13 @@ RENDERTHREAD_TEST(DeferredLayerUpdater, updateLayer) { EXPECT_EQ(Matrix4::identity(), layerUpdater->backingLayer()->getTexTransform()); // push the deferred updates to the layer - Matrix4 scaledMatrix; - scaledMatrix.loadScale(0.5, 0.5, 0.0); - layerUpdater->updateLayer(true, scaledMatrix.data, HAL_DATASPACE_UNKNOWN); - if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) { - GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer()); - glLayer->setRenderTarget(GL_TEXTURE_EXTERNAL_OES); - } + SkMatrix scaledMatrix = SkMatrix::MakeScale(0.5, 0.5); + SkBitmap bitmap; + bitmap.allocN32Pixels(16, 16); + sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap); + layerUpdater->updateLayer(true, scaledMatrix, HAL_DATASPACE_UNKNOWN, layerImage); // the backing layer should now have all the properties applied. - if (layerUpdater->backingLayer()->getApi() == Layer::Api::OpenGL) { - GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer()); - EXPECT_EQ((uint32_t)GL_TEXTURE_EXTERNAL_OES, glLayer->getRenderTarget()); - } EXPECT_EQ(100u, layerUpdater->backingLayer()->getWidth()); EXPECT_EQ(100u, layerUpdater->backingLayer()->getHeight()); EXPECT_TRUE(layerUpdater->backingLayer()->getForceFilter()); diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h index 9693ce7b6784..89f0c52b49ec 100644 --- a/libs/hwui/tests/unit/FatalTestCanvas.h +++ b/libs/hwui/tests/unit/FatalTestCanvas.h @@ -42,10 +42,6 @@ public: const SkPaint& paint) { ADD_FAILURE() << "onDrawPosTextH not expected in this test"; } - void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, - const SkMatrix* matrix, const SkPaint& paint) { - ADD_FAILURE() << "onDrawTextOnPath not expected in this test"; - } void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform[], const SkRect* cullRect, const SkPaint& paint) { ADD_FAILURE() << "onDrawTextRSXform not expected in this test"; diff --git a/libs/hwui/tests/unit/FontRendererTests.cpp b/libs/hwui/tests/unit/FontRendererTests.cpp deleted file mode 100644 index c78f131ce2ce..000000000000 --- a/libs/hwui/tests/unit/FontRendererTests.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> - -#include "GammaFontRenderer.h" -#include "tests/common/TestUtils.h" - -using namespace android::uirenderer; - -static bool isZero(uint8_t* data, int size) { - for (int i = 0; i < size; i++) { - if (data[i]) return false; - } - return true; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FontRenderer, renderDropShadow) { - SkPaint paint; - paint.setTextSize(10); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - GammaFontRenderer gammaFontRenderer; - FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); - fontRenderer.setFont(&paint, SkMatrix::I()); - - std::vector<glyph_t> glyphs; - std::vector<float> positions; - float totalAdvance; - Rect bounds; - TestUtils::layoutTextUnscaled(paint, "This is a test", &glyphs, &positions, &totalAdvance, - &bounds); - - for (int radius : {28, 20, 2}) { - auto result = fontRenderer.renderDropShadow(&paint, glyphs.data(), glyphs.size(), radius, - positions.data()); - ASSERT_NE(nullptr, result.image); - EXPECT_FALSE(isZero(result.image, result.width * result.height)); - EXPECT_LE(bounds.getWidth() + radius * 2, (int)result.width); - EXPECT_LE(bounds.getHeight() + radius * 2, (int)result.height); - delete result.image; - } -} diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp deleted file mode 100644 index 4eb77514f4ae..000000000000 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ /dev/null @@ -1,2705 +0,0 @@ -/* - * 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 <GlLayer.h> -#include <LayerUpdateQueue.h> -#include <RecordedOp.h> -#include <RecordingCanvas.h> -#include <tests/common/TestUtils.h> - -#include <unordered_map> - -namespace android { -namespace uirenderer { - -const FrameBuilder::LightGeometry sLightGeometry = {{100, 100, 100}, 50}; - -/** - * Virtual class implemented by each test to redirect static operation / state transitions to - * virtual methods. - * - * Virtual dispatch allows for default behaviors to be specified (very common case in below tests), - * and allows Renderer vs Dispatching behavior to be merged. - * - * onXXXOp methods fail by default - tests should override ops they expect - * startRepaintLayer fails by default - tests should override if expected - * startFrame/endFrame do nothing by default - tests should override to intercept - */ -class TestRendererBase { -public: - virtual ~TestRendererBase() {} - virtual OffscreenBuffer* startTemporaryLayer(uint32_t, uint32_t) { - ADD_FAILURE() << "Temporary layers not expected in this test"; - return nullptr; - } - virtual void recycleTemporaryLayer(OffscreenBuffer*) { - ADD_FAILURE() << "Temporary layers not expected in this test"; - } - virtual void startRepaintLayer(OffscreenBuffer*, const Rect& repaintRect) { - ADD_FAILURE() << "Layer repaint not expected in this test"; - } - virtual void endLayer() { ADD_FAILURE() << "Layer updates not expected in this test"; } - virtual void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) {} - virtual void endFrame(const Rect& repaintRect) {} - -// define virtual defaults for single draw methods -#define X(Type) \ - virtual void on##Type(const Type&, const BakedOpState&) { \ - ADD_FAILURE() << #Type " not expected in this test"; \ - } - MAP_RENDERABLE_OPS(X) -#undef X - -// define virtual defaults for merged draw methods -#define X(Type) \ - virtual void onMerged##Type##s(const MergedBakedOpList& opList) { \ - ADD_FAILURE() << "Merged " #Type "s not expected in this test"; \ - } - MAP_MERGEABLE_OPS(X) -#undef X - - int getIndex() { return mIndex; } - -protected: - int mIndex = 0; -}; - -/** - * Dispatches all static methods to similar formed methods on renderer, which fail by default but - * are overridden by subclasses per test. - */ -class TestDispatcher { -public: -// define single op methods, which redirect to TestRendererBase -#define X(Type) \ - static void on##Type(TestRendererBase& renderer, const Type& op, const BakedOpState& state) { \ - renderer.on##Type(op, state); \ - } - MAP_RENDERABLE_OPS(X); -#undef X - -// define merged op methods, which redirect to TestRendererBase -#define X(Type) \ - static void onMerged##Type##s(TestRendererBase& renderer, const MergedBakedOpList& opList) { \ - renderer.onMerged##Type##s(opList); \ - } - MAP_MERGEABLE_OPS(X); -#undef X -}; - -class FailRenderer : public TestRendererBase {}; - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 0, 0, 100, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25)); - canvas.drawRect(0, 0, 100, 200, SkPaint()); - canvas.drawBitmap(*bitmap, 10, 10, nullptr); - }); - FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SimpleTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 0, 0, 100, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - SkPaint strokedPaint; - strokedPaint.setStrokeWidth(10); - canvas.drawPoint(50, 50, strokedPaint); - }); - FrameBuilder frameBuilder(SkRect::MakeWH(100, 200), 100, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SimpleStrokeTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, arcStrokeClip) { - class ArcStrokeClipTestRenderer : public TestRendererBase { - public: - void onArcOp(const ArcOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - EXPECT_EQ(Rect(25, 25, 175, 175), op.unmappedBounds); - EXPECT_EQ(Rect(25, 25, 175, 175), state.computedState.clippedBounds); - EXPECT_EQ(OpClipSideFlags::Full, state.computedState.clipSideFlags) - << "Arc op clipped conservatively, since path texture may be expanded"; - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.clipRect(25, 25, 175, 175, SkClipOp::kIntersect); - SkPaint aaPaint; - aaPaint.setAntiAlias(true); - canvas.drawArc(25, 25, 175, 175, 40, 180, true, aaPaint); - }); - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - ArcStrokeClipTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, simpleRejection) { - auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RenderProperties& props, - RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(200, 200, 400, 400, SkClipOp::kIntersect); // intersection should be empty - canvas.drawRect(0, 0, 400, 400, SkPaint()); - canvas.restore(); - }); - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - FailRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - - sk_sp<Bitmap> bitmap(TestUtils::createBitmap( - 10, 10, - kAlpha_8_SkColorType)); // Disable merging by using alpha 8 bitmap - - // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects. - // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group. - canvas.save(SaveFlags::MatrixClip); - for (int i = 0; i < LOOPS; i++) { - canvas.translate(0, 10); - canvas.drawRect(0, 0, 10, 10, SkPaint()); - canvas.drawBitmap(*bitmap, 5, 0, nullptr); - } - canvas.restore(); - }); - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SimpleBatchingTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2 * LOOPS, renderer.getIndex()) << "Expect number of ops = 2 * loop count"; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferRenderNode_translateClip) { - class DeferRenderNodeTranslateClipTestRenderer : public TestRendererBase { - public: - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - EXPECT_EQ(Rect(5, 10, 55, 60), state.computedState.clippedBounds); - EXPECT_EQ(OpClipSideFlags::Right | OpClipSideFlags::Bottom, - state.computedState.clipSideFlags); - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.drawRect(0, 0, 100, 100, SkPaint()); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(5, 10, Rect(50, 50), // translate + clip node - *TestUtils::getSyncedNode(node)); - - DeferRenderNodeTranslateClipTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferRenderNodeScene) { - class DeferRenderNodeSceneTestRenderer : public TestRendererBase { - public: - void onRectOp(const RectOp& op, const BakedOpState& state) override { - const Rect& clippedBounds = state.computedState.clippedBounds; - Matrix4 expected; - switch (mIndex++) { - case 0: - // background - left side - EXPECT_EQ(Rect(600, 100, 700, 500), clippedBounds); - expected.loadTranslate(100, 100, 0); - break; - case 1: - // background - top side - EXPECT_EQ(Rect(100, 400, 600, 500), clippedBounds); - expected.loadTranslate(100, 100, 0); - break; - case 2: - // content - EXPECT_EQ(Rect(100, 100, 700, 500), clippedBounds); - expected.loadTranslate(-50, -50, 0); - break; - case 3: - // overlay - EXPECT_EQ(Rect(0, 0, 800, 200), clippedBounds); - break; - default: - ADD_FAILURE() << "Too many rects observed"; - } - EXPECT_EQ(expected, state.computedState.transform); - } - }; - - std::vector<sp<RenderNode>> nodes; - SkPaint transparentPaint; - transparentPaint.setAlpha(128); - - // backdrop - nodes.push_back(TestUtils::createNode<RecordingCanvas>( - 100, 100, 700, 500, // 600x400 - [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) { - canvas.drawRect(0, 0, 600, 400, transparentPaint); - })); - - // content - Rect contentDrawBounds(150, 150, 650, 450); // 500x300 - nodes.push_back(TestUtils::createNode<RecordingCanvas>( - 0, 0, 800, 600, [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) { - canvas.drawRect(0, 0, 800, 600, transparentPaint); - })); - - // overlay - nodes.push_back(TestUtils::createNode<RecordingCanvas>( - 0, 0, 800, 600, [&transparentPaint](RenderProperties& props, RecordingCanvas& canvas) { - canvas.drawRect(0, 0, 800, 200, transparentPaint); - })); - - for (auto& node : nodes) { - TestUtils::syncHierarchyPropertiesAndDisplayList(node); - } - - { - FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds); - - DeferRenderNodeSceneTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); - } - - for (auto& node : nodes) { - EXPECT_TRUE(node->isValid()); - EXPECT_FALSE(node->nothingToDraw()); - node->setStagingDisplayList(nullptr); - EXPECT_FALSE(node->isValid()); - EXPECT_FALSE(node->nothingToDraw()); - node->destroyHardwareResources(); - EXPECT_TRUE(node->nothingToDraw()); - EXPECT_FALSE(node->isValid()); - } - - { - // Validate no crashes if any nodes are missing DisplayLists - FrameBuilder frameBuilder(SkRect::MakeWH(800, 600), 800, 600, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNodeScene(nodes, contentDrawBounds); - - FailRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - } -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, empty_noFbo0) { - class EmptyNoFbo0TestRenderer : public TestRendererBase { - public: - void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { - ADD_FAILURE() << "Primary frame draw not expected in this test"; - } - void endFrame(const Rect& repaintRect) override { - ADD_FAILURE() << "Primary frame draw not expected in this test"; - } - }; - - // Use layer update constructor, so no work is enqueued for Fbo0 - LayerUpdateQueue emptyLayerUpdateQueue; - FrameBuilder frameBuilder(emptyLayerUpdateQueue, sLightGeometry, Caches::getInstance()); - EmptyNoFbo0TestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, empty_withFbo0) { - class EmptyWithFbo0TestRenderer : public TestRendererBase { - public: - void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { - EXPECT_EQ(0, mIndex++); - } - void endFrame(const Rect& repaintRect) override { EXPECT_EQ(1, mIndex++); } - }; - auto node = TestUtils::createNode<RecordingCanvas>( - 10, 10, 110, 110, [](RenderProperties& props, RecordingCanvas& canvas) { - // no drawn content - }); - - // Draw, but pass node without draw content, so no work is done for primary frame - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - EmptyWithFbo0TestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()) << "No drawing content produced," - " but fbo0 update lifecycle should still be observed"; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, avoidOverdraw_rects) { - class AvoidOverdrawRectsTestRenderer : public TestRendererBase { - public: - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(mIndex++, 0) << "Should be one rect"; - EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds) - << "Last rect should occlude others."; - } - }; - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.drawRect(0, 0, 200, 200, SkPaint()); - canvas.drawRect(0, 0, 200, 200, SkPaint()); - canvas.drawRect(10, 10, 190, 190, SkPaint()); - }); - - // Damage (and therefore clip) is same as last draw, subset of renderable area. - // This means last op occludes other contents, and they'll be rejected to avoid overdraw. - FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 190, 190), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - EXPECT_EQ(3u, node->getDisplayList()->getOps().size()) - << "Recording must not have rejected ops, in order for this test to be valid"; - - AvoidOverdrawRectsTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()) << "Expect exactly one op"; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, avoidOverdraw_bitmaps) { - static sk_sp<Bitmap> opaqueBitmap( - TestUtils::createBitmap(50, 50, SkColorType::kRGB_565_SkColorType)); - static sk_sp<Bitmap> transpBitmap( - TestUtils::createBitmap(50, 50, SkColorType::kAlpha_8_SkColorType)); - class AvoidOverdrawBitmapsTestRenderer : public TestRendererBase { - public: - void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { - switch (mIndex++) { - case 0: - EXPECT_EQ(opaqueBitmap.get(), op.bitmap); - break; - case 1: - EXPECT_EQ(transpBitmap.get(), op.bitmap); - break; - default: - ADD_FAILURE() << "Only two ops expected."; - } - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 50, 50, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.drawRect(0, 0, 50, 50, SkPaint()); - canvas.drawRect(0, 0, 50, 50, SkPaint()); - canvas.drawBitmap(*transpBitmap, 0, 0, nullptr); - - // only the below draws should remain, since they're - canvas.drawBitmap(*opaqueBitmap, 0, 0, nullptr); - canvas.drawBitmap(*transpBitmap, 0, 0, nullptr); - }); - FrameBuilder frameBuilder(SkRect::MakeWH(50, 50), 50, 50, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - EXPECT_EQ(5u, node->getDisplayList()->getOps().size()) - << "Recording must not have rejected ops, in order for this test to be valid"; - - AvoidOverdrawBitmapsTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()) << "Expect exactly two ops"; -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - sk_sp<Bitmap> bitmap(TestUtils::createBitmap(20, 20)); - - // left side clipped (to inset left half) - canvas.clipRect(10, 0, 50, 100, SkClipOp::kReplace_deprecated); - canvas.drawBitmap(*bitmap, 0, 40, nullptr); - - // top side clipped (to inset top half) - canvas.clipRect(0, 10, 100, 50, SkClipOp::kReplace_deprecated); - canvas.drawBitmap(*bitmap, 40, 0, nullptr); - - // right side clipped (to inset right half) - canvas.clipRect(50, 0, 90, 100, SkClipOp::kReplace_deprecated); - canvas.drawBitmap(*bitmap, 80, 40, nullptr); - - // bottom not clipped, just abutting (inset bottom half) - canvas.clipRect(0, 50, 100, 90, SkClipOp::kReplace_deprecated); - canvas.drawBitmap(*bitmap, 40, 70, nullptr); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - ClippedMergingTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, regionClipStopsMerge) { - class RegionClipStopsMergeTestRenderer : public TestRendererBase { - public: - void onTextOp(const TextOp& op, const BakedOpState& state) override { mIndex++; } - }; - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) { - SkPath path; - path.addCircle(200, 200, 200, SkPath::kCW_Direction); - canvas.save(SaveFlags::MatrixClip); - canvas.clipPath(&path, SkClipOp::kIntersect); - SkPaint paint; - paint.setAntiAlias(true); - paint.setTextSize(50); - TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); - TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 200); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - RegionClipStopsMergeTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>(0, 0, 400, 400, [](RenderProperties& props, - RecordingCanvas& canvas) { - SkPaint paint; - paint.setAntiAlias(true); - paint.setTextSize(50); - TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 0); // will be top clipped - TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped - }); - FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - TextMergingTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops"; -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 0, 0, 200, 2000, [](RenderProperties& props, RecordingCanvas& canvas) { - SkPaint textPaint; - textPaint.setAntiAlias(true); - textPaint.setTextSize(20); - textPaint.setFlags(textPaint.getFlags() | SkPaint::kStrikeThruText_ReserveFlag); - for (int i = 0; i < LOOPS; i++) { - TestUtils::drawUtf8ToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1)); - } - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 2000), 200, 2000, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - TextStrikethroughTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2 * LOOPS, renderer.getIndex()) << "Expect number of ops = 2 * loop count"; -} - -static auto styles = {SkPaint::kFill_Style, SkPaint::kStroke_Style, SkPaint::kStrokeAndFill_Style}; - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textStyle) { - class TextStyleTestRenderer : public TestRendererBase { - public: - void onMergedTextOps(const MergedBakedOpList& opList) override { - ASSERT_EQ(0, mIndex); - ASSERT_EQ(3u, opList.count); - mIndex += opList.count; - - int index = 0; - for (auto style : styles) { - auto state = opList.states[index++]; - ASSERT_EQ(style, state->op->paint->getStyle()) - << "Remainder of validation relies upon stable merged order"; - ASSERT_EQ(0, state->computedState.clipSideFlags) - << "Clipped bounds validation requires unclipped ops"; - } - - Rect fill = opList.states[0]->computedState.clippedBounds; - Rect stroke = opList.states[1]->computedState.clippedBounds; - EXPECT_EQ(stroke, opList.states[2]->computedState.clippedBounds) - << "Stroke+Fill should be same as stroke"; - - EXPECT_TRUE(stroke.contains(fill)); - EXPECT_FALSE(fill.contains(stroke)); - - // outset by half the stroke width - Rect outsetFill(fill); - outsetFill.outset(5); - EXPECT_EQ(stroke, outsetFill); - } - }; - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 400, 400, [](RenderProperties& props, RecordingCanvas& canvas) { - SkPaint paint; - paint.setAntiAlias(true); - paint.setTextSize(50); - paint.setStrokeWidth(10); - - // draw 3 copies of the same text overlapping, each with a different style. - // They'll get merged, but with - for (auto style : styles) { - paint.setStyle(style); - TestUtils::drawUtf8ToCanvas(&canvas, "Test string1", paint, 100, 100); - } - }); - FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - TextStyleTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(3, renderer.getIndex()) << "Expect 3 ops"; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_clipLocalMatrix) { - class TextureLayerClipLocalMatrixTestRenderer : public TestRendererBase { - public: - void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipRect()); - EXPECT_EQ(Rect(50, 50, 105, 105), state.computedState.clippedBounds); - - Matrix4 expected; - expected.loadTranslate(5, 5, 0); - EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); - } - }; - - auto layerUpdater = - TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5)); - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(50, 50, 150, 150, SkClipOp::kIntersect); - canvas.drawLayer(layerUpdater.get()); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - TextureLayerClipLocalMatrixTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_combineMatrices) { - class TextureLayerCombineMatricesTestRenderer : public TestRendererBase { - public: - void onTextureLayerOp(const TextureLayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - - Matrix4 expected; - expected.loadTranslate(35, 45, 0); - EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); - } - }; - - auto layerUpdater = - TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5)); - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.translate(30, 40); - canvas.drawLayer(layerUpdater.get()); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - TextureLayerCombineMatricesTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, textureLayer_reject) { - auto layerUpdater = - TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5)); - EXPECT_EQ(Layer::Api::OpenGL, layerUpdater->backingLayer()->getApi()); - - GlLayer* glLayer = static_cast<GlLayer*>(layerUpdater->backingLayer()); - glLayer->setRenderTarget(GL_NONE); // Should be rejected - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [&layerUpdater](RenderProperties& props, RecordingCanvas& canvas) { - canvas.drawLayer(layerUpdater.get()); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - FailRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, functor_reject) { - class FunctorTestRenderer : public TestRendererBase { - public: - void onFunctorOp(const FunctorOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - } - }; - Functor noopFunctor; - - // 1 million pixel tall view, scrolled down 80% - auto scrolledFunctorView = TestUtils::createNode<RecordingCanvas>( - 0, 0, 400, 1000000, [&noopFunctor](RenderProperties& props, RecordingCanvas& canvas) { - canvas.translate(0, -800000); - canvas.callDrawGLFunction(&noopFunctor, nullptr); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(scrolledFunctorView)); - - FunctorTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()) << "Functor should not be rejected"; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, deferColorOp_unbounded) { - class ColorTestRenderer : public TestRendererBase { - public: - void onColorOp(const ColorOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - EXPECT_EQ(Rect(200, 200), state.computedState.clippedBounds) - << "Color op should be expanded to bounds of surrounding"; - } - }; - - auto unclippedColorView = TestUtils::createNode<RecordingCanvas>( - 0, 0, 10, 10, [](RenderProperties& props, RecordingCanvas& canvas) { - props.setClipToBounds(false); - canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(unclippedColorView)); - - ColorTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()) << "ColorOp should not be rejected"; -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 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<RecordingCanvas>( - 0, 0, 200, 200, [&child](RenderProperties& props, RecordingCanvas& canvas) { - SkPaint paint; - paint.setColor(SK_ColorDKGRAY); - canvas.drawRect(0, 0, 200, 200, paint); - - canvas.save(SaveFlags::MatrixClip); - canvas.translate(40, 40); - canvas.drawRenderNode(child.get()); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); - - RenderNodeTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - sk_sp<Bitmap> bitmap(TestUtils::createBitmap(200, 200)); - canvas.drawBitmap(*bitmap, 0, 0, nullptr); - }); - - // clip to small area, should see in receiver - FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 20, 30, 40), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - ClippedTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_simple) { - class SaveLayerSimpleTestRenderer : public TestRendererBase { - public: - OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { - EXPECT_EQ(0, mIndex++); - EXPECT_EQ(180u, width); - EXPECT_EQ(180u, height); - return nullptr; - } - void endLayer() override { EXPECT_EQ(2, mIndex++); } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - EXPECT_EQ(Rect(10, 10, 190, 190), op.unmappedBounds); - EXPECT_EQ(Rect(180, 180), state.computedState.clippedBounds); - EXPECT_EQ(Rect(180, 180), state.computedState.clipRect()); - - Matrix4 expectedTransform; - expectedTransform.loadTranslate(-10, -10, 0); - EXPECT_MATRIX_APPROX_EQ(expectedTransform, state.computedState.transform); - } - void onLayerOp(const LayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(3, mIndex++); - EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds); - EXPECT_EQ(Rect(200, 200), state.computedState.clipRect()); - EXPECT_TRUE(state.computedState.transform.isIdentity()); - } - void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { - EXPECT_EQ(4, mIndex++); - EXPECT_EQ(nullptr, offscreenBuffer); - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.saveLayerAlpha(10, 10, 190, 190, 128, SaveFlags::ClipToLayer); - canvas.drawRect(10, 10, 190, 190, SkPaint()); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SaveLayerSimpleTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(5, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_nested) { - /* saveLayer1 { rect1, saveLayer2 { rect2 } } will play back as: - * - startTemporaryLayer2, rect2 endLayer2 - * - startTemporaryLayer1, rect1, drawLayer2, endLayer1 - * - startFrame, layerOp1, endFrame - */ - class SaveLayerNestedTestRenderer : public TestRendererBase { - public: - OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { - const int index = mIndex++; - if (index == 0) { - EXPECT_EQ(400u, width); - EXPECT_EQ(400u, height); - return (OffscreenBuffer*)0x400; - } else if (index == 3) { - EXPECT_EQ(800u, width); - EXPECT_EQ(800u, height); - return (OffscreenBuffer*)0x800; - } else { - ADD_FAILURE(); - } - return (OffscreenBuffer*)nullptr; - } - void endLayer() override { - int index = mIndex++; - EXPECT_TRUE(index == 2 || index == 6); - } - void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { - EXPECT_EQ(7, mIndex++); - } - void endFrame(const Rect& repaintRect) override { EXPECT_EQ(9, mIndex++); } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - const int index = mIndex++; - if (index == 1) { - EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner rect - } else if (index == 4) { - EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer rect - } else { - ADD_FAILURE(); - } - } - void onLayerOp(const LayerOp& op, const BakedOpState& state) override { - const int index = mIndex++; - if (index == 5) { - EXPECT_EQ((OffscreenBuffer*)0x400, *op.layerHandle); - EXPECT_EQ(Rect(400, 400), op.unmappedBounds); // inner layer - } else if (index == 8) { - EXPECT_EQ((OffscreenBuffer*)0x800, *op.layerHandle); - EXPECT_EQ(Rect(800, 800), op.unmappedBounds); // outer layer - } else { - ADD_FAILURE(); - } - } - void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { - const int index = mIndex++; - // order isn't important, but we need to see both - if (index == 10) { - EXPECT_EQ((OffscreenBuffer*)0x400, offscreenBuffer); - } else if (index == 11) { - EXPECT_EQ((OffscreenBuffer*)0x800, offscreenBuffer); - } else { - ADD_FAILURE(); - } - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 800, 800, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.saveLayerAlpha(0, 0, 800, 800, 128, SaveFlags::ClipToLayer); - { - canvas.drawRect(0, 0, 800, 800, SkPaint()); - canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer); - { canvas.drawRect(0, 0, 400, 400, SkPaint()); } - canvas.restore(); - } - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(800, 800), 800, 800, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SaveLayerNestedTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(12, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayer_contentRejection) { - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(200, 200, 400, 400, SkClipOp::kIntersect); - canvas.saveLayerAlpha(200, 200, 400, 400, 128, SaveFlags::ClipToLayer); - - // draw within save layer may still be recorded, but shouldn't be drawn - canvas.drawRect(200, 200, 400, 400, SkPaint()); - - canvas.restore(); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - FailRenderer renderer; - // should see no ops, even within the layer, since the layer should be rejected - frameBuilder.replayBakedOps<TestDispatcher>(renderer); -} - -RENDERTHREAD_OPENGL_PIPELINE_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(SkBlendMode::kClear, PaintUtils::getBlendModeDirect(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<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0)); - canvas.drawRect(0, 0, 200, 200, SkPaint()); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SaveLayerUnclippedSimpleTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_round) { - class SaveLayerUnclippedRoundTestRenderer : 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) - << "Bounds rect should round out"; - } - void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override {} - void onRectOp(const RectOp& op, const BakedOpState& state) override {} - void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - EXPECT_EQ(Rect(10, 10, 190, 190), state.computedState.clippedBounds) - << "Bounds rect should round out"; - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RenderProperties& props, - RecordingCanvas& canvas) { - canvas.saveLayerAlpha(10.95f, 10.5f, 189.75f, 189.25f, // values should all round out - 128, (SaveFlags::Flags)(0)); - canvas.drawRect(0, 0, 200, 200, SkPaint()); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SaveLayerUnclippedRoundTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - - int restoreTo = canvas.save(SaveFlags::MatrixClip); - canvas.scale(2, 2); - canvas.saveLayerAlpha(0, 0, 5, 5, 128, SaveFlags::MatrixClip); - canvas.saveLayerAlpha(95, 0, 100, 5, 128, SaveFlags::MatrixClip); - canvas.saveLayerAlpha(0, 95, 5, 100, 128, SaveFlags::MatrixClip); - canvas.saveLayerAlpha(95, 95, 100, 100, 128, SaveFlags::MatrixClip); - canvas.drawRect(0, 0, 100, 100, SkPaint()); - canvas.restoreToCount(restoreTo); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SaveLayerUnclippedMergedClearsTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(10, renderer.getIndex()) - << "Expect 4 copyTos, 4 copyFroms, 1 clear SimpleRects, and 1 rect."; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_clearClip) { - class SaveLayerUnclippedClearClipTestRenderer : public TestRendererBase { - public: - void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - } - void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - ASSERT_NE(nullptr, op.paint); - EXPECT_EQ(SkBlendMode::kClear, PaintUtils::getBlendModeDirect(op.paint)); - EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clippedBounds) - << "Expect dirty rect as clip"; - ASSERT_NE(nullptr, state.computedState.clipState); - EXPECT_EQ(Rect(50, 50, 150, 150), state.computedState.clipState->rect); - EXPECT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode); - } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(2, mIndex++); - } - void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(3, mIndex++); - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - // save smaller than clip, so we get unclipped behavior - canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0)); - canvas.drawRect(0, 0, 200, 200, SkPaint()); - canvas.restore(); - }); - - // draw with partial screen dirty, and assert we see that rect later - FrameBuilder frameBuilder(SkRect::MakeLTRB(50, 50, 150, 150), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SaveLayerUnclippedClearClipTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_reject) { - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - // unclipped savelayer + rect both in area that won't intersect with dirty - canvas.saveLayerAlpha(100, 100, 200, 200, 128, (SaveFlags::Flags)(0)); - canvas.drawRect(100, 100, 200, 200, SkPaint()); - canvas.restore(); - }); - - // draw with partial screen dirty that doesn't intersect with savelayer - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - FailRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); -} - -/* saveLayerUnclipped { saveLayer { saveLayerUnclipped { rect } } } will play back as: - * - startTemporaryLayer, onCopyToLayer, onSimpleRects, onRect, onCopyFromLayer, endLayer - * - startFrame, onCopyToLayer, onSimpleRects, drawLayer, onCopyFromLayer, endframe - */ -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, saveLayerUnclipped_complex) { - class SaveLayerUnclippedComplexTestRenderer : public TestRendererBase { - public: - OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) { - EXPECT_EQ(0, mIndex++); // savelayer first - return (OffscreenBuffer*)0xabcd; - } - void onCopyToLayerOp(const CopyToLayerOp& op, const BakedOpState& state) override { - int index = mIndex++; - EXPECT_TRUE(index == 1 || index == 7); - } - void onSimpleRectsOp(const SimpleRectsOp& op, const BakedOpState& state) override { - int index = mIndex++; - EXPECT_TRUE(index == 2 || index == 8); - } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(3, mIndex++); - Matrix4 expected; - expected.loadTranslate(-100, -100, 0); - EXPECT_EQ(Rect(100, 100, 200, 200), state.computedState.clippedBounds); - EXPECT_MATRIX_APPROX_EQ(expected, state.computedState.transform); - } - void onCopyFromLayerOp(const CopyFromLayerOp& op, const BakedOpState& state) override { - int index = mIndex++; - EXPECT_TRUE(index == 4 || index == 10); - } - void endLayer() override { EXPECT_EQ(5, mIndex++); } - void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { - EXPECT_EQ(6, mIndex++); - } - void onLayerOp(const LayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(9, mIndex++); - EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle); - } - void endFrame(const Rect& repaintRect) override { EXPECT_EQ(11, mIndex++); } - void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { - EXPECT_EQ(12, mIndex++); - EXPECT_EQ((OffscreenBuffer*)0xabcd, offscreenBuffer); - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 600, 600, // 500x500 triggers clipping - [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.saveLayerAlpha(0, 0, 500, 500, 128, (SaveFlags::Flags)0); // unclipped - canvas.saveLayerAlpha(100, 100, 400, 400, 128, SaveFlags::ClipToLayer); // clipped - canvas.saveLayerAlpha(200, 200, 300, 300, 128, (SaveFlags::Flags)0); // unclipped - canvas.drawRect(200, 200, 300, 300, SkPaint()); - canvas.restore(); - canvas.restore(); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(600, 600), 600, 600, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - SaveLayerUnclippedComplexTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(13, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 10, 10, 110, 110, [](RenderProperties& props, RecordingCanvas& canvas) { - props.mutateLayerProperties().setType(LayerType::RenderLayer); - SkPaint paint; - paint.setColor(SK_ColorWHITE); - canvas.drawRect(0, 0, 100, 100, paint); - }); - OffscreenBuffer** layerHandle = node->getLayerHandle(); - - // create RenderNode's layer here in same way prepareTree would - OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); - *layerHandle = &layer; - - auto syncedNode = TestUtils::getSyncedNode(node); - - // only enqueue partial damage - LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid - layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferLayers(layerUpdateQueue); - frameBuilder.deferRenderNode(*syncedNode); - - HwLayerSimpleTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(6, renderer.getIndex()); - - // clean up layer pointer, so we can safely destruct RenderNode - *layerHandle = nullptr; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, hwLayer_complex) { - /* parentLayer { greyRect, saveLayer { childLayer { whiteRect } } } will play back as: - * - startRepaintLayer(child), rect(grey), endLayer - * - startTemporaryLayer, drawLayer(child), endLayer - * - startRepaintLayer(parent), rect(white), drawLayer(saveLayer), endLayer - * - startFrame, drawLayer(parent), endLayerb - */ - class HwLayerComplexTestRenderer : public TestRendererBase { - public: - OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) { - EXPECT_EQ(3, mIndex++); // savelayer first - return (OffscreenBuffer*)0xabcd; - } - void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { - int index = mIndex++; - if (index == 0) { - // starting inner layer - EXPECT_EQ(100u, offscreenBuffer->viewportWidth); - EXPECT_EQ(100u, offscreenBuffer->viewportHeight); - } else if (index == 6) { - // starting outer layer - EXPECT_EQ(200u, offscreenBuffer->viewportWidth); - EXPECT_EQ(200u, offscreenBuffer->viewportHeight); - } else { - ADD_FAILURE(); - } - } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - int index = mIndex++; - if (index == 1) { - // inner layer's rect (white) - EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); - } else if (index == 7) { - // outer layer's rect (grey) - EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); - } else { - ADD_FAILURE(); - } - } - void endLayer() override { - int index = mIndex++; - EXPECT_TRUE(index == 2 || index == 5 || index == 9); - } - void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { - EXPECT_EQ(10, mIndex++); - } - void onLayerOp(const LayerOp& op, const BakedOpState& state) override { - OffscreenBuffer* layer = *op.layerHandle; - int index = mIndex++; - if (index == 4) { - EXPECT_EQ(100u, layer->viewportWidth); - EXPECT_EQ(100u, layer->viewportHeight); - } else if (index == 8) { - EXPECT_EQ((OffscreenBuffer*)0xabcd, *op.layerHandle); - } else if (index == 11) { - EXPECT_EQ(200u, layer->viewportWidth); - EXPECT_EQ(200u, layer->viewportHeight); - } else { - ADD_FAILURE(); - } - } - void endFrame(const Rect& repaintRect) override { EXPECT_EQ(12, mIndex++); } - void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { - EXPECT_EQ(13, mIndex++); - } - }; - - auto child = TestUtils::createNode<RecordingCanvas>( - 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<RecordingCanvas>( - 0, 0, 200, 200, [childPtr](RenderProperties& props, RecordingCanvas& canvas) { - props.mutateLayerProperties().setType(LayerType::RenderLayer); - SkPaint paint; - paint.setColor(SK_ColorDKGRAY); - canvas.drawRect(0, 0, 200, 200, paint); - - canvas.saveLayerAlpha(50, 50, 150, 150, 128, SaveFlags::ClipToLayer); - canvas.drawRenderNode(childPtr); - canvas.restore(); - }); - OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200); - *(parent->getLayerHandle()) = &parentLayer; - - auto syncedNode = TestUtils::getSyncedNode(parent); - - LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid - layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100)); - layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(200, 200)); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferLayers(layerUpdateQueue); - frameBuilder.deferRenderNode(*syncedNode); - - HwLayerComplexTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(14, renderer.getIndex()); - - // clean up layer pointers, so we can safely destruct RenderNodes - *(child->getLayerHandle()) = nullptr; - *(parent->getLayerHandle()) = nullptr; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, buildLayer) { - class BuildLayerTestRenderer : public TestRendererBase { - public: - void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { - EXPECT_EQ(0, mIndex++); - EXPECT_EQ(100u, offscreenBuffer->viewportWidth); - EXPECT_EQ(100u, offscreenBuffer->viewportHeight); - EXPECT_EQ(Rect(25, 25, 75, 75), repaintRect); - } - void onColorOp(const ColorOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - - EXPECT_TRUE(state.computedState.transform.isIdentity()) - << "Transform should be reset within layer"; - - EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipRect()) - << "Damage rect should be used to clip layer content"; - } - void endLayer() override { EXPECT_EQ(2, mIndex++); } - void startFrame(uint32_t width, uint32_t height, const Rect& repaintRect) override { - ADD_FAILURE() << "Primary frame draw not expected in this test"; - } - void endFrame(const Rect& repaintRect) override { - ADD_FAILURE() << "Primary frame draw not expected in this test"; - } - }; - - auto node = TestUtils::createNode<RecordingCanvas>( - 10, 10, 110, 110, [](RenderProperties& props, RecordingCanvas& canvas) { - props.mutateLayerProperties().setType(LayerType::RenderLayer); - canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); - }); - OffscreenBuffer** layerHandle = node->getLayerHandle(); - - // create RenderNode's layer here in same way prepareTree would - OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); - *layerHandle = &layer; - - TestUtils::syncHierarchyPropertiesAndDisplayList(node); - - // only enqueue partial damage - LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid - layerUpdateQueue.enqueueLayerWithDamage(node.get(), Rect(25, 25, 75, 75)); - - // Draw, but pass empty node list, so no work is done for primary frame - FrameBuilder frameBuilder(layerUpdateQueue, sLightGeometry, Caches::getInstance()); - BuildLayerTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(3, renderer.getIndex()); - - // clean up layer pointer, so we can safely destruct RenderNode - *layerHandle = nullptr; -} - -namespace { - -static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) { - SkPaint paint; - // order put in blue channel, transparent so overlapped content doesn't get rejected - paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder)); - canvas->drawRect(0, 0, 100, 100, paint); -} -static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder, float z) { - auto node = TestUtils::createNode<RecordingCanvas>( - 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 -} - -static void drawOrderedNode( - Canvas* canvas, uint8_t expectedDrawOrder, - std::function<void(RenderProperties& props, RecordingCanvas& canvas)> setup) { - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, - [expectedDrawOrder, setup](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedRect(&canvas, expectedDrawOrder); - if (setup) { - setup(props, canvas); - } - }); - canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership -} - -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"; - } -}; - -} // end anonymous namespace - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, zReorder) { - auto parent = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.insertReorderBarrier(true); - canvas.insertReorderBarrier(false); - 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 - canvas.insertReorderBarrier(true); // reorder a node ahead of drawrect op - drawOrderedRect(&canvas, 11); - drawOrderedNode(&canvas, 10, -1.0f); - canvas.insertReorderBarrier(false); - canvas.insertReorderBarrier(true); // test with two empty reorder sections - canvas.insertReorderBarrier(true); - canvas.insertReorderBarrier(false); - drawOrderedRect(&canvas, 12); - }); - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(13, renderer.getIndex()); -}; - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorder) { - static const int scrollX = 5; - static const int scrollY = 10; - class ProjectionReorderTestRenderer : public TestRendererBase { - public: - void onRectOp(const RectOp& op, const BakedOpState& state) override { - const int index = mIndex++; - - Matrix4 expectedMatrix; - switch (index) { - case 0: - EXPECT_EQ(Rect(100, 100), op.unmappedBounds); - EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); - expectedMatrix.loadIdentity(); - EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask); - break; - case 1: - EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds); - EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor()); - expectedMatrix.loadTranslate(50 - scrollX, 50 - scrollY, 0); - ASSERT_NE(nullptr, state.computedState.localProjectionPathMask); - EXPECT_EQ(Rect(-35, -30, 45, 50), - Rect(state.computedState.localProjectionPathMask->getBounds())); - break; - case 2: - EXPECT_EQ(Rect(100, 50), op.unmappedBounds); - EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); - expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0); - EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask); - break; - default: - ADD_FAILURE(); - } - EXPECT_EQ(expectedMatrix, state.computedState.transform); - } - }; - - /** - * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C) - * with a projecting child (P) of its own. P would normally draw between B and C's "background" - * draw, but because it is projected backwards, it's drawn in between B and C. - * - * The parent is scrolled by scrollX/scrollY, but this does not affect the background - * (which isn't affected by scroll). - */ - auto receiverBackground = TestUtils::createNode<RecordingCanvas>( - 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<RecordingCanvas>( - 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<RecordingCanvas>( - 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<RecordingCanvas>( - 0, 0, 100, 100, - [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { - // Set a rect outline for the projecting ripple to be masked against. - properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f); - - canvas.save(SaveFlags::MatrixClip); - canvas.translate(-scrollX, - -scrollY); // Apply scroll (note: bg undoes this internally) - canvas.drawRenderNode(receiverBackground.get()); - canvas.drawRenderNode(child.get()); - canvas.restore(); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); - - ProjectionReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(3, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionHwLayer) { - static const int scrollX = 5; - static const int scrollY = 10; - class ProjectionHwLayerTestRenderer : public TestRendererBase { - public: - void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { - EXPECT_EQ(0, mIndex++); - } - void onArcOp(const ArcOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); - } - void endLayer() override { EXPECT_EQ(2, mIndex++); } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(3, mIndex++); - ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); - } - void onOvalOp(const OvalOp& op, const BakedOpState& state) override { - EXPECT_EQ(4, mIndex++); - ASSERT_NE(nullptr, state.computedState.localProjectionPathMask); - Matrix4 expected; - expected.loadTranslate(100 - scrollX, 100 - scrollY, 0); - EXPECT_EQ(expected, state.computedState.transform); - EXPECT_EQ(Rect(-85, -80, 295, 300), - Rect(state.computedState.localProjectionPathMask->getBounds())); - } - void onLayerOp(const LayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(5, mIndex++); - ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask); - } - }; - auto receiverBackground = TestUtils::createNode<RecordingCanvas>( - 0, 0, 400, 400, [](RenderProperties& properties, RecordingCanvas& canvas) { - properties.setProjectionReceiver(true); - // scroll doesn't apply to background, so undone via translationX/Y - // NOTE: translationX/Y only! no other transform properties may be set for a proj - // receiver! - properties.setTranslationX(scrollX); - properties.setTranslationY(scrollY); - - canvas.drawRect(0, 0, 400, 400, SkPaint()); - }); - auto projectingRipple = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& properties, RecordingCanvas& canvas) { - properties.setProjectBackwards(true); - properties.setClipToBounds(false); - canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds - }); - auto child = TestUtils::createNode<RecordingCanvas>( - 100, 100, 300, 300, - [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { - properties.mutateLayerProperties().setType(LayerType::RenderLayer); - canvas.drawRenderNode(projectingRipple.get()); - canvas.drawArc(0, 0, 200, 200, 0.0f, 280.0f, true, SkPaint()); - }); - auto parent = TestUtils::createNode<RecordingCanvas>( - 0, 0, 400, 400, - [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { - // Set a rect outline for the projecting ripple to be masked against. - properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f); - canvas.translate(-scrollX, - -scrollY); // Apply scroll (note: bg undoes this internally) - canvas.drawRenderNode(receiverBackground.get()); - canvas.drawRenderNode(child.get()); - }); - - OffscreenBuffer** layerHandle = child->getLayerHandle(); - - // create RenderNode's layer here in same way prepareTree would, setting windowTransform - OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200, 200); - Matrix4 windowTransform; - windowTransform.loadTranslate(100, 100, 0); // total transform of layer's origin - layer.setWindowTransform(windowTransform); - *layerHandle = &layer; - - auto syncedNode = TestUtils::getSyncedNode(parent); - - LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid - layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200)); - - FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferLayers(layerUpdateQueue); - frameBuilder.deferRenderNode(*syncedNode); - - ProjectionHwLayerTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(6, renderer.getIndex()); - - // clean up layer pointer, so we can safely destruct RenderNode - *layerHandle = nullptr; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionChildScroll) { - static const int scrollX = 500000; - static const int scrollY = 0; - class ProjectionChildScrollTestRenderer : public TestRendererBase { - public: - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - EXPECT_TRUE(state.computedState.transform.isIdentity()); - } - void onOvalOp(const OvalOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - ASSERT_NE(nullptr, state.computedState.clipState); - ASSERT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode); - ASSERT_EQ(Rect(400, 400), state.computedState.clipState->rect); - EXPECT_TRUE(state.computedState.transform.isIdentity()); - } - }; - auto receiverBackground = TestUtils::createNode<RecordingCanvas>( - 0, 0, 400, 400, [](RenderProperties& properties, RecordingCanvas& canvas) { - properties.setProjectionReceiver(true); - canvas.drawRect(0, 0, 400, 400, SkPaint()); - }); - auto projectingRipple = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& properties, RecordingCanvas& canvas) { - // scroll doesn't apply to background, so undone via translationX/Y - // NOTE: translationX/Y only! no other transform properties may be set for a proj - // receiver! - properties.setTranslationX(scrollX); - properties.setTranslationY(scrollY); - properties.setProjectBackwards(true); - properties.setClipToBounds(false); - canvas.drawOval(0, 0, 200, 200, SkPaint()); - }); - auto child = TestUtils::createNode<RecordingCanvas>( - 0, 0, 400, 400, - [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) { - // Record time clip will be ignored by projectee - canvas.clipRect(100, 100, 300, 300, SkClipOp::kIntersect); - - canvas.translate(-scrollX, - -scrollY); // Apply scroll (note: bg undoes this internally) - canvas.drawRenderNode(projectingRipple.get()); - }); - auto parent = TestUtils::createNode<RecordingCanvas>( - 0, 0, 400, 400, - [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) { - canvas.drawRenderNode(receiverBackground.get()); - canvas.drawRenderNode(child.get()); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(400, 400), 400, 400, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); - - ProjectionChildScrollTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); -} - -// creates a 100x100 shadow casting node with provided translationZ -static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) { - return TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [translationZ](RenderProperties& properties, RecordingCanvas& canvas) { - properties.setTranslationZ(translationZ); - properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f); - SkPaint paint; - paint.setColor(SK_ColorWHITE); - canvas.drawRect(0, 0, 100, 100, paint); - }); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadow) { - class ShadowTestRenderer : public TestRendererBase { - public: - void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - EXPECT_FLOAT_EQ(1.0f, op.casterAlpha); - EXPECT_TRUE(op.shadowTask->casterPerimeter.isRect(nullptr)); - EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.shadowTask->transformXY); - - Matrix4 expectedZ; - expectedZ.loadTranslate(0, 0, 5); - EXPECT_MATRIX_APPROX_EQ(expectedZ, op.shadowTask->transformZ); - } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - } - }; - - auto parent = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.insertReorderBarrier(true); - canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); - - ShadowTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowSaveLayer) { - class ShadowSaveLayerTestRenderer : public TestRendererBase { - public: - OffscreenBuffer* startTemporaryLayer(uint32_t width, uint32_t height) override { - EXPECT_EQ(0, mIndex++); - return nullptr; - } - void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x); - EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y); - } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(2, mIndex++); - } - void endLayer() override { EXPECT_EQ(3, mIndex++); } - void onLayerOp(const LayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(4, mIndex++); - } - void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { - EXPECT_EQ(5, mIndex++); - } - }; - - auto parent = TestUtils::createNode<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - // save/restore outside of reorderBarrier, so they don't get moved out of place - canvas.translate(20, 10); - int count = canvas.saveLayerAlpha(30, 50, 130, 150, 128, SaveFlags::ClipToLayer); - canvas.insertReorderBarrier(true); - canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); - canvas.insertReorderBarrier(false); - canvas.restoreToCount(count); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, - (FrameBuilder::LightGeometry){{100, 100, 100}, 50}, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); - - ShadowSaveLayerTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(6, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowHwLayer) { - class ShadowHwLayerTestRenderer : public TestRendererBase { - public: - void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override { - EXPECT_EQ(0, mIndex++); - } - void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - EXPECT_FLOAT_EQ(50, op.shadowTask->lightCenter.x); - EXPECT_FLOAT_EQ(40, op.shadowTask->lightCenter.y); - EXPECT_FLOAT_EQ(30, op.shadowTask->lightRadius); - } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(2, mIndex++); - } - void endLayer() override { EXPECT_EQ(3, mIndex++); } - void onLayerOp(const LayerOp& op, const BakedOpState& state) override { - EXPECT_EQ(4, mIndex++); - } - }; - - auto parent = TestUtils::createNode<RecordingCanvas>( - 50, 60, 150, 160, [](RenderProperties& props, RecordingCanvas& canvas) { - props.mutateLayerProperties().setType(LayerType::RenderLayer); - canvas.insertReorderBarrier(true); - canvas.save(SaveFlags::MatrixClip); - canvas.translate(20, 10); - canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); - canvas.restore(); - }); - OffscreenBuffer** layerHandle = parent->getLayerHandle(); - - // create RenderNode's layer here in same way prepareTree would, setting windowTransform - OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100); - Matrix4 windowTransform; - windowTransform.loadTranslate(50, 60, 0); // total transform of layer's origin - layer.setWindowTransform(windowTransform); - *layerHandle = &layer; - - auto syncedNode = TestUtils::getSyncedNode(parent); - LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid - layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100)); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, - (FrameBuilder::LightGeometry){{100, 100, 100}, 30}, - Caches::getInstance()); - frameBuilder.deferLayers(layerUpdateQueue); - frameBuilder.deferRenderNode(*syncedNode); - - ShadowHwLayerTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(5, renderer.getIndex()); - - // clean up layer pointer, so we can safely destruct RenderNode - *layerHandle = nullptr; -} - -RENDERTHREAD_OPENGL_PIPELINE_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<RecordingCanvas>( - 0, 0, 200, 200, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.insertReorderBarrier(true); - canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); - canvas.drawRenderNode(createWhiteRectShadowCaster(5.0001f).get()); - }); - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, - (FrameBuilder::LightGeometry){{100, 100, 100}, 50}, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); - - ShadowLayeringTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, shadowClipping) { - class ShadowClippingTestRenderer : public TestRendererBase { - public: - void onShadowOp(const ShadowOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - EXPECT_EQ(Rect(25, 25, 75, 75), state.computedState.clipState->rect) - << "Shadow must respect pre-barrier canvas clip value."; - } - void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_EQ(1, mIndex++); - } - }; - auto parent = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - // Apply a clip before the reorder barrier/shadow casting child is drawn. - // This clip must be applied to the shadow cast by the child. - canvas.clipRect(25, 25, 75, 75, SkClipOp::kIntersect); - canvas.insertReorderBarrier(true); - canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get()); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, - (FrameBuilder::LightGeometry){{100, 100, 100}, 50}, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(parent)); - - ShadowClippingTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); -} - -static void testProperty( - std::function<void(RenderProperties&)> propSetupCallback, - std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) { - class PropertyTestRenderer : public TestRendererBase { - public: - explicit 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<RecordingCanvas>( - 0, 0, 100, 100, [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) { - propSetupCallback(props); - SkPaint paint; - paint.setColor(SK_ColorWHITE); - canvas.drawRect(0, 0, 100, 100, paint); - }); - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - PropertyTestRenderer renderer(opValidateCallback); - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op"; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropOverlappingRenderingAlpha) { - testProperty( - [](RenderProperties& properties) { - properties.setAlpha(0.5f); - properties.setHasOverlappingRendering(false); - }, - [](const RectOp& op, const BakedOpState& state) { - EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op"; - }); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropClipping) { - testProperty( - [](RenderProperties& properties) { - properties.setClipToBounds(true); - properties.setClipBounds(Rect(10, 20, 300, 400)); - }, - [](const RectOp& op, const BakedOpState& state) { - EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds) - << "Clip rect should be intersection of node bounds and clip bounds"; - }); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropRevealClip) { - testProperty( - [](RenderProperties& properties) { - properties.mutableRevealClip().set(true, 50, 50, 25); - }, - [](const RectOp& op, const BakedOpState& state) { - ASSERT_NE(nullptr, state.roundRectClipState); - EXPECT_TRUE(state.roundRectClipState->highPriority); - EXPECT_EQ(25, state.roundRectClipState->radius); - EXPECT_EQ(Rect(50, 50, 50, 50), state.roundRectClipState->innerRect); - }); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropOutlineClip) { - testProperty( - [](RenderProperties& properties) { - properties.mutableOutline().setShouldClip(true); - properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f); - }, - [](const RectOp& op, const BakedOpState& state) { - ASSERT_NE(nullptr, state.roundRectClipState); - EXPECT_FALSE(state.roundRectClipState->highPriority); - EXPECT_EQ(5, state.roundRectClipState->radius); - EXPECT_EQ(Rect(15, 25, 25, 35), state.roundRectClipState->innerRect); - }); -} - -RENDERTHREAD_OPENGL_PIPELINE_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; - Matrix4 drawLayerMatrix; -}; -/** - * 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: - explicit 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++); - mOutData->drawLayerMatrix = state.computedState.transform; - } - void recycleTemporaryLayer(OffscreenBuffer* offscreenBuffer) override { - EXPECT_EQ(4, mIndex++); - } - - private: - SaveLayerAlphaData* mOutData; - }; - - ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize()) - << "Node must be bigger than max texture size to exercise saveLayer codepath"; - auto node = TestUtils::createNode<RecordingCanvas>( - 0, 0, 10000, 10000, - [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) { - properties.setHasOverlappingRendering(true); - properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer - // apply other properties - propSetupCallback(properties); - - SkPaint paint; - paint.setColor(SK_ColorWHITE); - canvas.drawRect(0, 0, 10000, 10000, paint); - }); - auto syncedNode = TestUtils::getSyncedNode(node); // sync before querying height - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*syncedNode); - - SaveLayerAlphaClipTestRenderer renderer(outObservedData); - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - - // assert, since output won't be valid if we haven't seen a save layer triggered - ASSERT_EQ(5, renderer.getIndex()) << "Test must trigger saveLayer alpha behavior."; -} - -RENDERTHREAD_OPENGL_PIPELINE_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"; - expected.loadTranslate(10, 0, 0); - EXPECT_MATRIX_APPROX_EQ(expected, observedData.drawLayerMatrix) - << "expect drawLayer to be translated as part of being clipped"; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropSaveLayerAlphaRotate) { - SaveLayerAlphaData observedData; - testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { - // Translate and rotate the view so that the only visible part is the top left corner of - // the view. It will form an isosceles right triangle with a long side length of 200 at the - // bottom of the viewport. - properties.setTranslationX(100); - properties.setTranslationY(100); - properties.setPivotX(0); - properties.setPivotY(0); - properties.setRotation(45); - }); - // ceil(sqrt(2) / 2 * 200) = 142 - EXPECT_EQ(142u, observedData.layerWidth); - EXPECT_EQ(142u, observedData.layerHeight); - EXPECT_EQ(Rect(142, 142), observedData.rectClippedBounds); - EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, renderPropSaveLayerAlphaScale) { - SaveLayerAlphaData observedData; - testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) { - properties.setPivotX(0); - properties.setPivotY(0); - properties.setScaleX(2); - properties.setScaleY(0.5f); - }); - EXPECT_EQ(100u, observedData.layerWidth); - EXPECT_EQ(400u, observedData.layerHeight); - EXPECT_EQ(Rect(100, 400), observedData.rectClippedBounds); - EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), observedData.rectMatrix); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clip_replace) { - class ClipReplaceTestRenderer : public TestRendererBase { - public: - void onColorOp(const ColorOp& op, const BakedOpState& state) override { - EXPECT_EQ(0, mIndex++); - EXPECT_TRUE(op.localClip->intersectWithRoot); - EXPECT_EQ(Rect(20, 10, 30, 40), state.computedState.clipState->rect) - << "Expect resolved clip to be intersection of viewport clip and clip op"; - } - }; - auto node = TestUtils::createNode<RecordingCanvas>( - 20, 20, 30, 30, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace_deprecated); - canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); - }); - - FrameBuilder frameBuilder(SkRect::MakeLTRB(10, 10, 40, 40), 50, 50, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - - ClipReplaceTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(1, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedInMiddle) { - /* R is backward projected on B - A - / \ - B C - | - R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { - props.setProjectionReceiver(true); - }); // nodeB - drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 1, - [](RenderProperties& props, RecordingCanvas& canvas) { - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeC - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(3, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectLast) { - /* R is backward projected on E - A - / | \ - / | \ - B C E - | - R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, nullptr); // nodeB - drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 3, [](RenderProperties& props, - RecordingCanvas& canvas) { // drawn as 2 - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeC - drawOrderedNode(&canvas, 2, [](RenderProperties& props, - RecordingCanvas& canvas) { // drawn as 3 - props.setProjectionReceiver(true); - }); // nodeE - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderNoReceivable) { - /* R is backward projected without receiver - A - / \ - B C - | - R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, nullptr); // nodeB - drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 255, - [](RenderProperties& props, RecordingCanvas& canvas) { - // not having a projection receiver is an undefined behavior - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeC - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderParentReceivable) { - /* R is backward projected on C - A - / \ - B C - | - R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, nullptr); // nodeB - drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { - props.setProjectionReceiver(true); - drawOrderedNode(&canvas, 2, - [](RenderProperties& props, RecordingCanvas& canvas) { - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeC - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(3, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderSameNodeReceivable) { - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, nullptr); // nodeB - drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 255, - [](RenderProperties& props, RecordingCanvas& canvas) { - // having a node that is projected on itself is an - // undefined/unexpected behavior - props.setProjectionReceiver(true); - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeC - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedSibling) { - // TODO: this test together with the next "projectionReorderProjectedSibling2" likely expose a - // bug in HWUI. First test draws R, while the second test does not draw R for a nearly identical - // tree setup. The correct behaviour is to not draw R, because the receiver cannot be a sibling - /* R is backward projected on B. R is not expected to be drawn (see Sibling2 outcome below), - but for some reason it is drawn. - A - /|\ - / | \ - B C R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { - props.setProjectionReceiver(true); - }); // nodeB - drawOrderedNode(&canvas, 2, - [](RenderProperties& props, RecordingCanvas& canvas) {}); // nodeC - drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(3, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderProjectedSibling2) { - /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed. - A - | - G - /|\ - / | \ - B C R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, [](RenderProperties& props, - RecordingCanvas& canvas) { // G - drawOrderedNode(&canvas, 1, - [](RenderProperties& props, RecordingCanvas& canvas) { // B - props.setProjectionReceiver(true); - }); // nodeB - drawOrderedNode(&canvas, 2, - [](RenderProperties& props, RecordingCanvas& canvas) { // C - }); // nodeC - drawOrderedNode(&canvas, 255, - [](RenderProperties& props, RecordingCanvas& canvas) { // R - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeG - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(3, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderGrandparentReceivable) { - /* R is backward projected on B - A - | - B - | - C - | - R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { - props.setProjectionReceiver(true); - drawOrderedNode(&canvas, 1, - [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 2, [](RenderProperties& props, - RecordingCanvas& canvas) { - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeC - }); // nodeB - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(3, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivables) { - /* B and G are receivables, R is backward projected - A - / \ - B C - / \ - G R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, - [](RenderProperties& props, RecordingCanvas& canvas) { // B - props.setProjectionReceiver(true); - }); // nodeB - drawOrderedNode(&canvas, 2, [](RenderProperties& props, - RecordingCanvas& canvas) { // C - drawOrderedNode(&canvas, 3, - [](RenderProperties& props, RecordingCanvas& canvas) { // G - props.setProjectionReceiver(true); - }); // nodeG - drawOrderedNode(&canvas, 1, - [](RenderProperties& props, RecordingCanvas& canvas) { // R - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeC - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivablesLikelyScenario) { - /* B and G are receivables, G is backward projected - A - / \ - B C - / \ - G R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, - [](RenderProperties& props, RecordingCanvas& canvas) { // B - props.setProjectionReceiver(true); - }); // nodeB - drawOrderedNode(&canvas, 2, [](RenderProperties& props, - RecordingCanvas& canvas) { // C - drawOrderedNode(&canvas, 1, - [](RenderProperties& props, RecordingCanvas& canvas) { // G - props.setProjectionReceiver(true); - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeG - drawOrderedNode(&canvas, 3, - [](RenderProperties& props, RecordingCanvas& canvas) { // R - }); // nodeR - }); // nodeC - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(4, renderer.getIndex()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, projectionReorderTwoReceivablesDeeper) { - /* B and G are receivables, R is backward projected - A - / \ - B C - / \ - G D - | - R - */ - auto nodeA = TestUtils::createNode<RecordingCanvas>( - 0, 0, 100, 100, [](RenderProperties& props, RecordingCanvas& canvas) { - drawOrderedNode(&canvas, 0, - [](RenderProperties& props, RecordingCanvas& canvas) { // B - props.setProjectionReceiver(true); - }); // nodeB - drawOrderedNode(&canvas, 1, [](RenderProperties& props, - RecordingCanvas& canvas) { // C - drawOrderedNode(&canvas, 2, - [](RenderProperties& props, RecordingCanvas& canvas) { // G - props.setProjectionReceiver(true); - }); // nodeG - drawOrderedNode( - &canvas, 4, [](RenderProperties& props, RecordingCanvas& canvas) { // D - drawOrderedNode(&canvas, 3, [](RenderProperties& props, - RecordingCanvas& canvas) { // R - props.setProjectBackwards(true); - props.setClipToBounds(false); - }); // nodeR - }); // nodeD - }); // nodeC - }); // nodeA - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometry, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA)); - - ZReorderTestRenderer renderer; - frameBuilder.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(5, renderer.getIndex()); -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/tests/unit/GlopBuilderTests.cpp b/libs/hwui/tests/unit/GlopBuilderTests.cpp deleted file mode 100644 index c8bfc99fac92..000000000000 --- a/libs/hwui/tests/unit/GlopBuilderTests.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> - -#include "Glop.h" -#include "GlopBuilder.h" -#include "Rect.h" -#include "tests/common/TestUtils.h" -#include "utils/Color.h" - -#include <SkPaint.h> - -using namespace android::uirenderer; - -static void expectFillEq(Glop::Fill& expectedFill, Glop::Fill& builtFill) { - EXPECT_EQ(expectedFill.colorEnabled, builtFill.colorEnabled); - if (expectedFill.colorEnabled) EXPECT_EQ(expectedFill.color, builtFill.color); - - EXPECT_EQ(expectedFill.filterMode, builtFill.filterMode); - if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Blend) { - EXPECT_EQ(expectedFill.filter.color, builtFill.filter.color); - } else if (expectedFill.filterMode == ProgramDescription::ColorFilterMode::Matrix) { - Glop::Fill::Filter::Matrix& expectedMatrix = expectedFill.filter.matrix; - Glop::Fill::Filter::Matrix& builtMatrix = expectedFill.filter.matrix; - EXPECT_TRUE(std::memcmp(expectedMatrix.matrix, builtMatrix.matrix, - sizeof(Glop::Fill::Filter::Matrix::matrix))); - EXPECT_TRUE(std::memcmp(expectedMatrix.vector, builtMatrix.vector, - sizeof(Glop::Fill::Filter::Matrix::vector))); - } - EXPECT_EQ(expectedFill.skiaShaderData.skiaShaderType, builtFill.skiaShaderData.skiaShaderType); - EXPECT_EQ(expectedFill.texture.clamp, builtFill.texture.clamp); - EXPECT_EQ(expectedFill.texture.filter, builtFill.texture.filter); - EXPECT_TRUE((expectedFill.texture.texture && builtFill.texture.texture) || - (!expectedFill.texture.texture && !builtFill.texture.texture)); - if (expectedFill.texture.texture) { - EXPECT_EQ(expectedFill.texture.texture->target(), builtFill.texture.texture->target()); - } - EXPECT_EQ(expectedFill.texture.textureTransform, builtFill.texture.textureTransform); -} - -static void expectBlendEq(Glop::Blend& expectedBlend, Glop::Blend& builtBlend) { - EXPECT_EQ(expectedBlend.src, builtBlend.src); - EXPECT_EQ(expectedBlend.dst, builtBlend.dst); -} - -static void expectMeshEq(Glop::Mesh& expectedMesh, Glop::Mesh& builtMesh) { - EXPECT_EQ(expectedMesh.elementCount, builtMesh.elementCount); - EXPECT_EQ(expectedMesh.primitiveMode, builtMesh.primitiveMode); - EXPECT_EQ(expectedMesh.indices.indices, builtMesh.indices.indices); - EXPECT_EQ(expectedMesh.indices.bufferObject, builtMesh.indices.bufferObject); - EXPECT_EQ(expectedMesh.vertices.attribFlags, builtMesh.vertices.attribFlags); - EXPECT_EQ(expectedMesh.vertices.bufferObject, builtMesh.vertices.bufferObject); - EXPECT_EQ(expectedMesh.vertices.color, builtMesh.vertices.color); - EXPECT_EQ(expectedMesh.vertices.position, builtMesh.vertices.position); - EXPECT_EQ(expectedMesh.vertices.stride, builtMesh.vertices.stride); - EXPECT_EQ(expectedMesh.vertices.texCoord, builtMesh.vertices.texCoord); - - if (builtMesh.vertices.position) { - for (int i = 0; i < 4; i++) { - TextureVertex& expectedVertex = expectedMesh.mappedVertices[i]; - TextureVertex& builtVertex = builtMesh.mappedVertices[i]; - EXPECT_EQ(expectedVertex.u, builtVertex.u); - EXPECT_EQ(expectedVertex.v, builtVertex.v); - EXPECT_EQ(expectedVertex.x, builtVertex.x); - EXPECT_EQ(expectedVertex.y, builtVertex.y); - } - } -} - -static void expectTransformEq(Glop::Transform& expectedTransform, Glop::Transform& builtTransform) { - EXPECT_EQ(expectedTransform.canvas, builtTransform.canvas); - EXPECT_EQ(expectedTransform.modelView, builtTransform.modelView); - EXPECT_EQ(expectedTransform.transformFlags, expectedTransform.transformFlags); -} - -static void expectGlopEq(Glop& expectedGlop, Glop& builtGlop) { - expectBlendEq(expectedGlop.blend, builtGlop.blend); - expectFillEq(expectedGlop.fill, builtGlop.fill); - expectMeshEq(expectedGlop.mesh, builtGlop.mesh); - expectTransformEq(expectedGlop.transform, builtGlop.transform); -} - -static std::unique_ptr<Glop> blackUnitQuadGlop(RenderState& renderState) { - std::unique_ptr<Glop> glop(new Glop()); - glop->blend = {GL_ZERO, GL_ZERO}; - glop->mesh.elementCount = 4; - glop->mesh.primitiveMode = GL_TRIANGLE_STRIP; - glop->mesh.indices.indices = nullptr; - glop->mesh.indices.bufferObject = GL_ZERO; - glop->mesh.vertices = {renderState.meshState().getUnitQuadVBO(), - VertexAttribFlags::None, - nullptr, - nullptr, - nullptr, - kTextureVertexStride}; - glop->transform.modelView.loadIdentity(); - glop->fill.colorEnabled = true; - glop->fill.color.set(Color::Black); - glop->fill.skiaShaderData.skiaShaderType = kNone_SkiaShaderType; - glop->fill.filterMode = ProgramDescription::ColorFilterMode::None; - glop->fill.texture = {nullptr, GL_INVALID_ENUM, GL_INVALID_ENUM, nullptr}; - return glop; -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(GlopBuilder, rectSnapTest) { - RenderState& renderState = renderThread.renderState(); - Caches& caches = Caches::getInstance(); - SkPaint paint; - Rect dest(1, 1, 100, 100); - Matrix4 simpleTranslate; - simpleTranslate.loadTranslate(0.7, 0.7, 0); - Glop glop; - GlopBuilder(renderState, caches, &glop) - .setRoundRectClipState(nullptr) - .setMeshUnitQuad() - .setFillPaint(paint, 1.0f) - .setTransform(simpleTranslate, TransformFlags::None) - .setModelViewMapUnitToRectSnap(dest) - .build(); - - std::unique_ptr<Glop> goldenGlop(blackUnitQuadGlop(renderState)); - // Rect(1,1,100,100) is the set destination, - // so unit quad should be translated by (1,1) and scaled by (99, 99) - // Tricky part: because translate (0.7, 0.7) and snapping were set in glopBuilder, - // unit quad also should be translate by additional (0.3, 0.3) to snap to exact pixels. - goldenGlop->transform.modelView.loadTranslate(1.3, 1.3, 0); - goldenGlop->transform.modelView.scale(99, 99, 1); - goldenGlop->transform.canvas = simpleTranslate; - goldenGlop->fill.texture.filter = GL_NEAREST; - expectGlopEq(*goldenGlop, glop); -} diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp index 9bfb08292be2..08b967964c59 100644 --- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp +++ b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp @@ -39,7 +39,7 @@ public: // current thread can spoof being a GPU thread static void destroyEglContext() { if (TestUtils::isRenderThreadRunning()) { - TestUtils::runOnRenderThread([](RenderThread& thread) { thread.eglManager().destroy(); }); + TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyGlContext(); }); } } diff --git a/libs/hwui/tests/unit/GradientCacheTests.cpp b/libs/hwui/tests/unit/GradientCacheTests.cpp deleted file mode 100644 index 6710c71c386f..000000000000 --- a/libs/hwui/tests/unit/GradientCacheTests.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> - -#include "Extensions.h" -#include "GradientCache.h" -#include "tests/common/TestUtils.h" - -using namespace android; -using namespace android::uirenderer; - -RENDERTHREAD_OPENGL_PIPELINE_TEST(GradientCache, addRemove) { - Extensions extensions; - GradientCache cache(extensions); - ASSERT_LT(1000u, cache.getMaxSize()) << "Expect non-trivial size"; - - SkColor colors[] = {0xFF00FF00, 0xFFFF0000, 0xFF0000FF}; - float positions[] = {1, 2, 3}; - Texture* texture = cache.get(colors, positions, 3); - ASSERT_TRUE(texture); - ASSERT_FALSE(texture->cleanup); - ASSERT_EQ((uint32_t)texture->objectSize(), cache.getSize()); - ASSERT_TRUE(cache.getSize()); - cache.clear(); - ASSERT_EQ(cache.getSize(), 0u); -} diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp deleted file mode 100644 index 20ec0848212f..000000000000 --- a/libs/hwui/tests/unit/LeakCheckTests.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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 "BakedOpDispatcher.h" -#include "BakedOpRenderer.h" -#include "FrameBuilder.h" -#include "LayerUpdateQueue.h" -#include "RecordingCanvas.h" -#include "tests/common/TestUtils.h" - -#include <gtest/gtest.h> - -using namespace android; -using namespace android::uirenderer; - -const FrameBuilder::LightGeometry sLightGeometery = {{100, 100, 100}, 50}; -const BakedOpRenderer::LightInfo sLightInfo = {128, 128}; - -RENDERTHREAD_OPENGL_PIPELINE_TEST(LeakCheck, saveLayer_overdrawRejection) { - auto node = TestUtils::createNode(0, 0, 100, 100, [](RenderProperties& props, Canvas& canvas) { - canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer); - canvas.drawRect(0, 0, 100, 100, SkPaint()); - canvas.restore(); - - // opaque draw, rejects saveLayer beneath - canvas.drawRect(0, 0, 100, 100, SkPaint()); - }); - RenderState& renderState = renderThread.renderState(); - Caches& caches = Caches::getInstance(); - - FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100, sLightGeometery, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo); - frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(LeakCheck, saveLayerUnclipped_simple) { - auto node = TestUtils::createNode(0, 0, 200, 200, [](RenderProperties& props, Canvas& canvas) { - canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SaveFlags::Flags)(0)); - canvas.drawRect(0, 0, 200, 200, SkPaint()); - canvas.restore(); - }); - RenderState& renderState = renderThread.renderState(); - Caches& caches = Caches::getInstance(); - - FrameBuilder frameBuilder(SkRect::MakeWH(200, 200), 200, 200, sLightGeometery, - Caches::getInstance()); - frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(node)); - BakedOpRenderer renderer(caches, renderState, true, false, sLightInfo); - frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer); -} diff --git a/libs/hwui/tests/unit/MeshStateTests.cpp b/libs/hwui/tests/unit/MeshStateTests.cpp deleted file mode 100644 index 1573fd30d5cb..000000000000 --- a/libs/hwui/tests/unit/MeshStateTests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 <debug/MockGlesDriver.h> -#include <debug/ScopedReplaceDriver.h> -#include <gmock/gmock.h> -#include <gtest/gtest.h> -#include <renderstate/MeshState.h> -#include <tests/common/TestUtils.h> - -using namespace android::uirenderer; -using namespace testing; - -RENDERTHREAD_OPENGL_PIPELINE_TEST(MeshState, genOrUpdate) { - debug::ScopedReplaceDriver<debug::MockGlesDriver> driverRef; - auto& mockGlDriver = driverRef.get(); - EXPECT_CALL(mockGlDriver, glGenBuffers_(_, _)).WillOnce(SetArgPointee<1>(35)); - EXPECT_CALL(mockGlDriver, glBindBuffer_(_, 35)); - EXPECT_CALL(mockGlDriver, glBufferData_(_, _, _, _)); - - GLuint buffer = 0; - renderThread.renderState().meshState().genOrUpdateMeshBuffer(&buffer, 10, nullptr, - GL_DYNAMIC_DRAW); -} diff --git a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp b/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp deleted file mode 100644 index 0d4736757629..000000000000 --- a/libs/hwui/tests/unit/OffscreenBufferPoolTests.cpp +++ /dev/null @@ -1,244 +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 <Rect.h> -#include <gtest/gtest.h> -#include <renderstate/OffscreenBufferPool.h> - -#include <tests/common/TestUtils.h> - -using namespace android::uirenderer; - -TEST(OffscreenBuffer, computeIdealDimension) { - EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(1)); - EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(31)); - EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(33)); - EXPECT_EQ(64u, OffscreenBuffer::computeIdealDimension(64)); - EXPECT_EQ(1024u, OffscreenBuffer::computeIdealDimension(1000)); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, construct) { - OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u); - EXPECT_EQ(49u, layer.viewportWidth); - EXPECT_EQ(149u, layer.viewportHeight); - - EXPECT_EQ(64u, layer.texture.width()); - EXPECT_EQ(192u, layer.texture.height()); - - EXPECT_EQ(64u * 192u * 4u, layer.getSizeInBytes()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, constructWideColorGamut) { - OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 49u, 149u, true); - EXPECT_EQ(49u, layer.viewportWidth); - EXPECT_EQ(149u, layer.viewportHeight); - - EXPECT_EQ(64u, layer.texture.width()); - EXPECT_EQ(192u, layer.texture.height()); - - EXPECT_TRUE(layer.wideColorGamut); - - EXPECT_EQ(64u * 192u * 8u, layer.getSizeInBytes()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, getTextureCoordinates) { - OffscreenBuffer layerAligned(renderThread.renderState(), Caches::getInstance(), 256u, 256u); - EXPECT_EQ(Rect(0, 1, 1, 0), layerAligned.getTextureCoordinates()); - - OffscreenBuffer layerUnaligned(renderThread.renderState(), Caches::getInstance(), 200u, 225u); - EXPECT_EQ(Rect(0, 225.0f / 256.0f, 200.0f / 256.0f, 0), layerUnaligned.getTextureCoordinates()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBuffer, dirty) { - OffscreenBuffer buffer(renderThread.renderState(), Caches::getInstance(), 256u, 256u); - buffer.dirty(Rect(-100, -100, 100, 100)); - EXPECT_EQ(android::Rect(100, 100), buffer.region.getBounds()); -} - -RENDERTHREAD_TEST(OffscreenBufferPool, construct) { - OffscreenBufferPool pool; - EXPECT_EQ(0u, pool.getCount()) << "pool must be created empty"; - EXPECT_EQ(0u, pool.getSize()) << "pool must be created empty"; - // TODO: Does this really make sense as a test? - EXPECT_EQ(DeviceInfo::multiplyByResolution(4 * 4), pool.getMaxSize()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClear) { - OffscreenBufferPool pool; - - auto layer = pool.get(renderThread.renderState(), 100u, 200u); - EXPECT_EQ(100u, layer->viewportWidth); - EXPECT_EQ(200u, layer->viewportHeight); - - ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize()); - - pool.putOrDelete(layer); - ASSERT_EQ(layer->getSizeInBytes(), pool.getSize()); - - auto layer2 = pool.get(renderThread.renderState(), 102u, 202u); - EXPECT_EQ(layer, layer2) << "layer should be recycled"; - ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer"; - - pool.putOrDelete(layer); - EXPECT_EQ(1u, pool.getCount()); - pool.clear(); - EXPECT_EQ(0u, pool.getSize()); - EXPECT_EQ(0u, pool.getCount()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, getPutClearWideColorGamut) { - OffscreenBufferPool pool; - - auto layer = pool.get(renderThread.renderState(), 100u, 200u, true); - EXPECT_EQ(100u, layer->viewportWidth); - EXPECT_EQ(200u, layer->viewportHeight); - EXPECT_TRUE(layer->wideColorGamut); - - ASSERT_LT(layer->getSizeInBytes(), pool.getMaxSize()); - - pool.putOrDelete(layer); - ASSERT_EQ(layer->getSizeInBytes(), pool.getSize()); - - auto layer2 = pool.get(renderThread.renderState(), 102u, 202u, true); - EXPECT_EQ(layer, layer2) << "layer should be recycled"; - ASSERT_EQ(0u, pool.getSize()) << "pool should have been emptied by removing only layer"; - - pool.putOrDelete(layer2); - EXPECT_EQ(1u, pool.getCount()); - pool.clear(); - EXPECT_EQ(0u, pool.getSize()); - EXPECT_EQ(0u, pool.getCount()); - - // add non wide gamut layer - auto layer3 = pool.get(renderThread.renderState(), 100u, 200u); - EXPECT_FALSE(layer3->wideColorGamut); - pool.putOrDelete(layer3); - EXPECT_EQ(1u, pool.getCount()); - - auto layer4 = pool.get(renderThread.renderState(), 100u, 200u, true); - EXPECT_TRUE(layer4->wideColorGamut); - EXPECT_EQ(1u, pool.getCount()); - ASSERT_NE(layer3, layer4); - - pool.putOrDelete(layer4); - - pool.clear(); - EXPECT_EQ(0u, pool.getSize()); - EXPECT_EQ(0u, pool.getCount()); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resize) { - OffscreenBufferPool pool; - - auto layer = pool.get(renderThread.renderState(), 64u, 64u); - layer->dirty(Rect(64, 64)); - - // resize in place - ASSERT_EQ(layer, pool.resize(layer, 60u, 55u)); - EXPECT_TRUE(layer->region.isEmpty()) << "In place resize should clear usage region"; - EXPECT_EQ(60u, layer->viewportWidth); - EXPECT_EQ(55u, layer->viewportHeight); - EXPECT_EQ(64u, layer->texture.width()); - EXPECT_EQ(64u, layer->texture.height()); - - // resized to use different object in pool - auto layer2 = pool.get(renderThread.renderState(), 128u, 128u); - layer2->dirty(Rect(128, 128)); - EXPECT_FALSE(layer2->region.isEmpty()); - pool.putOrDelete(layer2); - ASSERT_EQ(1u, pool.getCount()); - - ASSERT_EQ(layer2, pool.resize(layer, 120u, 125u)); - EXPECT_TRUE(layer2->region.isEmpty()) << "Swap resize should clear usage region"; - EXPECT_EQ(120u, layer2->viewportWidth); - EXPECT_EQ(125u, layer2->viewportHeight); - EXPECT_EQ(128u, layer2->texture.width()); - EXPECT_EQ(128u, layer2->texture.height()); - - // original allocation now only thing in pool - EXPECT_EQ(1u, pool.getCount()); - EXPECT_EQ(layer->getSizeInBytes(), pool.getSize()); - - pool.putOrDelete(layer2); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, resizeWideColorGamut) { - OffscreenBufferPool pool; - - auto layer = pool.get(renderThread.renderState(), 64u, 64u, true); - - // 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()); - - EXPECT_TRUE(layer->wideColorGamut); - EXPECT_EQ(64u * 64u * 8u, layer->getSizeInBytes()); - - // resized to use different object in pool - auto layer2 = pool.get(renderThread.renderState(), 128u, 128u, true); - pool.putOrDelete(layer2); - ASSERT_EQ(1u, pool.getCount()); - - // add a non-wide gamut layer - auto layer3 = pool.get(renderThread.renderState(), 128u, 128u); - pool.putOrDelete(layer3); - ASSERT_EQ(2u, 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()); - - EXPECT_TRUE(layer2->wideColorGamut); - EXPECT_EQ(128u * 128u * 8u, layer2->getSizeInBytes()); - - pool.putOrDelete(layer2); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, putAndDestroy) { - OffscreenBufferPool pool; - // layer too big to return to the pool - // Note: this relies on the fact that the pool won't reject based on max texture size - auto hugeLayer = pool.get(renderThread.renderState(), pool.getMaxSize() / 64, 64); - EXPECT_GT(hugeLayer->getSizeInBytes(), pool.getMaxSize()); - pool.putOrDelete(hugeLayer); - EXPECT_EQ(0u, pool.getCount()); // failed to put (so was destroyed instead) -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(OffscreenBufferPool, clear) { - EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer)); - OffscreenBufferPool pool; - - // Create many buffers, with several at each size - std::vector<OffscreenBuffer*> buffers; - for (int size = 32; size <= 128; size += 32) { - for (int i = 0; i < 10; i++) { - buffers.push_back(pool.get(renderThread.renderState(), size, size)); - } - } - EXPECT_EQ(0u, pool.getCount()) << "Expect nothing inside"; - for (auto& buffer : buffers) pool.putOrDelete(buffer); - EXPECT_EQ(40u, pool.getCount()) << "Expect all items added"; - EXPECT_EQ(40, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer)); - pool.clear(); - EXPECT_EQ(0u, pool.getCount()) << "Expect all items cleared"; - - EXPECT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::OffscreenBuffer)); -} diff --git a/libs/hwui/tests/unit/OpDumperTests.cpp b/libs/hwui/tests/unit/OpDumperTests.cpp deleted file mode 100644 index ef30e872a7bd..000000000000 --- a/libs/hwui/tests/unit/OpDumperTests.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 "OpDumper.h" -#include "tests/common/TestUtils.h" - -using namespace android; -using namespace android::uirenderer; - -TEST(OpDumper, dump) { - SkPaint paint; - RectOp op(uirenderer::Rect(100, 100), Matrix4::identity(), nullptr, &paint); - - std::stringstream stream; - OpDumper::dump(op, stream); - EXPECT_STREQ("RectOp [100 x 100]", stream.str().c_str()); - - stream.str(""); - OpDumper::dump(op, stream, 2); - EXPECT_STREQ(" RectOp [100 x 100]", stream.str().c_str()); - - ClipRect clipRect(uirenderer::Rect(50, 50)); - op.localClip = &clipRect; - - stream.str(""); - OpDumper::dump(op, stream, 2); - EXPECT_STREQ(" RectOp [100 x 100] clip=[50 x 50] mode=0", stream.str().c_str()); -} diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp deleted file mode 100644 index 8a9e34f81c6d..000000000000 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ /dev/null @@ -1,847 +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 <DeferredLayerUpdater.h> -#include <RecordedOp.h> -#include <RecordingCanvas.h> -#include <hwui/Paint.h> -#include <minikin/Layout.h> -#include <tests/common/TestUtils.h> -#include <utils/Color.h> - -#include <SkGradientShader.h> -#include <SkImagePriv.h> -#include <SkShader.h> - -namespace android { -namespace uirenderer { - -static void playbackOps(const DisplayList& displayList, - std::function<void(const RecordedOp&)> opReceiver) { - for (auto& chunk : displayList.getChunks()) { - for (size_t opIndex = chunk.beginOpIndex; opIndex < chunk.endOpIndex; opIndex++) { - RecordedOp* op = displayList.getOps()[opIndex]; - opReceiver(*op); - } - } -} - -static void validateSingleOp(std::unique_ptr<DisplayList>& dl, - std::function<void(const RecordedOp& op)> opValidator) { - ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; - opValidator(*(dl->getOps()[0])); -} - -// The RecordingCanvas is only ever used by the OpenGL RenderPipeline and never when Skia is in use. -// Thus, even though many of these test are not directly dependent on the current RenderPipeline, we -// set them all to be OPENGL_PIPELINE_TESTs in case the underlying code in RecordingCanvas ever -// changes to require the use of the OPENGL_PIPELINE. Currently the textureLayer test is the only -// test that requires being an OPENGL_PIPELINE_TEST. - -OPENGL_PIPELINE_TEST(RecordingCanvas, emptyPlayback) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.restore(); - }); - playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, clipRect) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect); - 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"; -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, emptyClipRect) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(0, 0, 100, 100, SkClipOp::kIntersect); - canvas.clipRect(100, 100, 200, 200, SkClipOp::kIntersect); - canvas.drawRect(0, 0, 50, 50, SkPaint()); // rejected at record time - canvas.restore(); - }); - ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected."; -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, emptyPaintRejection) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - SkPaint emptyPaint; - emptyPaint.setColor(Color::Transparent); - - float points[] = {0, 0, 200, 200}; - canvas.drawPoints(points, 4, emptyPaint); - canvas.drawLines(points, 4, emptyPaint); - canvas.drawRect(0, 0, 200, 200, emptyPaint); - canvas.drawRegion(SkRegion(SkIRect::MakeWH(200, 200)), emptyPaint); - canvas.drawRoundRect(0, 0, 200, 200, 10, 10, emptyPaint); - canvas.drawCircle(100, 100, 100, emptyPaint); - canvas.drawOval(0, 0, 200, 200, emptyPaint); - canvas.drawArc(0, 0, 200, 200, 0, 360, true, emptyPaint); - SkPath path; - path.addRect(0, 0, 200, 200); - canvas.drawPath(path, emptyPaint); - }); - EXPECT_EQ(0u, dl->getOps().size()) << "Op should be rejected"; -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawArc) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint()); - canvas.drawArc(0, 0, 100, 100, 0, 360, true, SkPaint()); - }); - - auto&& ops = dl->getOps(); - ASSERT_EQ(2u, ops.size()) << "Must be exactly two ops"; - EXPECT_EQ(RecordedOpId::ArcOp, ops[0]->opId); - EXPECT_EQ(Rect(200, 200), ops[0]->unmappedBounds); - - EXPECT_EQ(RecordedOpId::OvalOp, ops[1]->opId) << "Circular arcs should be converted to ovals"; - EXPECT_EQ(Rect(100, 100), ops[1]->unmappedBounds); -} - -OPENGL_PIPELINE_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"; -} - -OPENGL_PIPELINE_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); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawRoundRect) { - // Round case - stays rounded - auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { - canvas.drawRoundRect(0, 0, 100, 100, 10, 10, SkPaint()); - }); - ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; - ASSERT_EQ(RecordedOpId::RoundRectOp, dl->getOps()[0]->opId); - - // Non-rounded case - turned into drawRect - dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { - canvas.drawRoundRect(0, 0, 100, 100, 0, -1, SkPaint()); - }); - ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; - ASSERT_EQ(RecordedOpId::RectOp, dl->getOps()[0]->opId) - << "Non-rounded rects should be converted"; -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - SkPaint paint; - paint.setAntiAlias(true); - paint.setTextSize(20); - TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); - }); - - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - count++; - ASSERT_EQ(RecordedOpId::TextOp, op.opId); - EXPECT_EQ(nullptr, op.localClip); - EXPECT_TRUE(op.localMatrix.isIdentity()); - EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25)) - << "Op expected to be 25+ pixels wide, 10+ pixels tall"; - }); - ASSERT_EQ(1, count); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_strikeThruAndUnderline) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - SkPaint paint; - paint.setAntiAlias(true); - paint.setTextSize(20); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - uint32_t flags = paint.getFlags(); - if (i != 0) { - flags |= SkPaint::kUnderlineText_ReserveFlag; - } else { - flags &= ~SkPaint::kUnderlineText_ReserveFlag; - } - if (j != 0) { - flags |= SkPaint::kStrikeThruText_ReserveFlag; - } else { - flags &= ~SkPaint::kStrikeThruText_ReserveFlag; - } - paint.setFlags(flags); - TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); - } - } - }); - - auto ops = dl->getOps(); - ASSERT_EQ(8u, ops.size()); - - int index = 0; - EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough - - EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); - EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only - - EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); - EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only - - EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); - EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline - EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawGlyphs_forceAlignLeft) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - SkPaint paint; - paint.setAntiAlias(true); - paint.setTextSize(20); - paint.setTextAlign(SkPaint::kLeft_Align); - TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); - paint.setTextAlign(SkPaint::kCenter_Align); - TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); - paint.setTextAlign(SkPaint::kRight_Align); - TestUtils::drawUtf8ToCanvas(&canvas, "test text", paint, 25, 25); - }); - - int count = 0; - float lastX = FLT_MAX; - playbackOps(*dl, [&count, &lastX](const RecordedOp& op) { - count++; - ASSERT_EQ(RecordedOpId::TextOp, op.opId); - EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign()) - << "recorded drawText commands must force kLeft_Align on their paint"; - - // verify TestUtils alignment offsetting (TODO: move asserts to Canvas base class) - EXPECT_GT(lastX, ((const TextOp&)op).x) - << "x coordinate should reduce across each of the draw commands, from alignment"; - lastX = ((const TextOp&)op).x; - }); - ASSERT_EQ(3, count); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawColor) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.drawColor(Color::Black, SkBlendMode::kSrcOver); - }); - - ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; - auto op = *(dl->getOps()[0]); - EXPECT_EQ(RecordedOpId::ColorOp, op.opId); - EXPECT_EQ(nullptr, op.localClip); - EXPECT_TRUE(op.unmappedBounds.isEmpty()) << "Expect undefined recorded bounds"; -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, backgroundAndImage) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { - sk_sp<Bitmap> bitmap(TestUtils::createBitmap(25, 25)); - SkPaint paint; - paint.setColor(SK_ColorBLUE); - - canvas.save(SaveFlags::MatrixClip); - { - // a background! - canvas.save(SaveFlags::MatrixClip); - canvas.drawRect(0, 0, 100, 200, paint); - canvas.restore(); - } - { - // an image! - canvas.save(SaveFlags::MatrixClip); - canvas.translate(25, 25); - canvas.scale(2, 2); - canvas.drawBitmap(*bitmap, 0, 0, nullptr); - canvas.restore(); - } - canvas.restore(); - }); - - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - if (count == 0) { - ASSERT_EQ(RecordedOpId::RectOp, op.opId); - ASSERT_NE(nullptr, op.paint); - EXPECT_EQ(SK_ColorBLUE, op.paint->getColor()); - EXPECT_EQ(Rect(100, 200), op.unmappedBounds); - EXPECT_EQ(nullptr, op.localClip); - - Matrix4 expectedMatrix; - expectedMatrix.loadIdentity(); - EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); - } else { - ASSERT_EQ(RecordedOpId::BitmapOp, op.opId); - EXPECT_EQ(nullptr, op.paint); - EXPECT_EQ(Rect(25, 25), op.unmappedBounds); - EXPECT_EQ(nullptr, op.localClip); - - Matrix4 expectedMatrix; - expectedMatrix.loadTranslate(25, 25, 0); - expectedMatrix.scale(2, 2, 1); - EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); - } - count++; - }); - ASSERT_EQ(2, count); -} - -RENDERTHREAD_OPENGL_PIPELINE_TEST(RecordingCanvas, textureLayer) { - auto layerUpdater = - TestUtils::createTextureLayerUpdater(renderThread, 100, 100, SkMatrix::MakeTrans(5, 5)); - - auto dl = TestUtils::createDisplayList<RecordingCanvas>( - 200, 200, - [&layerUpdater](RecordingCanvas& canvas) { canvas.drawLayer(layerUpdater.get()); }); - - validateSingleOp(dl, [](const RecordedOp& op) { - ASSERT_EQ(RecordedOpId::TextureLayerOp, op.opId); - ASSERT_TRUE(op.localMatrix.isIdentity()) << "Op must not apply matrix at record time."; - }); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simple) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.saveLayerAlpha(10, 20, 190, 180, 128, SaveFlags::ClipToLayer); - canvas.drawRect(10, 20, 190, 180, SkPaint()); - canvas.restore(); - }); - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - Matrix4 expectedMatrix; - switch (count++) { - case 0: - EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId); - EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); - EXPECT_EQ(nullptr, op.localClip); - EXPECT_TRUE(op.localMatrix.isIdentity()); - break; - case 1: - EXPECT_EQ(RecordedOpId::RectOp, op.opId); - EXPECT_CLIP_RECT(Rect(180, 160), op.localClip); - EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); - expectedMatrix.loadTranslate(-10, -20, 0); - EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); - break; - case 2: - EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); - // Don't bother asserting recording state data - it's not used - break; - default: - ADD_FAILURE(); - } - }); - EXPECT_EQ(3, count); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rounding) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { - canvas.saveLayerAlpha(10.25f, 10.75f, 89.25f, 89.75f, 128, SaveFlags::ClipToLayer); - canvas.drawRect(20, 20, 80, 80, 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, 10, 90, 90), op.unmappedBounds) << "Expect bounds rounded out"; - break; - case 1: - EXPECT_EQ(RecordedOpId::RectOp, op.opId); - expectedMatrix.loadTranslate(-10, -10, 0); - EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix) << "Expect rounded offset"; - 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); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_missingRestore) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); - canvas.drawRect(0, 0, 200, 200, SkPaint()); - // Note: restore omitted, shouldn't result in unmatched save - }); - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - if (count++ == 2) { - EXPECT_EQ(RecordedOpId::EndLayerOp, op.opId); - } - }); - EXPECT_EQ(3, count) << "Missing a restore shouldn't result in an unmatched saveLayer"; -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_simpleUnclipped) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped - canvas.drawRect(10, 20, 190, 180, SkPaint()); - canvas.restore(); - }); - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - switch (count++) { - case 0: - EXPECT_EQ(RecordedOpId::BeginUnclippedLayerOp, op.opId); - EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); - EXPECT_EQ(nullptr, op.localClip); - EXPECT_TRUE(op.localMatrix.isIdentity()); - break; - case 1: - EXPECT_EQ(RecordedOpId::RectOp, op.opId); - EXPECT_EQ(nullptr, op.localClip); - EXPECT_EQ(Rect(10, 20, 190, 180), op.unmappedBounds); - EXPECT_TRUE(op.localMatrix.isIdentity()); - break; - case 2: - EXPECT_EQ(RecordedOpId::EndUnclippedLayerOp, op.opId); - // Don't bother asserting recording state data - it's not used - break; - default: - ADD_FAILURE(); - } - }); - EXPECT_EQ(3, count); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_addClipFlag) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(10, 20, 190, 180, SkClipOp::kIntersect); - canvas.saveLayerAlpha(10, 20, 190, 180, 128, (SaveFlags::Flags)0); // unclipped - canvas.drawRect(10, 20, 190, 180, SkPaint()); - canvas.restore(); - canvas.restore(); - }); - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - if (count++ == 0) { - EXPECT_EQ(RecordedOpId::BeginLayerOp, op.opId) - << "Clip + unclipped saveLayer should result in a clipped layer"; - } - }); - EXPECT_EQ(3, count); -} - -OPENGL_PIPELINE_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, SkClipOp::kReplace_deprecated); - - canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer); - canvas.drawRect(0, 0, 400, 400, SkPaint()); - canvas.restore(); - }); - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - if (count++ == 1) { - Matrix4 expectedMatrix; - EXPECT_EQ(RecordedOpId::RectOp, op.opId); - EXPECT_CLIP_RECT(Rect(100, 100), op.localClip) // Recorded clip rect should be - // intersection of viewport and saveLayer bounds, in layer space; - EXPECT_EQ(Rect(400, 400), op.unmappedBounds); - expectedMatrix.loadTranslate(-100, -100, 0); - EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); - } - }); - EXPECT_EQ(3, count); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateUnclipped) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.translate(100, 100); - canvas.rotate(45); - canvas.translate(-50, -50); - - canvas.saveLayerAlpha(0, 0, 100, 100, 128, SaveFlags::ClipToLayer); - canvas.drawRect(0, 0, 100, 100, SkPaint()); - canvas.restore(); - - canvas.restore(); - }); - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - if (count++ == 1) { - EXPECT_EQ(RecordedOpId::RectOp, op.opId); - EXPECT_CLIP_RECT(Rect(100, 100), op.localClip); - EXPECT_EQ(Rect(100, 100), op.unmappedBounds); - EXPECT_MATRIX_APPROX_EQ(Matrix4::identity(), op.localMatrix) - << "Recorded op shouldn't see any canvas transform before the saveLayer"; - } - }); - EXPECT_EQ(3, count); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rotateClipped) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.translate(100, 100); - canvas.rotate(45); - canvas.translate(-200, -200); - - // area of saveLayer will be clipped to parent viewport, so we ask for 400x400... - canvas.saveLayerAlpha(0, 0, 400, 400, 128, SaveFlags::ClipToLayer); - canvas.drawRect(0, 0, 400, 400, SkPaint()); - canvas.restore(); - - canvas.restore(); - }); - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - if (count++ == 1) { - Matrix4 expectedMatrix; - EXPECT_EQ(RecordedOpId::RectOp, op.opId); - - // ...and get about 58.6, 58.6, 341.4 341.4, because the bounds are clipped by - // the parent 200x200 viewport, but prior to rotation - ASSERT_NE(nullptr, op.localClip); - ASSERT_EQ(ClipMode::Rectangle, op.localClip->mode); - // NOTE: this check relies on saveLayer altering the clip post-viewport init. This - // causes the clip to be recorded by contained draw commands, though it's not necessary - // since the same clip will be computed at draw time. If such a change is made, this - // check could be done at record time by querying the clip, or the clip could be altered - // slightly so that it is serialized. - EXPECT_EQ(Rect(59, 59, 341, 341), op.localClip->rect); - EXPECT_EQ(Rect(400, 400), op.unmappedBounds); - expectedMatrix.loadIdentity(); - EXPECT_MATRIX_APPROX_EQ(expectedMatrix, op.localMatrix); - } - }); - EXPECT_EQ(3, count); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_rejectBegin) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.translate(0, -20); // avoid identity case - // empty clip rect should force layer + contents to be rejected - canvas.clipRect(0, -20, 200, -20, SkClipOp::kIntersect); - canvas.saveLayerAlpha(0, 0, 200, 200, 128, SaveFlags::ClipToLayer); - canvas.drawRect(0, 0, 200, 200, SkPaint()); - canvas.restore(); - canvas.restore(); - }); - - ASSERT_EQ(0u, dl->getOps().size()) << "Begin/Rect/End should all be rejected."; -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_rejection) { - auto child = - TestUtils::createNode(50, 50, 150, 150, [](RenderProperties& props, Canvas& canvas) { - SkPaint paint; - paint.setColor(SK_ColorWHITE); - canvas.drawRect(0, 0, 100, 100, paint); - }); - - auto dl = TestUtils::createDisplayList<RecordingCanvas>( - 200, 200, [&child](RecordingCanvas& canvas) { - canvas.clipRect(0, 0, 0, 0, SkClipOp::kIntersect); // empty clip, reject node - canvas.drawRenderNode(child.get()); // shouldn't crash when rejecting node... - }); - ASSERT_TRUE(dl->isEmpty()); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawRenderNode_projection) { - sp<RenderNode> background = - TestUtils::createNode(50, 50, 150, 150, [](RenderProperties& props, Canvas& 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()); - } -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, firstClipWillReplace) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - // since no explicit clip set on canvas, this should be the one observed on op: - canvas.clipRect(-100, -100, 300, 300, SkClipOp::kIntersect); - - SkPaint paint; - paint.setColor(SK_ColorWHITE); - canvas.drawRect(0, 0, 100, 100, paint); - - canvas.restore(); - }); - ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op"; - // first clip must be preserved, even if it extends beyond canvas bounds - EXPECT_CLIP_RECT(Rect(-100, -100, 300, 300), dl->getOps()[0]->localClip); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, replaceClipIntersectWithRoot) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { - canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(-10, -10, 110, 110, SkClipOp::kReplace_deprecated); - canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); - canvas.restore(); - }); - ASSERT_EQ(1u, dl->getOps().size()) << "Must have one op"; - // first clip must be preserved, even if it extends beyond canvas bounds - EXPECT_CLIP_RECT(Rect(-10, -10, 110, 110), dl->getOps()[0]->localClip); - EXPECT_TRUE(dl->getOps()[0]->localClip->intersectWithRoot); -} - -OPENGL_PIPELINE_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); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, insertReorderBarrier_clip) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - // first chunk: no recorded clip - canvas.insertReorderBarrier(true); - canvas.drawRect(0, 0, 400, 400, SkPaint()); - - // second chunk: no recorded clip, since inorder region - canvas.clipRect(0, 0, 200, 200, SkClipOp::kIntersect); - canvas.insertReorderBarrier(false); - canvas.drawRect(0, 0, 400, 400, SkPaint()); - - // third chunk: recorded clip - canvas.insertReorderBarrier(true); - canvas.drawRect(0, 0, 400, 400, SkPaint()); - }); - - auto chunks = dl->getChunks(); - ASSERT_EQ(3u, chunks.size()); - - EXPECT_TRUE(chunks[0].reorderChildren); - EXPECT_EQ(nullptr, chunks[0].reorderClip); - - EXPECT_FALSE(chunks[1].reorderChildren); - EXPECT_EQ(nullptr, chunks[1].reorderClip); - - EXPECT_TRUE(chunks[2].reorderChildren); - ASSERT_NE(nullptr, chunks[2].reorderClip); - EXPECT_EQ(Rect(200, 200), chunks[2].reorderClip->rect); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, refPaint) { - SkPaint paint; - - auto dl = TestUtils::createDisplayList<RecordingCanvas>( - 200, 200, [&paint](RecordingCanvas& canvas) { - paint.setColor(SK_ColorBLUE); - // first two should use same paint - canvas.drawRect(0, 0, 200, 10, paint); - SkPaint paintCopy(paint); - canvas.drawRect(0, 10, 200, 20, paintCopy); - - // only here do we use different paint ptr - paint.setColor(SK_ColorRED); - canvas.drawRect(0, 20, 200, 30, paint); - }); - auto ops = dl->getOps(); - ASSERT_EQ(3u, ops.size()); - - // first two are the same - EXPECT_NE(nullptr, ops[0]->paint); - EXPECT_NE(&paint, ops[0]->paint); - EXPECT_EQ(ops[0]->paint, ops[1]->paint); - - // last is different, but still copied / non-null - EXPECT_NE(nullptr, ops[2]->paint); - EXPECT_NE(ops[0]->paint, ops[2]->paint); - EXPECT_NE(&paint, ops[2]->paint); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmap) { - sk_sp<Bitmap> bitmap(TestUtils::createBitmap(100, 100)); - auto dl = TestUtils::createDisplayList<RecordingCanvas>( - 100, 100, - [&bitmap](RecordingCanvas& canvas) { canvas.drawBitmap(*bitmap, 0, 0, nullptr); }); - auto& bitmaps = dl->getBitmapResources(); - EXPECT_EQ(1u, bitmaps.size()); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_bitmapShader) { - sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100); - auto dl = TestUtils::createDisplayList<RecordingCanvas>( - 100, 100, [&bitmap](RecordingCanvas& canvas) { - SkPaint paint; - SkBitmap skBitmap; - bitmap->getSkBitmap(&skBitmap); - sk_sp<SkImage> image = - SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode); - sk_sp<SkShader> shader = - image->makeShader(SkShader::TileMode::kClamp_TileMode, - SkShader::TileMode::kClamp_TileMode, nullptr); - paint.setShader(std::move(shader)); - canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint); - }); - auto& bitmaps = dl->getBitmapResources(); - EXPECT_EQ(1u, bitmaps.size()); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, refBitmapInShader_composeShader) { - sk_sp<Bitmap> bitmap = TestUtils::createBitmap(100, 100); - auto dl = TestUtils::createDisplayList<RecordingCanvas>( - 100, 100, [&bitmap](RecordingCanvas& canvas) { - SkPaint paint; - SkBitmap skBitmap; - bitmap->getSkBitmap(&skBitmap); - sk_sp<SkImage> image = - SkMakeImageFromRasterBitmap(skBitmap, kNever_SkCopyPixelsMode); - sk_sp<SkShader> shader1 = - image->makeShader(SkShader::TileMode::kClamp_TileMode, - SkShader::TileMode::kClamp_TileMode, nullptr); - - SkPoint center; - center.set(50, 50); - SkColor colors[2]; - colors[0] = Color::Black; - colors[1] = Color::White; - sk_sp<SkShader> shader2 = SkGradientShader::MakeRadial( - center, 50, colors, nullptr, 2, SkShader::TileMode::kRepeat_TileMode); - - sk_sp<SkShader> composeShader = SkShader::MakeComposeShader( - std::move(shader1), std::move(shader2), SkBlendMode::kMultiply); - paint.setShader(std::move(composeShader)); - canvas.drawRoundRect(0, 0, 100, 100, 20.0f, 20.0f, paint); - }); - auto& bitmaps = dl->getBitmapResources(); - EXPECT_EQ(1u, bitmaps.size()); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawText) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - Paint paint; - paint.setAntiAlias(true); - paint.setTextSize(20); - TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", 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.getHeight() >= 10); - EXPECT_TRUE(op.unmappedBounds.getWidth() >= 25); - }); - ASSERT_EQ(1, count); -} - -OPENGL_PIPELINE_TEST(RecordingCanvas, drawTextInHighContrast) { - Properties::enableHighContrastText = true; - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - Paint paint; - paint.setColor(SK_ColorWHITE); - paint.setAntiAlias(true); - paint.setTextSize(20); - TestUtils::drawUtf8ToCanvas(&canvas, "HELLO", paint, 25, 25); - }); - Properties::enableHighContrastText = false; - - int count = 0; - playbackOps(*dl, [&count](const RecordedOp& op) { - ASSERT_EQ(RecordedOpId::TextOp, op.opId); - if (count++ == 0) { - EXPECT_EQ(SK_ColorBLACK, op.paint->getColor()); - EXPECT_EQ(SkPaint::kStrokeAndFill_Style, op.paint->getStyle()); - } else { - EXPECT_EQ(SK_ColorWHITE, op.paint->getColor()); - EXPECT_EQ(SkPaint::kFill_Style, op.paint->getStyle()); - } - - }); - ASSERT_EQ(2, count); -} - -} // namespace uirenderer -} // namespace android diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp index 15c0ab1ad8d2..2c73940b9b9c 100644 --- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp +++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp @@ -18,13 +18,13 @@ #include <gtest/gtest.h> #include <SkClipStack.h> -#include <SkLiteRecorder.h> #include <SkSurface_Base.h> #include <string.h> #include "AnimationContext.h" #include "DamageAccumulator.h" #include "FatalTestCanvas.h" #include "IContextFactory.h" +#include "RecordingCanvas.h" #include "SkiaCanvas.h" #include "pipeline/skia/SkiaDisplayList.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" @@ -32,6 +32,7 @@ #include "pipeline/skia/SkiaRecordingCanvas.h" #include "renderthread/CanvasContext.h" #include "tests/common/TestUtils.h" +#include "utils/Color.h" using namespace android; using namespace android::uirenderer; @@ -44,8 +45,8 @@ TEST(RenderNodeDrawable, create) { canvas.drawColor(Color::Red_500, SkBlendMode::kSrcOver); }); - SkLiteDL skLiteDL; - SkLiteRecorder canvas; + DisplayListData skLiteDL; + RecordingCanvas canvas; canvas.reset(&skLiteDL, SkIRect::MakeWH(1, 1)); canvas.translate(100, 100); RenderNodeDrawable drawable(rootNode.get(), &canvas); @@ -537,7 +538,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) { layerUpdateQueue.enqueueLayerWithDamage(child.get(), android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT)); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - pipeline->renderLayersImpl(layerUpdateQueue, true, false); + pipeline->renderLayersImpl(layerUpdateQueue, true); EXPECT_EQ(1, drawCounter); // assert index 0 is drawn on the layer RenderNodeDrawable drawable(parent.get(), surfaceLayer1->getCanvas(), true); @@ -1094,7 +1095,7 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) { class ShadowTestCanvas : public SkCanvas { public: ShadowTestCanvas(int width, int height) : SkCanvas(width, height) {} - int getIndex() { return mDrawCounter; } + int getDrawCounter() { return mDrawCounter; } virtual void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { // expect to draw 2 RenderNodeDrawable, 1 StartReorderBarrierDrawable, @@ -1109,17 +1110,36 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) { EXPECT_EQ(dy, TRANSLATE_Y); } + virtual void didSetMatrix(const SkMatrix& matrix) override { + mDrawCounter++; + // First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix. + // Second invocation is preparing the matrix for an elevated RenderNodeDrawable. + EXPECT_TRUE(matrix.isIdentity()); + EXPECT_TRUE(getTotalMatrix().isIdentity()); + } + virtual void didConcat(const SkMatrix& matrix) override { - // This function is invoked by EndReorderBarrierDrawable::drawShadow to apply shadow - // matrix. mDrawCounter++; - EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X, CASTER_Y), matrix); - EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y), - getTotalMatrix()); + if (mFirstDidConcat) { + // First invocation is EndReorderBarrierDrawable::drawShadow to apply shadow matrix. + mFirstDidConcat = false; + EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y), + matrix); + EXPECT_EQ(SkMatrix::MakeTrans(CASTER_X + TRANSLATE_X, CASTER_Y + TRANSLATE_Y), + getTotalMatrix()); + } else { + // Second invocation is preparing the matrix for an elevated RenderNodeDrawable. + EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y), + matrix); + EXPECT_EQ(SkMatrix::MakeTrans(TRANSLATE_X, TRANSLATE_Y), + getTotalMatrix()); + } } protected: int mDrawCounter = 0; + private: + bool mFirstDidConcat = true; }; auto parent = TestUtils::createSkiaNode( @@ -1143,7 +1163,7 @@ TEST(ReorderBarrierDrawable, testShadowMatrix) { ShadowTestCanvas canvas(CANVAS_WIDTH, CANVAS_HEIGHT); RenderNodeDrawable drawable(parent.get(), &canvas, false); canvas.drawDrawable(&drawable); - EXPECT_EQ(6, canvas.getIndex()); + EXPECT_EQ(9, canvas.getDrawCounter()); } // Draw a vector drawable twice but with different bounds and verify correct bounds are used. diff --git a/libs/hwui/tests/unit/RenderPropertiesTests.cpp b/libs/hwui/tests/unit/RenderPropertiesTests.cpp index 85655fc2728a..3e8e0576bf49 100644 --- a/libs/hwui/tests/unit/RenderPropertiesTests.cpp +++ b/libs/hwui/tests/unit/RenderPropertiesTests.cpp @@ -22,8 +22,6 @@ using namespace android; using namespace android::uirenderer; TEST(RenderProperties, layerValidity) { - DeviceInfo::initialize(); - const int maxTextureSize = DeviceInfo::get()->maxTextureSize(); ASSERT_LE(2048, maxTextureSize); ASSERT_GT(100000, maxTextureSize); diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 43080a9460b3..1433aa0349f4 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -48,11 +48,18 @@ public: */ static void terminate(ShaderCache& cache, bool saveContent) { std::lock_guard<std::mutex> lock(cache.mMutex); - if (cache.mInitialized && cache.mBlobCache && saveContent) { - cache.mBlobCache->writeToFile(); - } + cache.mSavePending = saveContent; + cache.saveToDiskLocked(); cache.mBlobCache = NULL; } + + /** + * + */ + template <typename T> + static bool validateCache(ShaderCache& cache, std::vector<T> hash) { + return cache.validateCache(hash.data(), hash.size() * sizeof(T)); + } }; } /* namespace skiapipeline */ @@ -75,26 +82,39 @@ bool folderExist(const std::string& folderName) { return false; } -bool checkShader(const sk_sp<SkData>& shader, const char* program) { +inline bool +checkShader(const sk_sp<SkData>& shader1, const sk_sp<SkData>& shader2) { + return nullptr != shader1 && nullptr != shader2 && shader1->size() == shader2->size() + && 0 == memcmp(shader1->data(), shader2->data(), shader1->size()); +} + +inline bool +checkShader(const sk_sp<SkData>& shader, const char* program) { sk_sp<SkData> shader2 = SkData::MakeWithCString(program); - return shader->size() == shader2->size() - && 0 == memcmp(shader->data(), shader2->data(), shader->size()); + return checkShader(shader, shader2); } -bool checkShader(const sk_sp<SkData>& shader, std::vector<char>& program) { - sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size()); - return shader->size() == shader2->size() - && 0 == memcmp(shader->data(), shader2->data(), shader->size()); +template <typename T> +bool checkShader(const sk_sp<SkData>& shader, std::vector<T>& program) { + sk_sp<SkData> shader2 = SkData::MakeWithCopy(program.data(), program.size() * sizeof(T)); + return checkShader(shader, shader2); } void setShader(sk_sp<SkData>& shader, const char* program) { shader = SkData::MakeWithCString(program); } -void setShader(sk_sp<SkData>& shader, std::vector<char>& program) { - shader = SkData::MakeWithCopy(program.data(), program.size()); +template <typename T> +void setShader(sk_sp<SkData>& shader, std::vector<T>& buffer) { + shader = SkData::MakeWithCopy(buffer.data(), buffer.size() * sizeof(T)); } +template <typename T> +void genRandomData(std::vector<T>& buffer) { + for (auto& data : buffer) { + data = T(std::rand()); + } +} #define GrProgramDescTest(a) (*SkData::MakeWithCString(#a).get()) @@ -110,6 +130,7 @@ TEST(ShaderCacheTest, testWriteAndRead) { //remove any test files from previous test run int deleteFile = remove(cacheFile1.c_str()); ASSERT_TRUE(0 == deleteFile || ENOENT == errno); + std::srand(0); //read the cache from a file that does not exist ShaderCache::get().setFilename(cacheFile1.c_str()); @@ -158,10 +179,8 @@ TEST(ShaderCacheTest, testWriteAndRead) { //write and read big data chunk (50K) size_t dataSize = 50*1024; - std::vector<char> dataBuffer(dataSize); - for (size_t i = 0; i < dataSize; i++) { - dataBuffer[0] = dataSize % 256; - } + std::vector<uint8_t> dataBuffer(dataSize); + genRandomData(dataBuffer); setShader(inVS, dataBuffer); ShaderCache::get().store(GrProgramDescTest(432), *inVS.get()); ShaderCacheTestUtils::terminate(ShaderCache::get(), true); @@ -173,4 +192,96 @@ TEST(ShaderCacheTest, testWriteAndRead) { remove(cacheFile1.c_str()); } +TEST(ShaderCacheTest, testCacheValidation) { + if (!folderExist(getExternalStorageFolder())) { + //don't run the test if external storage folder is not available + return; + } + std::string cacheFile1 = getExternalStorageFolder() + "/shaderCacheTest1"; + std::string cacheFile2 = getExternalStorageFolder() + "/shaderCacheTest2"; + + //remove any test files from previous test run + int deleteFile = remove(cacheFile1.c_str()); + ASSERT_TRUE(0 == deleteFile || ENOENT == errno); + std::srand(0); + + //generate identity and read the cache from a file that does not exist + ShaderCache::get().setFilename(cacheFile1.c_str()); + ShaderCacheTestUtils::setSaveDelay(ShaderCache::get(), 0); //disable deferred save + std::vector<uint8_t> identity(1024); + genRandomData(identity); + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + + // generate random content in cache and store to disk + constexpr size_t numBlob(10); + constexpr size_t keySize(1024); + constexpr size_t dataSize(50 * 1024); + + std::vector< std::pair<sk_sp<SkData>, sk_sp<SkData>> > blobVec(numBlob); + for (auto& blob : blobVec) { + std::vector<uint8_t> keyBuffer(keySize); + std::vector<uint8_t> dataBuffer(dataSize); + genRandomData(keyBuffer); + genRandomData(dataBuffer); + + sk_sp<SkData> key, data; + setShader(key, keyBuffer); + setShader(data, dataBuffer); + + blob = std::make_pair(key, data); + ShaderCache::get().store(*key.get(), *data.get()); + } + ShaderCacheTestUtils::terminate(ShaderCache::get(), true); + + // change to a file that does not exist and verify validation fails + ShaderCache::get().setFilename(cacheFile2.c_str()); + ShaderCache::get().initShaderDiskCache(); + ASSERT_FALSE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + ShaderCacheTestUtils::terminate(ShaderCache::get(), false); + + // restore the original file and verify validation succeeds + ShaderCache::get().setFilename(cacheFile1.c_str()); + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + for (const auto& blob : blobVec) { + auto outVS = ShaderCache::get().load(*blob.first.get()); + ASSERT_TRUE( checkShader(outVS, blob.second) ); + } + + // generate error identity and verify load fails + ShaderCache::get().initShaderDiskCache(identity.data(), -1); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + ShaderCache::get().initShaderDiskCache(nullptr, identity.size() * + sizeof(decltype(identity)::value_type)); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + + // verify the cache validation again after load fails + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + ASSERT_TRUE( ShaderCacheTestUtils::validateCache(ShaderCache::get(), identity) ); + for (const auto& blob : blobVec) { + auto outVS = ShaderCache::get().load(*blob.first.get()); + ASSERT_TRUE( checkShader(outVS, blob.second) ); + } + + // generate another identity and verify load fails + for (auto& data : identity) { + data += std::rand(); + } + ShaderCache::get().initShaderDiskCache(identity.data(), identity.size() * + sizeof(decltype(identity)::value_type)); + for (const auto& blob : blobVec) { + ASSERT_EQ( ShaderCache::get().load(*blob.first.get()), sk_sp<SkData>() ); + } + + ShaderCacheTestUtils::terminate(ShaderCache::get(), false); + remove(cacheFile1.c_str()); +} + } // namespace diff --git a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp index bc742b0c5a63..df5f45618070 100644 --- a/libs/hwui/tests/unit/SkiaBehaviorTests.cpp +++ b/libs/hwui/tests/unit/SkiaBehaviorTests.cpp @@ -35,23 +35,6 @@ SkBitmap createSkBitmap(int width, int height) { return bitmap; } -/** - * 1x1 bitmaps must not be optimized into solid color shaders, since HWUI can't - * compose/render color shaders - */ -TEST(SkiaBehavior, CreateBitmapShader1x1) { - SkBitmap origBitmap = createSkBitmap(1, 1); - sk_sp<SkImage> image = SkMakeImageFromRasterBitmap(origBitmap, kNever_SkCopyPixelsMode); - sk_sp<SkShader> s = - image->makeShader(SkShader::kClamp_TileMode, SkShader::kRepeat_TileMode, nullptr); - - SkBitmap bitmap; - SkShader::TileMode xy[2]; - ASSERT_TRUE(s->isABitmap(&bitmap, nullptr, xy)) - << "1x1 bitmap shader must query as bitmap shader"; - EXPECT_EQ(origBitmap.pixelRef(), bitmap.pixelRef()); -} - TEST(SkiaBehavior, genIds) { SkBitmap bitmap = createSkBitmap(100, 100); uint32_t genId = bitmap.getGenerationID(); diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp index 1d7dc3d06ee4..634ceffe0741 100644 --- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp +++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp @@ -16,7 +16,6 @@ #include "tests/common/TestUtils.h" -#include <RecordingCanvas.h> #include <SkBlurDrawLooper.h> #include <SkCanvasStateUtils.h> #include <SkPicture.h> @@ -26,41 +25,6 @@ using namespace android; using namespace android::uirenderer; -/** - * Verify that we get the same culling bounds for text for (1) drawing glyphs - * directly to a Canvas or (2) going through a SkPicture as an intermediate step. - */ -OPENGL_PIPELINE_TEST(SkiaCanvasProxy, drawGlyphsViaPicture) { - auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { - // setup test variables - SkPaint paint; - paint.setAntiAlias(true); - paint.setTextSize(20); - static const char* text = "testing text bounds"; - - // draw text directly into Recording canvas - TestUtils::drawUtf8ToCanvas(&canvas, text, paint, 25, 25); - - // record the same text draw into a SkPicture and replay it into a Recording canvas - SkPictureRecorder recorder; - SkCanvas* skCanvas = recorder.beginRecording(200, 200, NULL, 0); - std::unique_ptr<Canvas> pictCanvas(Canvas::create_canvas(skCanvas)); - TestUtils::drawUtf8ToCanvas(pictCanvas.get(), text, paint, 25, 25); - sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture(); - - canvas.asSkCanvas()->drawPicture(picture); - }); - - // verify that the text bounds and matrices match - ASSERT_EQ(2U, dl->getOps().size()); - auto directOp = dl->getOps()[0]; - auto pictureOp = dl->getOps()[1]; - ASSERT_EQ(RecordedOpId::TextOp, directOp->opId); - EXPECT_EQ(directOp->opId, pictureOp->opId); - EXPECT_EQ(directOp->unmappedBounds, pictureOp->unmappedBounds); - EXPECT_EQ(directOp->localMatrix, pictureOp->localMatrix); -} - TEST(SkiaCanvas, drawShadowLayer) { auto surface = SkSurface::MakeRasterN32Premul(10, 10); SkiaCanvas canvas(surface->getCanvas()); diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp index 88d6dcf990a5..415f9e8517ff 100644 --- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp +++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp @@ -20,6 +20,7 @@ #include "AnimationContext.h" #include "DamageAccumulator.h" #include "IContextFactory.h" +#include "pipeline/skia/GLFunctorDrawable.h" #include "pipeline/skia/SkiaDisplayList.h" #include "renderthread/CanvasContext.h" #include "tests/common/TestUtils.h" @@ -36,32 +37,37 @@ TEST(SkiaDisplayList, create) { } TEST(SkiaDisplayList, reset) { - SkiaDisplayList skiaDL; + std::unique_ptr<SkiaDisplayList> skiaDL; + { + SkiaRecordingCanvas canvas{nullptr, 1, 1}; + canvas.drawColor(0, SkBlendMode::kSrc); + skiaDL.reset(canvas.finishRecording()); + } SkCanvas dummyCanvas; RenderNodeDrawable drawable(nullptr, &dummyCanvas); - skiaDL.mChildNodes.emplace_back(nullptr, &dummyCanvas); - skiaDL.mChildFunctors.emplace_back(nullptr, nullptr, &dummyCanvas); - skiaDL.mMutableImages.push_back(nullptr); - skiaDL.mVectorDrawables.push_back(nullptr); - skiaDL.mDisplayList.drawAnnotation(SkRect::MakeWH(200, 200), "testAnnotation", nullptr); - skiaDL.mProjectionReceiver = &drawable; - - ASSERT_FALSE(skiaDL.mChildNodes.empty()); - ASSERT_FALSE(skiaDL.mChildFunctors.empty()); - ASSERT_FALSE(skiaDL.mMutableImages.empty()); - ASSERT_FALSE(skiaDL.mVectorDrawables.empty()); - ASSERT_FALSE(skiaDL.isEmpty()); - ASSERT_TRUE(skiaDL.mProjectionReceiver); - - skiaDL.reset(); - - ASSERT_TRUE(skiaDL.mChildNodes.empty()); - ASSERT_TRUE(skiaDL.mChildFunctors.empty()); - ASSERT_TRUE(skiaDL.mMutableImages.empty()); - ASSERT_TRUE(skiaDL.mVectorDrawables.empty()); - ASSERT_TRUE(skiaDL.isEmpty()); - ASSERT_FALSE(skiaDL.mProjectionReceiver); + skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas); + GLFunctorDrawable functorDrawable(nullptr, nullptr, &dummyCanvas); + skiaDL->mChildFunctors.push_back(&functorDrawable); + skiaDL->mMutableImages.push_back(nullptr); + skiaDL->mVectorDrawables.push_back(nullptr); + skiaDL->mProjectionReceiver = &drawable; + + ASSERT_FALSE(skiaDL->mChildNodes.empty()); + ASSERT_FALSE(skiaDL->mChildFunctors.empty()); + ASSERT_FALSE(skiaDL->mMutableImages.empty()); + ASSERT_FALSE(skiaDL->mVectorDrawables.empty()); + ASSERT_FALSE(skiaDL->isEmpty()); + ASSERT_TRUE(skiaDL->mProjectionReceiver); + + skiaDL->reset(); + + ASSERT_TRUE(skiaDL->mChildNodes.empty()); + ASSERT_TRUE(skiaDL->mChildFunctors.empty()); + ASSERT_TRUE(skiaDL->mMutableImages.empty()); + ASSERT_TRUE(skiaDL->mVectorDrawables.empty()); + ASSERT_TRUE(skiaDL->isEmpty()); + ASSERT_FALSE(skiaDL->mProjectionReceiver); } TEST(SkiaDisplayList, reuseDisplayList) { @@ -91,7 +97,8 @@ TEST(SkiaDisplayList, syncContexts) { SkCanvas dummyCanvas; TestUtils::MockFunctor functor; - skiaDL.mChildFunctors.emplace_back(&functor, nullptr, &dummyCanvas); + GLFunctorDrawable functorDrawable(&functor, nullptr, &dummyCanvas); + skiaDL.mChildFunctors.push_back(&functorDrawable); SkRect bounds = SkRect::MakeWH(200, 200); VectorDrawableRoot vectorDrawable(new VectorDrawable::Group()); diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 42a92fcc7d89..65b4e26ab62f 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -18,13 +18,13 @@ #include <gtest/gtest.h> #include <SkClipStack.h> -#include <SkLiteRecorder.h> #include <SkSurface_Base.h> #include <string.h> #include "AnimationContext.h" #include "DamageAccumulator.h" #include "IContextFactory.h" #include "SkiaCanvas.h" +#include "pipeline/skia/SkiaUtils.h" #include "pipeline/skia/SkiaDisplayList.h" #include "pipeline/skia/SkiaOpenGLPipeline.h" #include "pipeline/skia/SkiaRecordingCanvas.h" @@ -42,7 +42,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver); }); LayerUpdateQueue layerUpdateQueue; - SkRect dirty = SkRect::MakeLargest(); + SkRect dirty = SkRectMakeLargest(); std::vector<sp<RenderNode>> renderNodes; renderNodes.push_back(redNode); bool opaque = true; @@ -51,7 +51,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) { auto surface = SkSurface::MakeRasterN32Premul(1, 1); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } @@ -63,7 +63,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, testOnPrepareTree) { }); LayerUpdateQueue layerUpdateQueue; - SkRect dirty = SkRect::MakeLargest(); + SkRect dirty = SkRectMakeLargest(); std::vector<sp<RenderNode>> renderNodes; renderNodes.push_back(redNode); bool opaque = true; @@ -84,7 +84,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, testOnPrepareTree) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); // drawFrame will crash if "SkiaPipeline::onPrepareTree" did not clean invalid VD pointer - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED); } @@ -98,7 +98,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { bottomHalfGreenCanvas.drawRect(0, 1, 2, 2, greenPaint); }); LayerUpdateQueue layerUpdateQueue; - SkRect dirty = SkRect::MakeLargest(); + SkRect dirty = SkRectMakeLargest(); std::vector<sp<RenderNode>> renderNodes; renderNodes.push_back(halfGreenNode); android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2); @@ -106,11 +106,11 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) { auto surface = SkSurface::MakeRasterN32Premul(2, 2); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, false, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned int)SK_ColorTRANSPARENT); ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN); @@ -130,7 +130,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) { auto surface = SkSurface::MakeRasterN32Premul(2, 2); surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surface, 1, 0), SK_ColorBLUE); @@ -161,18 +161,18 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) { // attach both layers to the update queue LayerUpdateQueue layerUpdateQueue; - SkRect dirty = SkRect::MakeLargest(); + SkRect dirty = SkRectMakeLargest(); layerUpdateQueue.enqueueLayerWithDamage(redNode.get(), dirty); layerUpdateQueue.enqueueLayerWithDamage(blueNode.get(), SkRect::MakeWH(2, 1)); ASSERT_EQ(layerUpdateQueue.entries().size(), 2UL); bool opaque = true; - FrameBuilder::LightGeometry lightGeometry; + LightGeometry lightGeometry; lightGeometry.radius = 1.0f; lightGeometry.center = {0.0f, 0.0f, 0.0f}; - BakedOpRenderer::LightInfo lightInfo; + LightInfo lightInfo; auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); - pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, false, lightInfo); + pipeline->renderLayers(lightGeometry, &layerUpdateQueue, opaque, lightInfo); ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorRED); ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorBLUE); ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 1), SK_ColorWHITE); @@ -203,37 +203,37 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) { ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE); // Single draw, should be white. - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE); // 1 Overdraw, should be blue blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffd0d0ff); // 2 Overdraw, should be green blended onto white renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffd0ffd0); // 3 Overdraw, should be pink blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffffc0c0); // 4 Overdraw, should be red blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080); // 5 Overdraw, should be red blended onto white. renderNodes.push_back(whiteNode); - pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, false, contentDrawBounds, + pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface); ASSERT_EQ(TestUtils::getColor(surface, 0, 0), (unsigned)0xffff8080); } @@ -319,7 +319,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) { SkRect dirty = SkRect::MakeWH(800, 600); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); sk_sp<DeferLayer<DeferTestCanvas>> surface(new DeferLayer<DeferTestCanvas>()); - pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false, contentDrawBounds, surface); + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, contentDrawBounds, surface); EXPECT_EQ(4, surface->canvas()->mDrawCounter); } @@ -349,7 +349,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) { SkRect dirty = SkRect::MakeLTRB(10, 20, 30, 40); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); sk_sp<DeferLayer<ClippedTestCanvas>> surface(new DeferLayer<ClippedTestCanvas>()); - pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false, + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); EXPECT_EQ(1, surface->canvas()->mDrawCounter); } @@ -379,7 +379,17 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) { SkRect dirty = SkRect::MakeLTRB(10, 10, 40, 40); auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); sk_sp<DeferLayer<ClipReplaceTestCanvas>> surface(new DeferLayer<ClipReplaceTestCanvas>()); - pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, false, + pipeline->renderFrame(layerUpdateQueue, dirty, nodes, true, SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), surface); EXPECT_EQ(1, surface->canvas()->mDrawCounter); } + +RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) { + auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread); + EXPECT_FALSE(pipeline->isSurfaceReady()); + EXPECT_TRUE(pipeline->setSurface((Surface*)0x01, SwapBehavior::kSwap_default, ColorMode::SRGB)); + EXPECT_TRUE(pipeline->isSurfaceReady()); + renderThread.destroyGlContext(); + EXPECT_FALSE(pipeline->isSurfaceReady()); +} + diff --git a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp index ad5fdacc8594..479c462bd1a6 100644 --- a/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp +++ b/libs/hwui/tests/unit/SkiaRenderPropertiesTests.cpp @@ -18,7 +18,6 @@ #include <gtest/gtest.h> #include <SkClipStack.h> -#include <SkLiteRecorder.h> #include <SkSurface_Base.h> #include <string.h> #include "AnimationContext.h" diff --git a/libs/hwui/tests/unit/SnapshotTests.cpp b/libs/hwui/tests/unit/SnapshotTests.cpp deleted file mode 100644 index 9d673c82d4d0..000000000000 --- a/libs/hwui/tests/unit/SnapshotTests.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> - -#include <Snapshot.h> - -#include <tests/common/TestUtils.h> - -using namespace android::uirenderer; - -TEST(Snapshot, serializeIntersectedClip) { - auto actualRoot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 100)); - auto root = TestUtils::makeSnapshot(Matrix4::identity(), Rect(10, 10, 90, 90)); - auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90)); - root->previous = actualRoot.get(); - child->previous = root.get(); - - LinearAllocator allocator; - ClipRect rect(Rect(0, 0, 75, 75)); - { - auto intersectWithChild = - child->serializeIntersectedClip(allocator, &rect, Matrix4::identity()); - ASSERT_NE(nullptr, intersectWithChild); - EXPECT_EQ(Rect(50, 50, 75, 75), intersectWithChild->rect) << "Expect intersect with child"; - } - - rect.intersectWithRoot = true; - { - auto intersectWithRoot = - child->serializeIntersectedClip(allocator, &rect, Matrix4::identity()); - ASSERT_NE(nullptr, intersectWithRoot); - EXPECT_EQ(Rect(10, 10, 75, 75), intersectWithRoot->rect) << "Expect intersect with root"; - } -} - -TEST(Snapshot, applyClip) { - auto actualRoot = TestUtils::makeSnapshot(Matrix4::identity(), Rect(0, 0, 100, 100)); - auto root = TestUtils::makeSnapshot(Matrix4::identity(), Rect(10, 10, 90, 90)); - root->previous = actualRoot.get(); - - ClipRect rect(Rect(0, 0, 75, 75)); - { - auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90)); - child->previous = root.get(); - child->applyClip(&rect, Matrix4::identity()); - - EXPECT_TRUE(child->getClipArea().isSimple()); - EXPECT_EQ(Rect(50, 50, 75, 75), child->getRenderTargetClip()); - } - - { - rect.intersectWithRoot = true; - auto child = TestUtils::makeSnapshot(Matrix4::identity(), Rect(50, 50, 90, 90)); - child->previous = root.get(); - child->applyClip(&rect, Matrix4::identity()); - - EXPECT_TRUE(child->getClipArea().isSimple()); - EXPECT_EQ(Rect(10, 10, 75, 75), child->getRenderTargetClip()); - } -} diff --git a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp b/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp deleted file mode 100644 index 92d05e44c6ca..000000000000 --- a/libs/hwui/tests/unit/TextDropShadowCacheTests.cpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> - -#include "GammaFontRenderer.h" -#include "TextDropShadowCache.h" -#include "tests/common/TestUtils.h" -#include "utils/Blur.h" - -#include <SkPaint.h> - -using namespace android; -using namespace android::uirenderer; - -RENDERTHREAD_OPENGL_PIPELINE_TEST(TextDropShadowCache, addRemove) { - SkPaint paint; - paint.setTextSize(20); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - - GammaFontRenderer gammaFontRenderer; - FontRenderer& fontRenderer = gammaFontRenderer.getFontRenderer(); - fontRenderer.setFont(&paint, SkMatrix::I()); - TextDropShadowCache cache(MB(5)); - cache.setFontRenderer(fontRenderer); - - std::vector<glyph_t> glyphs; - std::vector<float> positions; - float totalAdvance; - uirenderer::Rect bounds; - TestUtils::layoutTextUnscaled(paint, "This is a test", &glyphs, &positions, &totalAdvance, - &bounds); - EXPECT_TRUE(bounds.contains(5, -10, 100, 0)) << "Expect input to be nontrivially sized"; - - ShadowTexture* texture = cache.get(&paint, glyphs.data(), glyphs.size(), 10, positions.data()); - - ASSERT_TRUE(texture); - ASSERT_FALSE(texture->cleanup); - ASSERT_EQ((uint32_t)texture->objectSize(), cache.getSize()); - ASSERT_TRUE(cache.getSize()); - cache.clear(); - ASSERT_EQ(cache.getSize(), 0u); -} diff --git a/libs/hwui/tests/unit/TextureCacheTests.cpp b/libs/hwui/tests/unit/TextureCacheTests.cpp deleted file mode 100644 index ab740dd8330e..000000000000 --- a/libs/hwui/tests/unit/TextureCacheTests.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> - -#include "Extensions.h" -#include "TextureCache.h" -#include "tests/common/TestUtils.h" - -using namespace android; -using namespace android::uirenderer; - -RENDERTHREAD_OPENGL_PIPELINE_TEST(TextureCache, clear) { - TextureCache cache; - ASSERT_EQ(cache.getSize(), 0u); - // it is not 0, because FontRenderer allocates one texture - int initialCount = GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture); - SkBitmap skBitmap; - SkImageInfo info = SkImageInfo::Make(100, 100, kN32_SkColorType, kPremul_SkAlphaType); - skBitmap.setInfo(info); - sk_sp<Bitmap> hwBitmap(renderThread.allocateHardwareBitmap(skBitmap)); - cache.get(hwBitmap.get()); - ASSERT_EQ(GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture), initialCount + 1); - cache.clear(); - ASSERT_EQ(GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture), initialCount); -} diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index e424a266bf72..b645aeb55074 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -55,7 +55,8 @@ std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) { sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData))); LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName); std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>( - std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>()); + std::move(typeface), data, st.st_size, fileName, 0, + std::vector<minikin::FontVariation>()); std::vector<minikin::Font> fonts; fonts.push_back(minikin::Font::Builder(font).build()); return std::make_shared<minikin::FontFamily>(std::move(fonts)); diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp index 9e6d9a8c27de..aecceb3609f5 100644 --- a/libs/hwui/tests/unit/main.cpp +++ b/libs/hwui/tests/unit/main.cpp @@ -17,12 +17,13 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "Caches.h" #include "debug/GlesDriver.h" #include "debug/NullGlesDriver.h" #include "hwui/Typeface.h" #include "Properties.h" #include "tests/common/LeakChecker.h" +#include "thread/TaskProcessor.h" +#include "thread/Task.h" #include "thread/TaskManager.h" #include <signal.h> diff --git a/libs/hwui/thread/ThreadBase.h b/libs/hwui/thread/ThreadBase.h index 8068121f64cf..f9de8a5037e5 100644 --- a/libs/hwui/thread/ThreadBase.h +++ b/libs/hwui/thread/ThreadBase.h @@ -47,6 +47,8 @@ public: void join() { Thread::join(); } + bool isRunning() const { return Thread::isRunning(); } + protected: void waitForWork() { nsecs_t nextWakeup; diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp index 75740e8b5baf..3fb6a31a7d97 100644 --- a/libs/hwui/utils/Color.cpp +++ b/libs/hwui/utils/Color.cpp @@ -16,8 +16,10 @@ #include "Color.h" - #include <utils/Log.h> +#include <ui/ColorSpace.h> + +#include <algorithm> #include <cmath> namespace android { @@ -55,6 +57,26 @@ bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace) { return false; } +android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType) { + switch (colorType) { + case kRGBA_8888_SkColorType: + return PIXEL_FORMAT_RGBA_8888; + case kRGBA_F16_SkColorType: + return PIXEL_FORMAT_RGBA_FP16; + case kRGB_565_SkColorType: + return PIXEL_FORMAT_RGB_565; + case kRGB_888x_SkColorType: + return PIXEL_FORMAT_RGBX_8888; + case kRGBA_1010102_SkColorType: + return PIXEL_FORMAT_RGBA_1010102; + case kARGB_4444_SkColorType: + return PIXEL_FORMAT_RGBA_4444; + default: + ALOGW("Unsupported colorType: %d, return RGBA_8888 by default", (int)colorType); + return PIXEL_FORMAT_RGBA_8888; + } +} + sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { SkColorSpace::Gamut gamut; @@ -107,5 +129,97 @@ sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) { } } +template<typename T> +static constexpr T clamp(T x, T min, T max) { + return x < min ? min : x > max ? max : x; +} + +//static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f}; +static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f}; +static const mat3 BRADFORD = mat3{ + float3{ 0.8951f, -0.7502f, 0.0389f}, + float3{ 0.2664f, 1.7135f, -0.0685f}, + float3{-0.1614f, 0.0367f, 1.0296f} +}; + +static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) { + float3 srcLMS = matrix * srcWhitePoint; + float3 dstLMS = matrix * dstWhitePoint; + return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix; +} + +namespace LabColorSpace { + +static constexpr float A = 216.0f / 24389.0f; +static constexpr float B = 841.0f / 108.0f; +static constexpr float C = 4.0f / 29.0f; +static constexpr float D = 6.0f / 29.0f; + +float3 toXyz(const Lab& lab) { + float3 v { lab.L, lab.a, lab.b }; + v[0] = clamp(v[0], 0.0f, 100.0f); + v[1] = clamp(v[1], -128.0f, 128.0f); + v[2] = clamp(v[2], -128.0f, 128.0f); + + float fy = (v[0] + 16.0f) / 116.0f; + float fx = fy + (v[1] * 0.002f); + float fz = fy - (v[2] * 0.005f); + float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C); + float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C); + float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C); + + v[0] = X * ILLUMINANT_D50_XYZ[0]; + v[1] = Y * ILLUMINANT_D50_XYZ[1]; + v[2] = Z * ILLUMINANT_D50_XYZ[2]; + + return v; +} + +Lab fromXyz(const float3& v) { + float X = v[0] / ILLUMINANT_D50_XYZ[0]; + float Y = v[1] / ILLUMINANT_D50_XYZ[1]; + float Z = v[2] / ILLUMINANT_D50_XYZ[2]; + + float fx = X > A ? pow(X, 1.0f / 3.0f) : B * X + C; + float fy = Y > A ? pow(Y, 1.0f / 3.0f) : B * Y + C; + float fz = Z > A ? pow(Z, 1.0f / 3.0f) : B * Z + C; + + float L = 116.0f * fy - 16.0f; + float a = 500.0f * (fx - fy); + float b = 200.0f * (fy - fz); + + return Lab { + clamp(L, 0.0f, 100.0f), + clamp(a, -128.0f, 128.0f), + clamp(b, -128.0f, 128.0f) + }; +} + +}; + +Lab sRGBToLab(SkColor color) { + auto colorSpace = ColorSpace::sRGB(); + float3 rgb; + rgb.r = SkColorGetR(color) / 255.0f; + rgb.g = SkColorGetG(color) / 255.0f; + rgb.b = SkColorGetB(color) / 255.0f; + float3 xyz = colorSpace.rgbToXYZ(rgb); + float3 srcXYZ = ColorSpace::XYZ(float3{colorSpace.getWhitePoint(), 1}); + xyz = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * xyz; + return LabColorSpace::fromXyz(xyz); +} + +SkColor LabToSRGB(const Lab& lab, SkAlpha alpha) { + auto colorSpace = ColorSpace::sRGB(); + float3 xyz = LabColorSpace::toXyz(lab); + float3 dstXYZ = ColorSpace::XYZ(float3{colorSpace.getWhitePoint(), 1}); + xyz = adaptation(BRADFORD, ILLUMINANT_D50_XYZ, dstXYZ) * xyz; + float3 rgb = colorSpace.xyzToRGB(xyz); + return SkColorSetARGB(alpha, + static_cast<uint8_t>(rgb.r * 255), + static_cast<uint8_t>(rgb.g * 255), + static_cast<uint8_t>(rgb.b * 255)); +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/utils/Color.h b/libs/hwui/utils/Color.h index 2bec1f5f7852..4daccda78e23 100644 --- a/libs/hwui/utils/Color.h +++ b/libs/hwui/utils/Color.h @@ -18,9 +18,11 @@ #include <math.h> #include <system/graphics.h> +#include <ui/PixelFormat.h> #include <SkColor.h> #include <SkColorSpace.h> +#include <SkImageInfo.h> namespace android { namespace uirenderer { @@ -113,7 +115,19 @@ static constexpr float EOCF(float srgb) { // returns true for sRGB, gamma 2.2 and Display P3 for instance bool transferFunctionCloseToSRGB(const SkColorSpace* colorSpace); +android::PixelFormat ColorTypeToPixelFormat(SkColorType colorType); + sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace); + +struct Lab { + float L; + float a; + float b; +}; + +Lab sRGBToLab(SkColor color); +SkColor LabToSRGB(const Lab& lab, SkAlpha alpha); + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/utils/GLUtils.cpp b/libs/hwui/utils/GLUtils.cpp index bf27300029c6..fcd036c451e9 100644 --- a/libs/hwui/utils/GLUtils.cpp +++ b/libs/hwui/utils/GLUtils.cpp @@ -59,5 +59,22 @@ bool GLUtils::dumpGLErrors() { #endif } +const char* GLUtils::getGLFramebufferError() { + switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) { + case GL_FRAMEBUFFER_COMPLETE: + return "GL_FRAMEBUFFER_COMPLETE"; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; + case GL_FRAMEBUFFER_UNSUPPORTED: + return "GL_FRAMEBUFFER_UNSUPPORTED"; + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + return "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; + default: + return "Unknown error"; + } +} + }; // namespace uirenderer }; // namespace android diff --git a/libs/hwui/utils/GLUtils.h b/libs/hwui/utils/GLUtils.h index debfb5d06944..ca8810bde070 100644 --- a/libs/hwui/utils/GLUtils.h +++ b/libs/hwui/utils/GLUtils.h @@ -20,6 +20,12 @@ #include <log/log.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <GLES3/gl3.h> + namespace android { namespace uirenderer { @@ -43,8 +49,53 @@ public: */ static bool dumpGLErrors(); + static const char* getGLFramebufferError(); }; // class GLUtils +class AutoEglImage { +public: + AutoEglImage(EGLDisplay display, EGLClientBuffer clientBuffer) : mDisplay(display) { + EGLint imageAttrs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE}; + image = eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, clientBuffer, + imageAttrs); + } + + ~AutoEglImage() { + if (image != EGL_NO_IMAGE_KHR) { + eglDestroyImageKHR(mDisplay, image); + } + } + + EGLImageKHR image = EGL_NO_IMAGE_KHR; + +private: + EGLDisplay mDisplay = EGL_NO_DISPLAY; +}; + +class AutoSkiaGlTexture { +public: + AutoSkiaGlTexture() { + glGenTextures(1, &mTexture); + glBindTexture(GL_TEXTURE_2D, mTexture); + } + + ~AutoSkiaGlTexture() { glDeleteTextures(1, &mTexture); } + + GLuint mTexture = 0; +}; + +class AutoGLFramebuffer { +public: + AutoGLFramebuffer() { + glGenFramebuffers(1, &mFb); + glBindFramebuffer(GL_FRAMEBUFFER, mFb); + } + + ~AutoGLFramebuffer() { glDeleteFramebuffers(1, &mFb); } + + GLuint mFb; +}; + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/utils/LinearAllocator.cpp b/libs/hwui/utils/LinearAllocator.cpp index 5a59de8b922a..3e5021cd45d4 100644 --- a/libs/hwui/utils/LinearAllocator.cpp +++ b/libs/hwui/utils/LinearAllocator.cpp @@ -29,6 +29,7 @@ #include <stdlib.h> #include <utils/Log.h> +#include <utils/Macros.h> // The ideal size of a page allocation (these need to be multiples of 8) #define INITIAL_PAGE_SIZE ((size_t)512) // 512b @@ -41,15 +42,6 @@ // Must be smaller than INITIAL_PAGE_SIZE #define MAX_WASTE_RATIO (0.5f) -#if ALIGN_DOUBLE -#define ALIGN_SZ (sizeof(double)) -#else -#define ALIGN_SZ (sizeof(int)) -#endif - -#define ALIGN(x) (((x) + ALIGN_SZ - 1) & ~(ALIGN_SZ - 1)) -#define ALIGN_PTR(p) ((void*)(ALIGN((size_t)(p)))) - #if LOG_NDEBUG #define ADD_ALLOCATION() #define RM_ALLOCATION() diff --git a/libs/hwui/utils/Macros.h b/libs/hwui/utils/Macros.h index d758f29d6dcb..eee6785afd53 100644 --- a/libs/hwui/utils/Macros.h +++ b/libs/hwui/utils/Macros.h @@ -34,4 +34,13 @@ private: \ #define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#if ALIGN_DOUBLE +#define ALIGN_SZ (sizeof(double)) +#else +#define ALIGN_SZ (sizeof(int)) +#endif + +#define ALIGN(x) (((x) + ALIGN_SZ - 1) & ~(ALIGN_SZ - 1)) +#define ALIGN_PTR(p) ((void*)(ALIGN((size_t)(p)))) + #endif /* MACROS_H */ diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h index 233adae1df24..ebf2343c5518 100644 --- a/libs/hwui/utils/PaintUtils.h +++ b/libs/hwui/utils/PaintUtils.h @@ -16,10 +16,12 @@ #ifndef PAINT_UTILS_H #define PAINT_UTILS_H +#include <GLES2/gl2.h> #include <utils/Blur.h> #include <SkColorFilter.h> #include <SkDrawLooper.h> +#include <SkPaint.h> #include <SkShader.h> namespace android { diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp deleted file mode 100644 index 700d3b3cf000..000000000000 --- a/libs/hwui/utils/TestWindowContext.cpp +++ /dev/null @@ -1,197 +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 "TestWindowContext.h" - -#include "AnimationContext.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" - -#include <cutils/memory.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: - explicit 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(800.0f, 255 * 0.075f, 255 * 0.15f); - mProxy->setLightCenter(lightVector); - mCanvas.reset(new android::uirenderer::RecordingCanvas(mSize.width(), mSize.height())); - } - - SkCanvas* prepareToDraw() { - // mCanvas->reset(mSize.width(), mSize.height()); - mCanvas->clipRect(0, 0, mSize.width(), mSize.height(), SkClipOp::kReplace_deprecated); - 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) { - sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB(); - SkImageInfo destinationConfig = - SkImageInfo::Make(mSize.width(), mSize.height(), kRGBA_8888_SkColorType, - kPremul_SkAlphaType, colorSpace); - bmp->allocPixels(destinationConfig); - android_memset32((uint32_t*)bmp->getPixels(), SK_ColorRED, - mSize.width() * mSize.height() * 4); - - android::CpuConsumer::LockedBuffer nativeBuffer; - android::status_t retval = mCpuConsumer->lockNextBuffer(&nativeBuffer); - if (retval == android::BAD_VALUE) { - SkDebugf("write_canvas_png() got no buffer; returning transparent"); - // No buffer ready to read - commonly triggered by dm sending us - // a no-op source, or calling code that doesn't do anything on this - // backend. - bmp->eraseColor(SK_ColorTRANSPARENT); - return false; - } else if (retval) { - SkDebugf("Failed to lock buffer to read pixels: %d.", retval); - return false; - } - - // Move the pixels into the destination SkBitmap - - LOG_ALWAYS_FATAL_IF(nativeBuffer.format != android::PIXEL_FORMAT_RGBA_8888, - "Native buffer not RGBA!"); - SkImageInfo nativeConfig = SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height, - kRGBA_8888_SkColorType, kPremul_SkAlphaType); - - // Android stride is in pixels, Skia stride is in bytes - SkBitmap nativeWrapper; - bool success = nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, - nativeBuffer.stride * 4); - if (!success) { - SkDebugf("Failed to wrap HWUI buffer in a SkBitmap"); - return false; - } - - LOG_ALWAYS_FATAL_IF(bmp->colorType() != kRGBA_8888_SkColorType, - "Destination buffer not RGBA!"); - success = nativeWrapper.readPixels(destinationConfig, bmp->getPixels(), bmp->rowBytes(), 0, - 0); - if (!success) { - SkDebugf("Failed to extract pixels from HWUI buffer"); - return false; - } - - mCpuConsumer->unlockBuffer(nativeBuffer); - - return true; - } - -private: - std::unique_ptr<android::uirenderer::RenderNode> mRootNode; - std::unique_ptr<android::uirenderer::renderthread::RenderProxy> mProxy; - std::unique_ptr<android::uirenderer::RecordingCanvas> mCanvas; - 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 deleted file mode 100644 index 17ad1e3fef55..000000000000 --- a/libs/hwui/utils/TestWindowContext.h +++ /dev/null @@ -1,64 +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. - */ -#ifndef TESTWINDOWCONTEXT_H_ -#define TESTWINDOWCONTEXT_H_ - -#include <cutils/compiler.h> - -class SkBitmap; -class SkCanvas; - -namespace android { - -namespace uirenderer { - -/** - Wraps all libui/libgui classes and types that external tests depend on, - exposing only primitive Skia types. -*/ - -class ANDROID_API TestWindowContext { -public: - TestWindowContext(); - ~TestWindowContext(); - - /// We need to know the size of the window. - void initialize(int width, int height); - - /// Returns a canvas to draw into; NULL if not yet initialize()d. - SkCanvas* prepareToDraw(); - - /// Flushes all drawing commands to HWUI; no-op if not yet initialize()d. - void finishDrawing(); - - /// Blocks until HWUI has processed all pending drawing commands; - /// no-op if not yet initialize()d. - void fence(); - - /// Returns false if not yet initialize()d. - bool capturePixels(SkBitmap* bmp); - -private: - /// Hidden implementation. - class TestWindowData; - - TestWindowData* mData; -}; - -} // namespace uirenderer -} // namespace android - -#endif // TESTWINDOWCONTEXT_H_ diff --git a/libs/hwui/utils/TypeLogic.h b/libs/hwui/utils/TypeLogic.h new file mode 100644 index 000000000000..dbdad33d8335 --- /dev/null +++ b/libs/hwui/utils/TypeLogic.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 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 <type_traits> + +namespace android::uirenderer { + +template <typename D, typename S> struct copy_const { + using type = std::conditional_t<std::is_const<S>::value, std::add_const_t<D>, D>; +}; +template <typename D, typename S> using copy_const_t = typename copy_const<D, S>::type; + +template <typename D, typename S> struct copy_volatile { + using type = std::conditional_t<std::is_volatile<S>::value, std::add_volatile_t<D>, D>; +}; +template <typename D, typename S> using copy_volatile_t = typename copy_volatile<D, S>::type; + +template <typename D, typename S> struct copy_cv { + using type = copy_volatile_t<copy_const_t<D, S>, S>; +}; + +template <typename D, typename S> using same_cv = copy_cv<std::remove_cv_t<D>, S>; +template <typename D, typename S> using same_cv_t = typename same_cv<D, S>::type; + +}; // namespace android::uirenderer
\ No newline at end of file diff --git a/libs/incident/proto/android/section.proto b/libs/incident/proto/android/section.proto index 45f3c91850e7..5afe22a3095f 100644 --- a/libs/incident/proto/android/section.proto +++ b/libs/incident/proto/android/section.proto @@ -51,10 +51,9 @@ enum SectionType { message SectionFlags { optional SectionType type = 1 [default = SECTION_NONE]; optional string args = 2; - optional bool device_specific = 3 [default = false]; // If true, then the section will only be generated for userdebug and eng // builds. - optional bool userdebug_and_eng_only = 4 [default = false]; + optional bool userdebug_and_eng_only = 3 [default = false]; } extend google.protobuf.FieldOptions { diff --git a/libs/input/Android.bp b/libs/input/Android.bp index 67682a06cb6a..c5a6ec590d30 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -36,9 +36,8 @@ cc_library_shared { cflags: [ "-Wall", + "-Wextra", "-Werror", - "-Wunused", - "-Wunreachable-code", ], } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 4794f3da824c..eb3469e0b3f0 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -28,7 +28,6 @@ #include <utils/BitSet.h> #include <utils/RefBase.h> #include <utils/Looper.h> -#include <utils/String8.h> #include <gui/DisplayEventReceiver.h> namespace android { diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 173cd507d943..eb2bc98ec9e9 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -23,14 +23,10 @@ #include <utils/String8.h> #include <gui/Surface.h> -// ToDo: Fix code to be warning free -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" #include <SkBitmap.h> #include <SkCanvas.h> #include <SkColor.h> #include <SkPaint.h> -#pragma GCC diagnostic pop #include <android/native_window.h> diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp index 7ad83ca79695..44bc97adb946 100644 --- a/libs/protoutil/Android.bp +++ b/libs/protoutil/Android.bp @@ -12,9 +12,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -cc_library { - name: "libprotoutil", +cc_defaults { + name: "libprotoutil_defaults", cflags: [ "-Wall", @@ -30,27 +29,39 @@ cc_library { "src/protobuf.cpp", ], - export_include_dirs: ["include"], - shared_libs: [ + "libbase", "libcutils", "liblog", ], } +cc_library { + name: "libprotoutil", + + defaults: ["libprotoutil_defaults"], + + export_include_dirs: ["include"], +} + cc_test { name: "libprotoutil_test", - srcs: [ - "tests/EncodedBuffer_test.cpp", - ], + defaults: ["libprotoutil_defaults"], + + local_include_dirs: ["include"], + + srcs: ["tests/*"], shared_libs: [ - "libcutils", - "libprotoutil", + "libprotobuf-cpp-full", ], static_libs: [ "libgmock", ], + + proto: { + type: "full", + } } diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h index e17f48b98037..ad765592eb55 100644 --- a/libs/protoutil/include/android/util/ProtoOutputStream.h +++ b/libs/protoutil/include/android/util/ProtoOutputStream.h @@ -17,10 +17,11 @@ #ifndef ANDROID_UTIL_PROTOOUTPUT_STREAM_H #define ANDROID_UTIL_PROTOOUTPUT_STREAM_H -#include <android/util/EncodedBuffer.h> - +#include <cstdint> #include <string> +#include <android/util/EncodedBuffer.h> + namespace android { namespace util { @@ -104,7 +105,7 @@ public: /** * Starts a sub-message write session. * Returns a token of this write session. - * Must call end(token) when finish write this sub-message. + * Must call end(token) exactly once when finish write this sub-message. */ uint64_t start(uint64_t fieldId); void end(uint64_t token); @@ -143,16 +144,16 @@ private: inline void writeDoubleImpl(uint32_t id, double val); inline void writeFloatImpl(uint32_t id, float val); - inline void writeInt64Impl(uint32_t id, long long val); - inline void writeInt32Impl(uint32_t id, int val); + inline void writeInt64Impl(uint32_t id, int64_t val); + inline void writeInt32Impl(uint32_t id, int32_t val); inline void writeUint64Impl(uint32_t id, uint64_t val); inline void writeUint32Impl(uint32_t id, uint32_t val); inline void writeFixed64Impl(uint32_t id, uint64_t val); inline void writeFixed32Impl(uint32_t id, uint32_t val); - inline void writeSFixed64Impl(uint32_t id, long long val); - inline void writeSFixed32Impl(uint32_t id, int val); - inline void writeZigzagInt64Impl(uint32_t id, long long val); - inline void writeZigzagInt32Impl(uint32_t id, int val); + inline void writeSFixed64Impl(uint32_t id, int64_t val); + inline void writeSFixed32Impl(uint32_t id, int32_t val); + inline void writeZigzagInt64Impl(uint32_t id, int64_t val); + inline void writeZigzagInt32Impl(uint32_t id, int32_t val); inline void writeEnumImpl(uint32_t id, int val); inline void writeBoolImpl(uint32_t id, bool val); inline void writeUtf8StringImpl(uint32_t id, const char* val, size_t size); @@ -161,6 +162,9 @@ private: bool compact(); size_t editEncodedSize(size_t rawSize); bool compactSize(size_t rawSize); + + template<typename T> + bool internalWrite(uint64_t fieldId, T val, const char* typeName); }; } diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp index 22b9709ef066..ff3fad6055e1 100644 --- a/libs/protoutil/src/ProtoOutputStream.cpp +++ b/libs/protoutil/src/ProtoOutputStream.cpp @@ -15,8 +15,10 @@ */ #define LOG_TAG "libprotoutil" -#include <inttypes.h> +#include <cinttypes> +#include <type_traits> +#include <android-base/file.h> #include <android/util/protobuf.h> #include <android/util/ProtoOutputStream.h> #include <cutils/log.h> @@ -50,112 +52,73 @@ ProtoOutputStream::clear() mExpectedObjectToken = UINT64_C(-1); } +template<typename T> bool -ProtoOutputStream::write(uint64_t fieldId, double val) +ProtoOutputStream::internalWrite(uint64_t fieldId, T val, const char* typeName) { if (mCompact) return false; const uint32_t id = (uint32_t)fieldId; switch (fieldId & FIELD_TYPE_MASK) { case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break; case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break; - case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break; + case FIELD_TYPE_INT64: writeInt64Impl(id, (int64_t)val); break; case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break; - case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break; + case FIELD_TYPE_INT32: writeInt32Impl(id, (int32_t)val); break; case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break; case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break; case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break; - case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break; - case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break; - case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break; - case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break; + case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int32_t)val); break; + case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (int64_t)val); break; + case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int32_t)val); break; + case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (int64_t)val); break; + case FIELD_TYPE_ENUM: + if (std::is_integral<T>::value) { + writeEnumImpl(id, (int)val); + } else { + goto unsupported; + } + break; + case FIELD_TYPE_BOOL: + if (std::is_integral<T>::value) { + writeBoolImpl(id, val != 0); + } else { + goto unsupported; + } + break; default: - ALOGW("Field type %d is not supported when writing double val.", - (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT)); - return false; + goto unsupported; } return true; + +unsupported: + ALOGW("Field type %" PRIu64 " is not supported when writing %s val.", + (fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT, typeName); + return false; +} + +bool +ProtoOutputStream::write(uint64_t fieldId, double val) +{ + return internalWrite(fieldId, val, "double"); } + bool ProtoOutputStream::write(uint64_t fieldId, float val) { - if (mCompact) return false; - const uint32_t id = (uint32_t)fieldId; - switch (fieldId & FIELD_TYPE_MASK) { - case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break; - case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break; - case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break; - case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break; - case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break; - case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break; - case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break; - case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break; - case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break; - case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break; - case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break; - case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break; - default: - ALOGW("Field type %d is not supported when writing float val.", - (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT)); - return false; - } - return true; + return internalWrite(fieldId, val, "float"); } bool ProtoOutputStream::write(uint64_t fieldId, int val) { - if (mCompact) return false; - const uint32_t id = (uint32_t)fieldId; - switch (fieldId & FIELD_TYPE_MASK) { - case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break; - case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break; - case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break; - case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break; - case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break; - case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break; - case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break; - case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break; - case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break; - case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break; - case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break; - case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break; - case FIELD_TYPE_ENUM: writeEnumImpl(id, (int)val); break; - case FIELD_TYPE_BOOL: writeBoolImpl(id, val != 0); break; - default: - ALOGW("Field type %d is not supported when writing int val.", - (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT)); - return false; - } - return true; + return internalWrite(fieldId, val, "int"); } bool ProtoOutputStream::write(uint64_t fieldId, long long val) { - if (mCompact) return false; - const uint32_t id = (uint32_t)fieldId; - switch (fieldId & FIELD_TYPE_MASK) { - case FIELD_TYPE_DOUBLE: writeDoubleImpl(id, (double)val); break; - case FIELD_TYPE_FLOAT: writeFloatImpl(id, (float)val); break; - case FIELD_TYPE_INT64: writeInt64Impl(id, (long long)val); break; - case FIELD_TYPE_UINT64: writeUint64Impl(id, (uint64_t)val); break; - case FIELD_TYPE_INT32: writeInt32Impl(id, (int)val); break; - case FIELD_TYPE_FIXED64: writeFixed64Impl(id, (uint64_t)val); break; - case FIELD_TYPE_FIXED32: writeFixed32Impl(id, (uint32_t)val); break; - case FIELD_TYPE_UINT32: writeUint32Impl(id, (uint32_t)val); break; - case FIELD_TYPE_SFIXED32: writeSFixed32Impl(id, (int)val); break; - case FIELD_TYPE_SFIXED64: writeSFixed64Impl(id, (long long)val); break; - case FIELD_TYPE_SINT32: writeZigzagInt32Impl(id, (int)val); break; - case FIELD_TYPE_SINT64: writeZigzagInt64Impl(id, (long long)val); break; - case FIELD_TYPE_ENUM: writeEnumImpl(id, (int)val); break; - case FIELD_TYPE_BOOL: writeBoolImpl(id, val != 0); break; - default: - ALOGW("Field type %d is not supported when writing long long val.", - (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT)); - return false; - } - return true; + return internalWrite(fieldId, val, "long long"); } bool @@ -168,8 +131,8 @@ ProtoOutputStream::write(uint64_t fieldId, bool val) writeBoolImpl(id, val); return true; default: - ALOGW("Field type %d is not supported when writing bool val.", - (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT)); + ALOGW("Field type %" PRIu64 " is not supported when writing bool val.", + (fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT); return false; } } @@ -184,8 +147,8 @@ ProtoOutputStream::write(uint64_t fieldId, std::string val) writeUtf8StringImpl(id, val.c_str(), val.size()); return true; default: - ALOGW("Field type %d is not supported when writing string val.", - (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT)); + ALOGW("Field type %" PRIu64 " is not supported when writing string val.", + (fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT); return false; } } @@ -205,8 +168,8 @@ ProtoOutputStream::write(uint64_t fieldId, const char* val, size_t size) writeMessageBytesImpl(id, val, size); return true; default: - ALOGW("Field type %d is not supported when writing char[] val.", - (int)((fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT)); + ALOGW("Field type %" PRIu64 " is not supported when writing char[] val.", + (fieldId & FIELD_TYPE_MASK) >> FIELD_TYPE_SHIFT); return false; } } @@ -281,12 +244,14 @@ ProtoOutputStream::end(uint64_t token) { if (token != mExpectedObjectToken) { ALOGE("Unexpected token: 0x%" PRIx64 ", should be 0x%" PRIx64, token, mExpectedObjectToken); + mDepth = UINT32_C(-1); // make depth invalid return; } uint32_t depth = getDepthFromToken(token); if (depth != (mDepth & 0x01ff)) { ALOGE("Unexpected depth: %" PRIu32 ", should be %" PRIu32, depth, mDepth); + mDepth = UINT32_C(-1); // make depth invalid return; } mDepth--; @@ -319,7 +284,7 @@ bool ProtoOutputStream::compact() { if (mCompact) return true; if (mDepth != 0) { - ALOGE("Can't compact when depth(%" PRIu32 ") is not zero. Missing calls to end.", mDepth); + ALOGE("Can't compact when depth(%" PRIu32 ") is not zero. Missing or extra calls to end.", mDepth); return false; } // record the size of the original buffer. @@ -462,24 +427,11 @@ ProtoOutputStream::size() { if (!compact()) { ALOGE("compact failed, the ProtoOutputStream data is corrupted!"); - // TODO: handle this error + return 0; } return mBuffer.size(); } -static bool write_all(int fd, uint8_t const* buf, size_t size) -{ - while (size > 0) { - ssize_t amt = ::write(fd, buf, size); - if (amt < 0) { - return false; - } - size -= amt; - buf += amt; - } - return true; -} - bool ProtoOutputStream::flush(int fd) { @@ -488,7 +440,7 @@ ProtoOutputStream::flush(int fd) EncodedBuffer::iterator it = mBuffer.begin(); while (it.readBuffer() != NULL) { - if (!write_all(fd, it.readBuffer(), it.currentToRead())) return false; + if (!android::base::WriteFully(fd, it.readBuffer(), it.currentToRead())) return false; it.rp()->move(it.currentToRead()); } return true; @@ -499,7 +451,7 @@ ProtoOutputStream::data() { if (!compact()) { ALOGE("compact failed, the ProtoOutputStream data is corrupted!"); - // TODO: handle this error + mBuffer.clear(); } return mBuffer.begin(); } @@ -554,17 +506,17 @@ ProtoOutputStream::writeFloatImpl(uint32_t id, float val) } inline void -ProtoOutputStream::writeInt64Impl(uint32_t id, long long val) +ProtoOutputStream::writeInt64Impl(uint32_t id, int64_t val) { mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint64((uint64_t)val); + mBuffer.writeRawVarint64(val); } inline void -ProtoOutputStream::writeInt32Impl(uint32_t id, int val) +ProtoOutputStream::writeInt32Impl(uint32_t id, int32_t val) { mBuffer.writeHeader(id, WIRE_TYPE_VARINT); - mBuffer.writeRawVarint32((uint32_t)val); + mBuffer.writeRawVarint32(val); } inline void @@ -596,28 +548,28 @@ ProtoOutputStream::writeFixed32Impl(uint32_t id, uint32_t val) } inline void -ProtoOutputStream::writeSFixed64Impl(uint32_t id, long long val) +ProtoOutputStream::writeSFixed64Impl(uint32_t id, int64_t val) { mBuffer.writeHeader(id, WIRE_TYPE_FIXED64); - mBuffer.writeRawFixed64((uint64_t)val); + mBuffer.writeRawFixed64(val); } inline void -ProtoOutputStream::writeSFixed32Impl(uint32_t id, int val) +ProtoOutputStream::writeSFixed32Impl(uint32_t id, int32_t val) { mBuffer.writeHeader(id, WIRE_TYPE_FIXED32); - mBuffer.writeRawFixed32((uint32_t)val); + mBuffer.writeRawFixed32(val); } inline void -ProtoOutputStream::writeZigzagInt64Impl(uint32_t id, long long val) +ProtoOutputStream::writeZigzagInt64Impl(uint32_t id, int64_t val) { mBuffer.writeHeader(id, WIRE_TYPE_VARINT); mBuffer.writeRawVarint64((val << 1) ^ (val >> 63)); } inline void -ProtoOutputStream::writeZigzagInt32Impl(uint32_t id, int val) +ProtoOutputStream::writeZigzagInt32Impl(uint32_t id, int32_t val) { mBuffer.writeHeader(id, WIRE_TYPE_VARINT); mBuffer.writeRawVarint32((val << 1) ^ (val >> 31)); diff --git a/libs/protoutil/tests/EncodedBuffer_test.cpp b/libs/protoutil/tests/EncodedBuffer_test.cpp index 615ab4ab29ed..964fc8ec9ee0 100644 --- a/libs/protoutil/tests/EncodedBuffer_test.cpp +++ b/libs/protoutil/tests/EncodedBuffer_test.cpp @@ -17,9 +17,138 @@ using namespace android::util; +constexpr size_t TEST_CHUNK_SIZE = 16UL; +constexpr size_t TEST_CHUNK_HALF_SIZE = TEST_CHUNK_SIZE / 2; +constexpr size_t TEST_CHUNK_3X_SIZE = 3 * TEST_CHUNK_SIZE; + +static void expectPointer(EncodedBuffer::Pointer* p, size_t pos) { + EXPECT_EQ(p->pos(), pos); + EXPECT_EQ(p->index(), pos / TEST_CHUNK_SIZE); + EXPECT_EQ(p->offset(), pos % TEST_CHUNK_SIZE); +} + +TEST(EncodedBufferTest, WriteSimple) { + EncodedBuffer buffer(TEST_CHUNK_SIZE); + EXPECT_EQ(buffer.size(), 0UL); + expectPointer(buffer.wp(), 0); + EXPECT_EQ(buffer.currentToWrite(), TEST_CHUNK_SIZE); + for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) { + buffer.writeRawByte(50 + i); + } + EXPECT_EQ(buffer.size(), TEST_CHUNK_HALF_SIZE); + expectPointer(buffer.wp(), TEST_CHUNK_HALF_SIZE); + EXPECT_EQ(buffer.currentToWrite(), TEST_CHUNK_HALF_SIZE); + for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) { + buffer.writeRawByte(80 + i); + } + EXPECT_EQ(buffer.size(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE); + expectPointer(buffer.wp(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE); + EXPECT_EQ(buffer.currentToWrite(), TEST_CHUNK_HALF_SIZE); + + // verifies the buffer's data + expectPointer(buffer.ep(), 0); + for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) { + EXPECT_EQ(buffer.readRawByte(), 50 + i); + } + for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) { + EXPECT_EQ(buffer.readRawByte(), 80 + i); + } + + // clears the buffer + buffer.clear(); + EXPECT_EQ(buffer.size(), 0UL); + expectPointer(buffer.wp(), 0); +} + +TEST(EncodedBufferTest, WriteVarint) { + EncodedBuffer buffer(TEST_CHUNK_SIZE); + size_t expected_buffer_size = 0; + EXPECT_EQ(buffer.writeRawVarint32(13), 1); + expected_buffer_size += 1; + EXPECT_EQ(buffer.size(), expected_buffer_size); + EXPECT_EQ(buffer.writeRawVarint32(UINT32_C(-1)), 5); + expected_buffer_size += 5; + EXPECT_EQ(buffer.size(), expected_buffer_size); + + EXPECT_EQ(buffer.writeRawVarint64(200), 2); + expected_buffer_size += 2; + EXPECT_EQ(buffer.size(), expected_buffer_size); + EXPECT_EQ(buffer.writeRawVarint64(UINT64_C(-1)), 10); + expected_buffer_size += 10; + EXPECT_EQ(buffer.size(), expected_buffer_size); + + buffer.writeRawFixed32(UINT32_C(-1)); + expected_buffer_size += 4; + EXPECT_EQ(buffer.size(), expected_buffer_size); + buffer.writeRawFixed64(UINT64_C(-1)); + expected_buffer_size += 8; + EXPECT_EQ(buffer.size(), expected_buffer_size); + + EXPECT_EQ(buffer.writeHeader(32, 2), 2); + expected_buffer_size += 2; + EXPECT_EQ(buffer.size(), expected_buffer_size); + + // verify data are correctly written to the buffer. + expectPointer(buffer.ep(), 0); + EXPECT_EQ(buffer.readRawVarint(), UINT32_C(13)); + EXPECT_EQ(buffer.readRawVarint(), UINT32_C(-1)); + EXPECT_EQ(buffer.readRawVarint(), UINT64_C(200)); + EXPECT_EQ(buffer.readRawVarint(), UINT64_C(-1)); + EXPECT_EQ(buffer.readRawFixed32(), UINT32_C(-1)); + EXPECT_EQ(buffer.readRawFixed64(), UINT64_C(-1)); + EXPECT_EQ(buffer.readRawVarint(), UINT64_C((32 << 3) + 2)); + expectPointer(buffer.ep(), expected_buffer_size); +} + +TEST(EncodedBufferTest, Edit) { + EncodedBuffer buffer(TEST_CHUNK_SIZE); + buffer.writeRawFixed64(0xdeadbeefdeadbeef); + EXPECT_EQ(buffer.readRawFixed64(), UINT64_C(0xdeadbeefdeadbeef)); + + buffer.editRawFixed32(4, 0x12345678); + // fixed 64 is little endian order. + buffer.ep()->rewind(); // rewind ep for readRawFixed64 from 0 + EXPECT_EQ(buffer.readRawFixed64(), UINT64_C(0x12345678deadbeef)); + + buffer.wp()->rewind(); + expectPointer(buffer.wp(), 0); + buffer.copy(4, 3); + buffer.ep()->rewind(); // rewind ep for readRawFixed64 from 0 + EXPECT_EQ(buffer.readRawFixed64(), UINT64_C(0x12345678de345678)); +} + +TEST(EncodedBufferTest, ReadSimple) { + EncodedBuffer buffer(TEST_CHUNK_SIZE); + for (size_t i = 0; i < TEST_CHUNK_3X_SIZE; i++) { + buffer.writeRawByte(i); + } + auto iter = buffer.begin(); + EXPECT_EQ(iter.size(), TEST_CHUNK_3X_SIZE); + EXPECT_EQ(iter.bytesRead(), 0); + + expectPointer(iter.rp(), 0); + while (iter.readBuffer() != NULL) { + iter.rp()->move(iter.currentToRead()); + } + EXPECT_EQ(iter.bytesRead(), TEST_CHUNK_3X_SIZE); + expectPointer(iter.rp(), TEST_CHUNK_3X_SIZE); + + iter.rp()->rewind(); + expectPointer(iter.rp(), 0); + uint8_t val = 0; + while (iter.hasNext()) { + EXPECT_EQ(iter.next(), val); + val++; + } + EXPECT_EQ(iter.bytesRead(), TEST_CHUNK_3X_SIZE); + expectPointer(iter.rp(), TEST_CHUNK_3X_SIZE); +} + TEST(EncodedBufferTest, ReadVarint) { EncodedBuffer buffer; uint64_t val = UINT64_C(1522865904593); - buffer.writeRawVarint64(val); - EXPECT_EQ(val, buffer.begin().readRawVarint()); + size_t len = buffer.writeRawVarint64(val); + auto iter = buffer.begin(); + EXPECT_EQ(iter.size(), len); + EXPECT_EQ(iter.readRawVarint(), val); } diff --git a/libs/protoutil/tests/ProtoOutputStream_test.cpp b/libs/protoutil/tests/ProtoOutputStream_test.cpp new file mode 100644 index 000000000000..27ee13d4dfe1 --- /dev/null +++ b/libs/protoutil/tests/ProtoOutputStream_test.cpp @@ -0,0 +1,228 @@ +// Copyright (C) 2018 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 <android-base/file.h> +#include <android-base/test_utils.h> +#include <android/util/protobuf.h> +#include <android/util/ProtoOutputStream.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "frameworks/base/libs/protoutil/tests/test.pb.h" + +using namespace android::base; +using namespace android::util; +using ::testing::StrEq; + +static std::string flushToString(ProtoOutputStream* proto) { + TemporaryFile tf; + std::string content; + + EXPECT_NE(tf.fd, -1); + EXPECT_TRUE(proto->flush(tf.fd)); + EXPECT_TRUE(ReadFileToString(tf.path, &content)); + return content; +} + +static std::string iterateToString(ProtoOutputStream* proto) { + std::string content; + content.reserve(proto->size()); + auto iter = proto->data(); + while (iter.hasNext()) { + content.push_back(iter.next()); + } + return content; +} + +TEST(ProtoOutputStreamTest, Primitives) { + std::string s = "hello"; + const char b[5] = { 'a', 'p', 'p', 'l', 'e' }; + + ProtoOutputStream proto; + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | PrimitiveProto::kValInt32FieldNumber, 123)); + EXPECT_TRUE(proto.write(FIELD_TYPE_INT64 | PrimitiveProto::kValInt64FieldNumber, -1LL)); + EXPECT_TRUE(proto.write(FIELD_TYPE_FLOAT | PrimitiveProto::kValFloatFieldNumber, -23.5f)); + EXPECT_TRUE(proto.write(FIELD_TYPE_DOUBLE | PrimitiveProto::kValDoubleFieldNumber, 324.5)); + EXPECT_TRUE(proto.write(FIELD_TYPE_UINT32 | PrimitiveProto::kValUint32FieldNumber, 3424)); + EXPECT_TRUE(proto.write(FIELD_TYPE_UINT64 | PrimitiveProto::kValUint64FieldNumber, 57LL)); + EXPECT_TRUE(proto.write(FIELD_TYPE_FIXED32 | PrimitiveProto::kValFixed32FieldNumber, -20)); + EXPECT_TRUE(proto.write(FIELD_TYPE_FIXED64 | PrimitiveProto::kValFixed64FieldNumber, -37LL)); + EXPECT_TRUE(proto.write(FIELD_TYPE_BOOL | PrimitiveProto::kValBoolFieldNumber, true)); + EXPECT_TRUE(proto.write(FIELD_TYPE_STRING | PrimitiveProto::kValStringFieldNumber, s)); + EXPECT_TRUE(proto.write(FIELD_TYPE_BYTES | PrimitiveProto::kValBytesFieldNumber, b, 5)); + EXPECT_TRUE(proto.write(FIELD_TYPE_SFIXED32 | PrimitiveProto::kValSfixed32FieldNumber, 63)); + EXPECT_TRUE(proto.write(FIELD_TYPE_SFIXED64 | PrimitiveProto::kValSfixed64FieldNumber, -54)); + EXPECT_TRUE(proto.write(FIELD_TYPE_SINT32 | PrimitiveProto::kValSint32FieldNumber, -533)); + EXPECT_TRUE(proto.write(FIELD_TYPE_SINT64 | PrimitiveProto::kValSint64FieldNumber, -61224762453LL)); + EXPECT_TRUE(proto.write(FIELD_TYPE_ENUM | PrimitiveProto::kValEnumFieldNumber, 2)); + + PrimitiveProto primitives; + ASSERT_TRUE(primitives.ParseFromString(flushToString(&proto))); + EXPECT_EQ(primitives.val_int32(), 123); + EXPECT_EQ(primitives.val_int64(), -1); + EXPECT_EQ(primitives.val_float(), -23.5f); + EXPECT_EQ(primitives.val_double(), 324.5f); + EXPECT_EQ(primitives.val_uint32(), 3424); + EXPECT_EQ(primitives.val_uint64(), 57); + EXPECT_EQ(primitives.val_fixed32(), -20); + EXPECT_EQ(primitives.val_fixed64(), -37); + EXPECT_EQ(primitives.val_bool(), true); + EXPECT_THAT(primitives.val_string(), StrEq(s.c_str())); + EXPECT_THAT(primitives.val_bytes(), StrEq("apple")); + EXPECT_EQ(primitives.val_sfixed32(), 63); + EXPECT_EQ(primitives.val_sfixed64(), -54); + EXPECT_EQ(primitives.val_sint32(), -533); + EXPECT_EQ(primitives.val_sint64(), -61224762453LL); + EXPECT_EQ(primitives.val_enum(), PrimitiveProto_Count_TWO); +} + +TEST(ProtoOutputStreamTest, Complex) { + std::string name1 = "cat"; + std::string name2 = "dog"; + const char data1[6] = { 'f', 'u', 'n', 'n', 'y', '!' }; + const char data2[4] = { 'f', 'o', 'o', 'd' }; + + ProtoOutputStream proto; + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | ComplexProto::kIntsFieldNumber, 23)); + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | ComplexProto::kIntsFieldNumber, 101)); + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | ComplexProto::kIntsFieldNumber, -72)); + uint64_t token1 = proto.start(FIELD_TYPE_MESSAGE | ComplexProto::kLogsFieldNumber); + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | ComplexProto::Log::kIdFieldNumber, 12)); + EXPECT_TRUE(proto.write(FIELD_TYPE_STRING | ComplexProto::Log::kNameFieldNumber, name1)); + // specify the length to test the write(id, bytes, length) function. + EXPECT_TRUE(proto.write(FIELD_TYPE_BYTES | ComplexProto::Log::kDataFieldNumber, data1, 5)); + proto.end(token1); + uint64_t token2 = proto.start(FIELD_TYPE_MESSAGE | ComplexProto::kLogsFieldNumber); + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | ComplexProto::Log::kIdFieldNumber, 98)); + EXPECT_TRUE(proto.write(FIELD_TYPE_STRING | ComplexProto::Log::kNameFieldNumber, name2)); + EXPECT_TRUE(proto.write(FIELD_TYPE_BYTES | ComplexProto::Log::kDataFieldNumber, data2, 4)); + proto.end(token2); + + ComplexProto complex; + ASSERT_TRUE(complex.ParseFromString(iterateToString(&proto))); + EXPECT_EQ(complex.ints_size(), 3); + EXPECT_EQ(complex.ints(0), 23); + EXPECT_EQ(complex.ints(1), 101); + EXPECT_EQ(complex.ints(2), -72); + EXPECT_EQ(complex.logs_size(), 2); + ComplexProto::Log log1 = complex.logs(0); + EXPECT_EQ(log1.id(), 12); + EXPECT_THAT(log1.name(), StrEq(name1.c_str())); + EXPECT_THAT(log1.data(), StrEq("funny")); // should not contain '!' + ComplexProto::Log log2 = complex.logs(1); + EXPECT_EQ(log2.id(), 98); + EXPECT_THAT(log2.name(), StrEq(name2.c_str())); + EXPECT_THAT(log2.data(), StrEq("food")); +} + +TEST(ProtoOutputStreamTest, Reusability) { + ProtoOutputStream proto; + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | ComplexProto::kIntsFieldNumber, 32)); + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | ComplexProto::kIntsFieldNumber, 15)); + EXPECT_EQ(proto.bytesWritten(), 4); + EXPECT_EQ(proto.size(), 4); + // Can't write to proto after compact + EXPECT_FALSE(proto.write(FIELD_TYPE_INT32 | ComplexProto::kIntsFieldNumber, 94)); + + ComplexProto beforeClear; + ASSERT_TRUE(beforeClear.ParseFromString(flushToString(&proto))); + EXPECT_EQ(beforeClear.ints_size(), 2); + EXPECT_EQ(beforeClear.ints(0), 32); + EXPECT_EQ(beforeClear.ints(1), 15); + + proto.clear(); + EXPECT_EQ(proto.bytesWritten(), 0); + EXPECT_TRUE(proto.write(FIELD_TYPE_INT32 | ComplexProto::kIntsFieldNumber, 1076)); + + ComplexProto afterClear; + ASSERT_TRUE(afterClear.ParseFromString(flushToString(&proto))); + EXPECT_EQ(afterClear.ints_size(), 1); + EXPECT_EQ(afterClear.ints(0), 1076); +} + +TEST(ProtoOutputStreamTest, AdvancedEncoding) { + ProtoOutputStream proto; + proto.writeRawVarint((ComplexProto::kIntsFieldNumber << FIELD_ID_SHIFT) + WIRE_TYPE_VARINT); + proto.writeRawVarint(UINT64_C(-123809234)); + proto.writeLengthDelimitedHeader(ComplexProto::kLogsFieldNumber, 8); + proto.writeRawByte((ComplexProto::Log::kDataFieldNumber << FIELD_ID_SHIFT) + WIRE_TYPE_LENGTH_DELIMITED); + proto.writeRawByte(6); + proto.writeRawByte('b'); + proto.writeRawByte('a'); + proto.writeRawByte('n'); + proto.writeRawByte('a'); + proto.writeRawByte('n'); + proto.writeRawByte('a'); + uint64_t token = proto.start(FIELD_TYPE_MESSAGE | ComplexProto::kLogsFieldNumber); + proto.write(FIELD_TYPE_INT32 | ComplexProto::Log::kIdFieldNumber, 14); + proto.end(token); + + ComplexProto complex; + ASSERT_TRUE(complex.ParseFromString(flushToString(&proto))); + EXPECT_EQ(complex.ints_size(), 1); + EXPECT_EQ(complex.ints(0), UINT64_C(-123809234)); + EXPECT_EQ(complex.logs_size(), 2); + ComplexProto::Log log1 = complex.logs(0); + EXPECT_FALSE(log1.has_id()); + EXPECT_FALSE(log1.has_name()); + EXPECT_THAT(log1.data(), StrEq("banana")); + ComplexProto::Log log2 = complex.logs(1); + EXPECT_EQ(log2.id(), 14); + EXPECT_FALSE(log2.has_name()); + EXPECT_FALSE(log2.has_data()); +} + +TEST(ProtoOutputStreamTest, InvalidTypes) { + ProtoOutputStream proto; + EXPECT_FALSE(proto.write(FIELD_TYPE_UNKNOWN | PrimitiveProto::kValInt32FieldNumber, 790)); + EXPECT_FALSE(proto.write(FIELD_TYPE_ENUM | PrimitiveProto::kValEnumFieldNumber, 234.34)); + EXPECT_FALSE(proto.write(FIELD_TYPE_BOOL | PrimitiveProto::kValBoolFieldNumber, 18.73f)); + EXPECT_EQ(proto.size(), 0); +} + +TEST(ProtoOutputStreamTest, NoEndCalled) { + ProtoOutputStream proto; + proto.start(FIELD_TYPE_MESSAGE | ComplexProto::kLogsFieldNumber); + proto.write(FIELD_TYPE_INT32 | ComplexProto::Log::kIdFieldNumber, 53); + // no proto.end called + EXPECT_NE(proto.bytesWritten(), 0); + EXPECT_EQ(proto.size(), 0); + EXPECT_EQ(proto.data().size(), 0); + EXPECT_FALSE(proto.flush(STDOUT_FILENO)); +} + + +TEST(ProtoOutputStreamTest, TwoEndCalled) { + ProtoOutputStream proto; + uint64_t token = proto.start(FIELD_TYPE_MESSAGE | ComplexProto::kLogsFieldNumber); + proto.write(FIELD_TYPE_INT32 | ComplexProto::Log::kIdFieldNumber, 53); + proto.end(token); + proto.end(token); + EXPECT_NE(proto.bytesWritten(), 0); + EXPECT_EQ(proto.size(), 0); + EXPECT_EQ(proto.data().size(), 0); + EXPECT_FALSE(proto.flush(STDOUT_FILENO)); +} + +TEST(ProtoOutputStreamTest, NoStartCalled) { + ProtoOutputStream proto; + uint64_t wrongToken = UINT64_C(324536345); + // no proto.start called + proto.write(FIELD_TYPE_INT32 | ComplexProto::Log::kIdFieldNumber, 53); + proto.end(wrongToken); + EXPECT_NE(proto.bytesWritten(), 0); + EXPECT_EQ(proto.size(), 0); + EXPECT_EQ(proto.data().size(), 0); + EXPECT_FALSE(proto.flush(STDOUT_FILENO)); +} diff --git a/libs/protoutil/tests/protobuf_test.cpp b/libs/protoutil/tests/protobuf_test.cpp new file mode 100644 index 000000000000..5ca3e6477c01 --- /dev/null +++ b/libs/protoutil/tests/protobuf_test.cpp @@ -0,0 +1,51 @@ +// Copyright (C) 2018 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 <android/util/protobuf.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using namespace android::util; + +TEST(ProtobufTest, All) { + EXPECT_EQ(read_wire_type(UINT32_C(17)), 1); + EXPECT_EQ(read_field_id(UINT32_C(17)), 2); + EXPECT_EQ(get_varint_size(UINT64_C(234134)), 3); + EXPECT_EQ(get_varint_size(UINT64_C(-1)), 10); + + constexpr uint8_t UNSET_BYTE = 0xAB; + + uint8_t buf[11]; + memset(buf, UNSET_BYTE, sizeof(buf)); + EXPECT_EQ(write_raw_varint(buf, UINT64_C(150)) - buf, 2); + EXPECT_EQ(buf[0], 0x96); + EXPECT_EQ(buf[1], 0x01); + EXPECT_EQ(buf[2], UNSET_BYTE); + + memset(buf, UNSET_BYTE, sizeof(buf)); + EXPECT_EQ(write_raw_varint(buf, UINT64_C(-2)) - buf, 10); + EXPECT_EQ(buf[0], 0xfe); + for (int i = 1; i < 9; i++) { + EXPECT_EQ(buf[i], 0xff); + } + EXPECT_EQ(buf[9], 0x01); + EXPECT_EQ(buf[10], UNSET_BYTE); + + uint8_t header[20]; + memset(header, UNSET_BYTE, sizeof(header)); + EXPECT_EQ(write_length_delimited_tag_header(header, 3, 150) - header, 3); + EXPECT_EQ(header[0], 26); + EXPECT_EQ(header[1], 0x96); + EXPECT_EQ(header[2], 0x01); + EXPECT_EQ(header[3], UNSET_BYTE); +}
\ No newline at end of file diff --git a/libs/protoutil/tests/test.proto b/libs/protoutil/tests/test.proto new file mode 100644 index 000000000000..52c55f39f326 --- /dev/null +++ b/libs/protoutil/tests/test.proto @@ -0,0 +1,42 @@ +// This proto file is only used for testing purpose. +syntax = "proto2"; + +package android.util; + +message PrimitiveProto { + + optional int32 val_int32 = 1; + optional int64 val_int64 = 2; + optional float val_float = 3; + optional double val_double = 4; + optional uint32 val_uint32 = 5; + optional uint64 val_uint64 = 6; + optional fixed32 val_fixed32 = 7; + optional fixed64 val_fixed64 = 8; + optional bool val_bool = 9; + optional string val_string = 10; + optional bytes val_bytes = 11; + optional sfixed32 val_sfixed32 = 12; + optional sfixed64 val_sfixed64 = 13; + optional sint32 val_sint32 = 14; + optional sint64 val_sint64 = 15; + + enum Count { + ZERO = 0; + ONE = 1; + TWO = 2; + }; + optional Count val_enum = 16; +} + +message ComplexProto { + + repeated int32 ints = 1; + + message Log { + optional int32 id = 1; + optional string name = 2; + optional bytes data = 3; + } + repeated Log logs = 2; +} diff --git a/libs/services/include/android/os/StatsLogEventWrapper.h b/libs/services/include/android/os/StatsLogEventWrapper.h index 255619c6226c..f60c338bf9c4 100644 --- a/libs/services/include/android/os/StatsLogEventWrapper.h +++ b/libs/services/include/android/os/StatsLogEventWrapper.h @@ -25,6 +25,63 @@ namespace android { namespace os { +/** + * A wrapper for a union type to contain multiple types of values. + * + */ +struct StatsLogValue { + // Keep in sync with FieldValue.h + enum STATS_LOG_VALUE_TYPE { + UNKNOWN = 0, + INT = 1, + LONG = 2, + FLOAT = 3, + DOUBLE = 4, + STRING = 5, + STORAGE = 6 + }; + + StatsLogValue() : type(UNKNOWN) {} + + StatsLogValue(int32_t v) { + int_value = v; + type = INT; + } + + StatsLogValue(int64_t v) { + long_value = v; + type = LONG; + } + + StatsLogValue(float v) { + float_value = v; + type = FLOAT; + } + + StatsLogValue(double v) { + double_value = v; + type = DOUBLE; + } + + StatsLogValue(const std::string& v) { + str_value = v; + type = STRING; + } + + void setType(STATS_LOG_VALUE_TYPE t) { type = t; } + + union { + int32_t int_value; + int64_t long_value; + float float_value; + double double_value; + }; + std::string str_value; + std::vector<uint8_t> storage_value; + + STATS_LOG_VALUE_TYPE type; +}; + // Represents a parcelable object. Only used to send data from Android OS to statsd. class StatsLogEventWrapper : public android::Parcelable { public: @@ -36,8 +93,22 @@ class StatsLogEventWrapper : public android::Parcelable { android::status_t readFromParcel(const android::Parcel* in); - // These are public for ease of conversion. - std::vector<uint8_t> bytes; + int getTagId() const { return mTagId; } + + int64_t getElapsedRealTimeNs() const { return mElapsedRealTimeNs; } + + int64_t getWallClockTimeNs() const { return mWallClockTimeNs; } + + std::vector<StatsLogValue> getElements() const { return mElements; } + + private: + int mTagId; + + int64_t mElapsedRealTimeNs; + + int64_t mWallClockTimeNs; + + std::vector<StatsLogValue> mElements; }; } // Namespace os } // Namespace android diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp index c2907a66fb99..8282518f75c6 100644 --- a/libs/services/src/os/DropBoxManager.cpp +++ b/libs/services/src/os/DropBoxManager.cpp @@ -236,7 +236,7 @@ DropBoxManager::getNextEntry(const String16& tag, long msec, Entry* entry) if (service == NULL) { return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service"); } - return service->getNextEntry(tag, msec, entry); + return service->getNextEntry(tag, msec, android::String16("android"), entry); } }} // namespace android::os diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp index 8b3aa9ab4257..a1a6d9fe0e22 100644 --- a/libs/services/src/os/StatsLogEventWrapper.cpp +++ b/libs/services/src/os/StatsLogEventWrapper.cpp @@ -32,13 +32,73 @@ namespace os { StatsLogEventWrapper::StatsLogEventWrapper(){}; status_t StatsLogEventWrapper::writeToParcel(Parcel* out) const { - out->writeByteVector(bytes); - return ::android::NO_ERROR; + // Implement me if desired. We don't currently use this. + ALOGE( + "Cannot do c++ StatsLogEventWrapper.writeToParcel(); it is not " + "implemented."); + (void)out; // To prevent compile error of unused parameter 'in' + return UNKNOWN_ERROR; }; status_t StatsLogEventWrapper::readFromParcel(const Parcel* in) { - in->readByteVector(&bytes); - return ::android::NO_ERROR; + status_t res = OK; + if (in == NULL) { + ALOGE("statsd received parcel argument was NULL."); + return BAD_VALUE; + } + if ((res = in->readInt32(&mTagId)) != OK) { + ALOGE("statsd could not read tagId from parcel"); + return res; + } + if ((res = in->readInt64(&mElapsedRealTimeNs)) != OK) { + ALOGE("statsd could not read elapsed real time from parcel"); + return res; + } + if ((res = in->readInt64(&mWallClockTimeNs)) != OK) { + ALOGE("statsd could not read wall clock time from parcel"); + return res; + } + int dataSize = 0; + if ((res = in->readInt32(&dataSize)) != OK) { + ALOGE("statsd could not read data size from parcel"); + return res; + } + if (mTagId <= 0 || mElapsedRealTimeNs <= 0 || mWallClockTimeNs <= 0 || + dataSize <= 0) { + ALOGE("statsd received invalid parcel"); + return BAD_VALUE; + } + + for (int i = 0; i < dataSize; i++) { + int type = in->readInt32(); + switch (type) { + case StatsLogValue::INT: + mElements.push_back(StatsLogValue(in->readInt32())); + break; + case StatsLogValue::LONG: + mElements.push_back(StatsLogValue(in->readInt64())); + break; + case StatsLogValue::STRING: + mElements.push_back( + StatsLogValue(std::string(String8(in->readString16()).string()))); + break; + case StatsLogValue::FLOAT: + mElements.push_back(StatsLogValue(in->readFloat())); + break; + case StatsLogValue::DOUBLE: + mElements.push_back(StatsLogValue(in->readDouble())); + break; + case StatsLogValue::STORAGE: + mElements.push_back(StatsLogValue()); + mElements.back().setType(StatsLogValue::STORAGE); + in->readByteVector(&(mElements.back().storage_value)); + break; + default: + ALOGE("unrecognized data type: %d", type); + return BAD_TYPE; + } + } + return NO_ERROR; }; } // Namespace os diff --git a/libs/storage/Android.bp b/libs/storage/Android.bp index 911bd1d25393..c19933e39c96 100644 --- a/libs/storage/Android.bp +++ b/libs/storage/Android.bp @@ -6,6 +6,7 @@ cc_library_static { "IMountShutdownObserver.cpp", "IObbActionListener.cpp", "IMountService.cpp", + "ObbInfo.cpp", ], export_include_dirs: ["include"], diff --git a/libs/storage/IMountService.cpp b/libs/storage/IMountService.cpp index fa3d8bd0930f..fd6e6e932ebc 100644 --- a/libs/storage/IMountService.cpp +++ b/libs/storage/IMountService.cpp @@ -443,7 +443,7 @@ public: } void mountObb(const String16& rawPath, const String16& canonicalPath, const String16& key, - const sp<IObbActionListener>& token, int32_t nonce) + const sp<IObbActionListener>& token, int32_t nonce, const sp<ObbInfo>& obbInfo) { Parcel data, reply; data.writeInterfaceToken(IMountService::getInterfaceDescriptor()); @@ -452,6 +452,7 @@ public: data.writeString16(key); data.writeStrongBinder(IInterface::asBinder(token)); data.writeInt32(nonce); + obbInfo->writeToParcel(&data); if (remote()->transact(TRANSACTION_mountObb, data, &reply) != NO_ERROR) { ALOGD("mountObb could not contact remote\n"); return; diff --git a/libs/storage/ObbInfo.cpp b/libs/storage/ObbInfo.cpp new file mode 100644 index 000000000000..1bb6b3a89b86 --- /dev/null +++ b/libs/storage/ObbInfo.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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 <storage/ObbInfo.h> + +#include <binder/Parcel.h> +#include <utils/String16.h> +#include <sys/types.h> + +namespace android { + +ObbInfo::ObbInfo(const String16 fileName, const String16 packageName, int32_t version, + int32_t flags, size_t saltSize, const uint8_t* salt) : mFileName(fileName), + mPackageName(packageName), mVersion(version), mFlags(flags), mSaltSize(saltSize), + mSalt(salt) {} + +ObbInfo::~ObbInfo() {} + +status_t ObbInfo::readFromParcel(const Parcel*) { + return INVALID_OPERATION; +} + +status_t ObbInfo::writeToParcel(Parcel* p) const { + // Parcel write code must be kept in sync with + // frameworks/base/core/java/android/content/res/ObbInfo.java + p->writeString16(mFileName); + p->writeString16(mPackageName); + p->writeInt32(mVersion); + p->writeInt32(mFlags); + p->writeByteArray(mSaltSize, mSalt); + return OK; +} + +}; // namespace android
\ No newline at end of file diff --git a/libs/storage/include/storage/IMountService.h b/libs/storage/include/storage/IMountService.h index c3d34d84958b..2463e023efc1 100644 --- a/libs/storage/include/storage/IMountService.h +++ b/libs/storage/include/storage/IMountService.h @@ -20,6 +20,7 @@ #include <storage/IMountServiceListener.h> #include <storage/IMountShutdownObserver.h> #include <storage/IObbActionListener.h> +#include <storage/ObbInfo.h> #include <utils/String8.h> @@ -64,7 +65,7 @@ public: virtual void finishMediaUpdate() = 0; virtual void mountObb(const String16& rawPath, const String16& canonicalPath, const String16& key, const sp<IObbActionListener>& token, - const int32_t nonce) = 0; + const int32_t nonce, const sp<ObbInfo>& obbInfo) = 0; virtual void unmountObb(const String16& filename, const bool force, const sp<IObbActionListener>& token, const int32_t nonce) = 0; virtual bool isObbMounted(const String16& filename) = 0; diff --git a/libs/storage/include/storage/ObbInfo.h b/libs/storage/include/storage/ObbInfo.h new file mode 100644 index 000000000000..e4cc353d64c7 --- /dev/null +++ b/libs/storage/include/storage/ObbInfo.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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_OBBINFO_H +#define ANDROID_OBBINFO_H + +#include <binder/Parcelable.h> +#include <utils/RefBase.h> +#include <utils/String16.h> +#include <sys/types.h> + +namespace android { + +class ObbInfo : public Parcelable, public virtual RefBase { + +public: + ObbInfo(const String16 fileName, const String16 packageName, int32_t version, + int32_t flags, size_t saltSize, const uint8_t* salt); + ~ObbInfo(); + + status_t writeToParcel(Parcel* parcel) const override; + status_t readFromParcel(const Parcel* parcel) override; + +private: + const String16 mFileName; + const String16 mPackageName; + int32_t mVersion; + int32_t mFlags; + size_t mSaltSize; + const uint8_t* mSalt; +}; + +}; // namespace android + +#endif // ANDROID_OBBINFO_H
\ No newline at end of file |